自从WPF引入page之后,整个windows程序的开发就再也不像是传统的窗口式的开发了。这种类似网页一样的跳转布局,其实更加符合日益复杂的工作需求,毕竟使用tab切换和使用窗口切换这之间的差别还是挺大的。
使用WPF自然很容易做出那种类似网页的效果来,当然,如果你开始参考网页布局的时候,你就开始要考虑如何建立一个可以自由切换的Tab页面,更加重要的是这个Tab页面是可以关掉的。
好在WPF实在是灵活的很,我们既可以使用类似在winform年代集成控件之后自绘,也可以用xaml语言来定义控件的界面和部分事件。并且,当依赖属性引入之后,当MVVM模式成为主流,当真正的数据驱动UI理念付诸实施之后,一切看起来真的都是相当不错的。
好了,开始进入主题,看我们如何一步步的实现一个可以关闭的TabControl。
1、先来定义界面,实际上我们点击tab标签页的时候,并不是点击的TabControl,而是其下的TabItem,因此我们先添加一个界面xaml文件TabItemExStyle.xaml。
2、在TabItemExStyle.xaml中我们需要先定义个关闭的按钮,当我们点击到关闭按钮的时候,它的圆圈背景变成红色。定义完关闭按钮后,我们才定义TabItemEx,当然在控件模板里面我们是一定要加入这个关闭按钮的,并命名为TabItemCloseButton。
整个文件如下:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <SolidColorBrush x:Key="TabItemSelectedBrush" Color="#FFE8F1FD"/> <!--关闭按钮--> <Style x:Key="TabItemCloseButtonStyle" TargetType="{x:Type Button}"> <Setter Property="Foreground" Value="Black"/> <!--修改模板属性--> <Setter Property="Template"> <Setter.Value> <!--控件模板--> <ControlTemplate TargetType="Button"> <!--背景色--> <Grid> <Ellipse x:Name="back" Fill="#00FFFFFF" /> <!--按钮内容--> <Path x:Name="cp" Width="10" Height="10" Stroke="#FF666666" StrokeThickness="2" > <Path.Data> < PathGeometry Figures="M 1,1 L 8,8 M 1,8 L 8,1" /> </Path.Data> </Path> </Grid> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Fill" TargetName="back" Value="#FFFF3333"/> </Trigger> <Trigger Property="IsPressed" Value="True"> <Setter Property="Fill" TargetName="back" Value="#5BBC1717"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <!--BOF TabItemStyle--> <ControlTemplate x:Key="TabItemExControlTemplate" TargetType="{x:Type TabItem}"> <Border Margin="0,0,10,0" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True" CornerRadius="4,4,0,0" > <StackPanel Orientation="Horizontal" > <ContentPresenter Grid.Column="0" ContentTemplate="{TemplateBinding HeaderTemplate}" Content="{TemplateBinding Header}" ContentStringFormat="{TemplateBinding HeaderStringFormat}" ContentSource="Header" HorizontalAlignment="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource FindAncestor, AncestorLevel=1, AncestorType={x:Type ItemsControl}}}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{Binding VerticalContentAlignment, RelativeSource={RelativeSource FindAncestor, AncestorLevel=1, AncestorType={x:Type ItemsControl}}}"/> <!--关闭按钮--> <Button x:Name="TabItemCloseButton" Width="15" Height="15" Style="{DynamicResource TabItemCloseButtonStyle}"/> </StackPanel> </Border> </ControlTemplate> <Style x:Key="TabItemExStyle" TargetType="{x:Type TabItem}"> <Setter Property="FocusVisualStyle"> <Setter.Value> <Style> <Setter Property="Control.Template"> <Setter.Value> <ControlTemplate> <Rectangle Margin="4,4,4,2" SnapsToDevicePixels="True" Stroke="{DynamicResource BorderBrush}" StrokeThickness="1" StrokeDashArray="1 2"/> </ControlTemplate> </Setter.Value> </Setter> </Style> </Setter.Value> </Setter> <Setter Property="Foreground" Value="black"/> <Setter Property="FontSize" Value="12"/> <Setter Property="Background" Value="#4CDCFFD2"/> <Setter Property="Template" Value="{StaticResource TabItemExControlTemplate}"/> <Style.Triggers> <Trigger Property="IsSelected" Value="True"> <Setter Property="Panel.ZIndex" Value="1"/> <Setter Property="Background" Value="#FFFFFFFF"/> <Setter Property="BorderThickness" Value="1,1,1,0"/> <Setter Property="BorderBrush" Value="#FF999999"/> <Setter Property="Margin" Value="0,0,0,-1"/> </Trigger> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Background" Value="#FFFFFFFF"/> </Trigger> </Style.Triggers> </Style> <!--EOF TabItemExStyle--> </ResourceDictionary>
3、定义完资源字典之后,我们并不能直接使用它,还需要在App中引用它。因此在App.xaml文件的Resource中要加入文件的引用:
<Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="Controls/TabItemExStyle.xaml" /> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources>
4、好了这会儿我们一个独立的控件算是成型了,如果你直接把一个tabItem的style设置为如上我们定义的样式的话,其实样子已经出来了,问题是,点击关闭按钮没法关闭啊。
于是乎我们还要解决关闭的问题:
定义一个TabItemEx的类,继承自TabItem,这是不是有点winform自绘控件的味道?
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Controls; using System.Windows; using System.Windows.Media; namespace LQMS { public class TabItemEx : TabItem { public TabItemEx() { InitializeStyle(); this.Loaded += new RoutedEventHandler(InitializeEvent); } private void InitializeEvent(object sender, RoutedEventArgs e) { Button btnClose = (Button)this.Template.FindName("TabItemCloseButton", this); if (btnClose == null) return; btnClose.Click += delegate { DependencyObject parent = VisualTreeHelper.GetParent(this); parent = VisualTreeHelper.GetParent(parent); parent = VisualTreeHelper.GetParent(parent); TabControlEx tabParent = (TabControlEx)parent; tabParent.Items.Remove(this); }; } private void InitializeStyle() { this.Style = (Style)App.Current.Resources["TabItemExStyle"]; } } }
看得懂以上的代码么?首先是在构造函数中把控件的Style设置为TabItemExStyle,那么该控件的风格就是如上我们定义的样子啦。订阅时间Loaded的控件,在控件加载的时候,用Template.FindName()函数找到我们原来定义的关闭按钮,然后订阅关闭按钮的事件。当关闭按钮点击的时候,我们通过VisualTreeHelper.GetParent()找到父元素,直到找到的父元素为TabControl,这个时候,我们就直接把自己(tabItem)给移除了!
5、做到这里,我们实际上完成了一个WPF自定义控件的设计。在你的文件里面如果需要引用这个控件的话,你需要带上命名空间:
xmlns:src="clr-namespace:LQMS" <src:TabItemEx Header="首 页" x:Name="tiHeader" Height="30" > </src:TabItemEx>
当然至于边框什么的,你还得定义一下TabControl的样式了。
数据驱动UI,再说一次,本质上来说,所有的软件,都是数据啊,只是呈现的方式不同罢了。
你同样也可以说,这世上的一切东西不过都是数据,罢了。
吵吵微信朋友圈,请付款实名加入: