WPF学习笔记06-关于ListView

<0x00> ListView是什么

贴张图就好了
微软给的图
就是类似这种一行一行展示信息的控件
当然也不一定是像这种的类似表格的样子(这种后面会讲)
但都是一行一行的以行为单位的显示一些集合类的显示控件

<0x01> 先讲WPF里面自带的ListView

先给个代码,做一个最简单的演示

<!--MainWindow.xaml-->
<Window ...>
    <Window.DataContext>
        <local:ViewModel/>
    </Window.DataContext>
    <ListView ItemsSource="{Binding SomeItems}"/>
</Window>
//ViewModel.cs
//常用引用
namespace WPFTest
{
    internal class ViewModel
    {
        public List<string> SomeItems { get; set; } = new List<string>()
        {
            "aaa",
            "bbb",
            "ccc",
            "ddd",
            "fff",
        };
    }
}

演示的结果
演示
这个就是最简单的ListView,也是WPF默认的样式
(从这期开始的图会多一点,以前的博文也会慢慢补图,感觉没图还是不好理解)

我们可以先分析下这个控件需要什么,我们能得到什么
首先就肯定是需要一个集合啦
集合无所谓类型(应该都能用,看你怎么绑定)
然后我们能得到一个这样的显示
我们可以看到,在这个控件里面已经实现了页面滚动与子项的选择
(可以说除了有点不符合当代审美之外都没啥问题)

单项添加

有时候可能也不是集合提供子项,可能本身就是写死的
这时候就要自己写ListViewItem

<!--MainWindow.xaml-->
<Window ...>
    <ListView
        SelectionMode="Single">
        <ListViewItem Content="something1"/>
        <ListViewItem Content="something2"/>
        <ListViewItem Content="something3"/>
        <ListViewItem Content="something4"/>
        <ListViewItem Content="something5"/>
    </ListView>
</Window>

效果

每个ListViewItem也都能设置自己的Background之类的属性,这里就不多写了

美化子项

那我们还是要稍微美化一下的,虽然这样搞已经能用了
我们有两种方式
一个是操作ItemStyle,另一个是操作ItemTemplate

操作ItemStyle

<!--MainWindow.xaml-->
<Window ...>
    <Window.DataContext>
        <local:ViewModel/>
    </Window.DataContext>
    <ListView ItemsSource="{Binding SomeItems}">
        <ListView.ItemContainerStyle>
            <Style TargetType="ListViewItem">
                <Setter Property="FontSize" Value="20"/>
                <Setter Property="Foreground" Value="Red"/>
                <Setter Property="Background" Value="BurlyWood"/>
                <Style.Triggers>
                    <!--Trigger这里就偷懒了-->
                </Style.Triggers>
            </Style>
        </ListView.ItemContainerStyle>
    </ListView>
</Window>

通过ListView.ItemContainerStyle里面设置ListViewItem的样式
样式就按一般的方式走,这里就随便设置了下,Triggers也能设置,这里懒得写了
ItemViewStyle方法

操作ItemTemplate

这里需要修改下ViewModel,为了更方便的绑定

//ViewModel2.cs
//常用引用
namespace WPFTest
{
    internal class ViewModel2
    {
        public List<TypicalItem> SomeItems { get; set; } = new List<TypicalItem>()
        {
            new TypicalItem("aaa", new SolidColorBrush(Color.FromRgb(82,114,248))),
            new TypicalItem("bbb", new SolidColorBrush(Color.FromRgb(72,137,217))),
            new TypicalItem("ccc", new SolidColorBrush(Color.FromRgb(91,199,240))),
            new TypicalItem("ddd", new SolidColorBrush(Color.FromRgb(72,214,217))),
            new TypicalItem("eee", new SolidColorBrush(Color.FromRgb(82,248,205))),
        };
    }
    class TypicalItem
    {
        public string Name { get; set; }
        public Brush Color { get; set; }
        public TypicalItem(string name, Brush color)
        {
            Name = name;
            Color = color;
        }
    }
}
<!--MainWindow.xaml-->
<Window ...>
    <Window.DataContext>
        <local:ViewModel2/>
    </Window.DataContext>
    <ListView ItemsSource="{Binding SomeItems}">
        <ListView.ItemTemplate>
            <DataTemplate DataType="ListViewItem">
                <Border>
                    <TextBox
                        Text="{Binding Name}"
                        Background="{Binding Color}"
                        FontSize="20"/>
                </Border>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</Window>

ItemTemplate方法
效果就差不多这样

区别

实际上两个的侧重点是不一样的
ItemContainerStyle重点在每个子项的样式设置
比方说就是每个子项的背景,子项按下的动画效果之类的
ItemTemplate侧重在子项内部是怎么显示的
比方说写了个数据类型,里面有好几条属性
那么就可以用ItemTemplate来自定义这些属性怎么显示

