首页 > 代码库 > [Aaronyang] 写给自己的WPF4.5 笔记9[复杂数据处理三步曲,数据展示ListView泪奔2/3]
[Aaronyang] 写给自己的WPF4.5 笔记9[复杂数据处理三步曲,数据展示ListView泪奔2/3]
我的文章一定要做到对读者负责,否则就是失败的文章 --------- www.ayjs.net aaronyang技术分享
作者留言:
小小的推荐,作者的肯定,读者的支持。推不推荐不重要,重要的是希望大家能把WPF推广出去,别让这么好的技术消失了,求求了,让我们为WPF技术做一份贡献。其实写这篇文章时候已经哭了,最近几篇文章,在我个人看来都是wpf的宝藏文章。每天读者也就200-400多人,也说明了WPF的人越来也少了。但是我的Blend教程和WPF控件开发,3D WPF的思路教程还是会慢慢出来的。学习最好的工具,无需买书:MSDN
博文摘要:
- 先深入浅出让你了解ListView控件
- ListView控件View的了解,已经Header模板和cell的模板的简单深入
- 深入浅出ComponentResourceKey,TextBlock的多值绑定时候,换行的解决
- 了解View后,我们知道了ListView.View原来是ViewBase,我们成功地自定义一个View,我的电脑,资源视图方式展示
- 完整效果图展示:
- demo下载:百度云下载,里面有本次图标的素材
1.我们接着上一课的项目,继续写。新建窗体Window2.xaml
基本布局,跟上节课一样,窗口被平均分为4份:
<Window x:Class="TemplateDemo.Window2" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window2" Height="600" Width="1000"> <UniformGrid Columns="2" Rows="2"> <DockPanel Grid.Column="0" Grid.Row="0" Background="#FFF7F6F6" LastChildFill="True" x:Name="demo1"> <Grid DockPanel.Dock="Top" Background="#0786F6" Height="28"> <TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="#fff">DEMO1 ListView数据展示</TextBlock> </Grid> <Grid> <ListView x:Name="lvDemo1"> </ListView> </Grid> </DockPanel> </UniformGrid></Window>
1. 创建一个假数据源
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Windows;using System.Windows.Controls;using System.Windows.Data;using System.Windows.Documents;using System.Windows.Input;using System.Windows.Media;using System.Windows.Media.Imaging;using System.Windows.Shapes;namespace TemplateDemo{ /// <summary> /// Window2.xaml 的交互逻辑 /// </summary> public partial class Window2 : Window { public Window2() { InitializeComponent(); } private void Window_Loaded(object sender, RoutedEventArgs e) { //DEMO1 DateTime dt = DateTime.Now.AddMonths(-13); Random monthRandom = new Random(); Random dayRandom = new Random(); Random minRandom = new Random(); BlogCollectionLive bl = new BlogCollectionLive(); for (int i = 1; i <= 100; i++) { AyBlogLive ay = new AyBlogLive(); ay.Name = "Ay的WPF博客" + i; ay.Content = "Ay的WPF是window上最好的桌面技术,真的很好" + i; ay.CreateTime = dt.AddMonths(monthRandom.Next(1, 12)).AddDays(dayRandom.Next(-30, 60)).AddMinutes(minRandom.Next(-60, 120)); bl.Add(ay); }
lvDemo1.ItemsSource = bl; } }}
接下来,我们指定ListView.View 这里我们使用GridView,如果学过 asp.net就好理解了。标题列名Header属性,DisplayMemberBinding前面见过了,绑定对象中的属性
<ListView x:Name="lvDemo1"> <ListView.View> <GridView> <GridView.Columns> <GridViewColumn Header="文章名" DisplayMemberBinding="{Binding Name}"></GridViewColumn> <GridViewColumn Header="发布日期" DisplayMemberBinding="{Binding CreateTime,StringFormat={}{0:yyyy-MM-dd HH:mm:ss}}"></GridViewColumn> </GridView.Columns> </GridView> </ListView.View> </ListView>
效果图:这里没有指定列宽,你可以自己指定。wpf会适应最大的宽度去调节
指定宽度:
<GridViewColumn Header="文章名" DisplayMemberBinding="{Binding Name}" Width="315"></GridViewColumn> <GridViewColumn Header="发布日期" DisplayMemberBinding="{Binding CreateTime,StringFormat={}{0:yyyy-MM-dd HH:mm:ss}}" Width="150"></GridViewColumn>
效果图:GridView默认列可以拖拽,列宽可以调节,双击列的右侧边缘,可以让列自动调整到可以看见内容的宽度。GridView没有类似MaxWidth或者MinWidth的属性
指定列的单元格模板,在列中指定列,这列数据每个单元格怎么显示
<ListView.View> <GridView> <GridView.Columns> <!--<GridViewColumn Header="文章名" DisplayMemberBinding="{Binding Name}" Width="315"></GridViewColumn>--> <GridViewColumn Header="文章名" Width="315"> <GridViewColumn.CellTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock>[<Hyperlink NavigateUri="http://www.ayjs.net">下载</Hyperlink>]</TextBlock> <TextBlock Text="{Binding Path=Name}" Margin="1,0,0,0" Foreground="CadetBlue"></TextBlock> </StackPanel> </DataTemplate> </GridViewColumn.CellTemplate> </GridViewColumn> <GridViewColumn Header="发布日期" DisplayMemberBinding="{Binding CreateTime,StringFormat={}{0:yyyy-MM-dd HH:mm:ss}}" Width="150"></GridViewColumn> </GridView.Columns> </GridView> </ListView.View>
效果图:当然这只是简单的例子,比如这里我们可以显示图片,使用图片地址换器,单元格就可以显示一张图片了。
同样地,列标题Header也是有模板的,GridViewColumn.HeaderTemplate,同样我们也发现了HeaderTemplateSelector,所以每个列可以展示的不一样
<GridViewColumn.HeaderTemplate> <DataTemplate> <Border Width="312" CornerRadius="5" BorderBrush="Yellow" BorderThickness="1" Background="YellowGreen"> <Grid> <Rectangle HorizontalAlignment="Stretch"> </Rectangle> <TextBlock Text="文章标题" Foreground="White" HorizontalAlignment="Center"></TextBlock> </Grid> </Border> </DataTemplate> </GridViewColumn.HeaderTemplate>
效果预览:
慢着,别以为就这样结束了,在写代码的过程中,我还发现了,单元格模板选择器,所以每格的显示效果可以不一样的
下面有个地址转换成Image控件的Source的转换器,自己写的以前的转换器学习博文:查看
public class ImagePathConverter : IValueConverter { private string imageDirectory = Directory.GetCurrentDirectory(); public string ImageDirectory { get { return imageDirectory; } set { imageDirectory = value; } } public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { string imagePath = Path.Combine(ImageDirectory, (string)value); return new BitmapImage(new Uri(imagePath)); } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotSupportedException(""); } }
我还发现树状数据显示模板,感兴趣的自己可以去拓展
、
同样的模板,我们也可以在GridView下面一级定义
当让ColumnHeaderContainerStyle用于全局修改每一个列的头部样式,GridViewColumn中的是单独修改。同样也有全局模板选择器
=============潇洒的版权线==========www.ayjs.net===== Aaronyang ========= AY =========== 安徽 六安 杨洋 ========== 未经允许不许转载 =========
在做第2个demo时候,有必要介绍下ComponentResourceKey,主要为从外部程序集加载的资源定义和引用键。这使得资源查找功能可以在程序集内指定目标类型,而不是在程序集内指定显式的资源字典。主要应用:定义共享的资源在一个程序集里,使用ComponentResourceKey,可以找到他,覆盖样式。详细请参考:查看
MSDN地址说明查看
XAML 属性用法(设置键,精简版)
<object x:Key="{ComponentResourceKey {x:Type targetTypeName}, targetID}" .../>
XAML 属性用法(设置键,详细版)
<object x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type targetTypeName}, ResourceID=targetID}" .../>
XAML 属性用法(请求资源,精简版)
<object property="{DynamicResource {ComponentResourceKey {x:Type targetTypeName}, targetID}}" .../>
XAML 属性用法(请求资源,详细版)
<object property="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type targetTypeName}, ResourceID=targetID}}" .../>
ay总结:一句话,控件提供者,给用户提供了 约定资源,相当于资源接口,你可以通过ComponentResourceKey找到该位置,并填写它,覆盖它。如果控件提供者给了你控件 约定资源 Key的方法,或者重写Key名字的方法,那就更好了。
DEMO2 简单的模拟我的电脑的 盘符显示方式
<DockPanel Grid.Column="0" Grid.Row="0" Background="#FFF7F6F6" LastChildFill="True" x:Name="demo2"> <Grid DockPanel.Dock="Top" Background="YellowGreen" Height="28"> <TextBlock VerticalAlignment="Center" HorizontalAlignment="Center">DEMO2 ListView自定义视图</TextBlock> </Grid> <Grid DockPanel.Dock="Bottom" Height="28" HorizontalAlignment="Stretch" Background="YellowGreen" > <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" > <Button Width="60" Margin="2" x:Name="btnDetail" Click="">详细信息</Button> <Button Width="60" Margin="2" x:Name="btnContent" Click="">内容</Button> <Button Width="60" Margin="2" x:Name="btnNext" Click="">平铺</Button> <Button Width="60" Margin="2" x:Name="btnSmallImage" Click="">小图标</Button> <Button Width="60" Margin="2" x:Name="btnNormalImage" Click="">中图标</Button> </StackPanel> </Grid> <Grid> <ListView Name="lstAyDisk"> </ListView> </Grid> </DockPanel>
我们先完成数据源,WPF提供的GridView的展示方式,这里的切换放到 详细信息 按钮的 事件里
//demo2 创建数据源 ObservableCollection<DiskInfo> disk = new ObservableCollection<DiskInfo> { new DiskInfo{DiskChar=‘C‘,DiskIcon="1.png",DiskType="NTFS",AllMemory=50,UsedMemory=39,DiskName="本地磁盘"}, new DiskInfo{DiskChar=‘D‘,DiskIcon="2.png",DiskType="NTFS",AllMemory=120,UsedMemory=21,DiskName="本地磁盘"}, new DiskInfo{DiskChar=‘E‘,DiskIcon="3.png",DiskType="NTFS",AllMemory=20,UsedMemory=10,DiskName="软件"}, new DiskInfo{DiskChar=‘F‘,DiskIcon="4.png",DiskType="NTFS",AllMemory=250,UsedMemory=212.5,DiskName="游戏"}, new DiskInfo{DiskChar=‘G‘,DiskIcon="5.png",DiskType="NTFS",AllMemory=100,UsedMemory=40,DiskName="资料备份"} }; lstAyDisk.ItemsSource = disk; } private void btnDetail_Click(object sender, RoutedEventArgs e) { } private void btnContent_Click(object sender, RoutedEventArgs e) { } private void btnTile_Click(object sender, RoutedEventArgs e) { } private void btnSmallImage_Click(object sender, RoutedEventArgs e) { } private void btnNormalImage_Click(object sender, RoutedEventArgs e) { } } public class DiskInfo { public char DiskChar { get; set; } public string DiskName { get; set; } public string DiskType { get; set; } public double UsedMemory { get; set; } public double AllMemory { get; set; } public string DiskIcon { get; set; } }
接下来,我们把listview的view全部写到Window.Resources里面,我们先在项目里面引入几张测试图片,我的文件夹叫imageDisk,先写wpf自带的gridView
列表要显示图片需要图片地址变成图片文件流,就需要转换器
using System;using System.Collections.Generic;using System.Text;using System.Windows.Data;using System.Windows.Media.Imaging;using System.IO;namespace TemplateDemo{ public class ImagePathConverter : IValueConverter { private string imageDirectory = Directory.GetCurrentDirectory(); public string ImageDirectory { get { return imageDirectory; } set { imageDirectory = value; } } public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { string p = parameter as string; string imagePath = ""; if (p != null) { imagePath = Path.Combine(ImageDirectory, p+(string)value); } else { imagePath = Path.Combine(ImageDirectory, (string)value); } return new BitmapImage(new Uri(imagePath)); } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotSupportedException("等待实现.."); } }}
接下来,windows.Resources先完成最基本的GridView的View,因为ListView要显示东西只认View,这个View要继承ViewBase类,所以过会我们自己定义个ViewBase的子类
<Window.Resources> <local:ImagePathConverter x:Key="ImagePathConverter"></local:ImagePathConverter> <GridView x:Key="GridView"> <GridView.Columns> <GridViewColumn Header="名称"> <GridViewColumn.CellTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <Viewbox Width="20"> <Image Source="{Binding DiskIcon,Converter={StaticResource ImagePathConverter},ConverterParameter=‘imageDisk/‘}"/> </Viewbox> <TextBlock Text="{Binding Path=DiskName}" Margin="1,0,0,0" Foreground="Black"></TextBlock> <TextBlock Text="{Binding Path=DiskChar,StringFormat=‘({0}:)‘}" Margin="1,0,0,0" Foreground="Black"></TextBlock> </StackPanel> </DataTemplate> </GridViewColumn.CellTemplate> </GridViewColumn> <GridViewColumn Header="类型" DisplayMemberBinding="{Binding Path=DiskType}" /> <GridViewColumn Header="总大小" DisplayMemberBinding="{Binding Path=AllMemory, StringFormat={}{0} GB}" /> <GridViewColumn Header="已用空间" DisplayMemberBinding="{Binding Path=UsedMemory, StringFormat={}{0} GB}" /> </GridView.Columns> </GridView> </Window.Resources>
指定ListView的默认View
<ListView x:Name="lstAyDisk" View="{StaticResource GridView}"> </ListView>
运行后,效果图
接下来,我们自定义个继承ViewBase的AView,表示笑了
using System;using System.Collections.Generic;using System.Text;using System.Windows;using System.Windows.Controls;namespace TemplateDemo{ public class AView:ViewBase { private DataTemplate itemTemplate; public DataTemplate ItemTemplate { get { return itemTemplate; } set { itemTemplate = value; } } protected override object DefaultStyleKey { get { return new ComponentResourceKey(GetType(), "AView"); } } protected override object ItemContainerDefaultStyleKey { get { return new ComponentResourceKey(GetType(), "AViewItem"); } } }}
我们公开了一个DataTemplate方便使用者定义Item的样子,并准备重写ViewBase控件的DefaultStyleKey和ItemContainerDefaultStyleKey
ComponentResourceKey封装了拥有样式的类的类型,以及一个资源标识ResourceId。通过上一篇三巴掌系列的学习,我们知道了ItemContainerStyle是控制例如ListBoxItem,容器中Item的样式的。默认的Style控制整个的Style的。
这里我们已经自己定义了2个样式资源名字,AView还有AViewItem
接下来,我们使用ComponentResourceKey找到这两个资源,并填充内容。
因为我们重写ViewBase的样式,ViewBase属于外部程序集,为了确保WPF能找到希望使用的样式,确保能够自动获得样式,我们使用ComponentResourceKey
我们新建Themes,然后新建generic.xaml文件,添加下面的样式,在AView.cs中查找ResourceId为AView的,即DefaultStyleKey对象,使用TargetType指定这个样式使用者是ListView,继承ListBox的样式
xmlns:local="clr-namespace:TemplateDemo"
<Style x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:AView}, ResourceId=AView}" TargetType="{x:Type ListView}" BasedOn="{StaticResource {x:Type ListBox}}"> <Setter Property="BorderBrush" Value="Gold"></Setter> <Setter Property="BorderThickness" Value="5"></Setter> </Style>
接下来我们在界面上创建一个空的AView,并指定ListView的View指定AView
<local:AView x:Key="ImageNormalView"> </local:AView>
<ListView x:Name="lstAyDisk" View="{StaticResource ImageNormalView}"> </ListView>
运行后效果图:由于没有指定模板,且样式继承了ListBox所以一列展示了,且基本的View的边框和颜色已经生效了。所以可以Style的作用范围是AViewItem外面一层的View
接下来,我们自定义一个模板,让显示成ImageButton类型,上方图片,下方文字加盘符
<local:AView x:Key="ImageNormalView"> <local:AView.ItemTemplate> <DataTemplate> <StackPanel> <Viewbox Width="64" HorizontalAlignment="Center"> <Image Source="{Binding DiskIcon,Converter={StaticResource ImagePathConverter},ConverterParameter=‘imageDisk/‘}"/> </Viewbox> <TextBlock HorizontalAlignment="Center" Margin="1,0,0,0" Foreground="Black"> <TextBlock.Text> <MultiBinding StringFormat="{}{0}({1}:)"> <Binding Path="DiskName"></Binding> <Binding Path="DiskChar"></Binding> </MultiBinding> </TextBlock.Text> </TextBlock> </StackPanel> </DataTemplate> </local:AView.ItemTemplate> </local:AView>
但是加上以后,运行后还是没有效果,而且还是Listbox的样式,我们上篇博客讲到了只要修改ItemPanel就可以改变布局了,我们试着在View的样式里指定ItemPanel
<Style x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:AView}, ResourceId=AView}" TargetType="{x:Type ListView}" BasedOn="{StaticResource {x:Type ListBox}}"> <Setter Property="BorderBrush" Value="Gold"></Setter> <Setter Property="BorderThickness" Value="5"></Setter> <Setter Property="ItemsPanel"> <Setter.Value> <ItemsPanelTemplate> <WrapPanel></WrapPanel> </ItemsPanelTemplate> </Setter.Value> </Setter> </Style>
但是上篇博客也说到了,例如Listbox如果横向的滚动条不禁止掉,wrap就不会换行了,我们给ListView增加ScrollViewer.HorizontalScrollBarVisibility="Disabled"
<ListView x:Name="lstAyDisk" View="{StaticResource ImageNormalView}" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
运行代码效果如下:虽然右侧还有点距离,但无妨。
同样的效果,如果我们设置了WrapPanel的宽度,它也会自动换行的,Wrap的宽度等于父类的宽度,我们也可以这样设置,且使用者不需要加代码
<WrapPanel Width="{Binding (FrameworkElement.ActualWidth), RelativeSource={RelativeSource AncestorType=ScrollContentPresenter}}"></WrapPanel>
接下来,发现Item的还是显示对象的ToString()的数据,且DataTemplate也没生效,原因是我们没有指定使用者ListView.View.ItemTemplate
<Style x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:AView},ResourceId=AViewItem}" TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}"> <Setter Property="ContentTemplate" Value="{Binding Path=View.ItemTemplate, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}}"></Setter> </Style>
效果图:
我们稍微调整边距和内容水平和垂直居中。
<Style x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:AView},ResourceId=AViewItem}" TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}"> <Setter Property="ContentTemplate" Value="{Binding Path=View.ItemTemplate, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}}"></Setter> <Setter Property="Padding" Value="5"/> <Setter Property="Margin" Value="1.5"/> <Setter Property="HorizontalContentAlignment" Value="Center"></Setter> <Setter Property="VerticalContentAlignment" Value="Center"></Setter> </Style>
运行项目后,用户如果不会操作就会出现虚线框,所以我们需要定义Item的模板,我们在上面的样式上写。我打开曾经做过的一个模板预览窗口,查看了ListBoxItem的系统定义的样式,大致模仿思路改写下,建议你也可以在blend中去修改,修改好后就可以移到这边来了
外方有个Border,然后中间放个内容显示控件,代表DataTemplate显示的内容。接着一些触发器触发各个状态的效果。
<Style x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:AView},ResourceId=AViewItem}" TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}"> <Setter Property="ContentTemplate" Value="{Binding Path=View.ItemTemplate, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}}"></Setter> <Setter Property="Padding" Value="5"/> <Setter Property="Margin" Value="1.5"/> <Setter Property="HorizontalContentAlignment" Value="Center"></Setter> <Setter Property="VerticalContentAlignment" Value="Center"></Setter> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListBoxItem}"> </ControlTemplate> </Setter.Value> </Setter> </Style>
此时运行项目时候,界面已经没有任何东西了,因为内容控件没有增加,如果加上 <ContentPresenter /> 运行项目时候,已经出现了,但是所有的动作效果没了,这就要求我们写效果了。
<Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListBoxItem}"> <ContentPresenter /> </ControlTemplate> </Setter.Value> </Setter>
我们还是用自带的
<Border x:Name="Bd" BorderThickness="{TemplateBinding Border.BorderThickness}" Padding="{TemplateBinding Control.Padding}" BorderBrush="{TemplateBinding Border.BorderBrush}" Background="{TemplateBinding Panel.Background}" SnapsToDevicePixels="True"> <ContentPresenter Content="{TemplateBinding ContentControl.Content}" ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}" ContentStringFormat="{TemplateBinding ContentControl.ContentStringFormat}" HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding Control.VerticalContentAlignment}" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" /> </Border>
打开blend,我自己随便拖了个Listbox瞎添加了几个ListBoxItem,然后编辑模板,将Tab切换到触发器
正如上面定义的一样,Item具有4个状态,禁用,鼠标移上,激活时候选中,非激活时候选中。当然我们不需要这么复杂,简单几个就够了,我自己参考模板去写的
<Style x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:AView},ResourceId=AViewItem}" TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}"> <Setter Property="ContentTemplate" Value="{Binding Path=View.ItemTemplate, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}}"></Setter> <Setter Property="Padding" Value="5"/> <Setter Property="Margin" Value="1.5"/> <Setter Property="HorizontalContentAlignment" Value="Center"></Setter> <Setter Property="VerticalContentAlignment" Value="Center"></Setter> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListBoxItem}"> <Border x:Name="Bd" BorderThickness="{TemplateBinding Border.BorderThickness}" Padding="{TemplateBinding Control.Padding}" BorderBrush="{TemplateBinding Border.BorderBrush}" Background="{TemplateBinding Panel.Background}" SnapsToDevicePixels="True"> <ContentPresenter Content="{TemplateBinding ContentControl.Content}" ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}" ContentStringFormat="{TemplateBinding ContentControl.ContentStringFormat}" HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding Control.VerticalContentAlignment}" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" /> </Border> <ControlTemplate.Triggers> <Trigger Property="IsSelected" Value="True"> <Setter TargetName="Bd" Property="BorderBrush" Value="#66A7E8"></Setter> <Setter TargetName="Bd" Property="Background" Value="#D1E8FF"></Setter> </Trigger> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="UIElement.IsMouseOver" Value="True"></Condition> <Condition Property="IsSelected" Value="False"></Condition> </MultiTrigger.Conditions> <Setter TargetName="Bd" Property="BorderBrush" Value="#70C0E7"></Setter> <Setter TargetName="Bd" Property="Background" Value="#E5F3FB"></Setter> </MultiTrigger> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="Selector.IsSelectionActive" Value="False"> </Condition> <Condition Property="Selector.IsSelected" Value="True"> </Condition> </MultiTrigger.Conditions> <Setter TargetName="Bd" Property="BorderBrush" Value="#3399FF"></Setter> <Setter TargetName="Bd" Property="Background" Value="#fff"></Setter> </MultiTrigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style>
去掉focus框
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
接下来,在DiskInfo类,增加可用空间字段
private double canUseMemory; public double CanUseMemory { get { return AllMemory - UsedMemory; } set { canUseMemory = value; } }
接下来,增加悬浮提示:但是遇到了个TextBlock的Text多binding时候,StringFormat如何换行
方法一:

方法二:
<TextBlock Text="{Binding StringFormat=‘第一行{0}第二行{0}第三行‘, Source={x:Static s:Environment.NewLine}}" />
修改后简单效果图:
接下来,我们要写平铺的排版,触发器都是共用的,我们只要稍微改下模板就OK了。关于下面的样式,我优先推荐使用Dock布局,在WPF开发中,布局是最基本的能力,你要能最快速度直到怎样搭建界面
<local:AView x:Key="ImageTileView"> <local:AView.ItemTemplate> <DataTemplate> <DockPanel LastChildFill="True"> <DockPanel.ToolTip> <ToolTip Placement="Mouse"> <StackPanel> <TextBlock HorizontalAlignment="Center" Margin="1,0,0,0" Foreground="Black"> <TextBlock.Text> <MultiBinding StringFormat="可用空间: {0} GB
总大小: {1} GB "> <Binding Path="CanUseMemory"></Binding> <Binding Path="AllMemory"></Binding> </MultiBinding> </TextBlock.Text> </TextBlock> </StackPanel> </ToolTip> </DockPanel.ToolTip> <Grid DockPanel.Dock="Left" > <Grid.ColumnDefinitions> <ColumnDefinition Width="50"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Image Source="{Binding DiskIcon,Converter={StaticResource ImagePathConverter},ConverterParameter=‘imageDisk/‘}" Grid.Column="0" Width="40"/> <StackPanel Grid.Column="1"> <TextBlock Margin="1,0,0,0" Foreground="Black"> <TextBlock.Text> <MultiBinding StringFormat="{}{0}({1}:)"> <Binding Path="DiskName"></Binding> <Binding Path="DiskChar"></Binding> </MultiBinding> </TextBlock.Text> </TextBlock> <ProgressBar Value="{Binding CanUseMemory}" Maximum="{Binding AllMemory}" Minimum="0" Height="13" Width="150" BorderBrush="#BCBCBC" Background="#E6E6E6" BorderThickness="1" Foreground="#26A0DA"></ProgressBar> <TextBlock Margin="1,0,0,0" Foreground="Black"> <TextBlock.Text> <MultiBinding StringFormat="{}{0} GB可用,共 {1} GB"> <Binding Path="CanUseMemory"></Binding> <Binding Path="AllMemory"></Binding> </MultiBinding> </TextBlock.Text> </TextBlock> </StackPanel> </Grid> </DockPanel> </DataTemplate> </local:AView.ItemTemplate> </local:AView>
我们指定这个ListView的View的模板,效果图如下;
接下来,我们要做的是内容方式展示的View的数据模板。打开我的电脑,它的进度条是根据窗体变的,说白了,第一眼就想到了百分比布局的Grid
<local:AView x:Key="ImageContentView"> <local:AView.ItemTemplate> <DataTemplate> <DockPanel LastChildFill="True" Grid.Column="0" Width="{Binding (FrameworkElement.ActualWidth), RelativeSource={RelativeSource AncestorType=ScrollContentPresenter}}"> <Grid DockPanel.Dock="Left" > <Grid.ColumnDefinitions> <ColumnDefinition Width="40"/> <ColumnDefinition x:Name="colPro" Width="1.5*"/> <ColumnDefinition Width="1*"/> </Grid.ColumnDefinitions> <Image Source="{Binding DiskIcon,Converter={StaticResource ImagePathConverter},ConverterParameter=‘imageDisk/‘}" Grid.Column="0" Width="30" Margin="1,0,5,0"/> <StackPanel Grid.Column="1" > <TextBlock Foreground="Black" Margin="0,0,0,5"> <TextBlock.Text> <MultiBinding StringFormat="{}{0}({1}:)"> <Binding Path="DiskName"></Binding> <Binding Path="DiskChar"></Binding> </MultiBinding> </TextBlock.Text> </TextBlock> <ProgressBar Value="{Binding CanUseMemory}" Maximum="{Binding AllMemory}" Minimum="0" Height="13" Width="{Binding ElementName=colPro,Path=Width}" BorderBrush="#BCBCBC" Background="#E6E6E6" BorderThickness="1" Foreground="#26A0DA"></ProgressBar> </StackPanel> <StackPanel Grid.Column="2" Margin="10,0,0,0"> <TextBlock Foreground="Black" Text="{Binding DiskType}" Margin="0,0,0,5"/> <TextBlock Foreground="Black"> <TextBlock.Text> <MultiBinding StringFormat="{}{0} GB可用,共 {1} GB"> <Binding Path="CanUseMemory"></Binding> <Binding Path="AllMemory"></Binding> </MultiBinding> </TextBlock.Text> </TextBlock> </StackPanel> </Grid> </DockPanel> </DataTemplate> </local:AView.ItemTemplate> </local:AView>
效果图:
将窗体拉宽后,知识中间变了
接下来,实现超大图标布局,这里我把小图标的版本按钮换成做大图标了,事件名称也改了VeryBig
<local:AView x:Key="ImageVeryBigView"> <local:AView.ItemTemplate> <DataTemplate> <StackPanel> <StackPanel.ToolTip> <StackPanel> <TextBlock HorizontalAlignment="Center" Margin="1,0,0,0" Foreground="Black"> <TextBlock.Text> <MultiBinding StringFormat="可用空间: {0} GB
总大小: {1} GB "> <Binding Path="CanUseMemory"></Binding> <Binding Path="AllMemory"></Binding> </MultiBinding> </TextBlock.Text> </TextBlock> </StackPanel> </StackPanel.ToolTip> <Viewbox Width="200" HorizontalAlignment="Center"> <Image Source="{Binding DiskIcon,Converter={StaticResource ImagePathConverter},ConverterParameter=‘imageDisk/‘}"/> </Viewbox> <TextBlock HorizontalAlignment="Center" Margin="1,0,0,0" Foreground="Black"> <TextBlock.Text> <MultiBinding StringFormat="{}{0}({1}:)"> <Binding Path="DiskName"></Binding> <Binding Path="DiskChar"></Binding> </MultiBinding> </TextBlock.Text> </TextBlock> </StackPanel> </DataTemplate> </local:AView.ItemTemplate> </local:AView>
效果图,这是窗口最大化截图的:
最后,我们补充按钮的事件,切换就OK了
private void btnDetail_Click(object sender, RoutedEventArgs e) { lstAyDisk.View = (ViewBase)this.FindResource("GridView"); } private void btnContent_Click(object sender, RoutedEventArgs e) { lstAyDisk.View = (ViewBase)this.FindResource("ImageContentView"); } private void btnTile_Click(object sender, RoutedEventArgs e) { lstAyDisk.View = (ViewBase)this.FindResource("ImageTileView"); } private void btnNormalImage_Click(object sender, RoutedEventArgs e) { lstAyDisk.View = (ViewBase)this.FindResource("ImageNormalView"); } private void btnVeryBigImage_Click(object sender, RoutedEventArgs e) { lstAyDisk.View = (ViewBase)this.FindResource("ImageVeryBigView"); }
快来看看效果图:
=============潇洒的版权线==========www.ayjs.net===== Aaronyang ========= AY =========== 安徽 六安 杨洋 ========== 未经允许不许转载 =========
小小的推荐,作者的肯定,读者的支持。推不推荐不重要,重要的是希望大家能把WPF推广出去,别让这么好的技术消失了,求求了,让我们为WPF技术做一份贡献。
[Aaronyang] 写给自己的WPF4.5 笔记9[复杂数据处理三步曲,数据展示ListView泪奔2/3]