-
列表控件:

ListBox控件

在上一章,我们看了ItemsControl,它是WPF中最简单的列表。 ListBox控件是下一个,它增加了一些功能。 其中一个主要区别是ListBox控件处理了选择,允许最终用户从列表中选择一个或多个项并自动提供可视化反馈。

这是一个非常简单的ListBox控件示例:

<Window x:Class="WpfTutorialSamples.ListBox_control.ListBoxSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ListBoxSample" Height="120" Width="200">
    <Grid Margin="10">
		<ListBox>
			<ListBoxItem>ListBox Item #1</ListBoxItem>
			<ListBoxItem>ListBox Item #2</ListBoxItem>
			<ListBoxItem>ListBox Item #3</ListBoxItem>
		</ListBox>
	</Grid>
</Window>

这很简单:我们声明一个ListBox控件,在里面我们声明了三个ListBoxItem,每个都有自己的文本。 由于ListBoxItem实际上是一个ContentControl,我们可以为它定义自定义内容:

<Window x:Class="WpfTutorialSamples.ListBox_control.ListBoxSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ListBoxSample" Height="120" Width="200">
	<Grid Margin="10">
		<ListBox>
			<ListBoxItem>
				<StackPanel Orientation="Horizontal">
					<Image Source="/WpfTutorialSamples;component/Images/bullet_blue.png" />
					<TextBlock>ListBox Item #1</TextBlock>
				</StackPanel>
			</ListBoxItem>
			<ListBoxItem>
				<StackPanel Orientation="Horizontal">
					<Image Source="/WpfTutorialSamples;component/Images/bullet_green.png" />
					<TextBlock>ListBox Item #2</TextBlock>
				</StackPanel>
			</ListBoxItem>
			<ListBoxItem>
				<StackPanel Orientation="Horizontal">
					<Image Source="/WpfTutorialSamples;component/Images/bullet_red.png" />
					<TextBlock>ListBox Item #3</TextBlock>
				</StackPanel>
			</ListBoxItem>
		</ListBox>
	</Grid>
</Window>

对于每个ListBoxItem,我们添加一个StackPanel,我们在里面添加一个Image和一个TextBlock。 这使我们可以完全控制内容以及文本渲染,正如您从屏幕截图中看到的那样,每个数字都使用了不同的颜色。

从屏幕截图中,您可能还注意到ItemsControl与ListBox进行比较时的另一个区别:默认情况下,控件周围会显示一个边框,使其看起来像一个真正的控件,而不仅仅是显示内容。

ListBox数据绑定

第一个例子是手动定义ListBox的项,但多数时候,ListBox控件将使用数据绑定来填充数据源中的项。 默认情况下,如果将项列表绑定到ListBox,则它们的ToString()方法将用于表示每个项。 少数情况下这是你想要的,但幸运的是,我们可以很容易地声明一个用于渲染每个项的模板。

我重新使用了ItemsControl章节中基于TODO的示例,其中我们使用简单的后台代码类构建了一个很酷的TODO列表,在本例中,是一个用于可视化表示的ListBox控件。 这是一个例子:

<Window x:Class="WpfTutorialSamples.ListBox_control.ListBoxDataBindingSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ListBoxDataBindingSample" Height="150" Width="300">
    <Grid Margin="10">
		<ListBox Name="lbTodoList" HorizontalContentAlignment="Stretch">
			<ListBox.ItemTemplate>
				<DataTemplate>
					<Grid Margin="0,2">
						<Grid.ColumnDefinitions>
							<ColumnDefinition Width="*" />
							<ColumnDefinition Width="100" />
						</Grid.ColumnDefinitions>
						<TextBlock Text="{Binding Title}" />
						<ProgressBar Grid.Column="1" Minimum="0" Maximum="100" Value="{Binding Completion}" />
					</Grid>
				</DataTemplate>
			</ListBox.ItemTemplate>
		</ListBox>
	</Grid>
</Window>
using System;
using System.Windows;
using System.Collections.Generic;

namespace WpfTutorialSamples.ListBox_control
{
	public partial class ListBoxDataBindingSample : Window
	{
		public ListBoxDataBindingSample()
		{
			InitializeComponent();
			List<TodoItem> items = new List<TodoItem>();
			items.Add(new TodoItem() { Title = "Complete this WPF tutorial", Completion = 45 });
			items.Add(new TodoItem() { Title = "Learn C#", Completion = 80 });
			items.Add(new TodoItem() { Title = "Wash the car", Completion = 0 });

			lbTodoList.ItemsSource = items;
		}
	}

	public class TodoItem
	{
		public string Title { get; set; }
		public int Completion { get; set; }
	}
}

所有的魔法都发生在我们为ListBox定义的ItemTemplate中。 在那里,我们指定每个ListBox项应该包含一个Grid,分为两列,TextBlock在第一列中显示标题,ProgressBar在第二列中显示进度。 为了获得这些值,我们使用了非常简单的数据绑定,本教程的数据绑定部分对此进行了解释。

在后台代码文件中,我们声明了一个非常简单的TodoItem类来保存我们的每个TODO项。 在窗口的构造函数中,我们初始化一个列表,向其添加三个TODO项,然后将其分配给ListBox的ItemsSource。 我们在XAML部分中指定了ItemsSource和ItemTemplate的结合,WPF将呈现TODO列表的所有项。