我怀疑ListViewItem里面就是包着自己的UI树的
但可视化树没显示,也不敢说死

这两个的效果肯定是能合并在一起的,毕竟这俩侧重点是完全不一样的

<!--MainWindow.xaml-->
<Window ...>
    <Window.DataContext>
        <local:ViewModel2/>
    </Window.DataContext>
    <ListView
        ItemsSource="{Binding SomeItems}">
        <!--其实就加了这一个块-->
        <ListView.ItemContainerStyle>
            <Style TargetType="ListViewItem">
                <Setter Property="Background" Value="BurlyWood"/>
            </Style>
        </ListView.ItemContainerStyle>
        <ListView.ItemTemplate>
            <DataTemplate DataType="ListViewItem">
                <Border>
                    <TextBox Text="{Binding Name}" Background="{Binding Color}" FontSize="20"/>
                </Border>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</Window>

合并

和程序行为合并

那下一步就是写选中子项的行为了
我们可以通过设置SelectionChanged事件
为了演示,先修改下前端代码,这里举选择子项切换Border背景颜色的例子

<!--MainWindow.xaml-->
<Window ...>
    <Window.DataContext>
        <local:ViewModel2/>
    </Window.DataContext>
    <!--加了个Grid,上下分割-->
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <ListView
            ItemsSource="{Binding SomeItems}"
            SelectionMode="Single"
            SelectionChanged="ListView_SelectionChanged">
            <ListView.ItemContainerStyle>
                <Style TargetType="ListViewItem">
                    <Setter Property="Background" Value="BurlyWood"/>
                </Style>
            </ListView.ItemContainerStyle>
            <ListView.ItemTemplate>
                <DataTemplate DataType="ListViewItem">
                    <Border>
                        <TextBox Text="{Binding Name}" Background="{Binding Color}" FontSize="20"/>
                    </Border>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
        <!--用来显示子项切换的效果的Border-->
        <Border Grid.Row="1" Name="MyBorder"/>
    </Grid>
</Window>
//MainWindow.xaml.cs
//引用
namespace WPFTest
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            MyBorder.Background = (e.AddedItems[0] as TypicalItem).Color;
        }
    }
}

简单的演示
这样就解决了
.xaml的代码就不解释了,.cs的代码还是能讲一下的
主要就是用e.AddedItems[0]拿到选中的TypicalItem对象
(e.AddedItems[0]这个语法确实很怪,估计是主要是为了支持多个选中的情况)

如果给ListView起了名字的话也可以这么写(这里起名是MyListView)

//MainWindow.xaml.cs
//引用
namespace WPFTest
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void MyListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            MyBorder.Background = (MyListView.SelectedItem as TypicalItem).Color;
        }
    }
}

一般来说这么访问选中的子项会多一点
因为有些时候会去想双击的代码,这就要用MouseDoubleClick事件了
MouseDoubleClick是不带SelectionChangedEventArgs e
也就是说我们是不能靠传参变量e来获取选择了哪个子项的
但靠MyListView.SelectedItem这种方式就方便很多,都能用了

顺便在这里提一点,如果要定义多个鼠标事件,不能采用传统的.xaml写事件然后在.cs
因为这么写运行的时候这些MouseHandler会相互屏蔽而导致有些就触发不了
比如按传统方式同时设置MouseDoubleClickMouseRightButtonDown,只能触发前者
要都能触发,应该在控件的初始化用代码初始化这些Handler
.xaml里面可以按传统方法留一个,但别的都要靠代码方式添加

//MainWindow.xaml.cs(随便举例的,与前面的代码无关)
//引用
namespace WPFTest
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            //就是在这里自己添加
            MyListView.AddHandler(MouseDoubleClickEvent, new MouseButtonEventHandler(this.MyListView_MouseDoubleClick), true);
            MyListView.AddHandler(MouseRightButtonDownEvent, new MouseButtonEventHandler(this.MyListView_MouseRightButtonDown), true);
        }

        private void MyListView_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            //处理代码
        }

        private void MyListView_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            //处理代码
        }
    }
}

还有就是我还没啥办法实现类似Button绑定ICommand的做法
但其实可以把方法本体写在VM里,然后在.cs里用DataContext调命令,实现猴版的MVVM
这里就不细讲了

网格布局GridView

博客最上面的图就是使用了GridView的效果
这张图
(就是这张)

GridView是嵌套在ListView里面的,具体代码如下
(还是用得ViewModel2.cs)

