WPF ListBox控件完全指南

可以根据Github拉取示例程序运行
GitHub程序演示地址(点击直达)
也可以在本文资源中下载
在这里插入图片描述

一、ListBox控件概述

ListBox是Windows Presentation Foundation (WPF)中最常用的列表展示控件之一,它允许用户从一个项目列表中进行单项或多项选择。与传统Windows Forms中的ListBox相比,WPF的ListBox具有更强大的功能和更高的可定制性。

ListBox继承自ItemsControl,因此它继承了ItemsControl的所有功能,同时增加了选择功能。这使得它非常适合于显示数据集合并允许用户与之交互。

Object
DependencyObject
Visual
UIElement
FrameworkElement
Control
ItemsControl
Selector
ListBox

二、基本用法

2.1 基本XAML创建

最简单的ListBox创建方式如下:

<ListBox Width="200" Height="150">
    <ListBoxItem>项目1</ListBoxItem>
    <ListBoxItem>项目2</ListBoxItem>
    <ListBoxItem>项目3</ListBoxItem>
    <ListBoxItem>项目4</ListBoxItem>
    <ListBoxItem>项目5</ListBoxItem>
</ListBox>

2.2 代码创建

通过C#代码创建ListBox也同样简单:

// 创建ListBox控件
ListBox myListBox = new ListBox();
myListBox.Width = 200;
myListBox.Height = 150;

// 添加项目
myListBox.Items.Add("项目1");
myListBox.Items.Add("项目2");
myListBox.Items.Add("项目3");
myListBox.Items.Add("项目4");
myListBox.Items.Add("项目5");

// 将ListBox添加到窗口的网格中
myGrid.Children.Add(myListBox);

三、核心属性和功能

3.1 项容器与项目源

ListBox有两种提供数据的主要方式:

3.1.1 Items属性

Items属性允许你直接向ListBox添加项目。每个项目会自动包装在一个ListBoxItem容器中:

<ListBox>
    <ListBox.Items>
        <ListBoxItem>红色</ListBoxItem>
        <ListBoxItem>绿色</ListBoxItem>
        <ListBoxItem>蓝色</ListBoxItem>
    </ListBox.Items>
</ListBox>
3.1.2 ItemsSource属性

ItemsSource属性允许你通过数据绑定的方式提供数据源,这是WPF中推荐的做法:

<ListBox ItemsSource="{Binding ColorList}" />

对应的C#代码:

public class ViewModel : INotifyPropertyChanged
{
    private ObservableCollection<string> _colorList;
    
    public ObservableCollection<string> ColorList
    {
        get { return _colorList; }
        set
        {
            _colorList = value;
            OnPropertyChanged("ColorList");
        }
    }
    
    public ViewModel()
    {
        // 初始化颜色列表
        _colorList = new ObservableCollection<string>
        {
            "红色", "绿色", "蓝色", "黄色", "紫色"
        };
    }
    
    // INotifyPropertyChanged实现代码省略...
}

3.2 选择模式

ListBox支持三种选择模式,通过SelectionMode属性进行设置:

<ListBox SelectionMode="Multiple" />
选择模式描述
Single只允许选择单个项目(默认设置)
Multiple允许选择多个项目,需要使用Ctrl键进行复选
Extended允许选择多个项目,可以使用Shift键进行范围选择,Ctrl键进行非连续选择

相关代码示例:

// 设置选择模式为多选
myListBox.SelectionMode = SelectionMode.Multiple;

// 获取选中的项目
ListBoxItem selectedItem = myListBox.SelectedItem as ListBoxItem;
// 或者获取索引
int selectedIndex = myListBox.SelectedIndex;

// 获取多选的项目
var selectedItems = myListBox.SelectedItems;
foreach (var item in selectedItems)
{
    Console.WriteLine(item.ToString());
}

3.3 选择相关属性

ListBox提供了多个与选择相关的重要属性:

  1. SelectedItem:获取或设置当前选中的项目。
  2. SelectedItems:获取当前选中的所有项目(只读)。
  3. SelectedIndex:获取或设置当前选中项目的索引。
  4. SelectedValue:获取或设置选中项目的值(与SelectedValuePath配合使用)。
  5. SelectedValuePath:指定对象的哪个属性用于SelectedValue。
// 设置选中索引
myListBox.SelectedIndex = 2;

// 通过代码获取选中的内容和值
var item = myListBox.SelectedItem;
var index = myListBox.SelectedIndex;
var value = myListBox.SelectedValue; // 需要配合SelectedValuePath使用