请注意我在ListBox上设置为StretchHorizontalContentAlignment属性。 ListBox项的默认内容对齐方式为Left,这意味着每个项只占用所需的水平空间。 结果如何? 好吧,不是我们想要的:

通过使用拉伸对齐,每个项都会拉伸以占用全部可用空间,如上一屏幕截图所示。

使用ListBox选择

如上所述,ItemsControl和ListBox之间的主要区别在于ListBox处理并显示用户选择。 因此ListBox有很多关于选择的问题。 为了解决其中一些问题,我创建了一个更复杂的例子,向您展示了一些与选择相关的技巧:

<Window x:Class="WpfTutorialSamples.ListBox_control.ListBoxSelectionSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ListBoxSelectionSample" Height="250" Width="450">
	<DockPanel Margin="10">
		<StackPanel DockPanel.Dock="Right" Margin="10,0">
			<StackPanel.Resources>
				<Style TargetType="Button">
					<Setter Property="Margin" Value="0,0,0,5" />
				</Style>
			</StackPanel.Resources>
			<TextBlock FontWeight="Bold" Margin="0,0,0,10">ListBox selection</TextBlock>
			<Button Name="btnShowSelectedItem" Click="btnShowSelectedItem_Click">Show selected</Button>
			<Button Name="btnSelectLast" Click="btnSelectLast_Click">Select last</Button>
			<Button Name="btnSelectNext" Click="btnSelectNext_Click">Select next</Button>
			<Button Name="btnSelectCSharp" Click="btnSelectCSharp_Click">Select C#</Button>
			<Button Name="btnSelectAll" Click="btnSelectAll_Click">Select all</Button>
		</StackPanel>
		<ListBox Name="lbTodoList" HorizontalContentAlignment="Stretch" SelectionMode="Extended" SelectionChanged="lbTodoList_SelectionChanged">
			<ListBox.ItemTemplate>
				<DataTemplate>
					<Grid Margin="0,2">
						<Grid.ColumnDefinitions>
							<ColumnDefinition Width="*" />
							<ColumnDefinition Width="100" />
						</Grid.ColumnDefinitions>
						<TextBlock Text="{Binding Title}" />
						<ProgressBar Grid.Column="1" Minimum="0" Maximum="100" Value="{Binding Completion}" />
					</Grid>
				</DataTemplate>
			</ListBox.ItemTemplate>
		</ListBox>
	</DockPanel>
</Window>
using System;
using System.Windows;
using System.Collections.Generic;

namespace WpfTutorialSamples.ListBox_control
{
	public partial class ListBoxSelectionSample : Window
	{
		public ListBoxSelectionSample()
		{
			InitializeComponent();
			List<TodoItem> items = new List<TodoItem>();
			items.Add(new TodoItem() { Title = "Complete this WPF tutorial", Completion = 45 });
			items.Add(new TodoItem() { Title = "Learn C#", Completion = 80 });
			items.Add(new TodoItem() { Title = "Wash the car", Completion = 0 });

			lbTodoList.ItemsSource = items;
		}

		private void lbTodoList_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
		{
			if(lbTodoList.SelectedItem != null)
				this.Title = (lbTodoList.SelectedItem as TodoItem).Title;
		}

		private void btnShowSelectedItem_Click(object sender, RoutedEventArgs e)
		{
			foreach(object o in lbTodoList.SelectedItems)
				MessageBox.Show((o as TodoItem).Title);
		}

		private void btnSelectLast_Click(object sender, RoutedEventArgs e)
		{
			lbTodoList.SelectedIndex = lbTodoList.Items.Count - 1;
		}

		private void btnSelectNext_Click(object sender, RoutedEventArgs e)
		{
			int nextIndex = 0;
			if((lbTodoList.SelectedIndex >= 0) && (lbTodoList.SelectedIndex < (lbTodoList.Items.Count - 1)))
				nextIndex = lbTodoList.SelectedIndex + 1;
			lbTodoList.SelectedIndex = nextIndex;
		}

		private void btnSelectCSharp_Click(object sender, RoutedEventArgs e)
		{
			foreach(object o in lbTodoList.Items)
			{
				if((o is TodoItem) && ((o as TodoItem).Title.Contains("C#")))
				{
					lbTodoList.SelectedItem = o;
					break;
				}
			}
		}

		private void btnSelectAll_Click(object sender, RoutedEventArgs e)
		{
			foreach(object o in lbTodoList.Items)
				lbTodoList.SelectedItems.Add(o);
		}


	}

	public class TodoItem
	{
		public string Title { get; set; }
		public int Completion { get; set; }
	}
}

如您所见,我已经在ListBox的右侧定义了一系列按钮,以获取或操作选择。 我还将SelectionMode更改为Extended,这样就可以多选。 这可以通过编程方式完成,就像我在示例中所做的那样,也可以通过按住[Ctrl][Shift]时单击项来完成。

对于每个按钮,我在后台代码中定义了一个单击处理程序。 每个动作都很明确,使用的C#代码相当简单,如果您仍有疑问,请尝试在您自己的机器上运行示例并测试示例中的各种功能。

小结

ListBox控件很像ItemsControl,可以使用几种相同的技术。 与ItemsControl相比,ListBox确实提供了更多功能,尤其是选择处理。 对于更多功能,比如列标题,您应该看一下ListView控件,稍后将在本教程中给出一个非常详尽的描述,其中有几个章节解释了所有功能。