WPF中文网

ItemsControl基类

一、集合控件概述

很多时候,我们需要显示大量的数据,这些数据虽然众多,但是数据类型结构相同的,由于内容控件只能显示单个元素,要显示或操作多个元素组成的集合,那么,集合控件就派上用场了。WPF中的集合控件种类丰富,有类似表格的DataGrid,有单列表的ListBox,也有介于两者之前的ListView,还有,软件的菜单通常也是一个集合控件,以及软件下方的状态栏,同样也是一个集合控件。

这些集合控件都有一个共同的基类控件,那就是ItemsControl类,下面我们以表格的形式展示一下即将要学习的集合控件。

控件名说明
ItemsControl集合控件的基类,本身也是一个可以实例化的控件
ListBox一个列表集合控件
ListView表示用于显示数据项列表的控件,它可以有列头标题
DataGrid表示可自定义的网格中显示数据的控件。
ComboBox表示带有下拉列表的选择控件,通过单击控件上的箭头可显示或隐藏下拉列表。
TabControl表示包含多个共享相同的空间在屏幕上的项的控件。
TreeView用树结构(其中的项可以展开和折叠)中显示分层数据的控件
Menu表示一个 Windows 菜单控件,该控件可用于按层次组织与命令和事件处理程序关联的元素。
ContextMenu表示使控件能够公开特定于控件的上下文的功能的弹出菜单。
StatusBar表示应用程序窗口中的水平栏中显示项和信息的控件。

二、ItemsControl类定义

public class ItemsControl : Control, IAddChild, IGeneratorHost, IContainItemStorage
{
    public static readonly DependencyProperty ItemsSourceProperty;
    public static readonly DependencyProperty HasItemsProperty;
    public static readonly DependencyProperty DisplayMemberPathProperty;
    public static readonly DependencyProperty ItemTemplateProperty;
    public static readonly DependencyProperty ItemTemplateSelectorProperty;
    public static readonly DependencyProperty ItemStringFormatProperty;
    public static readonly DependencyProperty ItemBindingGroupProperty;
    public static readonly DependencyProperty ItemContainerStyleProperty;
    public static readonly DependencyProperty ItemContainerStyleSelectorProperty;
    public static readonly DependencyProperty ItemsPanelProperty;
    public static readonly DependencyProperty IsGroupingProperty;
    public static readonly DependencyProperty GroupStyleSelectorProperty;
    public static readonly DependencyProperty AlternationCountProperty;
    public static readonly DependencyProperty AlternationIndexProperty;
    public static readonly DependencyProperty IsTextSearchEnabledProperty;
    public static readonly DependencyProperty IsTextSearchCaseSensitiveProperty;

    public ItemsControl();

    public int AlternationCount { get; set; }
    public GroupStyleSelector GroupStyleSelector { get; set; }
    public ObservableCollection<GroupStyle> GroupStyle { get; }
    public bool IsGrouping { get; }
    public ItemsPanelTemplate ItemsPanel { get; set; }
    public StyleSelector ItemContainerStyleSelector { get; set; }
    public Style ItemContainerStyle { get; set; }
    public BindingGroup ItemBindingGroup { get; set; }
    public string ItemStringFormat { get; set; }
    public DataTemplateSelector ItemTemplateSelector { get; set; }
    public DataTemplate ItemTemplate { get; set; }
    public string DisplayMemberPath { get; set; }
    public bool HasItems { get; }
    public ItemContainerGenerator ItemContainerGenerator { get; }
    public IEnumerable ItemsSource { get; set; }
    public ItemCollection Items { get; }
    public bool IsTextSearchCaseSensitive { get; set; }
    public bool IsTextSearchEnabled { get; set; }
    protected internal override IEnumerator LogicalChildren { get; }

    public static DependencyObject ContainerFromElement(ItemsControl itemsControl, DependencyObject element);
    public static int GetAlternationIndex(DependencyObject element);
    public static ItemsControl GetItemsOwner(DependencyObject element);
    public static ItemsControl ItemsControlFromItemContainer(DependencyObject container);
    public override void BeginInit();
    public DependencyObject ContainerFromElement(DependencyObject element);
    public override void EndInit();
    public bool IsItemItsOwnContainer(object item);
    public bool ShouldSerializeGroupStyle();
    public bool ShouldSerializeItems();
    public override string ToString();
    protected virtual void AddChild(object value);
    protected virtual void AddText(string text);
    protected virtual void ClearContainerForItemOverride(DependencyObject element, object item);
    protected virtual DependencyObject GetContainerForItemOverride();
    protected virtual bool IsItemItsOwnContainerOverride(object item);
    protected virtual void OnAlternationCountChanged(int oldAlternationCount, int newAlternationCount);
    protected virtual void OnDisplayMemberPathChanged(string oldDisplayMemberPath, string newDisplayMemberPath);
    protected virtual void OnGroupStyleSelectorChanged(GroupStyleSelector oldGroupStyleSelector, GroupStyleSelector newGroupStyleSelector);
    protected virtual void OnItemBindingGroupChanged(BindingGroup oldItemBindingGroup, BindingGroup newItemBindingGroup);
    protected virtual void OnItemContainerStyleChanged(Style oldItemContainerStyle, Style newItemContainerStyle);
    protected virtual void OnItemContainerStyleSelectorChanged(StyleSelector oldItemContainerStyleSelector, StyleSelector newItemContainerStyleSelector);
    protected virtual void OnItemsChanged(NotifyCollectionChangedEventArgs e);
    protected virtual void OnItemsPanelChanged(ItemsPanelTemplate oldItemsPanel, ItemsPanelTemplate newItemsPanel);
    protected virtual void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue);
    protected virtual void OnItemStringFormatChanged(string oldItemStringFormat, string newItemStringFormat);
    protected virtual void OnItemTemplateChanged(DataTemplate oldItemTemplate, DataTemplate newItemTemplate);
    protected virtual void OnItemTemplateSelectorChanged(DataTemplateSelector oldItemTemplateSelector, DataTemplateSelector newItemTemplateSelector);
    protected override void OnKeyDown(KeyEventArgs e);
    protected override void OnTextInput(TextCompositionEventArgs e);
    protected virtual void PrepareContainerForItemOverride(DependencyObject element, object item);
    protected virtual bool ShouldApplyItemContainerStyle(DependencyObject container, object item);

}

二、ItemsControl类分析

由于我们还没有讲模板、样式、数据绑定等内容,所以关于ItemsControl类的分析,我们先关注一些与模板样式和数据绑定无关的内容,先讲讲ItemsControl最基础的内容。

2.Items属性

ItemsControl类作为集合控件的基类,它提供了一个非常重要的属性,那就是Items属性。这个属性的类型是ItemCollection,也就是一个集合列表,那么这个列表的元素内容是什么呢?

答案是object。

说明我们可以在集合控件中放任意引用类型的元素。

2.2DisplayMemberPath属性

这个属性用来获取或设置要显示的内容,它通常指某个数据源的某个属性名称,所以它是string类型。

2.3HasItems属性

这个属性用来判断当前集合控件是否有元素。

24.IsTextSearchCaseSensitive属性

这个属性如果为true,则搜索元素时区分大小写。

2.5 IsTextSearchEnabled属性

表示是否启用文字搜索。

好,接下来的几个属性将在后续进行学习,不过,我们先在这里了解一下它们的用途。

2.6 ItemsPanel属性[重要]

由于一个集合控件里面会显示多个数据项(一个数据代表一个家),那么这些数据项怎么排版?是像StackPanel一样水平或垂直排列,还是像WrapPanel瀑布流一样排例?这个ItemsPanel属性来决定。

2.7 ItemTemplate属性[重要]

在集合控件里,数据项有可能是一个复杂的实体,那么这些数据以什么样的UI布局界面呈现?也就是说,数据本身穿什么衣服?ItemTemplate属性就是来决定数据的外观的。如果把每个Item元素看成一个家,那么前面的ItemsPanel属性就是来决定邻里之间的实际距离以及房子和房子的排例走势。

2.8 ItemContainerStyle属性[重要]

ItemTemplate属性只能决定数据的外观,相当于这个家的内部装修以及家电家具的样式,而这个家外墙的装饰,则必须由ItemContainerStyle属性来承包。

2.9 ItemContainerStyleSelector属性[重要]

当我们选中这个集合控件中的某一项,并希望突出这一项,那就可以在ItemTemplateSelector属性中进行定义,也就是说,选择了某一项,某一项的外墙装饰发生改变。那同时要改变内部的样式呢?

2.10 ItemTemplateSelector属性[重要]

如果选中了某一项,并希望它的数据模块被重新定义,以突出这一项被选中,可以设置ItemTemplateSelector属性

2.11 Template属性[重要]

还记得吗?ItemsControl类继承于Control类,而Control类中有一个叫Template的属性,所以ItemsControl类自然也就拥有了这个属性,这是一个什么属性?它是ControlTemplate类,也就是控件模板,所以,如果我们希望把ItemsControl类本身的外观进行重定义,那就需要去设置Template属性

重庆教主友情提示

上面我们一口气讲了这么多的模板概念,虽然看起来是在学习ItemsControl类的属性,但是别忘记了,将来要学习的那些集合子控件全都继承于ItemsControl类,意味着它们也都有这些模板属性可以使用呢,是不是有一种事半功倍的感觉呢!

接下来,我们来举个例子

三、ItemsControl示例

前端代码

<Window x:Class="HelloWorld.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:HelloWorld" 
        xmlns:forms="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
        mc:Ignorable="d" FontSize="14"
        Title="WPF中文网之控件课程 - www.wpfsoft.com" Height="350" Width="500">
    <Grid>
        <ItemsControl>
            <Button Content="content" Margin="0,5"/>
            <Border Height="30" Background="LightPink" Margin="0,5"/>
            <TextBlock Text="WPF中文网 www.wpfsoft.com" Background="LightGray" Margin="0,5"/>
            <ItemsControl Height="35" Background="AliceBlue"/>
            <CheckBox Content="CheckBox元素"/>
            <StackPanel Orientation="Horizontal" Margin="0,5">
                <RadioButton Content="初级"/>
                <RadioButton Content="中级"/>
                <RadioButton Content="高级"/>
            </StackPanel>
            这是一串字符串
            <Label Content="这是Label控件" Margin="0,5"/>
            <Control Background="Red" Height="30"/>
            <ProgressBar Value="50" Height="20" Maximum="100" />
        </ItemsControl>
    </Grid>
</Window>

首先,我们在XMAL中实例化了一个ItemsControl控件,然后在ItemsControl里面实例化了一系列子控件,它们分别是Button、Border、TextBlock、ItemsControl、CheckBox、StackPanel、RadioButton、字符串、Label、Control和ProgressBar。

除了Control没有显示出来,其它全部都呈现在ItemsControl控件之中,因为这些子控件全部都在ItemsControl类的Items集合里面,那么,Control虽然能实例化,为什么没有显示出来呢?就连没有控件的字符串都能显示出来,这里面肯定有原因。

是的,这里我们引出一个知识点,那就是控件模板,因为Control基类虽然有Background属性,但是我们并没有给Control基类的Template属性设置一个控件模板,所以Control能实例化,但是不能显示。只能看到一个高度为30的空白区域。

而Border在设置Background属性后,为什么能显示?因为Border是一个装饰器,它继承于Decorator基类。

为什么单纯的字符串也能显示呢?因为实际上这个字符串外面被包裹了一层ContentPresenter实例,这个字符串是被赋值到了ContentPresenter的Content属性上,而ContentPresenter的ContentTemplate有一个默认模板。

四、总结

ItemsControl集合基类可以显示绝大多数控件,也就意味着,ListBox,ListView,DataGrid,ComboBox,TabControl,TreeView,Menu,ContextMenu,StatusBar这些子控件在显示集合元素时,每一个元素的外观可以呈现出更复杂、更漂亮的UI效果,从而可以设计出更友好的交互界面。

有了这样一个基调,那接下来我们来一一细说各个子控件的基础功能,待学习模板和样式章节后,进一步探索这些子控件的强大功能。另外,ListBox,ListView,DataGrid,ComboBox,TabControl这5个控件又都有一个共同的基类——Selector类,Selector继承于ItemsControl基类。Selector基类又是一个怎样的类?它会给我们提供哪些功能呢?

下一节,我们先从Selector基类说起。

当前课程源码下载:(注明:本站所有源代码请按标题搜索)

文件名:038-《ItemsControl集合控件基类》-源代码
链接:https://pan.baidu.com/s/1yu-q4tUtl0poLVgmcMfgBA
提取码:wpff

——重庆教主 2023年9月1日

Selector继承于ItemsControl,但它是一个抽象类,所以不能被实例化。从命名上看,它是一个选择器。

一、Selector类定义

public abstract class Selector : ItemsControl
{
    public static readonly RoutedEvent SelectionChangedEvent;
    public static readonly RoutedEvent SelectedEvent;
    public static readonly RoutedEvent UnselectedEvent;
    public static readonly DependencyProperty IsSelectionActiveProperty;
    public static readonly DependencyProperty IsSelectedProperty;
    public static readonly DependencyProperty IsSynchronizedWithCurrentItemProperty;
    public static readonly DependencyProperty SelectedIndexProperty;
    public static readonly DependencyProperty SelectedItemProperty;
    public static readonly DependencyProperty SelectedValueProperty;
    public static readonly DependencyProperty SelectedValuePathProperty;

    protected Selector();

    public object SelectedValue { get; set; }
    public object SelectedItem { get; set; }
    public int SelectedIndex { get; set; }
    public bool? IsSynchronizedWithCurrentItem { get; set; }
    public string SelectedValuePath { get; set; }

    public event SelectionChangedEventHandler SelectionChanged;

    public static void AddSelectedHandler(DependencyObject element, RoutedEventHandler handler);
    public static void AddUnselectedHandler(DependencyObject element, RoutedEventHandler handler);
    public static bool GetIsSelected(DependencyObject element);
    public static bool GetIsSelectionActive(DependencyObject element);
    public static void RemoveSelectedHandler(DependencyObject element, RoutedEventHandler handler);
    public static void RemoveUnselectedHandler(DependencyObject element, RoutedEventHandler handler);
    public static void SetIsSelected(DependencyObject element, bool isSelected);
    protected override void ClearContainerForItemOverride(DependencyObject element, object item);
    protected override void OnInitialized(EventArgs e);
    protected override void OnIsKeyboardFocusWithinChanged(DependencyPropertyChangedEventArgs e);
    protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e);
    protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue);
    protected virtual void OnSelectionChanged(SelectionChangedEventArgs e);
    protected override void PrepareContainerForItemOverride(DependencyObject element, object item);

}

接下来,我们来看看它提供了哪些可用的属性。

二、Selector类的属性

属性名称说明
SelectedValue获取或设置SelectedValuePath属性指定的元素的属性值
SelectedItem获取或设置当前所选内容中的第一项或如果所选内容为空则返回 null
SelectedIndex获取或设置当前所选内容或返回的第一项的索引为负一 (-1) 如果所选内容为空。
SelectedValuePath获取或设置SelectedItem当前元素的某个属性名,这个元素属性名将决定SelectedValue的值
IsSynchronizedWithCurrentItem是否同步当前项。

SelectedItem和SelectedValue有点类似,都是object类型。但是,他们俩不一定指同一个内容。比如,我们将有这样一个数据实体类。

    public class Person
    {
        public string Name { get; set; }
        public string Address { get; set; }
        public int Age { get; set; }
    }

然后我们实例化多个Person组成一个集合绑定到Items属性中,这个时候选中某一个元素,SelectedItem便等于这个Person元素,但是SelectedValue是什么,要看SelectedValuePath的值,如果SelectedValuePath的值指向的是Person.Name,那么SelectedValue就是一个字符串,如果SelectedValuePath指向的是Person的Age ,那么SelectedValue就是一个int整数,只有不设置SelectedValuePath时,SelectedValue和SelectedItem两者才相等,即Person实例

具体关于这一个重点,我们在下一节讨论ListBox控件时进行演示和讲解。

重庆教主说:

SelectedItem、SelectedValue、SelectedValuePath非常重要,一定要理解透彻哦!另外,还有一个属性叫DisplayMemberPath,它在ItemsControl基类中,意思是设置要显示的属性名,而SelectedValuePath是设置要选择的属性名。

——重庆教主 2023年9月1日

ListBox是一个列表控件,用于显示条目类的数据,默认每行只能显示一个内容项,当然,我们可以通过修改它的数据模板,来自定义每一行(元素)的数据外观,达到显示更多数据的目的。

一、ListBox的定义

public class ListBox : Selector
{
    public static readonly DependencyProperty SelectionModeProperty;
    public static readonly DependencyProperty SelectedItemsProperty;

    public ListBox();

    public IList SelectedItems { get; }
    public SelectionMode SelectionMode { get; set; }
    protected object AnchorItem { get; set; }
    protected internal override bool HandlesScrolling { get; }

    public void ScrollIntoView(object item);
    public void SelectAll();
    public void UnselectAll();
    protected override DependencyObject GetContainerForItemOverride();
    protected override bool IsItemItsOwnContainerOverride(object item);
    protected override AutomationPeer OnCreateAutomationPeer();
    protected override void OnIsMouseCapturedChanged(DependencyPropertyChangedEventArgs e);
    protected override void OnKeyDown(KeyEventArgs e);
    protected override void OnMouseMove(MouseEventArgs e);
    protected override void OnSelectionChanged(SelectionChangedEventArgs e);
    protected override void PrepareContainerForItemOverride(DependencyObject element, object item);
    protected bool SetSelectedItems(IEnumerable selectedItems);

}

二、属性分析

ListBox自身的属性比较少,SelectionMode 属性比较重要,它可以决定当前的ListBox控件是单选还是多选,它的值为Extended时,表示用户需要按下shift键才能多选。如果SelectionMode为多选状态,那么多选的结果保存在哪去了?

答案是SelectedItems 属性。

另外,ListBox还自带了滚动条,如果内容超出显示区域,这时滚动条便起作用。

我们在上一章节提过DisplayMemberPath、SelectedValuePath、SelectedItem和SelectedValue,那么,我们以一个实际的例子来说明这几个属性的用途。

三、ListBox示例

前端代码

<Window x:Class="HelloWorld.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:HelloWorld" 
        xmlns:forms="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
        mc:Ignorable="d" FontSize="14"
        Title="WPF中文网之控件课程 - www.wpfsoft.com" Height="350" Width="500">
    <StackPanel>
        <ListBox x:Name="listbox" MinHeight="100" 
                 DisplayMemberPath="Name" 
                 SelectedValuePath="Age"/>
        <Button Content="查看结果" Click="Button_Click"/>
        <TextBlock x:Name="_TextBlock"/>
    </StackPanel>
</Window>

后端代码

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;

namespace HelloWorld
{
    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public string Address { get; set; }

    }

    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            listbox.Items.Add(new Person { Name = "张三", Age = 22, Address = "广东省廉江市车板镇大坝村" });
            listbox.Items.Add(new Person { Name = "李四", Age = 23, Address = "江西省景德镇市市辖区" });
            listbox.Items.Add(new Person { Name = "王五", Age = 24, Address = "上海市虹口区" });
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var selectedItem = listbox.SelectedItem;
            var selectedValue = listbox.SelectedValue;
            _TextBlock.Text = $"{selectedItem},{selectedValue}";
        }
    }
}

代码分析

在前端代码中,我们设置了DisplayMemberPath属性值为“Name”,而SelectedValuePath属性值为“Age",这两个值实际上是Person类的两个属性,F5启动调试后,我们可以在界面上看到张三、李四和王五的名字,但是看不到他们的年龄和地址,这是因为ListBox默认每行只能显示一个内容项,而且这里我们设置了DisplayMemberPath属性,只能显示名字。

我们选中ListBox中的李四,然后单点查看结果按钮,SelectedItem属性得到了一个Person类,所以输出的值为HelloWorld.Person,而SelectedValue属性得到了李四的年龄,所以输出的结果是23。

重庆教主说

如果把SelectionMode属性设为多选Multiple或Extended,试试看,会发生什么效果呢?

Items属性是一个只读属性,所以我们只能能过Items的Add方法向集合增加元素。

四、ListBoxItem子项

其实,ListBox还有它专门配合业务开发的子项控件——ListBoxItem。ListBoxItem继承于ContentControl内容控件,仔细想,这意味着什么?还记得我们在分享ContentControl提过”它有一个叫Content属性“一嘴吗?Content属性可以容纳任意引用类型,也就是说,ListBoxItem也可以容纳任意引用类型,也就是说,ListBox的子项也可以容纳任意的引用类型。

这么一说,感觉ListBoxr还蛮强大的呢!

所以,ListBoxItem可以这样使用

<Window x:Class="HelloWorld.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:HelloWorld" 
        xmlns:forms="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
        mc:Ignorable="d" FontSize="14"
        Title="WPF中文网之控件课程 - www.wpfsoft.com" Height="350" Width="500">
    <StackPanel>        
        <ListBox x:Name="listbox">
            <ListBoxItem>
                <Button Content="这是一个按钮"/>
            </ListBoxItem>
            <ListBoxItem>
                <Border Height="30" Background="Red"/>
            </ListBoxItem>
            <ListBoxItem Content="这是一个字符串"/>
            <ListBoxItem>
                <ProgressBar Maximum="100" Value="50" Height="25" Width="450"/>
            </ListBoxItem>
            这里直接写字符串
            <ListBoxItem>
                <StackPanel Orientation="Horizontal">
                    <CheckBox Content="复选框"/>
                    <RadioButton Content="单选框 "/>
                </StackPanel>
            </ListBoxItem>
        </ListBox>
        <Button Content="查看结果" Click="Button_Click"/>
        <TextBlock x:Name="textblock" TextWrapping="Wrap"/>
    </StackPanel>