四、样式和模板定制

4.1 ItemTemplate

ItemTemplate允许你自定义列表中每个项的外观:

<ListBox ItemsSource="{Binding PersonList}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <Image Source="{Binding ImagePath}" Width="32" Height="32" Margin="3"/>
                <StackPanel>
                    <TextBlock Text="{Binding Name}" FontWeight="Bold"/>
                    <TextBlock Text="{Binding Email}" FontStyle="Italic" Foreground="Gray"/>
                </StackPanel>
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

上述代码使用DataTemplate定义了一个包含图片和两个文本块的布局。

4.2 ItemContainerStyle

ItemContainerStyle允许你自定义列表项容器的样式(即ListBoxItem):

<ListBox ItemsSource="{Binding Items}">
    <ListBox.ItemContainerStyle>
        <Style TargetType="ListBoxItem">
            <Setter Property="HorizontalContentAlignment" Value="Stretch" />
            <Setter Property="Padding" Value="5" />
            <Setter Property="Margin" Value="0,2" />
            <Setter Property="Background" Value="LightBlue" />
            <Style.Triggers>
                <Trigger Property="IsSelected" Value="True">
                    <Setter Property="Background" Value="LightCoral" />
                    <Setter Property="Foreground" Value="White" />
                </Trigger>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="Background" Value="LightGreen" />
                </Trigger>
            </Style.Triggers>
        </Style>
    </ListBox.ItemContainerStyle>
</ListBox>

此代码定义了列表项的基本样式,以及选中状态和鼠标悬停状态的视觉反馈。

4.3 交替背景色

通过ItemContainerStyle可以轻松实现奇偶行的交替背景色:

<ListBox ItemsSource="{Binding Items}">
    <ListBox.ItemContainerStyle>
        <Style TargetType="ListBoxItem">
            <Style.Triggers>
                <Trigger Property="ItemsControl.AlternationIndex" Value="0">
                    <Setter Property="Background" Value="WhiteSmoke"/>
                </Trigger>
                <Trigger Property="ItemsControl.AlternationIndex" Value="1">
                    <Setter Property="Background" Value="Lavender"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </ListBox.ItemContainerStyle>
</ListBox>

注意:需要设置ListBox的AlternationCount属性指定交替周期:

<ListBox AlternationCount="2" ItemsSource="{Binding Items}" />

五、项目绑定

5.1 基本数据绑定

以下是一个绑定简单集合的例子:

// ViewModel代码
public class MainViewModel
{
    public ObservableCollection<string> Fruits { get; } = new ObservableCollection<string>
    {
        "苹果", "香蕉", "橘子", "葡萄", "西瓜"
    };
}

// 设置DataContext
public MainWindow()
{
    InitializeComponent();
    this.DataContext = new MainViewModel();
}

对应的XAML:

<ListBox ItemsSource="{Binding Fruits}" />

5.2 复杂对象绑定

当绑定复杂对象时,需要使用ItemTemplate指定如何显示:

// 定义数据模型
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string Category { get; set; }
}

// ViewModel代码
public class ShopViewModel
{
    public ObservableCollection<Product> Products { get; } = new ObservableCollection<Product>
    {
        new Product { Id = 1, Name = "笔记本电脑", Price = 5999, Category = "电子产品" },
        new Product { Id = 2, Name = "机械键盘", Price = 299, Category = "配件" },
        new Product { Id = 3, Name = "无线鼠标", Price = 129, Category = "配件" },
        new Product { Id = 4, Name = "显示器", Price = 1299, Category = "电子产品" }
    };
}

XAML代码:

<ListBox ItemsSource="{Binding Products}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="30"/>
                    <ColumnDefinition Width="150"/>
                    <ColumnDefinition Width="80"/>
                    <ColumnDefinition Width="100"/>
                </Grid.ColumnDefinitions>
                <TextBlock Grid.Column="0" Text="{Binding Id}" />
                <TextBlock Grid.Column="1" Text="{Binding Name}" FontWeight="Bold" />
                <TextBlock Grid.Column="2" Text="{Binding Price, StringFormat={}{0:C}}" HorizontalAlignment="Right" />
                <TextBlock Grid.Column="3" Text="{Binding Category}" Foreground="Gray" Margin="10,0,0,0" />
            </Grid>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

5.3 DisplayMemberPath

对于简单的显示需求,可以使用DisplayMemberPath属性指定要显示的属性名:

<ListBox ItemsSource="{Binding Products}" DisplayMemberPath="Name" />

这样只会显示Product对象的Name属性值。

六、事件处理

6.1 选择事件

ListBox提供了几个与选择相关的重要事件:

// 在XAML中订阅事件
<ListBox ItemsSource="{Binding Items}" 
         SelectionChanged="ListBox_SelectionChanged" />

// C#代码中的事件处理
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    // 获取新选中的项目
    var addedItems = e.AddedItems;
    foreach (var item in addedItems)
    {
        Debug.WriteLine($"新选中的项目: {item}");
    }
    
    // 获取取消选中的项目
    var removedItems = e.RemovedItems;
    foreach (var item in removedItems)
    {
        Debug.WriteLine($"取消选中的项目: {item}");
    }
    
    // 获取当前选中的项目
    ListBox listBox = sender as ListBox;
    var selectedItem = listBox.SelectedItem;
    var selectedItems = listBox.SelectedItems;
}

6.2 项目交互事件

除了选择事件外,还有一些项目级别的交互事件,需要在ItemContainerStyle中处理:

<ListBox.ItemContainerStyle>
    <Style TargetType="ListBoxItem">
        <EventSetter Event="MouseDoubleClick" Handler="ListBoxItem_MouseDoubleClick"/>
        <EventSetter Event="PreviewMouseRightButtonDown" Handler="ListBoxItem_PreviewMouseRightButtonDown"/>
    </Style>
</ListBox.ItemContainerStyle>

对应的C#事件处理代码:

private void ListBoxItem_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
    ListBoxItem item = sender as ListBoxItem;
    var data = item.Content; // 获取项目中的数据
    MessageBox.Show($"双击了项目: {data}");
}

private void ListBoxItem_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
    ListBoxItem item = sender as ListBoxItem;
    // 可以在这里显示上下文菜单
    ContextMenu contextMenu = new ContextMenu();
    contextMenu.Items.Add(new MenuItem { Header = "编辑" });
    contextMenu.Items.Add(new MenuItem { Header = "删除" });
    item.ContextMenu = contextMenu;
}

七、分组与排序

7.1 使用CollectionView进行数据分组

WPF提供了强大的CollectionView来处理数据分组和排序:

// ViewModel代码
public class GroupingViewModel
{
    public ObservableCollection<Product> AllProducts { get; } = new ObservableCollection<Product>
    {
        new Product { Id = 1, Name = "笔记本电脑", Price = 5999, Category = "电子产品" },
        new Product { Id = 2, Name = "机械键盘", Price = 299, Category = "配件" },
        new Product { Id = 3, Name = "无线鼠标", Price = 129, Category = "配件" },
        new Product { Id = 4, Name = "显示器", Price = 1299, Category = "电子产品" },
        new Product { Id = 5, Name = "手机", Price = 2999, Category = "电子产品" },
        new Product { Id = 6, Name = "耳机", Price = 499, Category = "配件" }
    };
    
    // 将数据按类别分组
    public ICollectionView ProductsCollectionView
    {
        get
        {
            var view = CollectionViewSource.GetDefaultView(AllProducts);
            // 清除现有分组描述
            view.GroupDescriptions.Clear();
            // 添加按Category分组的描述
            view.GroupDescriptions.Add(new PropertyGroupDescription("Category"));
            return view;
        }
    }
}

在XAML中绑定分组的CollectionView:

<ListBox ItemsSource="{Binding ProductsCollectionView}">
    <ListBox.GroupStyle>
        <GroupStyle>
            <GroupStyle.HeaderTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Name}" FontWeight="Bold" FontSize="14" Margin="5"/>
                </DataTemplate>
            </GroupStyle.HeaderTemplate>
        </GroupStyle>
    </ListBox.GroupStyle>
    
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal" Margin="5,2">
                <TextBlock Text="{Binding Name}" Width="150"/>
                <TextBlock Text="{Binding Price, StringFormat={}{0:C}}" Width="80" HorizontalAlignment="Right"/>
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

7.2 数据排序

使用CollectionView可以轻松实现数据排序:

public ICollectionView SortedProductsView
{
    get
    {
        var view = CollectionViewSource.GetDefaultView(AllProducts);
        // 清除现有排序描述
        view.SortDescriptions.Clear();
        // 添加按价格升序排序的描述
        view.SortDescriptions.Add(new SortDescription("Price", ListSortDirection.Ascending));
        return view;
    }
}

7.3 分组与排序结合

可以同时应用分组和排序:

public ICollectionView GroupedAndSortedView
{
    get
    {
        var view = CollectionViewSource.GetDefaultView(AllProducts);
        
        // 清除现有分组和排序描述
        view.GroupDescriptions.Clear();
        view.SortDescriptions.Clear();
        
        // 添加分组描述
        view.GroupDescriptions.Add(new PropertyGroupDescription("Category"));
        
        // 添加排序描述: 先按类别排序,再按名称排序
        view.SortDescriptions.Add(new SortDescription("Category", ListSortDirection.Ascending));
        view.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
        
        return view;
    }
}

八、虚拟化与性能优化

8.1 UI虚拟化

当ListBox包含大量项目时,UI虚拟化是提高性能的关键。WPF默认开启了UI虚拟化,但可以通过VirtualizingPanel附加属性进行控制:

<ListBox ItemsSource="{Binding LargeCollection}" 
         VirtualizingPanel.IsVirtualizing="True"
         VirtualizingPanel.VirtualizationMode="Recycling"
         VirtualizingPanel.CacheLength="5,5"
         VirtualizingPanel.CacheLengthUnit="Item" />

参数说明:

  • IsVirtualizing: 启用/禁用虚拟化
  • VirtualizationMode:
    • Standard: 标准虚拟化,移出视图的项目会被销毁
    • Recycling: 回收模式,移出视图的项目会被回收利用,性能更好
  • CacheLength: 指定在可见区域之前和之后缓存多少项目
  • CacheLengthUnit: 缓存单位(Item或Page)

8.2 大数据集优化

处理大型数据集时的一些建议:

// 处理大型数据集时,可以实现延迟加载
public class LazyLoadViewModel : INotifyPropertyChanged
{
    private ObservableCollection<DataItem> _items = new ObservableCollection<DataItem>();
    private CancellationTokenSource _cancellationTokenSource;
    
    public ObservableCollection<DataItem> Items => _items;
    
    public async Task LoadDataAsync(int startIndex, int count)
    {
        // 取消之前的加载操作
        _cancellationTokenSource?.Cancel();
        _cancellationTokenSource = new CancellationTokenSource();
        var token = _cancellationTokenSource.Token;
        
        try
        {
            // 模拟从数据库或API获取数据
            await Task.Delay(200, token); // 模拟网络延迟
            
            // 批量加载数据
            for (int i = startIndex; i < startIndex + count; i++)
            {
                if (token.IsCancellationRequested)
                    break;
                    
                var item = new DataItem { Id = i, Name = $"项目 {i}" };
                
                // 更新UI
                await Application.Current.Dispatcher.InvokeAsync(() =>
                {
                    _items.Add(item);
                });
                
                // 每批次添加后稍微暂停,避免UI冻结
                if (i % 20 == 0)
                    await Task.Delay(10, token);
            }
        }
        catch (TaskCanceledException)
        {
            // 处理取消操作
        }
    }
    
    // INotifyPropertyChanged实现省略...
}

在滚动事件中实现分页加载:

private void ListBox_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
    ListBox listBox = sender as ListBox;
    ScrollViewer scrollViewer = FindVisualChild<ScrollViewer>(listBox);
    
    // 当滚动到接近底部时加载更多数据
    if (scrollViewer != null &&
        scrollViewer.VerticalOffset > scrollViewer.ScrollableHeight * 0.8 &&
        !_isLoading)
    {
        _isLoading = true;
        LoadMoreDataAsync().ContinueWith(_ => _isLoading = false);
    }
}

// 辅助方法:查找子控件
private T FindVisualChild<T>(DependencyObject parent) where T : DependencyObject
{
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
    {
        DependencyObject child = VisualTreeHelper.GetChild(parent, i);
        
        if (child is T found)
            return found;
            
        T childOfChild = FindVisualChild<T>(child);
        if (childOfChild != null)
            return childOfChild;
    }
    
    return null;
}

九、高级技术和实用案例

9.1 拖放功能实现

ListBox支持拖放操作,可以在同一个ListBox内部或不同ListBox之间进行项目拖放:

// 1. 在XAML中设置
<ListBox x:Name="sourceListBox" AllowDrop="True"
         PreviewMouseLeftButtonDown="ListBox_PreviewMouseLeftButtonDown"
         PreviewMouseMove="ListBox_PreviewMouseMove"
         Drop="ListBox_Drop" />

// 2. C#中实现拖放逻辑
private Point _startPoint;
private ListBoxItem _draggedItem;

