<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也能设置,这里懒得写了
操作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>

效果就差不多这样
区别
实际上两个的侧重点是不一样的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会相互屏蔽而导致有些就触发不了
比如按传统方式同时设置MouseDoubleClick和MouseRightButtonDown,只能触发前者
要都能触发,应该在控件的初始化用代码初始化这些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>

这样就实现了类似前面那张图的效果了
这里不难看出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>
简单写一下就是这样了
前面讲的SelectionChanged和MouseDoubleClick之类的还是写在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>

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