</Window>

后台代码

public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();            
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                var selectedItem = listbox.SelectedItem;
                var content = ((ContentControl)selectedItem).Content;
                textblock.Text = $"selectedItem={selectedItem}\r\ncontent={content}";
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }            
        }
    }

如上所示,我们在ListBoxj控件里面实例化了好几个ListBoxItem,但是每个ListBoxItem的Content属性都不一样,有Button,Border ,ProgressBar ,字符串,最后,我们来获取这些选中项的内容。

除了直接写的字符串不能转换之外,其它项的结果,SelectedItem属性总是ListBoxItem,而Content可以是我们设置的其它控制。

要全面了解ListBoxItem,不能不看看它的定义。

public class ListBoxItem : ContentControl
{
    public static readonly DependencyProperty IsSelectedProperty;
    public static readonly RoutedEvent SelectedEvent;
    public static readonly RoutedEvent UnselectedEvent;

    public ListBoxItem();

    public bool IsSelected { get; set; }

    public event RoutedEventHandler Selected;
    public event RoutedEventHandler Unselected;

    protected override AutomationPeer OnCreateAutomationPeer();
    protected override void OnMouseEnter(MouseEventArgs e);
    protected override void OnMouseLeave(MouseEventArgs e);
    protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e);
    protected override void OnMouseRightButtonDown(MouseButtonEventArgs e);
    protected virtual void OnSelected(RoutedEventArgs e);
    protected virtual void OnUnselected(RoutedEventArgs e);
    protected internal override void OnVisualParentChanged(DependencyObject oldParent);

}

如上所示,可以看到ListBoxItem有一个叫IsSelected属性,表示该项是否被选中,同时,它还有两个事件,分别是Selected选中和Unselected未选中,我们可以去订阅这两个事件,以此来做一些业务。

关于ListBox以及ListBoxItem,我们就先介绍这么多,实际上它的用法远不止这些,如果加上模板、样式、数据绑定、触发器,它还可以实现许多意想不到的效果。关于这部分的内容,请参阅模板样式章节关于ListBox控件的用法。

所以,ListBox列表控件默认情况下,只能显示一个数据项,那如果我想把Person类的Name、Age、Address三个属性值都显示出来呢?有办法——ListView控件可以做到。

当前课程源码下载:(注明:本站所有源代码请按标题搜索)

文件名:039-《ListBox列表控件》-源代码-1,039-《ListBox列表控件》-源代码-2
链接:https://pan.baidu.com/s/1yu-q4tUtl0poLVgmcMfgBA
提取码:wpff

——重庆教主 2023年9月1日

ListView继承于ListBox,在ListBox控件的基础上增加了数据视图。从而让我们可以很轻松的设置每一列的标题,以显示某个数据表结构及内容。

一、ListView定义

public class ListView : ListBox
{
    public static readonly DependencyProperty ViewProperty;

    public ListView();

    public ViewBase View { get; set; }

    protected override void ClearContainerForItemOverride(DependencyObject element, object item);
    protected override DependencyObject GetContainerForItemOverride();
    protected override bool IsItemItsOwnContainerOverride(object item);
    protected override AutomationPeer OnCreateAutomationPeer();
    protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e);
    protected override void PrepareContainerForItemOverride(DependencyObject element, object item);

}

ListView类增加了一个叫View的属性,这个属性用来定义控件的数据样式,决定数据怎样显示。View属性的类型是ViewBase,但是,我们真正在使用View属性时,实际上实例化的是GridView类,因为GridView类是ViewBase的子类。所以,我们要看了解一下GridView的定义。

public class GridView : ViewBase, IAddChild
{
    public static readonly DependencyProperty ColumnCollectionProperty;
    public static readonly DependencyProperty ColumnHeaderContainerStyleProperty;
    public static readonly DependencyProperty ColumnHeaderTemplateProperty;
    public static readonly DependencyProperty ColumnHeaderTemplateSelectorProperty;
    public static readonly DependencyProperty ColumnHeaderStringFormatProperty;
    public static readonly DependencyProperty AllowsColumnReorderProperty;
    public static readonly DependencyProperty ColumnHeaderContextMenuProperty;
    public static readonly DependencyProperty ColumnHeaderToolTipProperty;

    public GridView();

    public static ResourceKey GridViewItemContainerStyleKey { get; }
    public static ResourceKey GridViewStyleKey { get; }
    public static ResourceKey GridViewScrollViewerStyleKey { get; }
    public string ColumnHeaderStringFormat { get; set; }
    public DataTemplateSelector ColumnHeaderTemplateSelector { get; set; }
    public DataTemplate ColumnHeaderTemplate { get; set; }
    public Style ColumnHeaderContainerStyle { get; set; }
    public GridViewColumnCollection Columns { get; }
    public object ColumnHeaderToolTip { get; set; }
    public bool AllowsColumnReorder { get; set; }
    public ContextMenu ColumnHeaderContextMenu { get; set; }
    protected internal override object ItemContainerDefaultStyleKey { get; }
    protected internal override object DefaultStyleKey { get; }

    public static GridViewColumnCollection GetColumnCollection(DependencyObject element);
    public static void SetColumnCollection(DependencyObject element, GridViewColumnCollection collection);
    public static bool ShouldSerializeColumnCollection(DependencyObject obj);
    public override string ToString();
    protected virtual void AddChild(object column);
    protected virtual void AddText(string text);
    protected internal override void ClearItem(ListViewItem item);
    protected internal override IViewAutomationPeer GetAutomationPeer(ListView parent);
    protected internal override void PrepareItem(ListViewItem item);

}

GridView提供了一些可供设置的模板和样式属性,这些我们先放一边,在WPF基础章节的内容学习中,我们先学习它的Columns 属性,它是一个集合属性,而集合中元素的类型是GridViewColumn。

GridViewColumn最关键的只有两个属性,分别是标题和要显示的成员(指向了Person实体的某个属性名)。

好了,我们以上一节中的Person实体为例。

二、ListView示例

前端代码

<Window x:Class="HelloWorld.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:HelloWorld" 
        xmlns:forms="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
        mc:Ignorable="d" FontSize="14"
        Title="WPF中文网之控件课程 - www.wpfsoft.com" Height="350" Width="500">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition Width="200"/>
        </Grid.ColumnDefinitions>
        <ListView Grid.Column="0" x:Name="listview" SelectionChanged="listview_SelectionChanged">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="姓名" DisplayMemberBinding="{Binding Name}"/>
                    <GridViewColumn Header="年龄" DisplayMemberBinding="{Binding Age}"/>
                    <GridViewColumn Header="地址" DisplayMemberBinding="{Binding Address}"/>
                </GridView>  
            </ListView.View>
        </ListView>
        <StackPanel Grid.Column="1">
            <StackPanel Orientation="Horizontal" Margin="5">
                <TextBlock Text="姓名:"/>
                <TextBlock x:Name="_TextBlockName"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal" Margin="5">
                <TextBlock Text="年龄:"/>
                <TextBlock x:Name="_TextBlockAge"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal" Margin="5">
                <TextBlock Text="地址:"/>
                <TextBlock x:Name="_TextBlockAddress"/>
            </StackPanel>
        </StackPanel>
    </Grid>
</Window>

后端代码

namespace HelloWorld
{
    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public string Address { get; set; }

    }

    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            
            listview.Items.Add(new Person { Name = "张三", Age = 22, Address = "广东省廉江市车板镇大坝村" });
            listview.Items.Add(new Person { Name = "李四", Age = 23, Address = "江西省景德镇市市辖区" });
            listview.Items.Add(new Person { Name = "王五", Age = 24, Address = "上海市虹口区" });
        }
        private void listview_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            ListView listView = sender as ListView;
            if (listView == null) return;

            var person = listView.SelectedItem as Person;
            if (person == null) return;

            _TextBlockName.Text = person.Name;
            _TextBlockAge.Text = person.Age + "岁";
            _TextBlockAddress.Text = person.Address;
        }
    }
}

三、代码分析

首先,我们在前端实例化了一个ListView控件,并为View属性实例化了一个GridView对象(注意xaml语法的写法),最后为GridView对象实例化了3列GridViewColumn ,分别设置为姓名年龄和地址,特别需要注意的是DisplayMemberBinding属性的写法,这里采用了数据绑定的写法,意思是将ListView控件的数据源的Name属性显示在姓名那一列,Age属性显示在年龄那一列,Address属性显示在地址那一列(我们明确知道ListView数据源的类型就是Person的实例集合)。

事件处理

在ListView控件的SelectionChanged事件中,我们先将sender转成ListView ,再从中获取当前选中项(即person),最后显示详细信息在界面上即可。这样就演示了数据怎么加载显示到ListView,又怎么样从ListView上获取的过程。

而类似于ListView的效果效果,还有一个专门用来显示数据的控件,它叫DataGrid,从某种意义上来说,它甚至可以开发类似Excel表格的效果。不过,我们在下一节,还是以学习它的基础功能先。

当前课程源码下载:(注明:本站所有源代码请按标题搜索)

文件名:040-《ListView数据列表控件》-源代码
链接:https://pan.baidu.com/s/1yu-q4tUtl0poLVgmcMfgBA
提取码:wpff

——重庆教主 2023年9月6日

DataGrid是一个可以多选的数据表格控件。所以,它继承一个支持多选的父类——MultiSelector。

public abstract class MultiSelector : Selector
{
    protected MultiSelector();

    public IList SelectedItems { get; }
    protected bool CanSelectMultipleItems { get; set; }
    protected bool IsUpdatingSelectedItems { get; }

    public void SelectAll();
    public void UnselectAll();
    protected void BeginUpdateSelectedItems();
    protected void EndUpdateSelectedItems();

}

从上面的定义来看,DataGrid多选的结果会保存在SelectedItems 只读属性中,CanSelectMultipleItems 属性用来设置是否开启多选。好,然后我们来看看DataGrid控件的定义,虽然它的属性众多,在学习模板样式之后,我们还会进一步学习这个控件的用法。

一、DataGrid定义