private void ListBox_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    // 获取起始点
    _startPoint = e.GetPosition(null);
    
    // 获取当前点击的ListBoxItem
    var item = FindAncestor<ListBoxItem>((DependencyObject)e.OriginalSource);
    if (item != null)
    {
        _draggedItem = item;
    }
}

private void ListBox_PreviewMouseMove(object sender, MouseEventArgs e)
{
    // 确认是否开始拖拽
    if (e.LeftButton == MouseButtonState.Pressed && _draggedItem != null)
    {
        Point currentPosition = e.GetPosition(null);
        
        // 判断鼠标是否移动足够距离开始拖拽
        if (Math.Abs(currentPosition.X - _startPoint.X) > SystemParameters.MinimumHorizontalDragDistance ||
            Math.Abs(currentPosition.Y - _startPoint.Y) > SystemParameters.MinimumVerticalDragDistance)
        {
            // 开始拖放
            var data = _draggedItem.DataContext;
            DragDrop.DoDragDrop(_draggedItem, data, DragDropEffects.Move);
            _draggedItem = null;
        }
    }
}

private void ListBox_Drop(object sender, DragEventArgs e)
{
    ListBox listBox = sender as ListBox;
    
    // 获取拖放的数据
    var data = e.Data.GetData(typeof(Product)) as Product;
    if (data != null)
    {
        // 获取目标位置
        Point dropPosition = e.GetPosition(listBox);
        
        // 获取目标项目
        var targetItem = GetItemAtPosition<ListBoxItem>(listBox, dropPosition);
        
        // 处理拖放操作,例如重排序
        if (targetItem != null)
        {
            int targetIndex = listBox.ItemContainerGenerator.IndexFromContainer(targetItem);
            
            // 源列表中找到并移除该项
            int sourceIndex = ((ObservableCollection<Product>)listBox.ItemsSource).IndexOf(data);
            ((ObservableCollection<Product>)listBox.ItemsSource).Move(sourceIndex, targetIndex);
        }
    }
}

// 辅助方法:查找祖先元素
private T FindAncestor<T>(DependencyObject current) where T : DependencyObject
{
    do
    {
        if (current is T ancestor)
            return ancestor;
        current = VisualTreeHelper.GetParent(current);
    }
    while (current != null);
    
    return null;
}

// 辅助方法:根据位置获取列表项
private T GetItemAtPosition<T>(ItemsControl itemsControl, Point position) where T : DependencyObject
{
    UIElement element = itemsControl.InputHitTest(position) as UIElement;
    while (element != null)
    {
        if (element is T item)
            return item;
        element = VisualTreeHelper.GetParent(element) as UIElement;
    }
    return null;
}

9.2 实现可编辑的ListBox

创建支持项目编辑的ListBox:

<ListBox ItemsSource="{Binding EditableItems}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="Auto"/>
                </Grid.ColumnDefinitions>
                
                <!-- 显示或编辑模式切换 -->
                <TextBlock Grid.Column="0" Text="{Binding Text}" 
                           Visibility="{Binding IsEditing, Converter={StaticResource InverseBoolToVisConverter}}"/>
                <TextBox Grid.Column="0" Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}" 
                         Visibility="{Binding IsEditing, Converter={StaticResource BoolToVisConverter}}"
                         LostFocus="EditTextBox_LostFocus"/>
                
                <!-- 编辑按钮 -->
                <Button Grid.Column="1" Content="编辑" Margin="5,0,0,0"
                        Command="{Binding DataContext.EditCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
                        CommandParameter="{Binding}"
                        Visibility="{Binding IsEditing, Converter={StaticResource InverseBoolToVisConverter}}"/>
            </Grid>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

对应的ViewModel代码:

public class EditableItemsViewModel : INotifyPropertyChanged
{
    public ObservableCollection<EditableItem> EditableItems { get; } = new ObservableCollection<EditableItem>();
    
    public ICommand EditCommand { get; private set; }
    
    public EditableItemsViewModel()
    {
        // 初始化数据
        for (int i = 1; i <= 5; i++)
        {
            EditableItems.Add(new EditableItem { Text = $"项目 {i}" });
        }
        
        // 编辑命令
        EditCommand = new RelayCommand<EditableItem>(item => 
        {
            item.IsEditing = true;
        });
    }
    
    // INotifyPropertyChanged实现省略...
}

// 可编辑项类
public class EditableItem : INotifyPropertyChanged
{
    private string _text;
    private bool _isEditing;
    
