在上一章,我们看了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,则它们的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上设置为Stretch的HorizontalContentAlignment属性。 ListBox项的默认内容对齐方式为Left,这意味着每个项只占用所需的水平空间。 结果如何? 好吧,不是我们想要的:
通过使用拉伸对齐,每个项都会拉伸以占用全部可用空间,如上一屏幕截图所示。
如上所述,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控件,稍后将在本教程中给出一个非常详尽的描述,其中有几个章节解释了所有功能。