public class DataGrid : MultiSelector
{
    public static readonly DependencyProperty CanUserResizeColumnsProperty;
    public static readonly DependencyProperty CurrentItemProperty;
    public static readonly DependencyProperty CurrentColumnProperty;
    public static readonly DependencyProperty CurrentCellProperty;
    public static readonly DependencyProperty CanUserAddRowsProperty;
    public static readonly DependencyProperty CanUserDeleteRowsProperty;
    public static readonly DependencyProperty RowDetailsVisibilityModeProperty;
    public static readonly DependencyProperty AreRowDetailsFrozenProperty;
    public static readonly DependencyProperty RowDetailsTemplateProperty;
    public static readonly DependencyProperty RowDetailsTemplateSelectorProperty;
    public static readonly DependencyProperty CanUserResizeRowsProperty;
    public static readonly DependencyProperty NewItemMarginProperty;
    public static readonly DependencyProperty SelectionModeProperty;
    public static readonly DependencyProperty SelectionUnitProperty;
    public static readonly DependencyProperty CanUserSortColumnsProperty;
    public static readonly DependencyProperty AutoGenerateColumnsProperty;
    public static readonly DependencyProperty FrozenColumnCountProperty;
    public static readonly DependencyProperty NonFrozenColumnsViewportHorizontalOffsetProperty;
    public static readonly DependencyProperty EnableColumnVirtualizationProperty;
    public static readonly DependencyProperty CanUserReorderColumnsProperty;
    public static readonly DependencyProperty DragIndicatorStyleProperty;
    public static readonly DependencyProperty DropLocationIndicatorStyleProperty;
    public static readonly DependencyProperty ClipboardCopyModeProperty;
    public static readonly DependencyProperty CellsPanelHorizontalOffsetProperty;
    public static readonly DependencyProperty IsReadOnlyProperty;
    public static readonly RoutedCommand CancelEditCommand;
    public static readonly DependencyProperty EnableRowVirtualizationProperty;
    public static readonly RoutedCommand BeginEditCommand;
    public static readonly RoutedCommand CommitEditCommand;
    public static readonly DependencyProperty ColumnWidthProperty;
    public static readonly DependencyProperty MinColumnWidthProperty;
    public static readonly DependencyProperty MaxColumnWidthProperty;
    public static readonly DependencyProperty HorizontalGridLinesBrushProperty;
    public static readonly DependencyProperty VerticalGridLinesBrushProperty;
    public static readonly DependencyProperty RowStyleProperty;
    public static readonly DependencyProperty RowValidationErrorTemplateProperty;
    public static readonly DependencyProperty RowStyleSelectorProperty;
    public static readonly DependencyProperty RowBackgroundProperty;
    public static readonly DependencyProperty AlternatingRowBackgroundProperty;
    public static readonly DependencyProperty RowHeightProperty;
    public static readonly DependencyProperty GridLinesVisibilityProperty;
    public static readonly DependencyProperty RowHeaderWidthProperty;
    public static readonly DependencyProperty VerticalScrollBarVisibilityProperty;
    public static readonly DependencyProperty MinRowHeightProperty;
    public static readonly DependencyProperty HorizontalScrollBarVisibilityProperty;
    public static readonly DependencyProperty RowHeaderTemplateProperty;
    public static readonly DependencyProperty RowHeaderStyleProperty;
    public static readonly DependencyProperty RowHeaderTemplateSelectorProperty;
    public static readonly DependencyProperty CellStyleProperty;
    public static readonly DependencyProperty HeadersVisibilityProperty;
    public static readonly DependencyProperty ColumnHeaderHeightProperty;
    public static readonly DependencyProperty RowHeaderActualWidthProperty;
    public static readonly DependencyProperty ColumnHeaderStyleProperty;

    public DataGrid();

    public static ComponentResourceKey FocusBorderBrushKey { get; }
    public static RoutedUICommand SelectAllCommand { get; }
    public static IValueConverter HeadersVisibilityConverter { get; }
    public static IValueConverter RowDetailsScrollingConverter { get; }
    public static RoutedUICommand DeleteCommand { get; }
    public DataTemplate RowHeaderTemplate { get; set; }
    public DataTemplateSelector RowHeaderTemplateSelector { get; set; }
    public ScrollBarVisibility VerticalScrollBarVisibility { get; set; }
    public ScrollBarVisibility HorizontalScrollBarVisibility { get; set; }
    public bool CanUserAddRows { get; set; }
    public object CurrentItem { get; set; }
    public DataGridColumn CurrentColumn { get; set; }
    public DataGridCellInfo CurrentCell { get; set; }
    public bool CanUserDeleteRows { get; set; }
    public Style RowHeaderStyle { get; set; }
    public DataGridRowDetailsVisibilityMode RowDetailsVisibilityMode { get; set; }
    public bool IsReadOnly { get; set; }
    public Style ColumnHeaderStyle { get; set; }
    public Style RowStyle { get; set; }
    public DataGridHeadersVisibility HeadersVisibility { get; set; }
    public bool AreRowDetailsFrozen { get; set; }
    public Brush AlternatingRowBackground { get; set; }
    public Brush RowBackground { get; set; }
    public StyleSelector RowStyleSelector { get; set; }
    public ObservableCollection<ValidationRule> RowValidationRules { get; }
    public ControlTemplate RowValidationErrorTemplate { get; set; }
    public Brush VerticalGridLinesBrush { get; set; }
    public Brush HorizontalGridLinesBrush { get; set; }
    public DataGridGridLinesVisibility GridLinesVisibility { get; set; }
    public double MaxColumnWidth { get; set; }
    public double MinColumnWidth { get; set; }
    public DataGridLength ColumnWidth { get; set; }
    public bool CanUserResizeColumns { get; set; }
    public ObservableCollection<DataGridColumn> Columns { get; }
    public double RowHeaderWidth { get; set; }
    public double RowHeaderActualWidth { get; }
    public double ColumnHeaderHeight { get; set; }
    public Style CellStyle { get; set; }
    public DataTemplate RowDetailsTemplate { get; set; }
    public double MinRowHeight { get; set; }
    public bool CanUserResizeRows { get; set; }
    public double RowHeight { get; set; }
    public DataTemplateSelector RowDetailsTemplateSelector { get; set; }
    public double CellsPanelHorizontalOffset { get; }
    public DataGridClipboardCopyMode ClipboardCopyMode { get; set; }
    public Style DropLocationIndicatorStyle { get; set; }
    public bool CanUserReorderColumns { get; set; }
    public bool EnableColumnVirtualization { get; set; }
    public bool EnableRowVirtualization { get; set; }
    public Style DragIndicatorStyle { get; set; }
    public double NonFrozenColumnsViewportHorizontalOffset { get; }
    public int FrozenColumnCount { get; set; }
    public bool AutoGenerateColumns { get; set; }
    public Thickness NewItemMargin { get; }
    public bool CanUserSortColumns { get; set; }
    public DataGridSelectionUnit SelectionUnit { get; set; }
    public DataGridSelectionMode SelectionMode { get; set; }
    public IList<DataGridCellInfo> SelectedCells { get; }
    protected internal override bool HandlesScrolling { get; }

    public event DataGridSortingEventHandler Sorting;
    public event EventHandler AutoGeneratedColumns;
    public event EventHandler<DataGridAutoGeneratingColumnEventArgs> AutoGeneratingColumn;
    public event EventHandler<DragDeltaEventArgs> ColumnHeaderDragDelta;
    public event EventHandler<DragStartedEventArgs> ColumnHeaderDragStarted;
    public event EventHandler<DragCompletedEventArgs> ColumnHeaderDragCompleted;
    public event SelectedCellsChangedEventHandler SelectedCellsChanged;
    public event EventHandler<DataGridColumnReorderingEventArgs> ColumnReordering;
    public event EventHandler<DataGridRowDetailsEventArgs> RowDetailsVisibilityChanged;
    public event EventHandler<DataGridRowEventArgs> UnloadingRow;
    public event EventHandler<DataGridRowDetailsEventArgs> LoadingRowDetails;
    public event InitializingNewItemEventHandler InitializingNewItem;
    public event EventHandler<DataGridPreparingCellForEditEventArgs> PreparingCellForEdit;
    public event EventHandler<DataGridBeginningEditEventArgs> BeginningEdit;
    public event EventHandler<EventArgs> CurrentCellChanged;
    public event EventHandler<DataGridCellEditEndingEventArgs> CellEditEnding;
    public event EventHandler<DataGridRowEditEndingEventArgs> RowEditEnding;
    public event EventHandler<DataGridRowEventArgs> LoadingRow;
    public event EventHandler<DataGridColumnEventArgs> ColumnDisplayIndexChanged;
    public event EventHandler<DataGridRowDetailsEventArgs> UnloadingRowDetails;
    public event EventHandler<AddingNewItemEventArgs> AddingNewItem;
    public event EventHandler<DataGridRowClipboardEventArgs> CopyingRowClipboardContent;
    public event EventHandler<DataGridColumnEventArgs> ColumnReordered;

    public static Collection<DataGridColumn> GenerateColumns(IItemProperties itemProperties);
    public bool BeginEdit();
    public bool BeginEdit(RoutedEventArgs editingEventArgs);
    public bool CancelEdit();
    public bool CancelEdit(DataGridEditingUnit editingUnit);
    public void ClearDetailsVisibilityForItem(object item);
    public DataGridColumn ColumnFromDisplayIndex(int displayIndex);
    public bool CommitEdit();
    public bool CommitEdit(DataGridEditingUnit editingUnit, bool exitEditingMode);
    public Visibility GetDetailsVisibilityForItem(object item);
    public override void OnApplyTemplate();
    public void ScrollIntoView(object item);
    public void ScrollIntoView(object item, DataGridColumn column);
    public void SelectAllCells();
    public void SetDetailsVisibilityForItem(object item, Visibility detailsVisibility);
    public void UnselectAllCells();
    protected override void ClearContainerForItemOverride(DependencyObject element, object item);
    protected override DependencyObject GetContainerForItemOverride();
    protected override bool IsItemItsOwnContainerOverride(object item);
    protected override Size MeasureOverride(Size availableSize);
    protected virtual void OnAddingNewItem(AddingNewItemEventArgs e);
    protected virtual void OnAutoGeneratedColumns(EventArgs e);
    protected virtual void OnAutoGeneratingColumn(DataGridAutoGeneratingColumnEventArgs e);
    protected virtual void OnBeginningEdit(DataGridBeginningEditEventArgs e);
    protected virtual void OnCanExecuteBeginEdit(CanExecuteRoutedEventArgs e);
    protected virtual void OnCanExecuteCancelEdit(CanExecuteRoutedEventArgs e);
    protected virtual void OnCanExecuteCommitEdit(CanExecuteRoutedEventArgs e);
    protected virtual void OnCanExecuteCopy(CanExecuteRoutedEventArgs args);
    protected virtual void OnCanExecuteDelete(CanExecuteRoutedEventArgs e);
    protected virtual void OnCellEditEnding(DataGridCellEditEndingEventArgs e);
    protected override void OnContextMenuOpening(ContextMenuEventArgs e);
    protected virtual void OnCopyingRowClipboardContent(DataGridRowClipboardEventArgs args);
    protected override AutomationPeer OnCreateAutomationPeer();
    protected virtual void OnCurrentCellChanged(EventArgs e);
    protected virtual void OnExecutedBeginEdit(ExecutedRoutedEventArgs e);
    protected virtual void OnExecutedCancelEdit(ExecutedRoutedEventArgs e);
    protected virtual void OnExecutedCommitEdit(ExecutedRoutedEventArgs e);
    protected virtual void OnExecutedCopy(ExecutedRoutedEventArgs args);
    protected virtual void OnExecutedDelete(ExecutedRoutedEventArgs e);
    protected virtual void OnInitializingNewItem(InitializingNewItemEventArgs e);
    protected override void OnIsMouseCapturedChanged(DependencyPropertyChangedEventArgs e);
    protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e);
    protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue);
    protected override void OnKeyDown(KeyEventArgs e);
    protected virtual void OnLoadingRow(DataGridRowEventArgs e);
    protected virtual void OnLoadingRowDetails(DataGridRowDetailsEventArgs e);
    protected override void OnMouseMove(MouseEventArgs e);
    protected virtual void OnRowEditEnding(DataGridRowEditEndingEventArgs e);
    protected virtual void OnSelectedCellsChanged(SelectedCellsChangedEventArgs e);
    protected override void OnSelectionChanged(SelectionChangedEventArgs e);
    protected virtual void OnSorting(DataGridSortingEventArgs eventArgs);
    protected override void OnTemplateChanged(ControlTemplate oldTemplate, ControlTemplate newTemplate);
    protected override void OnTextInput(TextCompositionEventArgs e);
    protected virtual void OnUnloadingRow(DataGridRowEventArgs e);
    protected virtual void OnUnloadingRowDetails(DataGridRowDetailsEventArgs e);
    protected override void PrepareContainerForItemOverride(DependencyObject element, object item);
    protected internal virtual void OnColumnDisplayIndexChanged(DataGridColumnEventArgs e);
    protected internal virtual void OnColumnHeaderDragCompleted(DragCompletedEventArgs e);
    protected internal virtual void OnColumnHeaderDragDelta(DragDeltaEventArgs e);
    protected internal virtual void OnColumnHeaderDragStarted(DragStartedEventArgs e);
    protected internal virtual void OnColumnReordered(DataGridColumnEventArgs e);
    protected internal virtual void OnColumnReordering(DataGridColumnReorderingEventArgs e);
    protected internal virtual void OnPreparingCellForEdit(DataGridPreparingCellForEditEventArgs e);
    protected internal virtual void OnRowDetailsVisibilityChanged(DataGridRowDetailsEventArgs e);

}

二、属性分析

DataGrid提供了大量的依赖属性,合理充分利用这些属性,在开发ERP、CMS、报表等软件时可达到事半功倍的效果。下面我们以表格的形式,先了解一下各属性的功能,然后在本节中学习一些基础属性,以掌握该控件的基本用法,剩下的属性放到模板样式的章节中学习。

属性名称说明备注
FocusBorderBrushKey获取引用焦点的单元格的默认边框画笔的键。
SelectAllCommand表示指示想要选择的所有单元格的命令
HeadersVisibilityConverter获取标题显示隐藏的转换器,即HeadersVisibility属性的转换器
RowDetailsScrollingConverter获取将转换为一个布尔值转换器
DeleteCommand表示指示想要删除当前行的命令。
RowHeaderTemplate获取或设置行标题的模板。重要
RowHeaderTemplateSelector获取或设置行标题的模板选择器。
VerticalScrollBarVisibility是否显示垂直滚动条
HorizontalScrollBarVisibility是否显示水平滚动条
CanUserAddRows是否可以添加新行重要
CurrentItem当前选中行(一般指绑定的数据源的某一个元素)常用
CurrentColumn获取或设置包含当前单元格的列。
CurrentCell获取或设置具有焦点的单元格。
CanUserDeleteRows是否可以删除行重要
RowHeaderStyle获取或设置应用于所有行标题的样式。重要
RowDetailsVisibilityMode获取或设置一个值,该值指示何时显示某行的详细信息部分。
IsReadOnly当前控件是否只读常用
ColumnHeaderStyle获取或设置所有列标题的样式重要
RowStyle获取或设置应用到的所有行的样式。重要
HeadersVisibility获取或设置用于指定行和列标题的可见性的值。
AreRowDetailsFrozen获取或设置一个值,该值指示是否可水平滚动行详细信息。
AlternatingRowBackground获取或设置交替行上使用的背景画笔。重要
RowBackground获取或设置用于行背景的默认画笔。
RowStyleSelector获取或设置行的样式选择器。
RowValidationRules获取用于验证每个行中的数据的规则。
RowValidationErrorTemplate获取或设置用于以可视方式指示行验证中的错误的模板。
VerticalGridLinesBrush获取或设置用于绘制垂直网格线的画笔。常用
HorizontalGridLinesBrush获取或设置用于绘制水平网格线的画笔。
GridLinesVisibility获取或设置一个值,该值指示显示哪些网格线。
MaxColumnWidth获取或设置列和标头中的最大宽度约束
MinColumnWidth获取或设置列和标头中的最小宽度约束
ColumnWidth获取或设置标准宽度和列和中的标头的大小调整模式
CanUserResizeColumns获取或设置用户是否可以通过使用鼠标调整列的宽度。
Columns获取一个集合中的所有列常用
RowHeaderWidth获取或设置行标题列的宽度。
RowHeaderActualWidth获取呈现的行标题列的宽度。
ColumnHeaderHeight获取或设置列标题行的高度。
CellStyle获取或设置所有单元格的样式常用
RowDetailsTemplate获取或设置用于显示行详细信息的模板。
MinRowHeight获取或设置行和中的标头的最小高度约束
CanUserResizeRows获取或设置用户是否可以通过使用鼠标调整行的高度。
RowHeight获取或设置的所有行的建议的高度。
RowDetailsTemplateSelector获取或设置用于行详细信息的模板选择器。
CellsPanelHorizontalOffset获取DataGridCellsPanel的水平偏移量
ClipboardCopyMode获取或设置一个值,指示如何将内容复制到剪贴板。
NonFrozenColumns
ViewportHorizontalOffset
获取在视区的可滚动列的水平偏移量。
FrozenColumnCount获取或设置非滚动列的数量。常用
AutoGenerateColumns获取或设置一个值,该值指示是否自动创建列。常用
NewItemMargin获取或设置新的项目行的边距。
CanUserSortColumns是否可以单击列标题来对列排序。常用
SelectionUnit选择行的模式
SelectionMode是否支持多选重要
SelectedCells获取当前选定的单元格的列表。
HandlesScrolling是否支持自定义键盘滚动。

在上述表格中,Columns属性是DataGrid最基本的一个属性。它是一个ObservableCollection<DataGridColumn>类型的集合,表示DataGrid的列的集合。其实DataGridColumn只是一个抽象基类,我们真正在实例化时,是实例化DataGridColumn的子类,然后放到Columns属性中。

那么DataGridColumn有哪些子类呢?

  • DataGridTextColumn 表示文本内容的列
  • DataGridCheckBoxColumn 表示复选框的列
  • DataGridComboBoxColumn 表示下拉框的列
  • DataGridTemplateColumn 表示模板的列(万金油)

在本列中,我们将以最简单的DataGridTextColumn 为例。

三、事件成员

DataGrid一共有23个事件成员,它们分别如下所示

事件名称说明
Sorting对列进行排序时发生。
AutoGeneratedColumns所有列的自动生成完成后发生。
AutoGeneratingColumn自动生成单独的列时出现。
ColumnHeaderDragDelta每次鼠标位置发生更改时在用户拖动列标题时发生。
ColumnHeaderDragStarted当用户开始使用鼠标拖动列标题时发生。
ColumnHeaderDragCompleted当用户使用鼠标拖动后释放列标题时发生。
SelectedCellsChanged发生时 DataGrid.SelectedCells 集合更改。
ColumnReordering在列移至的显示顺序中的新位置之前发生。
RowDetailsVisibilityChanged当某一行的可见性详细信息元素更改时发生。
UnloadingRow发生时 DataGridRow 对象将成为可供重用。
LoadingRowDetails新的行的详细信息模板应用于行时发生。
InitializingNewItem创建一个新项时出现。
PreparingCellForEdit在单元格进入编辑模式时发生。
BeginningEdit发生行或单元格进入编辑模式之前。
CurrentCellChanged在 DataGrid.CurrentCell 属性的值更改后发生。
CellEditEnding在单元格的编辑将在提交或取消前发生。
RowEditEnding在提交或取消行编辑之前发生。
LoadingRow加载row时
ColumnDisplayIndexChanged其中一个列更改属性时
UnloadingRowDetails行详细信息元素成为可供重用时发生。
AddingNewItem新项添加到DataGrid之前发生
CopyingRowClipboardContent默认行内容准备好之后发生。
ColumnReordered在列移至的显示顺序中的新位置时发生。

四、基本示例

前端代码

<Window x:Class="HelloWorld.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:HelloWorld" 
        xmlns:forms="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
        mc:Ignorable="d" FontSize="14"
        Title="WPF中文网之控件课程 - www.wpfsoft.com" Height="350" Width="500">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition Width="200"/>
        </Grid.ColumnDefinitions>
        <DataGrid x:Name="datagrid" 
                  SelectionMode="Extended"
                  IsReadOnly="True" 
                  SelectionChanged="datagrid_Selected">
            <DataGrid.Columns>
                <DataGridTextColumn Header="姓名" Binding="{Binding Name}" />
                <DataGridTextColumn Header="年龄" Binding="{Binding Age}" />
                <DataGridTextColumn Header="地址" Binding="{Binding Address}" />
            </DataGrid.Columns>
        </DataGrid>
        
        <StackPanel Grid.Column="1">
            <StackPanel Orientation="Horizontal" Margin="5">
                <TextBlock Text="姓名:"/>
                <TextBlock x:Name="_TextBlockName"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal" Margin="5">
                <TextBlock Text="年龄:"/>
                <TextBlock x:Name="_TextBlockAge"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal" Margin="5">
                <TextBlock Text="地址:"/>
                <TextBlock x:Name="_TextBlockAddress"/>
            </StackPanel>
        </StackPanel>
    </Grid>
</Window>

后端代码

namespace HelloWorld
{
    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public string Address { get; set; }

    }

    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            
            datagrid.Items.Add(new Person { Name = "张三", Age = 22, Address = "广东省廉江市车板镇大坝村" });
            datagrid.Items.Add(new Person { Name = "李四", Age = 23, Address = "江西省景德镇市市辖区" });
            datagrid.Items.Add(new Person { Name = "王五", Age = 24, Address = "上海市虹口区" });
        }

        private void datagrid_Selected(object sender, RoutedEventArgs e)
        {
            DataGrid datagrid = sender as DataGrid;
            if (datagrid == null) return;

            var person = datagrid.SelectedItem as Person;
            if (person == null) return;

            _TextBlockName.Text = person.Name;
            _TextBlockAge.Text = person.Age + "岁";
            _TextBlockAddress.Text = person.Address;
        }
    }
}