<!--MainWindow.xaml-->
<Window ...>
    <Window.DataContext>
        <local:ViewModel2/>
    </Window.DataContext>
    <ListView
        ItemsSource="{Binding SomeItems}">
        <ListView.View>
            <GridView>
                <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"/>
                <GridViewColumn Header="Color">
                    <GridViewColumn.CellTemplate>
                        <DataTemplate DataType="local:TypicalItem">
                            <TextBlock Text="{Binding Color}" Background="{Binding Color}"/>
                        </DataTemplate>
                    </GridViewColumn.CellTemplate>
                </GridViewColumn>
            </GridView>
        </ListView.View>
    </ListView>
</Window>

emmm
这样就实现了类似前面那张图的效果了

这里不难看出GridView是隶属于ListView
然后对于每一个格子,也是能自定义Template

最上面的是Header,也能自定义

<!--MainWindow.xaml-->
<Window ...>
    <Window.DataContext>
        <local:ViewModel2/>
    </Window.DataContext>
    <ListView
        ItemsSource="{Binding SomeItems}">
        <ListView.View>
            <GridView>
                <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"/>
                <GridViewColumn>
                    <!--就改了这里-->
                    <GridViewColumnHeader>
                        <TextBlock Text="emm" Background="BurlyWood"/>
                    </GridViewColumnHeader>
                    <GridViewColumn.CellTemplate>
                        <DataTemplate DataType="local:TypicalItem">
                            <TextBlock Text="{Binding Color}" Background="{Binding Color}"/>
                        </DataTemplate>
                    </GridViewColumn.CellTemplate>
                </GridViewColumn>
            </GridView>
        </ListView.View>
    </ListView>
</Window>

简单写一下就是这样了
前面讲的SelectionChangedMouseDoubleClick之类的还是写在ListView的块里
这样就可以实现表格化的效果了

<0x02> 讲讲ModernWPF的ListView

因为有自己的练手项目在用这个包,所以就顺便讲讲了
(Nuget上有好多叫ModernWPF的包,我自己用的是这个)
之前的博客也有讲过一点

ModernWPF的包里面有两种ListView实现
一种是使用WPF原生的ListView但修改了Style来匹配风格
另一种是包里面自己写的ListView
前者就要求别去改Style,不然所有的样式又要自己写
后者的化代码风格会更像UWP开发,有些写法确实挺新的

<!--MainWindow.xaml-->
<Window ...
    xmlns:ui="http://schemas.modernwpf.com/2019"
    ui:WindowHelper.UseModernWindowStyle="True">
    <Window.DataContext>
        <!--这里用的是最前面的ViewModel-->
        <local:ViewModel/>
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <ListView
            Grid.Row="0"
            ItemsSource="{Binding SomeItems}"/>
        <ui:ListView
            Grid.Row="1"
            ItemsSource="{Binding SomeItems}"/>
    </Grid>
</Window>

效果
这里演示了这个包里面的两种写法,效果上是一样的
其余大部分都可以按WPF原生的写法写
但如果要用GridView,那么只有原生的写法(这个包没自己写)

<!--MainWindow.xaml-->
<Window ...
    xmlns:ui="http://schemas.modernwpf.com/2019"
    ui:WindowHelper.UseModernWindowStyle="True">
    <Window.DataContext>
        <local:ViewModel2/>
    </Window.DataContext>
    <ListView
        ItemsSource="{Binding SomeItems}">
        <ListView.View>
            <GridView>
                <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"/>
                <GridViewColumn Header="Color" DisplayMemberBinding="{Binding Color}"/>
            </GridView>
        </ListView.View>
    </ListView>
</Window>

演示
这里可以看得,如果用GridView的话,右边的滚动条时不会收纳的
(准确来说自动从收纳状态变成展开状态)

别的自定义之类的也和WPF原生写法一样,就是尽量别动Style

<!--MainWindow.xaml-->
<Window ...
    xmlns:ui="http://schemas.modernwpf.com/2019"
    ui:WindowHelper.UseModernWindowStyle="True">
    <Window.DataContext>
        <local:ViewModel2/>
    </Window.DataContext>
    <ListView
        ItemsSource="{Binding SomeItems}">
        <ListView.View>
            <GridView>
                <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"/>
                <GridViewColumn Header="Color">
                    <GridViewColumn.CellTemplate>
                        <DataTemplate DataType="local:TypicalItem">
                            <Button Content="{Binding Color}" Background="{Binding Color}"/>
                        </DataTemplate>
                    </GridViewColumn.CellTemplate>
                </GridViewColumn>
            </GridView>
        </ListView.View>
    </ListView>
</Window>

演示

写这篇博客真的花了不少时间,大家看得开心就好

Licensed under CC BY-NC-SA 4.0