    public string Text 
    { 
        get => _text; 
        set 
        { 
            _text = value;
            OnPropertyChanged("Text");
        }
    }
    
    public bool IsEditing
    {
        get => _isEditing;
        set
        {
            _isEditing = value;
            OnPropertyChanged("IsEditing");
        }
    }
    
    // INotifyPropertyChanged实现省略...
}

处理编辑完成的事件:

private void EditTextBox_LostFocus(object sender, RoutedEventArgs e)
{
    TextBox textBox = sender as TextBox;
    EditableItem item = textBox.DataContext as EditableItem;
    if (item != null)
    {
        item.IsEditing = false;
    }
}

十、ListBox与MVVM模式

10.1 MVVM模式中使用ListBox

在MVVM架构中,ListBox与ViewModel的结合使用:

public class ListViewModel : INotifyPropertyChanged
{
    private ObservableCollection<ListItem> _items;
    private ListItem _selectedItem;
    
    public ObservableCollection<ListItem> Items
    {
        get => _items;
        set
        {
            _items = value;
            OnPropertyChanged(nameof(Items));
        }
    }
    
    public ListItem SelectedItem
    {
        get => _selectedItem;
        set
        {
            _selectedItem = value;
            OnPropertyChanged(nameof(SelectedItem));
            // 选择变更时可以触发其他逻辑
            OnItemSelected();
        }
    }
    
    public ICommand AddItemCommand { get; }
    public ICommand RemoveItemCommand { get; }
    
    public ListViewModel()
    {
        _items = new ObservableCollection<ListItem>();
        
        // 初始化命令
        AddItemCommand = new RelayCommand(AddNewItem);
        RemoveItemCommand = new RelayCommand<ListItem>(RemoveItem, CanRemoveItem);
        
        // 加载测试数据
        LoadSampleData();
    }
    
    private void LoadSampleData()
    {
        for (int i = 1; i <= 10; i++)
        {
            _items.Add(new ListItem { Id = i, Title = $"项目 {i}" });
        }
    }
    
    private void AddNewItem()
    {
        int nextId = Items.Count > 0 ? Items.Max(i => i.Id) + 1 : 1;
        Items.Add(new ListItem { Id = nextId, Title = $"新项目 {nextId}" });
    }
    
    private void RemoveItem(ListItem item)
    {
        if (item != null)
            Items.Remove(item);
    }
    
    private bool CanRemoveItem(ListItem item)
    {
        return item != null;
    }
    
    private void OnItemSelected()
    {
        // 在这里处理选择变更后的业务逻辑
        if (_selectedItem != null)
        {
            Debug.WriteLine($"选中了: {_selectedItem.Title}");
        }
    }
    
    // INotifyPropertyChanged实现省略...
}

XAML视图:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    
    <ListBox Grid.Row="0" ItemsSource="{Binding Items}" 
             SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="30"/>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="Auto"/>
                    </Grid.ColumnDefinitions>
                    <TextBlock Grid.Column="0" Text="{Binding Id}" />
                    <TextBlock Grid.Column="1" Text="{Binding Title}" Margin="5,0"/>
                    <Button Grid.Column="2" Content="删除" 
                            Command="{Binding DataContext.RemoveItemCommand, 
                                     RelativeSource={RelativeSource AncestorType=ListBox}}"
                            CommandParameter="{Binding}"/>
                </Grid>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
    
    <Button Grid.Row="1" Content="添加新项目" 
            Command="{Binding AddItemCommand}" 
            HorizontalAlignment="Left" 
            Margin="10" 
            Padding="10,5"/>
</Grid>

总结

WPF的ListBox控件是一个强大而灵活的列表控件,提供了以下核心功能:

  1. 数据显示与绑定:通过Items或ItemsSource属性可以轻松显示各种类型的数据
  2. 项目选择:支持单选、多选和扩展选择模式
  3. 样式自定义:通过ItemTemplate和ItemContainerStyle可以完全控制外观
  4. 数据分组排序:结合CollectionView可实现复杂的数据组织方式
  5. 性能优化:内置虚拟化功能,可有效处理大量数据
  6. 交互功能:支持丰富的事件和命令,可实现拖放、编辑等高级功能

在MVVM架构中,ListBox是连接数据与用户界面的重要桥梁,通过数据绑定可以实现视图与模型的完全分离,使应用程序更易于维护和测试。

学习资源

  1. 微软官方文档 - ListBox类
  2. WPF数据虚拟化指南
  3. WPF社区资源
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰茶_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值