在这个例子中,我们尽量还原了与ListView控件一致的功能, 需要注意的细节是:我们将DataGrid的IsReadOnly="True",这是因为我们直接将数据元素一条一条的加入到DataGrid的Items属性中,而Items属性本身是一个只读属性,不支持写入。这样的话,当鼠标双击时会报错。错误提示为:

如何解决这个问题呢?这就要用到ItemsControl基类中的ItemsSource数据源属性。

我们需要采用DataGrid另外一种赋值方式——数据源赋值。即把一个集合绑定到该属性上,这样在前端就可以编辑数据源,从而不会引发报错。

只需要改动一点点代码。

List<Person> list = new List<Person>();

list.Add(new Person { Name = "张三", Age = 22, Address = "广东省廉江市车板镇大坝村" });
list.Add(new Person { Name = "李四", Age = 23, Address = "江西省景德镇市市辖区" });
list.Add(new Person { Name = "王五", Age = 24, Address = "上海市虹口区" });

datagrid.ItemsSource = list;

前端代码也可以修改如下,AutoGenerateColumns属性设为不可自动创建列。

<DataGrid x:Name="datagrid" 
          SelectionMode="Extended"
          IsReadOnly="False"
          AutoGenerateColumns="False"
          SelectionChanged="datagrid_Selected">
    <DataGrid.Columns>
        <DataGridTextColumn Header="姓名" Binding="{Binding Name}" />
        <DataGridTextColumn Header="年龄" Binding="{Binding Age}" />
        <DataGridTextColumn Header="地址" Binding="{Binding Address}" />
    </DataGrid.Columns>
</DataGrid>

如此,我们便可以在DataGrid中新增一行,并输入新的数据。

本节内容我们用了两个例子来说明datagrid最基本的数据加载、显示与获取的功能,请在下面的地址中下载示例。

当前课程源码下载:(注明:本站所有源代码请按标题搜索)

文件名:041-《DataGrid数据表格控件》-源代码 -1,041-《DataGrid数据表格控件》-源代码 -2
链接:https://pan.baidu.com/s/1yu-q4tUtl0poLVgmcMfgBA
提取码:wpff

——重庆教主 2023年9月6日

ComboBox表示带有下拉列表的控件,实际上您可以把它看成两个部分组成,一个类似TextBox文本输入框,所以它有一个Text文本属性,用于获取ComboBox框的文本值,另一个是类似ListBox的列表框,用于显示ComboBox绑定的所有数据源。

ComboBox继承于Selector,所以,它只能是单选操作。由于这个控件由两个部分构成,所以在用法上,我们也可以有两种主要用法——类似TextBox用法和类似ListBox用法。

我们在使用这个控件之前,先熟悉一下它的定义

一、ComboBox定义

[Localizability(LocalizationCategory.ComboBox)]
    [StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(ComboBoxItem))]
    [TemplatePart(Name = "PART_EditableTextBox", Type = typeof(TextBox))]
    [TemplatePart(Name = "PART_Popup", Type = typeof(Popup))]
public class ComboBox : Selector
{
    public static readonly DependencyProperty MaxDropDownHeightProperty;
    public static readonly DependencyProperty IsDropDownOpenProperty;
    public static readonly DependencyProperty ShouldPreserveUserEnteredPrefixProperty;
    public static readonly DependencyProperty IsEditableProperty;
    public static readonly DependencyProperty TextProperty;
    public static readonly DependencyProperty IsReadOnlyProperty;
    public static readonly DependencyProperty SelectionBoxItemProperty;
    public static readonly DependencyProperty SelectionBoxItemTemplateProperty;
    public static readonly DependencyProperty SelectionBoxItemStringFormatProperty;
    public static readonly DependencyProperty StaysOpenOnEditProperty;

    public ComboBox();

    public bool ShouldPreserveUserEnteredPrefix { get; set; }
    public bool IsEditable { get; set; }
    public string Text { get; set; }
    public bool IsReadOnly { get; set; }
    public object SelectionBoxItem { get; }
    public double MaxDropDownHeight { get; set; }
    public string SelectionBoxItemStringFormat { get; }
    public bool StaysOpenOnEdit { get; set; }
    public bool IsSelectionBoxHighlighted { get; }
    public bool IsDropDownOpen { get; set; }
    public DataTemplate SelectionBoxItemTemplate { get; }
    protected internal override bool HandlesScrolling { get; }
    protected internal override bool HasEffectiveKeyboardFocus { get; }

    public event EventHandler DropDownClosed;
    public event EventHandler DropDownOpened;

    public override void OnApplyTemplate();
    protected override DependencyObject GetContainerForItemOverride();
    protected override bool IsItemItsOwnContainerOverride(object item);
    protected override AutomationPeer OnCreateAutomationPeer();
    protected virtual void OnDropDownClosed(EventArgs e);
    protected virtual void OnDropDownOpened(EventArgs e);
    protected override void OnIsKeyboardFocusWithinChanged(DependencyPropertyChangedEventArgs e);
    protected override void OnIsMouseCapturedChanged(DependencyPropertyChangedEventArgs e);
    protected override void OnKeyDown(KeyEventArgs e);
    protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e);
    protected override void OnPreviewKeyDown(KeyEventArgs e);
    protected override void OnSelectionChanged(SelectionChangedEventArgs e);
    protected override void PrepareContainerForItemOverride(DependencyObject element, object item);

}

二、属性成员

属性名称说明
ShouldPreserveUserEnteredPrefix是否保留用户的输入,或者输入替换匹配项。
IsEditable是否启用或禁用编辑文本框中文本
Text获取或设置当前选定项的文本。
IsReadOnly文本内容是否只读
SelectionBoxItem获取在选择框中显示的项。
MaxDropDownHeight获取或设置一个组合框下拉列表的最大高度。
SelectionBoxItemStringFormat指定选择框中文本的显示格式
StaysOpenOnEdit在编辑输入框文本时,希望下拉框保持打开,则为true
IsSelectionBoxHighlighted是否突出显示SelectionBoxItem
IsDropDownOpen是否打开组合框下拉列表。
SelectionBoxItemTemplate获取选择框内容的项模板。

接下来,我们还是一个实际的例子来说明combobox控件的用法。

三、ComboBox示例

前端代码

<Window x:Class="HelloWorld.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:HelloWorld" 
        xmlns:forms="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
        mc:Ignorable="d" FontSize="14"
        Title="WPF中文网之控件课程 - www.wpfsoft.com" Height="350" Width="500">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition Width="200"/>
        </Grid.ColumnDefinitions>
        <StackPanel>
            <ComboBox x:Name="combobox1" IsEditable="True"  Height="30" Margin="20,10" 
                      TextBoxBase.TextChanged="combobox1_TextChanged"/>
            <ComboBox x:Name="combobox2" StaysOpenOnEdit="True" VerticalAlignment="Top" 
                      SelectionChanged="combobox2_SelectionChanged"
                      Height="30" Margin="20,10" DisplayMemberPath="Name">
            </ComboBox>
        </StackPanel>
        
        
        <StackPanel Grid.Column="1">
            <StackPanel Orientation="Horizontal" Margin="5">
                <TextBlock Text="电话:"/>
                <TextBlock x:Name="_TextBlockTel"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal" Margin="5">
                <TextBlock Text="姓名:"/>
                <TextBlock x:Name="_TextBlockName"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal" Margin="5">
                <TextBlock Text="年龄:"/>
                <TextBlock x:Name="_TextBlockAge"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal" Margin="5">
                <TextBlock Text="地址:"/>
                <TextBlock x:Name="_TextBlockAddress"/>
            </StackPanel>
        </StackPanel>
    </Grid>
</Window>

后端代码

namespace HelloWorld
{
    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public string Address { get; set; }

    }

    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {        
        public MainWindow()
        {
            InitializeComponent();

            List<Person> list = new List<Person>();

            list.Add(new Person { Name = "张三", Age = 22, Address = "广东省廉江市车板镇大坝村" });
            list.Add(new Person { Name = "李四", Age = 23, Address = "江西省景德镇市市辖区" });
            list.Add(new Person { Name = "王五", Age = 24, Address = "上海市虹口区" });

            combobox2.ItemsSource = list;
        }

        private void combobox1_TextChanged(object sender, TextChangedEventArgs e)
        {
            _TextBlockTel.Text = combobox1.Text;
        }

        private void combobox2_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            ComboBox combobox = sender as ComboBox;
            if (combobox == null) return;

            var person = combobox.SelectedItem as Person;
            if (person == null) return;

            _TextBlockName.Text = person.Name;
            _TextBlockAge.Text = person.Age + "岁";
            _TextBlockAddress.Text = person.Address;
        }
    }
}

我们在xaml中实例化了两个ComboBox,第一个直接当成了TextBox来使用;第二个则绑定了一个数据源,并在Xaml中指定了DisplayMemberPath属性显示Person的Name,最后在后端代码中,依然使用SelectedItem 属性获取当前选中项,转化成Person,以获取实际的选中数据。

这些就是该控件的基本用法。

当前课程源码下载:(注明:本站所有源代码请按标题搜索)

文件名:042-《ComboBox下拉框控件》-源代码
链接:https://pan.baidu.com/s/1yu-q4tUtl0poLVgmcMfgBA
提取码:wpff

——重庆教主 2023年9月6日

TabControl表示包含多个共享相同的空间在屏幕上的项的控件。它也是继承于Selector基类,所以TabControl也只支持单选操作。另外,TabControl的元素只能是TabItem,这个TabItem继承于HeaderedContentControl类,所以TabControl的元素实际上是一个带标题的ContentControl内容控件。

我们曾经在聊GroupBox控件Expander折叠控件时都曾提到过这个HeaderedContentControl类,原来大家都用了这个带标题的内容控件。所以TabControl控件看起来就像是多个GroupBox组合而来。

一、TabControl的定义

public class TabControl : Selector
{
    public static readonly DependencyProperty TabStripPlacementProperty;
    public static readonly DependencyProperty SelectedContentProperty;
    public static readonly DependencyProperty SelectedContentTemplateProperty;
    public static readonly DependencyProperty SelectedContentTemplateSelectorProperty;
    public static readonly DependencyProperty SelectedContentStringFormatProperty;
    public static readonly DependencyProperty ContentTemplateProperty;
    public static readonly DependencyProperty ContentTemplateSelectorProperty;
    public static readonly DependencyProperty ContentStringFormatProperty;

    public TabControl();

