WPF控件开发之自定义控件
- 格式:pdf
- 大小:685.71 KB
- 文档页数:20
[WPF ⾃定义控件]从ContentControl 开始⼊门⾃定义控件1. 前⾔我去年写过⼀个在UWP ⾃定义控件的,⼤部分的经验都可以⽤在WPF 中(只有⼀点⼩区别)。
这篇⽂章的⽬的是快速⼊门⾃定义控件的开发,所以尽量精简了篇幅,更深⼊的概念在以后介绍各控件的⽂章中实际运⽤到才介绍。
是WPF 中最基础的⼀种控件,Window 、Button 、ScrollViewer 、Label 、ListBoxItem 等都继承⾃ContentControl 。
⽽且ContentControl 的结构⼗分简单,很适合⽤来⼊门⾃定义控件。
这篇⽂章通过⾃定义⼀个ContentControl 来介绍⾃定义控件的⼀些基础概念,包括⾃定义控件的基本步骤及其组成。
2. 什么是⾃定义控件在开始之前⾸先要了解什么是⾃定义控件以及为什么要⽤⾃定义控件。
在WPF 要创建⾃⼰的控件(Control ),通常可以使⽤⾃定义控件(CustomControl )或⽤户控件(UserControl ),两者最⼤的区别是前者可以通过对控件的外观灵活地进⾏定制。
如在下⾯的例⼦中,通过ControlTemplate 将Button改成⼀个圆形按钮:控件库中通常使⽤⾃定义控件⽽不是⽤户控件。
3. 创建⾃定义控件ContentControl 最简单的派⽣类应该是HeaderedContentControl 了吧,这篇⽂章会创建⼀个模仿HeaderedContentControl 的MyHeaderedContentControl ,它继承⾃ContentControl 并添加了⼀些细节。
在“添加新项”对话框选择“⾃定义控件(WPF )”,名称改为"MyHeaderedContentControl.cs"(⽤My-做前缀是⼗分差劲的命名⽅式,但只要⼀看到这种命名就明⽩这是个测试⽤的东西,不会和正规代码搞错,所以我习惯了测试⽤代码就这样命名。
简单易学图文并茂创作控件自己创作控件,分三种主要的形式:复合控件,扩展控件,和自定义控件。
复合控件,顾名思义就是把现有的进行组合,让它们协作形成功能强大的新控件;扩展控件,是以某一现有控件为基础,让它具有新的功能;自定义控件,则是由作者完全操刀,建立一个全新的控件。
可以用一个比喻来理解这三种形式的区别:复合控件,就是你买好各个电脑配件,组装成一台电脑;扩展控件,就是把显卡上的零件更换几个,让它能力比标准产品更强大;自定义控件,就是自己制作一个名为“生人勿近”的硬件,他可以通过PCI插槽,安装到电脑上,一旦生人走近,它能识别并发出狗叫……综上,三种形式中,复合控件相对来说是最简单的;扩展控件在其次;自定义控件最难。
通常,我们使用前两种技术,就能创作出很复杂的控件了。
范例1:Excel的单元格Cell,当它没有焦点的时候,就是一个TextBlock,当它获得焦点,可以编辑的时候,就是一个TextBox框。
这样一个控件,将是我们今后制作表格控件的基础。
范例1中,我们会用到两种技术,复合控件和扩展控件。
在VS中,要进行如下的工作(推荐使用VS2010)1.新建一个Solution,名为Cell;2.添加一个名为“TestAPP”的WFP项目。
我们用他来测试成果;3.添加一个名为“Ctrl_Cell”的WFP用户控件项目。
然后,我们需要一个TextBlock,和一个TextBox控件,这是我们的演员。
考虑一下它们应该怎样演出,才能达到我们需要的效果:●平时这个控件,应该表现出TextBlock的外观;●当我们点击这个Label时,隐藏的TextBox控件跑到TextBlock的前面,并且它显示的值和TextBlock一样。
同时,这个值是可以编辑的;●编辑完TextBox的内容,按下Enter,或者点击屏幕上的其他控件,让TextBox失去焦点,TextBox消失。
TextBlock跑到前面来,并且显示编辑后的内容。
该控件叫 Sum m ar y, 主要是一些汇总信息的显示,有几个地方用,之前都是分散到各个XA ML 文件里,不统一。
本人WPF新手,对XAML了解不多,做这个软件基本上都是用CM,界面布局用Avalon Dock。
由于缺乏相关经验,又没有一个能问得上的人指导,写这个控件费了我很长时间(啥时有空啥时动动)。
之前主要做一些功能方面的,没有心思美化界面,现在虽然还有很多功能没写,但是基本上够自己用了,放下心思来做一些界面上的东西,要不然何苦选择WPF?先看一下图:该Custo m Cont rol 由4部分组成:大标题,小标题,值及Detail。
虽然细分这么多,但实质上还是一个列表类的控件,所以选择继承自 Item sC ontro l.做这个控件的时候,遇到了一些详细的问题不知道怎么解决,Google/Bing 都没有找到我要了解的,Baidu更不用提了,漫天的转载,Copy.1, 类似 Com boB ox 的 Displa yMemb erPat h 如何弄?既然都自定控件了,当然是想让它适用不同场景,不能局限于某一个实体类,最好是能像Displa yMemb erPat h ValueM em ber Path这样的属性。
这里,我定义了:Item Ti tlePa thPro perty及 Item Va luePa thPro perty来处理。
2,有了上面两个依赖属性,但是还不足以处理更多的场景,最好是能有不同的 Tem pla te 。
这里我定义了:TitleT em pla te / ValueT em pla te及De tailT em pla te.第一个问题,只需定义一个简单的 DataTe m plat e ,然后用 Tem pla teBin ding即可做到。
WPF⾃定义TabControl控件样式⼀、前⾔程序中经常会⽤到TabControl控件,默认的控件样式很普通。
⽽且样式或功能不⼀定符合我们的要求。
⽐如:我们需要TabControl的标题能够居中、或平均分布;或者我们希望TabControl的标题能够进⾏关闭。
要实现这些功能我们需要对TabControl的样式进⾏定义。
⼆、实现TabControl的标题平均分布默认的TabControl标题是使⽤TabPanel容器包含的。
要想实现TabControl标题头平均分布,需要把TabPanel替换成UniformGrid;替换后的TabControl样式如下:<Style x:Key="TabControlStyle" TargetType="{x:Type TabControl}"><Setter Property="Padding" Value="2"/><Setter Property="HorizontalContentAlignment" Value="Center"/><Setter Property="VerticalContentAlignment" Value="Center"/><Setter Property="Background" Value="White"/><Setter Property="BorderBrush" Value="#FFACACAC"/><Setter Property="BorderThickness" Value="1"/><Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="{x:Type TabControl}"><Grid x:Name="templateRoot" ClipToBounds="True" SnapsToDevicePixels="True" KeyboardNavigation.TabNavigation="Local"><Grid.ColumnDefinitions><ColumnDefinition x:Name="ColumnDefinition0"/><ColumnDefinition x:Name="ColumnDefinition1" Width="0"/></Grid.ColumnDefinitions><Grid.RowDefinitions><RowDefinition x:Name="RowDefinition0" Height="Auto"/><RowDefinition x:Name="RowDefinition1" Height="*"/></Grid.RowDefinitions><UniformGrid x:Name="HeaderPanel" Rows="1" Background="Transparent" Grid.Column="0" IsItemsHost="True" Margin="0" Grid.Row="0" KeyboardNavigation.TabIndex="1" Panel.ZIndex<Line X1="0" X2="{Binding ActualWidth, RelativeSource={RelativeSource Self}}" Stroke="White" StrokeThickness="0.1" VerticalAlignment="Bottom" Margin="0 0 0 1" SnapsToDevicePixels="True"<Border x:Name="ContentPanel" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.Column<ContentPresenter x:Name="PART_SelectedContentHost" ContentTemplate="{TemplateBinding SelectedContentTemplate}" Content="{TemplateBinding SelectedContent}" ContentStringFormat</Border></Grid><ControlTemplate.Triggers><Trigger Property="TabStripPlacement" Value="Bottom"><Setter Property="Grid.Row" TargetName="HeaderPanel" Value="1"/><Setter Property="Grid.Row" TargetName="ContentPanel" Value="0"/><Setter Property="Height" TargetName="RowDefinition0" Value="*"/><Setter Property="Height" TargetName="RowDefinition1" Value="Auto"/></Trigger><Trigger Property="TabStripPlacement" Value="Left"><Setter Property="Grid.Row" TargetName="HeaderPanel" Value="0"/><Setter Property="Grid.Row" TargetName="ContentPanel" Value="0"/><Setter Property="Grid.Column" TargetName="HeaderPanel" Value="0"/><Setter Property="Grid.Column" TargetName="ContentPanel" Value="1"/><Setter Property="Width" TargetName="ColumnDefinition0" Value="Auto"/><Setter Property="Width" TargetName="ColumnDefinition1" Value="*"/><Setter Property="Height" TargetName="RowDefinition0" Value="*"/><Setter Property="Height" TargetName="RowDefinition1" Value="0"/></Trigger><Trigger Property="TabStripPlacement" Value="Right"><Setter Property="Grid.Row" TargetName="HeaderPanel" Value="0"/><Setter Property="Grid.Row" TargetName="ContentPanel" Value="0"/><Setter Property="Grid.Column" TargetName="HeaderPanel" Value="1"/><Setter Property="Grid.Column" TargetName="ContentPanel" Value="0"/><Setter Property="Width" TargetName="ColumnDefinition0" Value="*"/><Setter Property="Width" TargetName="ColumnDefinition1" Value="Auto"/><Setter Property="Height" TargetName="RowDefinition0" Value="*"/><Setter Property="Height" TargetName="RowDefinition1" Value="0"/></Trigger><Trigger Property="IsEnabled" Value="False"><Setter Property="TextElement.Foreground" TargetName="templateRoot" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/></Trigger></ControlTemplate.Triggers></ControlTemplate></Setter.Value></Setter></Style>即使这样设置了,TabControl的标题还是很丑,这个时候就需要通过设置TabItem来更改标题样式了。
简单易学图文并茂创作控件自己创作控件,分三种主要的形式:复合控件,扩展控件,和自定义控件。
复合控件,顾名思义就是把现有的进行组合,让它们协作形成功能强大的新控件;扩展控件,是以某一现有控件为基础,让它具有新的功能;自定义控件,则是由作者完全操刀,建立一个全新的控件。
可以用一个比喻来理解这三种形式的区别:复合控件,就是你买好各个电脑配件,组装成一台电脑;扩展控件,就是把显卡上的零件更换几个,让它能力比标准产品更强大;自定义控件,就是自己制作一个名为“生人勿近”的硬件,他可以通过PCI插槽,安装到电脑上,一旦生人走近,它能识别并发出狗叫……综上,三种形式中,复合控件相对来说是最简单的;扩展控件在其次;自定义控件最难。
通常,我们使用前两种技术,就能创作出很复杂的控件了。
范例1:Excel的单元格Cell,当它没有焦点的时候,就是一个TextBlock,当它获得焦点,可以编辑的时候,就是一个TextBox框。
这样一个控件,将是我们今后制作表格控件的基础。
范例1中,我们会用到两种技术,复合控件和扩展控件。
在VS中,要进行如下的工作(推荐使用VS2010)1.新建一个Solution,名为Cell;2.添加一个名为“TestAPP”的WFP项目。
我们用他来测试成果;3.添加一个名为“Ctrl_Cell”的WFP用户控件项目。
然后,我们需要一个TextBlock,和一个TextBox控件,这是我们的演员。
考虑一下它们应该怎样演出,才能达到我们需要的效果:●平时这个控件,应该表现出TextBlock的外观;●当我们点击这个Label时,隐藏的TextBox控件跑到TextBlock的前面,并且它显示的值和TextBlock一样。
同时,这个值是可以编辑的;●编辑完TextBox的内容,按下Enter,或者点击屏幕上的其他控件,让TextBox失去焦点,TextBox消失。
TextBlock跑到前面来,并且显示编辑后的内容。
WPF⾃定义控件与样式-⾃定义按钮(Button)⼀、前⾔程序界⾯上的按钮多种多样,常⽤的就这⼏种:普通按钮、图标按钮、⽂字按钮、图⽚⽂字混合按钮。
本⽂章记录了不同样式类型的按钮实现⽅法。
⼆、固定样式的按钮固定样式的按钮⼀般在临时使⽤时或程序的样式⽐较固定时才会使⽤,按钮整体样式不需要做⼤的改动。
2.1 普通按钮-扁平化风格先看效果:定义Button的样式,详见代码:<Style x:Key="BtnInfoStyle" TargetType="Button"><Setter Property="Width" Value="70"/><Setter Property="Height" Value="25"/><Setter Property="Foreground" Value="White"/><Setter Property="BorderThickness" Value="0"/><Setter Property="Background" Value="#43a9c7"/><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="Button"><Border x:Name="border" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels <TextBlock Text="{TemplateBinding Content}" Foreground="{TemplateBinding Foreground}" VerticalAlignment="Center" HorizontalAlignment="Center"/></Border><ControlTemplate.Triggers><Trigger Property="IsMouseOver" Value="True"><Setter TargetName="border" Property="Background" Value="#2f96b4"/></Trigger><Trigger Property="IsPressed" Value="True"><Setter TargetName="border" Property="Background" Value="#2a89a4"/></Trigger></ControlTemplate.Triggers></ControlTemplate></Setter.Value></Setter></Style>引⽤⽅法:<Grid Background="White"><StackPanel Orientation="Horizontal" Margin="10" VerticalAlignment="Top"><Button Style="{StaticResource BtnInfoStyle}" Content="信息" Margin="5 0"/></Grid>上述代码实现了Button按钮的扁平化样式,如果你想调整颜⾊风格,通过修改Background的值可实现默认颜⾊,⿏标经过颜⾊以及⿏标按下颜⾊。
WPF使⽤Winform⾃定义控件在WPF的⽤户控件中使⽤Winfrom⾃定义控件的过程:1、添加引⽤WindowsFormsIntegration.dllSystem.Windows.Forms.dll2、在要使⽤WinForm控件的WPF窗体的XAML⽂件中添加如下内容:xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"xmlns:wfi ="clr-namespace:System.Windows.Forms.Integration;assembly=WindowsFormsIntegration"如下图所⽰:3、在WPF的容器控件内如StackPanel内⾸先要添加WinForm控件的宿主容器,⽤于衔接WPF和WinForm,对应XAML如下:说明:<wfi:WindowsFormsHost></wfi:WindowsFormsHost>即为WinForm控件的宿主容器,每⼀个宿主容器只能放⼀个WinForm控件,如下例,放了三个WinForm控件,分别放在三个宿主容器⾥⾯,该容器可以设置属性来调整⼤⼩和布局注意:如上我添加的WinForm控件如在指定其Name时,必须加前缀x:,如添加Lable时<wf:Label x:Name="wpfLabel" Text="我是WPF中的WinForm控件” />,否则后台代码⽆法访问。
<local:UserControl1 Name="Header" Width="319" Height="30"></local:UserControl1>这个为Winform⾃定义控件。
.Net WInform开发笔记(三)谈谈自制控件(自定义控件)自定义控件的出现有利于用户更好的实现自己的想法,可以封装一些常用的方法,属性等等,本文详细介绍一下自定义控件的实现,感兴趣的朋友可以了解下末日这天写篇博客吧,既然没来,那就纪念一下。
这次谈谈自制控件,也就是自定义控件,先上图,再说1.扩展OpenFileDialog,在OpenFileDialog中添加各种文件(.txt,.jpg,.excel等等)的预览功能2.重写ListBox,增加折叠、鼠标背影、分类等功能-----------------------------分割线--------------------------------------------------------------一、扩展OpenFileDialog许多软件的打开对话框都有预览功能,最常见的就是图片预览,用鼠标选择一个图片文件后,右侧或者下侧就会有该图片的缩略图(photoshop中属于后者)。
在winform编程中,有专门的打开文件对话框的类O penFileDialog,但是他不提供文件预览功能,封装得实在太好。
看看它公开那些接口提到扩展,很多人可能想到继承它就可以扩展它,可惜OpenFileDialog声明为sealed,不允许从他继承。
稍微底层一点的,想到可以通过Win32 API来修改它的显示方式,只可惜,如你所见,它根本没提供Handle 属性,更别说HandleCreated、HandleDestroyed等事件了。
那么怎么样子搞呢?其实答案还是通过Win32 API,只是我们取得它的句柄的方式要复杂一点而且调用API的时机隐晦了一点。
提示:1.Win32 API操作窗体需要知道窗体的句柄;2.不熟悉Win32编程的同学可以先上网查查资料,特别是不知道SetParent、SetWindowPos等API是干嘛的,我觉得以下的看不懂。
为什么说取得它的句柄复杂了一点?难道不是用“FindWindow”、“FindWindowEx”、“EnumChildWindows”等获取OpenFileDialog的句柄,再用“SetParent”、“SetWindowPos”等API将.net控件(本例中是DataGri dView控件,当然可以使其他任何一种)添加到OpenFileDialog中去?没错,以上列举出来的API都是基本要用到的,“只是用在什么地方、什么时候用”是个比较麻烦的问题,原因如下:1)我们知道OpenfileDialog显示的是模式对话框,也就是说,一旦它ShowDialog(),它以下的代码是不会再执行的,具体原因是什么(我以后的博客会专门讲为什么),你现在可以理解为OpenFileDialog()方法会阻塞调用线程,既然阻塞了调用线程,那么我们再无法控制程序了(直到它返回),根本谈不上再调用API获取OpenFileDialog的句柄然后去操作它。
wpf⾃定义控件(包含依赖属性以及事件)创建了这个依赖属性,就可以直接在对应的控件中使⽤了,就像是button中⼀开始就内置的width等属性⼀样,这个在设计⾃定义控件的时候⽤的尤其多下⾯讲的是⾃定义分页控件代码:<UserControl x:Class="WPFDataGridPaging.Pager"xmlns="/winfx/2006/xaml/presentation"xmlns:x="/winfx/2006/xaml"xmlns:mc="/markup-compatibility/2006"xmlns:d="/expression/blend/2008"xmlns:local="clr-namespace:WPFDataGridPaging"mc:Ignorable="d"d:DesignHeight="30" d:DesignWidth="220"><UserControl.Resources><Style TargetType="{x:Type Button}"><Setter Property="Width" Value="22"/><Setter Property="Height" Value="22"/></Style></UserControl.Resources><Grid><StackPanel Orientation="Horizontal"><Button x:Name="FirstPageButton" Margin="5,0" Click="FirstPageButton_Click"><Path Width="7" Height="10" Data="M0,0L0,10 M0,5L6,2 6,8 0,5" Stroke="Black" StrokeThickness="1"Fill="Black" VerticalAlignment="Center" HorizontalAlignment="Center"/></Button><Button x:Name="PreviousPageButton" Margin="0,0,5,0" Click="PreviousPageButton_Click"><Path Width="8" Height="8" Data="M0,4L8,0 8,8z" Stroke="Black" Fill="Black" VerticalAlignment="Center" HorizontalAlignment="Center"/></Button><TextBlock VerticalAlignment="Center"><Run Text="第"/><Run x:Name="rCurrent" Text="0"/><Run Text="页"/></TextBlock><Button Margin="5,0" x:Name="NextPageButton" Click="NextPageButton_Click"><Path Width="8" Height="8" Data="M0,4L8,0 8,8z" Stroke="Black" Fill="Black" VerticalAlignment="Center" HorizontalAlignment="Center"><Path.RenderTransform><RotateTransform Angle="180" CenterX="4" CenterY="4"/></Path.RenderTransform></Path></Button><Button Margin="0,0,5,0" x:Name="LastPageButton" Click="LastPageButton_Click"><Path x:Name="MainPath" Width="7" Height="10" Data="M0,0L0,10 M0,5 L6,2 6,8 0,5"Stroke="Black" StrokeThickness="1" Fill="Black" VerticalAlignment="Center" HorizontalAlignment="Center"><Path.RenderTransform><RotateTransform Angle="180" CenterX="3" CenterY="5"/></Path.RenderTransform></Path></Button><TextBlock VerticalAlignment="Center"><Run Text="共"/><Run x:Name="rTotal" Text="0"/><Run Text="页"/></TextBlock></StackPanel></Grid></UserControl>界⾯:后台代码:public partial class Pager : UserControl{#region声明事件和依赖属性public static RoutedEvent FirstPageEvent;public static RoutedEvent PreviousPageEvent;public static RoutedEvent NextPageEvent;public static RoutedEvent LastPageEvent;public static readonly DependencyProperty CurrentPageProperty;public static readonly DependencyProperty TotalPageProperty;#endregionpublic string CurrentPage{get { return (string)GetValue(CurrentPageProperty); }set { SetValue(CurrentPageProperty, value); }}public string TotalPage{get { return (string)GetValue(TotalPageProperty); }set { SetValue(TotalPageProperty, value); }}public Pager(){InitializeComponent();}static Pager(){#region注册事件以及依赖属性//注册FirstPageEvent事件,事件的拥有者是Pager,路由事件的名称是FirstPage,这是唯⼀的FirstPageEvent = EventManager.RegisterRoutedEvent("FirstPage", RoutingStrategy.Direct,typeof(RoutedEventHandler), typeof(Pager));PreviousPageEvent = EventManager.RegisterRoutedEvent("PreviousPage", RoutingStrategy.Direct,typeof(RoutedEventHandler), typeof(Pager));NextPageEvent = EventManager.RegisterRoutedEvent("NextPage", RoutingStrategy.Direct,typeof(RoutedEventHandler), typeof(Pager));LastPageEvent = EventManager.RegisterRoutedEvent("LastPage", RoutingStrategy.Direct,typeof(RoutedEventHandler), typeof(Pager));//注册⾃定义的依赖属性CurrentPageProperty = DependencyProperty.Register("CurrentPage",typeof(string), typeof(Pager), new PropertyMetadata(string.Empty, new PropertyChangedCallback(OnCurrentPageChanged))); TotalPageProperty = DependencyProperty.Register("TotalPage",typeof(string), typeof(Pager), new PropertyMetadata(string.Empty, new PropertyChangedCallback(OnTotalPageChanged)));#endregion}public event RoutedEventHandler FirstPage{add { AddHandler(FirstPageEvent, value); }remove { RemoveHandler(FirstPageEvent, value); }}public event RoutedEventHandler PreviousPage{add { AddHandler(PreviousPageEvent, value); }remove { RemoveHandler(PreviousPageEvent, value); }}public event RoutedEventHandler NextPage{add { AddHandler(NextPageEvent, value); }remove { RemoveHandler(NextPageEvent, value); }}public event RoutedEventHandler LastPage{add { AddHandler(LastPageEvent, value); }remove { RemoveHandler(LastPageEvent, value); }}///<summary>///依赖属性回调⽅法///</summary>///<param name="d"></param>///<param name="e"></param>public static void OnTotalPageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){Pager p = d as Pager;if (p != null){Run rTotal = (Run)p.FindName("rTotal");rTotal.Text = (string)e.NewValue;}}private static void OnCurrentPageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){Pager p = d as Pager;if (p != null){Run rCurrrent = (Run)p.FindName("rCurrent");rCurrrent.Text = (string)e.NewValue;}}private void FirstPageButton_Click(object sender, RoutedEventArgs e){RaiseEvent(new RoutedEventArgs(FirstPageEvent, this));}private void PreviousPageButton_Click(object sender, RoutedEventArgs e){RaiseEvent(new RoutedEventArgs(PreviousPageEvent, this));}private void NextPageButton_Click(object sender, RoutedEventArgs e){RaiseEvent(new RoutedEventArgs(NextPageEvent, this));}private void LastPageButton_Click(object sender, RoutedEventArgs e){RaiseEvent(new RoutedEventArgs(LastPageEvent, this));}}上⾯就是⾃定义的⼀个分页控件,如果我们想要使⽤这个控件,如下:<Window x:Class="WPFDataGridPaging.MainWindow"xmlns="/winfx/2006/xaml/presentation"xmlns:x="/winfx/2006/xaml"xmlns:d="/expression/blend/2008"xmlns:mc="/markup-compatibility/2006"xmlns:i="/expression/2010/interactivity"xmlns:local="clr-namespace:WPFDataGridPaging"mc:Ignorable="d"Title="MainWindow" Height="350" Width="525"><Grid><Grid.RowDefinitions><RowDefinition Height="*"/><RowDefinition Height="Auto"/></Grid.RowDefinitions><DataGrid Grid.Row="0" ItemsSource="{Binding FakeSource}" AutoGenerateColumns="False" CanUserAddRows="False"> <DataGrid.Columns><DataGridTextColumn Header="Item Id" Binding="{Binding Id}" Width="80"/><DataGridTextColumn Header="Item Name" Binding="{Binding ItemName}" Width="180"/></DataGrid.Columns></DataGrid><!-- TotalPage 和CurrentPage就是在Pager中⾃定义的依赖属性,可以在这⾥直接来⽤--><local:Pager TotalPage="{Binding TotalPage}"CurrentPage="{Binding CurrentPage, Mode=TwoWay}"HorizontalAlignment="Center"Grid.Row="1"><i:Interaction.Triggers><!--EventName="FirstPage" 这⾥FirstPage就是前⾯注册事件的时候默认的事件名称,必须要唯⼀ --><i:EventTrigger EventName="FirstPage"><i:InvokeCommandAction Command="{Binding FirstPageCommand}"/></i:EventTrigger><i:EventTrigger EventName="PreviousPage"><i:InvokeCommandAction Command="{Binding PreviousPageCommand}"/></i:EventTrigger><i:EventTrigger EventName="NextPage"><i:InvokeCommandAction Command="{Binding NextPageCommand}"/></i:EventTrigger><i:EventTrigger EventName="LastPage"><i:InvokeCommandAction Command="{Binding LastPageCommand}"/></i:EventTrigger></i:Interaction.Triggers></local:Pager></Grid></Window>界⾯:后台代码:///<summary>/// Interaction logic for MainWindow.xaml///</summary>public partial class MainWindow : Window{public MainWindow(){InitializeComponent();//这⾥⼀定要注意,⼀定要将模型加到上下⽂中,要不然依赖属性和事件都不会触发,在实际开发中是不可能⼀打开 //界⾯就开始加载出来数据的,所以可以通过ObservableCollection形式绑定表格数据,//⽽需要绑定事件和依赖属性的需要将其绑定到上下⽂中DataContext = new MainViewModel();}}MainViewModel:public class MainViewModel : ViewModel{private ICommand _firstPageCommand;public ICommand FirstPageCommand{get{return _firstPageCommand;}set{_firstPageCommand = value;}}private ICommand _previousPageCommand;public ICommand PreviousPageCommand{get{return _previousPageCommand;}set{_previousPageCommand = value;}}private ICommand _nextPageCommand;public ICommand NextPageCommand{get{return _nextPageCommand;}set{_nextPageCommand = value;}}private ICommand _lastPageCommand;public ICommand LastPageCommand{get{return _lastPageCommand;}set{_lastPageCommand = value;}}private int _pageSize;public int PageSize{get{return _pageSize;}set{if(_pageSize != value){_pageSize = value;OnPropertyChanged("PageSize"); }}}private int _currentPage;public int CurrentPage{get{return _currentPage;}set{if(_currentPage != value){_currentPage = value;OnPropertyChanged("CurrentPage"); }}}private int _totalPage;public int TotalPage{get{return _totalPage;}set{if(_totalPage != value){_totalPage = value;OnPropertyChanged("TotalPage"); }}}///<summary>/// ObservableCollection表⽰⼀个动态数据集合,它可在添加、删除项⽬或刷新整个列表时提供通知。
WPF控件开发之自定义控件Windows Presentation Foundation(WPF)控件模型的扩展性极大减少了创建新控件的需要。
但在某些情况下,仍可能需要创建自定义控件。
本主题讨论可最大限度减少在Windows Presentation Foundation(WPF)中创建自定义控件以及其他控件创作模型的需要的功能。
本主题还演示如何创建新控件。
AD:Windows Presentation Foundation(WPF)控件模型的扩展性极大减少了创建新控件的需要。
但在某些情况下,仍可能需要创建自定义控件。
本主题讨论可最大限度减少在Windows Presentation Foundation(WPF)中创建自定义控件以及其他控件创作模型的需要的功能。
本主题还演示如何创建新控件。
编写新控件的替代方法以前,如果要通过现有控件获取自定义体验,您只能更改控件的标准属性,例如背景色、边框宽度和字号。
如果希望在这些预定义参数的基础之上扩展控件的外观或行为,则需要创建新的控件,通常的方法是继承现有控件并重写负责绘制该控件的方法。
虽然这仍是一种可选方法,但也可以利用WPF丰富内容模型、样式、模板和触发器来自定义现有的控件。
下面的列表提供了一些示例,演示如何在不创建新控件的情况下使用这些功能来实现统一的自定义体验。
丰富内容。
很多标准WPF控件支持丰富内容。
例如,Button的内容属性为Object类型,因此从理论上讲,任何内容都可以显示在Button上。
若要让按钮显示图像和文本,可以将图像和TextBlock添加到StackPanel中,然后将StackPanel分配给Content属性。
由于这些控件可以显示WPF可视化元素和任意数据,因此,减少了创建新控件或修改现有控件来支持复杂可视化效果的需要。
样式。
Style是表示控件属性的值的集合。
使用样式可创建所需控件外观和行为的可重用表示形式,而无需编写新控件。
例如,假设希望所有TextBlock控件都呈现字号为14的红色Airal字体。
您可以创建一个样式作为资源,然后相应地设置适当的属性。
这样,添加到应用程序中的每个TextBlock都将具有相同的外观。
数据模板。
DataTemplate可用于自定义数据在控件上的显示方式。
例如,DataTemplate 可用于指定数据在ListBox中的显示方式。
有关这种情况的示例,请参见数据模板概述。
除了自定义数据外观之外,DataTemplate还可以包含UI元素,这样大大增加了自定义UI 的灵活性。
例如,使用DataTemplate可以创建一个ComboBox,其中每一项都包含一个复选框。
控件模板WPF中的很多控件都使用ControlTemplate来定义控件的结构和外观,这样可将控件外观和控件功能分离开。
通过重新定义控件的ControlTemplate,可以彻底更改控件的外观。
例如,假设您希望控件看起来像一个交通信号灯。
此控件具有简单的用户界面和功能。
该控件有三个圆形,一次只能点亮其中的一个。
经过考虑之后,您可能意识到RadioButton提供了一次只选中一项的功能,但是RadioButton的默认外观完全不像交通信号灯上的灯。
由于RadioButton使用控件模板来定义其外观,因此很容易重新定义ControlTemplate以符合该控件的要求,从而使用单选按钮来制作交通信号灯。
说明:尽管RadioButton可以使用DataTemplate,但在本例中,只使用DataTemplate还不够。
DataTemplate定义控件内容的外观。
对于RadioButton,指示RadioButton是否选中的那个圆形右侧显示出来的全部都是该控件的内容。
在交通信号灯的示例中,单选按钮只需要成为可“点亮”的圆形。
由于交通信号灯的外观要求与RadioButton的默认外观存在很大差异,因此,有必要重新定义ControlTemplate。
一般而言,DataTemplate用于定义控件的内容(或数据),ControlTemplate用于定义控件的构成方式。
触发器。
Trigger用于在不创建新控件的情况下动态更改控件的外观和行为。
例如,假设应用程序中有多个ListBox控件,而您希望每个ListBox中的项在选中时都显示为红色粗体。
您首先想到的可能是创建一个从ListBox继承的类,然后重写OnSelectionChanged方法,以更改选中项的外观,不过,更好的方法是向ListBoxItem的样式添加一个更改选中项外观的触发器。
触发器用于更改属性值或根据属性值执行操作。
EventTrigger用于在发生事件时执行操作。
一般而言,如果控件完全复制现有控件的功能,但您希望该控件具有不同的外观,则应先考虑是否可以使用本节中讨论的某些方法来更改现有控件的外观。
控件创作模型通过丰富内容模型、样式、模板和触发器,最大程度地减少了创建新控件的需要。
但是,如果确实需要创建新控件,那么理解WPF中的不同控件创作模型就显得非常重要。
WPF提供三个用于创建控件的一般模型,每个模型都提供不同的功能集和灵活度。
这三个模型的基类分别为UserControl、Control和FrameworkElement。
从UserControl派生在WPF中创建控件的最简单方法是从UserControl派生。
如果生成继承自UserControl 的控件,需要将现有组件添加到UserControl,命名这些组件,然后在可扩展应用程序标记语言(XAML)中引用事件处理程序。
执行这些操作之后,即可在代码中引用这些命名元素和定义事件处理程序。
此开发模型非常类似于用于WPF应用程序开发的模型。
如果生成正确,UserControl可以利用丰富内容、样式和触发器的优点。
但是,如果控件继承自UserControl,则使用该控件的用户将无法使用DataTemplate或ControlTemplate来自定义其外观。
因此,有必要从Control类或其派生类(UserControl 除外)进行派生,以便创建支持模板的自定义控件。
从UserControl派生的优点如果符合以下所有情况,请考虑从UserControl派生:希望以类似于生成应用程序的方式生成控件。
控件仅由现有组件组成。
不需要支持复杂自定义项。
从Control派生从Control类派生是大多数现有WPF控件使用的模型。
在创建继承自Control类的控件时,可使用模板定义其外观。
通过这种方式,可以将运算逻辑从可视化表示形式中分离出来。
通过命令和绑定(而不是事件),也可确保分离UI和逻辑,尽可能避免引用ControlTemplate中的元素。
如果控件的UI和逻辑正确分离,该控件的用户即可重新定义其ControlTemplate,从而自定义其外观。
尽管生成自定义Control不像生成UserControl 那样简单,自定义Control还是提供了最大的灵活性。
从Control派生的优点如果符合以下任一情况,请考虑从Control派生,而不要使用UserControl类:希望控件外观能通过ControlTemplate进行自定义。
希望控件支持不同的主题。
从FrameworkElement派生从UserControl或Control派生的控件依赖于组合现有元素。
很多情况下,这是一种可接受的解决方案,因为从FrameworkElement继承的任何对象都可以位于ControlTemplate 中。
但是,某些时候,简单的元素组合不能满足控件的外观需要。
对于这些情况,使组件基于FrameworkElement才是正确的选择。
生成基于FrameworkElement的组件有两种标准方法:直接呈现和自定义元素组合。
直接呈现涉及的操作包括:重写FrameworkElement的OnRender方法,并提供显式定义组件视觉效果的DrawingContext操作。
此方法由Image和Border使用。
自定义元素组合涉及的操作包括使用Visual类型的对象组合组件的外观。
有关示例,请参见使用DrawingVisual对象。
Track是WPF中使用自定义元素组合的控件示例。
在同一控件中,也可以混合使用直接呈现和自定义元素组合。
从FrameworkElement派生的优点如果符合以下任一情况,请考虑从FrameworkElement派生:希望对控件的外观进行精确控制,而不仅仅是简单的元素组合提供的效果。
想要通过定义自己的呈现逻辑来定义控件的外观。
想要以一种UserControl和Control之外的新颖方式组合现有元素。
控件创作基础知识如前所述,WPF最强大的功能之一是,无需创建自定义控件即可实现远比设置控件的基本属性来更改其外观和行为更强的功能。
WPF属性系统和WPF事件系统使样式、数据绑定和触发器功能成为可能。
如果在控件中实现依赖项属性和路由事件,则无论使用什么模型创建自定义控件,自定义控件的用户都可以像对WPF随附的控件那样使用这些功能。
使用依赖项属性当属性为依赖项属性时,可以进行下面的操作:在样式中设置该属性。
将该属性绑定到数据源。
使用动态资源作为该属性的值。
动画处理该属性。
如果希望控件的属性支持以上任一功能,则应将该属性实现为依赖项属性。
下面的示例定义一个依赖项属性。
这段代码执行下面的操作:将一个名为ValueProperty的DependencyProperty标识符定义为publicstaticreadonly字段。
通过调用DependencyProperty..::.Register向属性系统注册该属性名,以指定以下内容:属性的名称。
属性的类型。
拥有该属性的类型。
属性的元数据。
元数据包含该属性的默认值、CoerceValueCallback和PropertyChangedCallback。
通过实现该属性的get和set访问器,定义一个名为Value的CLR“包装”属性,这个名称也就是用来注册该依赖项属性的名称。
请注意,get和set访问器只是分别调用GetValue和SetValue。
建议依赖项属性的访问器不要包含其他逻辑,这是因为客户端和WPF可绕过这两个访问器直接调用GetValue和SetValue。
例如,如果属性绑定到数据源,则不会调用该属性的set访问器。
不要向get和set访问器添加其他逻辑,而应使用ValidateValueCallback、CoerceValueCallback和PropertyChangedCallback委托在值更改时进行响应或检查该值。
为CoerceValueCallback定义一个名为CoerceValue的方法。
CoerceValue确保Value大于或等于MinValue且小于或等于MaxValue。