    public DataTemplate ContentTemplate { get; set; }
    public string SelectedContentStringFormat { get; }
    public DataTemplateSelector SelectedContentTemplateSelector { get; }
    public DataTemplate SelectedContentTemplate { get; }
    public object SelectedContent { get; }
    public Dock TabStripPlacement { get; set; }
    public string ContentStringFormat { get; set; }
    public DataTemplateSelector ContentTemplateSelector { get; set; }

    public override void OnApplyTemplate();
    protected override DependencyObject GetContainerForItemOverride();
    protected override bool IsItemItsOwnContainerOverride(object item);
    protected override AutomationPeer OnCreateAutomationPeer();
    protected override void OnInitialized(EventArgs e);
    protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e);
    protected override void OnKeyDown(KeyEventArgs e);
    protected override void OnSelectionChanged(SelectionChangedEventArgs e);

}

二、属性成员

属性名称 说明
ContentTemplate表示TabItem元素的内容模板
SelectedContentStringFormat当前所选内容的格式
SelectedContentTemplateSelector获取当前选定的TabItem项的模板选择器
SelectedContentTemplate当前选定的TabItem项的模板
SelectedContent当前选定的TabItem项里面的内容(也是一些控件)
TabStripPlacement获取或设置选项卡标题相对于选项卡上内容的对齐方式。
ContentStringFormat指定如何设置内容的格式
ContentTemplateSelector获取或设置内容模板选择器

TabControl的SelectedContent可能是我们比较常用的一个属性,事实上,TabControl通常被当成布局控件来使用。

三、TabControl示例

前端代码

<Window x:Class="HelloWorld.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:HelloWorld" 
        xmlns:forms="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
        mc:Ignorable="d" FontSize="14"
        Title="WPF中文网之控件课程 - www.wpfsoft.com" Height="350" Width="500">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="50"/>
        </Grid.RowDefinitions>
        <TabControl x:Name="_tabControl" Grid.Row="0" SelectionChanged="_tabControl_SelectionChanged">
            <TabItem Header="首页">
                <Border Background="LightBlue">
                    <TextBlock Text="首页的界面" FontSize="24" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                </Border>
            </TabItem>
            <TabItem Header="WPF目录">
                <Border Background="LightCoral">
                    <TextBlock Text="WPF目录的界面" FontSize="24" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                </Border>
            </TabItem>
            <TabItem Header="官方文档">
                <Border Background="LightCyan">
                    <TextBlock Text="官方文档的界面" FontSize="24" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                </Border>
            </TabItem>
            <TabItem Header="付费课程">
                <Border Background="LightGoldenrodYellow">
                    <TextBlock Text="付费课程的界面" FontSize="24" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                </Border>
            </TabItem>
            <TabItem Header="统计">
                <Border Background="LightGreen">
                    <TextBlock Text="统计的界面" FontSize="24" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                </Border>
            </TabItem>
        </TabControl>
        <TextBlock x:Name="_textBlock" TextWrapping="Wrap" Grid.Row="1"/>
    </Grid>
</Window>

后端代码

public partial class MainWindow : Window
{        
    public MainWindow()
    {
        InitializeComponent(); 
    }

    private void _tabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        var tab = sender as TabControl;
        var item = tab.SelectedItem as TabItem;
        var content = tab.SelectedContent;
        _textBlock.Text = "标题:" + item.Header.ToString() + " 内容:" + content;
    }
}

我们订阅了TabControl控件的SelectionChanged事件,并在回调函数中获取了当前选中的TabItem对象以及它里面的内容。

当前课程源码下载:(注明:本站所有源代码请按标题搜索)

文件名:043-《TabControl控件》-源代码
链接:https://pan.baidu.com/s/1yu-q4tUtl0poLVgmcMfgBA
提取码:wpff

——重庆教主 2023年9月6日

TreeView其实是一个比较复杂的控件,像操作系统的资源管理器就是一个TreeView。所以它常用于显示文件夹、目录等具有层级结构的数据。TreeView由节点和分支构成,每个节点可以包含零个或多个子节点,分支表示父子关系。在TreeView中,每个节点表示为TreeViewItem对象,可以通过TreeView的Items属性来获取或设置TreeViewItem对象集合。

在使用TreeView加载节点时,需要掌握一些递归思想。

一、TreeViewItem元素简介

TreeViewItem作为TreeView唯一的元素类型,它继承于HeaderedItemsControl(带标题),而HeaderedItemsControl又继承于ItemsControl,由此可见,TreeViewItem元素本身也是一个集合控件。

TreeViewItem有两个常用的属性,分别是IsSelected属性和IsExpanded属性,IsSelected表示当前元素是否选中,IsExpanded表示当前元素是否展开。

二、TreeView类的定义

public class TreeView : ItemsControl
{
    public static readonly DependencyProperty SelectedItemProperty;
    public static readonly DependencyProperty SelectedValueProperty;
    public static readonly DependencyProperty SelectedValuePathProperty;
    public static readonly RoutedEvent SelectedItemChangedEvent;

    public TreeView();

    public string SelectedValuePath { get; set; }
    public object SelectedValue { get; }
    public object SelectedItem { get; }
    protected internal override bool HandlesScrolling { get; }

    public event RoutedPropertyChangedEventHandler<object> SelectedItemChanged;

    protected virtual bool ExpandSubtree(TreeViewItem container);
    protected override DependencyObject GetContainerForItemOverride();
    protected override bool IsItemItsOwnContainerOverride(object item);
    protected override AutomationPeer OnCreateAutomationPeer();
    protected override void OnGotFocus(RoutedEventArgs e);
    protected override void OnIsKeyboardFocusWithinChanged(DependencyPropertyChangedEventArgs e);
    protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e);
    protected override void OnKeyDown(KeyEventArgs e);
    protected virtual void OnSelectedItemChanged(RoutedPropertyChangedEventArgs<object> e);

}

SelectedValuePath属性:获取或设置SelectedItem或SelectedValue的路径。

SelectedValue属性:获取SelectedItem的值

SelectedItem属性:获取当前选中的项

三、TreeView示例

接下来,我们以一个简单的示例,模仿操作系统的资源管理器的目录加载。

前端代码

<Window x:Class="HelloWorld.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:HelloWorld" 
        xmlns:forms="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
        mc:Ignorable="d" FontSize="14"
        Title="WPF中文网之控件课程 - www.wpfsoft.com" Height="350" Width="500">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="根目录" VerticalAlignment="Center" Margin="3"/>
            <TextBox x:Name="_TextBox" Width="380" Height="25" Margin="3"/>
            <Button Content="选择..." MinWidth="45" Margin="3" Click="Button_Click"/>
        </StackPanel>
        <TreeView x:Name="_TreeView" Grid.Row="1" SelectedItemChanged="_TreeView_SelectedItemChanged"/>
    </Grid>
</Window>

后端代码

public partial class MainWindow : Window
{        
    public MainWindow()
    {
        InitializeComponent();            
    }        

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        System.Windows.Forms.FolderBrowserDialog dialog = new System.Windows.Forms.FolderBrowserDialog();
        if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
        {
            _TextBox.Text = dialog.SelectedPath;
            LoadTreeView(dialog.SelectedPath);

        }
    }

    private void LoadTreeView(string rootPath)
    {
        // 设置根节点
        TreeViewItem rootNode = new TreeViewItem();
        rootNode.Header = "根目录";

        // 加载子文件夹和文件
        LoadSubDirectory(rootNode, rootPath);

        // 将根节点添加到TreeView中
        _TreeView.Items.Add(rootNode);
    }

    private void LoadSubDirectory(TreeViewItem node, string path)
    {
        try
        {
            DirectoryInfo dirInfo = new DirectoryInfo(path);

            // 加载子文件夹
            foreach (DirectoryInfo subDirInfo in dirInfo.GetDirectories())
            {
                TreeViewItem subNode = new TreeViewItem();
                subNode.Header = subDirInfo.Name;

                LoadSubDirectory(subNode, subDirInfo.FullName);

                node.Items.Add(subNode);
            }

            // 加载文件
            foreach (FileInfo fileInfo in dirInfo.GetFiles())
            {
                TreeViewItem subNode = new TreeViewItem();
                subNode.Header = fileInfo.Name;

                node.Items.Add(subNode);
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }

    private void _TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
    {
        // 获取选中的节点
        TreeViewItem selectedNode = _TreeView.SelectedItem as TreeViewItem;

        // 显示选中节点的Header
        if (selectedNode != null)
        {
            MessageBox.Show(selectedNode.Header.ToString());
        }
    }
}

首先,通过鼠标操作,选择TreeView的根目录,然后,利用DirectoryInfo获取当前所有目录,再利用递归调用,一层一层的获取所有子目录,最后以TreeViewItem元素一层层加载到控件中。

当前课程源码下载:(注明:本站所有源代码请按标题搜索)

文件名:044-《TreeView树控件》-源代码
链接:https://pan.baidu.com/s/1yu-q4tUtl0poLVgmcMfgBA
提取码:wpff

——重庆教主 2023年9月8日

Menu控件继承于MenuBase,而MenuBase继承于ItemsControl。所以学习Menu之前,要先了解一下MenuBase基类。它是一个抽象类,拥有一个ItemContainerTemplateSelector模板选择器,并重写了一些关于键盘和鼠标的方法。

Menu的子项必须为MenuItem。这个MenuItem和前面的TreeViewItem类似,拥有共同的HeaderedItemsControl父类,也就是说,MenuItem本身也是一个集合控件,若要以代码形式加载Menu的内容,也必须要掌握递归的加载思路。

在本节中,我们将以两种方式加载Menu的数据。但是在学习之前,先熟悉一下MenuItem元素,因为,实际上,我们主要是操作MenuItem元素。

一、MenuItem元素

public class MenuItem : HeaderedItemsControl, ICommandSource
{
    public static readonly RoutedEvent ClickEvent;
    public static readonly DependencyProperty UsesItemContainerTemplateProperty;
    public static readonly DependencyProperty ItemContainerTemplateSelectorProperty;
    public static readonly DependencyProperty IsSuspendingPopupAnimationProperty;
    public static readonly DependencyProperty IconProperty;
    public static readonly DependencyProperty InputGestureTextProperty;
    public static readonly DependencyProperty StaysOpenOnClickProperty;
    public static readonly DependencyProperty IsCheckedProperty;
    public static readonly DependencyProperty IsHighlightedProperty;
    public static readonly DependencyProperty IsCheckableProperty;
    public static readonly DependencyProperty IsPressedProperty;
    public static readonly DependencyProperty IsSubmenuOpenProperty;
    public static readonly DependencyProperty CommandTargetProperty;
    public static readonly DependencyProperty CommandParameterProperty;
    public static readonly DependencyProperty CommandProperty;
    public static readonly RoutedEvent SubmenuClosedEvent;
    public static readonly RoutedEvent SubmenuOpenedEvent;
    public static readonly RoutedEvent UncheckedEvent;
    public static readonly RoutedEvent CheckedEvent;
    public static readonly DependencyProperty RoleProperty;

    public MenuItem();

    public static ResourceKey SubmenuHeaderTemplateKey { get; }
    public static ResourceKey SubmenuItemTemplateKey { get; }
    public static ResourceKey SeparatorStyleKey { get; }
    public static ResourceKey TopLevelItemTemplateKey { get; }
    public static ResourceKey TopLevelHeaderTemplateKey { get; }
    public bool IsCheckable { get; set; }
    public object CommandParameter { get; set; }
    public IInputElement CommandTarget { get; set; }
    public bool IsSubmenuOpen { get; set; }
    public MenuItemRole Role { get; }
    public bool IsPressed { get; protected set; }
    public bool IsHighlighted { get; protected set; }
    public bool StaysOpenOnClick { get; set; }
    public string InputGestureText { get; set; }
    public object Icon { get; set; }
    public bool IsSuspendingPopupAnimation { get; }
    public ItemContainerTemplateSelector ItemContainerTemplateSelector { get; set; }
    public bool UsesItemContainerTemplate { get; set; }
    public bool IsChecked { get; set; }
    public ICommand Command { get; set; }
    protected override bool IsEnabledCore { get; }
    protected internal override bool HandlesScrolling { get; }

    public event RoutedEventHandler Unchecked;
    public event RoutedEventHandler Click;
    public event RoutedEventHandler Checked;
    public event RoutedEventHandler SubmenuClosed;
    public event RoutedEventHandler SubmenuOpened;

    public override void OnApplyTemplate();
    protected override DependencyObject GetContainerForItemOverride();
    protected override bool IsItemItsOwnContainerOverride(object item);
    protected override void OnAccessKey(AccessKeyEventArgs e);
    protected virtual void OnChecked(RoutedEventArgs e);
    protected virtual void OnClick();
    protected override AutomationPeer OnCreateAutomationPeer();
    protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e);
    protected override void OnInitialized(EventArgs e);
    protected override void OnIsKeyboardFocusWithinChanged(DependencyPropertyChangedEventArgs e);
    protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e);
    protected override void OnKeyDown(KeyEventArgs e);
    protected override void OnMouseEnter(MouseEventArgs e);
    protected override void OnMouseLeave(MouseEventArgs e);
    protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e);
    protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e);
    protected override void OnMouseMove(MouseEventArgs e);
    protected override void OnMouseRightButtonDown(MouseButtonEventArgs e);
    protected override void OnMouseRightButtonUp(MouseButtonEventArgs e);
    protected virtual void OnSubmenuClosed(RoutedEventArgs e);
    protected virtual void OnSubmenuOpened(RoutedEventArgs e);
    protected virtual void OnUnchecked(RoutedEventArgs e);
    protected override void PrepareContainerForItemOverride(DependencyObject element, object item);
    protected override bool ShouldApplyItemContainerStyle(DependencyObject container, object item);
    protected internal override void OnVisualParentChanged(DependencyObject oldParent);

}

MenuItem从鼠标的交互上,提供了两种方式。第一种是提供了Click事件,开发者可以订阅该事件以编写相应的业务逻辑。第二种是提供了ICommand接口属性和CommandParameter命令参数,以WPF命令的形式开发业务逻辑。

下面,我们以一种交互方式为例。

二、Menu示例

前端代码

<Window x:Class="HelloWorld.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:HelloWorld" 
        xmlns:forms="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
        mc:Ignorable="d" FontSize="14"
        Title="WPF中文网之控件课程 - www.wpfsoft.com" Height="350" Width="500">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>        
        <Menu x:Name="_Menu">
            <MenuItem Header="文件">
                <MenuItem Header="新建" Click="MenuItem_Click"/>
                <MenuItem Header="打开" Click="MenuItem_Click">
                    <MenuItem.Icon>
                        <Image Source="/Images/logo.png"/>
                    </MenuItem.Icon>
                </MenuItem>
            </MenuItem>                      
            <MenuItem Header="编辑"/>
            <MenuItem Header="视图"/>
            <MenuItem Header="项目"/>
            <MenuItem Header="调试"/>
            <MenuItem Header="测试"/>
            <MenuItem Header="分析"/>
            <MenuItem Header="工具"/>
            <MenuItem Header="帮助"/>
        </Menu>
        <TextBlock x:Name="_TextBlock" Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center"></TextBlock>
    </Grid>
</Window>

后端代码

public partial class MainWindow : Window
{        
    public MainWindow()
    {
        InitializeComponent();            
    }

    private void MenuItem_Click(object sender, RoutedEventArgs e)
    {
        var item = sender as MenuItem;
        _TextBlock.Text = $"你单击了 {item.Header}";
    }
}

上面演示了Menu最基本的用法,如果希望采用数据绑定的方式加载菜单,则可以参考下面的作法。

三、Menu数据绑定

我们需要创建一个实体类,来代表Menu的每一个子项。

/// <summary>
/// 主菜单的实体
/// </summary>
public class MenuModel
{
    public string Name { get; set; }
    public List<MenuModel> Children { get; set; } = new List<MenuModel>();
    public string View { get; set; }
}

在前端代码中,需要设置Menu的ItemTemplate元素模板。

<Window x:Class="HelloWorld.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:HelloWorld" 
        xmlns:forms="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
        mc:Ignorable="d" FontSize="14"
        Title="WPF中文网之控件课程 - www.wpfsoft.com" Height="350" Width="500">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>        
        <Menu x:Name="_Menu">
            <Menu.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding Children}">
                    <TextBlock Text="{Binding Name}"/>
                </HierarchicalDataTemplate>
            </Menu.ItemTemplate>
        </Menu>
    </Grid>
</Window>

因为MenuModel实体中有Children集合,所以在前端将Children作为HierarchicalDataTemplate的ItemsSource。并将Name显示出来。

最后,实例化一些子项数据,形成一个数据源,将这个数据源绑定到Menu的ItemsSource即可

来看看后端代码

public partial class MainWindow : Window
{
    public List<MenuModel> Menus { get; set; } = new List<MenuModel>();

    public MainWindow()
    {
        InitializeComponent();            
         
        for (int i = 0; i < 5; i++)
        {
            MenuModel parent = new MenuModel();
            parent.Name = $"一级菜单 ";
            for (int j = 0; j < 5; j++)
            {
                MenuModel child = new MenuModel();
                child.Name = $"二级菜单 ";
                parent.Children.Add(child);
            }
            Menus.Add(parent);
        }

        _Menu.ItemsSource = Menus;
    }
    
}

当前课程源码下载:(注明:本站所有源代码请按标题搜索)

文件名:045-《Menu菜单控件》-源代码 -1,045-《Menu菜单控件》-源代码 -2
链接:https://pan.baidu.com/s/1yu-q4tUtl0poLVgmcMfgBA
提取码:wpff

——重庆教主 2023年9月8日

ContextMenu上下文菜单必须要依附于一个“宿主控件”。由于FrameworkElement基类有一个叫ContextMenu的属性,代表了鼠标右键时弹出一个菜单,所以大多数控件都可以设置“上下文菜单”。

ContextMenu继承于MenuBase,而MenuBase继承于ItemsControl。所以,ContextMenu本质上也是一个集合控件。而它的元素则是MenuItem。在用法上,与Menu控件差不多。

一、ContextMenu的定义

public class ContextMenu : MenuBase
{
    public static readonly DependencyProperty HorizontalOffsetProperty;
    public static readonly RoutedEvent OpenedEvent;
    public static readonly DependencyProperty StaysOpenProperty;
    public static readonly DependencyProperty CustomPopupPlacementCallbackProperty;
    public static readonly DependencyProperty HasDropShadowProperty;
    public static readonly RoutedEvent ClosedEvent;
    public static readonly DependencyProperty PlacementRectangleProperty;
    public static readonly DependencyProperty PlacementTargetProperty;
    public static readonly DependencyProperty IsOpenProperty;
    public static readonly DependencyProperty VerticalOffsetProperty;
    public static readonly DependencyProperty PlacementProperty;

    public ContextMenu();

    public double HorizontalOffset { get; set; }
    public bool StaysOpen { get; set; }
    public CustomPopupPlacementCallback CustomPopupPlacementCallback { get; set; }
    public bool HasDropShadow { get; set; }
    public PlacementMode Placement { get; set; }
    public Rect PlacementRectangle { get; set; }
    public UIElement PlacementTarget { get; set; }
    public bool IsOpen { get; set; }
    public double VerticalOffset { get; set; }
    protected internal override bool HandlesScrolling { get; }

    public event RoutedEventHandler Closed;
    public event RoutedEventHandler Opened;

    protected virtual void OnClosed(RoutedEventArgs e);
    protected override AutomationPeer OnCreateAutomationPeer();
    protected override void OnIsKeyboardFocusWithinChanged(DependencyPropertyChangedEventArgs e);
    protected override void OnKeyDown(KeyEventArgs e);
    protected override void OnKeyUp(KeyEventArgs e);
    protected virtual void OnOpened(RoutedEventArgs e);
    protected override void PrepareContainerForItemOverride(DependencyObject element, object item);
    protected internal override void OnVisualParentChanged(DependencyObject oldParent);

}

二、属性成员

属性名称说明
HorizontalOffset获取或设置目标原点和弹出项对齐之间的水平距离点。
StaysOpen是否保持打开状态
CustomPopupPlacementCallback获取或设置ContextMenu指示在屏幕位置的回调
HasDropShadow是否有投影出现的上下文菜单。
Placement获取或设置ContextMenu显示的相对位置
PlacementRectangle获取或设置相对于其上下文菜单位于在打开时的区域。
PlacementTarget获取或设置ContextMenu打开时的相对控件
IsOpen是否打开
VerticalOffset获取或设置目标原点和弹出项对齐之间的垂直距离点。

三、ContextMenu示例

<Grid>
    <Border Background="LightBlue" Width="200" Height="100" CornerRadius="15">
        <Border.ContextMenu>
            <ContextMenu>
                <MenuItem Header="复制"/>
                <MenuItem Header="粘贴"/>
                <MenuItem Header="删除"/>
                <MenuItem Header="关于"/>
            </ContextMenu>
        </Border.ContextMenu>
    </Border>        
</Grid>

当前课程源码下载:(注明:本站所有源代码请按标题搜索)

文件名:046-《ContextMenu上下文菜单》-源代码
链接:https://pan.baidu.com/s/1yu-q4tUtl0poLVgmcMfgBA
提取码:wpff

——重庆教主 2023年9月8日

copyright @重庆教主 WPF中文网 联系站长:(QQ)23611316 (微信)movieclip (QQ群).NET小白课堂:864486030 | 本文由WPF中文网原创发布,谢绝转载 渝ICP备2023009518号-1