WPF TabControl中纵向显示TabItem及标题文字

2017-09-23 19:53:18来源:CSDN作者:whshiyun人点击

分享

WPF TabControl中纵向显示TabItem及标题文字

tags: wpf C#


这次要实现个功能,先把最终结效果图放上来:
微信截图_20170923101306.png-12.3kB

其中就是红色框框圈出来的部分,这里有两种实现效果,第一种,将文字还是水平排列,但是整体旋转90度(第一个框框中的效果),第二种,文字不旋转,但变为垂直排列(第二个框框中的效果)。

因为才接触wpf两三天,很多内部机制都不懂,所以为了这两个效果,整整折腾了两天,实验了各种方式,尝试了XAML很多特性(或者说是wpf的特性)……但是经过这个例程,大致弄明白了怎么写ControlTemplate,怎么写DataTemplate,怎么写Converter,怎么去修改一个已有的Control控件以及怎么使用这些资源。这里把实现过程记录一下。

实现这个效果,其实是使用TabControl加TabItem控件达到的,但是从我的实验来看,原生的TabControl加TabItem控件只能做出这种效果:
微信截图_20170923102946.png-3kB
就是TabControl中的标签即便被置于垂直方向排列,但是标签里面的文字内容依然是水平排列,这个时候看起感觉十分别扭,希望将文字也垂直排列。
由于原生的TabControl及TabItem好像不支持这个操作,(如果有求大神指教,反正我从这两个控件的源码里面也没发现这个功能)所以要自己重新写或者修改TabControl及TabItem控件,来达到预期效果。

去修改控件需要利用ControlTemplate这个强大的特性,首先在预览窗口右键单击需要重构的空间,然后选择编辑模板->编辑副本,这个时候会弹出对话框,看看是新建一个资源编辑还是将这个空间的XAML代码放入已有的资源中进行编辑,这个随便。

这一步之后,系统会自动把TabControl的代码创建出来,然后就开始改吧。
结果把TabControl打开一分析,好像没有跟这个有关的……抛开DataTrigger以外,TabControl中最关键的两句话就是这个了:

<TabPanel x:Name="headerPanel" Background="Transparent" Grid.Column="0" IsItemsHost="True" Margin="2,2,2,0" Grid.Row="0" KeyboardNavigation.TabIndex="1" Panel.ZIndex="1"/><Border x:Name="contentPanel" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.Column="0" KeyboardNavigation.DirectionalNavigation="Contained" Grid.Row="1" KeyboardNavigation.TabIndex="2" KeyboardNavigation.TabNavigation="Local"><ContentPresenter x:Name="PART_SelectedContentHost" ContentSource="SelectedContent" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/></Border>

这个代码排版是不是看起来比较乱?因为微软编写这个代码的时候一行写的过于长了……我不知道这种排版时什么原因?有大神可以赐教一下,谢谢。因为之前一直是从事驱动开发及通信系统开发的,使用C语言,在我开发的过程中很反感一行写如此之长的行为……好像所有拿C语言做开发的地方都比较反感这种做法……

这段代码其实就是说了两个东西,把TabControl分成了两行,第一行是一个TabPanel,第二行是一个Border,Border中显示一个被选中的ItemControl的Content。

这里面TabPanel是显示标题用的,Border是显示内容用的,加起来就是我们看到的TabControl了。那么TabPanel里面好像没有指定显示的源?这里其实是用的IsItemsHost=”True”这个属性,这个属性是指定该Panel是容器的一个宿主。这是MSDN里面说的,但是对于初学者来说这句话好像不好理解,其实我觉得可以这样理解这句话,IsItemsHost属性如果为True,则表示TabPanel仅仅是一个相框,显示的内容是挂载在他底下的Item中的内容,若TabPanel底下没有挂载任何Item,则显示空,并且该TabPanel的大小由Item的大小决定,这里这个大小很重要,因为这个关系到一种实现方法行不通,后面再来讨论。

这样看来,旋转文字的工作在TabControl中好像不能实现,只能寄希望于TabItem了。

同样的方式,创建一个TabItem模板副本。

这个TabItem在界面上显示了什么?其实只有第一个图红色框框中的那一点点,就是那个标签。最开始想到的就是旋转这个标签,只要把标签旋转过来了,问题不就解决了么?然而事与愿违,旋转标签之后的效果:
微信截图_20170923113209.png-6.8kB
旋转的代码:

<Setter Property="RenderTransform">    <Setter.Value>        <TransformGroup>            <RotateTransform Angle="90"/>        </TransformGroup>    </Setter.Value></Setter>

这里有个问题我先忽略掉,就是旋转点的问题,这个有办法解决,关键问题是长和宽的值!!第一,这里两个TabItem为什么会重叠?第二,最左边为什么会有这么宽一个空白区?其实这个是TabControl中TabPanel尺寸导致的,这也是为什么之前把那里加粗的原因。当TabItem旋转后,其占用的宽度还是等于原来的宽度,占用的高度还是等于原来的高度,而这个TabItem的相框是TabPanel绘制的,TabPanel的宽度和高度又等于其索要显示的内容TabItem的高度和宽度,在旋转后TabItem的高度和宽度没变,但是这时其实我们期望显示的宽度其实应该等于旋转之前的高度,我们期待显示的高度应该等于旋转之前的宽度才对,所以出现了这种状况,后来尝试了各种手段,都无法解决这个问题,于是最终放弃了该方式。

后来,把目标集中在旋转文字上面,网上搜了好多文章之后发现了旋转的方式其实有多种,之前使用的是RenderTransform,其实还有一种,LayoutTransform,前一种旋转是对整个Control的旋转,而后一个旋转是只旋转内容,不旋转Control实体。利用这个,我们来旋转Header!!

不过……Header是什么……先来看看TabItem源码中的这一句话:

<ContentPresenter x:Name="contentPresenter" ContentSource="Header" Focusable="False" HorizontalAlignment="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>

又是一坨……
这里我们看到ContentSource="Header"这个属性,这个是把Content的显示源设置为了Header,那么这个Header到底是什么来头?这个Header是HeaderedContentControl的一个属性,HeaderedContentControl是ContentControl的一个派生类,HeaderedContentControl与ContentControl的区别?没错就是多了一个Headered……见名知意,就是这个Content不是一个一般的Content,是一个有Header的Content,或者这样简单理解,一般的Content里面只有一个Content,HeaderedContentControl里面有两个Content,一个Content还是叫Content用来存放该窗口的实际内容,另外一个Content叫Header用来存放该窗口的标题,这样一个HeaderedContentControl就可以分开记录该窗口的标题和实际内容了。
那么回到ContentSource="Header"这里,这里其实就是说,这个地方需要显示的是窗口的标题。这也是为什么TabControl中的TabPanel里面会是显示的TabItem的标题了,因为TabItem把Content的显示源指定为了Header,而TabPanel中的IsItemsHost=”True”设置就是直接把TabItem显示出来,这时TabItem显示的是Header,所以TabPanel中就显示出了Header。

所以……绕了这么大一圈……终于回到文字旋转上来了,我们其实应该旋转的是Header,终于找到最终的背锅侠了!!!

怎么旋转Header?问题又来了……又在网上胡乱查找了一些资料,没看到啥靠谱的……后来在HeaderedContentControl里面找到一个叫HeaderTemplate的东西,这是个DataTemplate,好像是显示Header区域的DataTemplate,于是就尝试了一下,写了一个HeaderTemplate,就是这样的:

<Setter Property="HeaderTemplate">    <Setter.Value>        <DataTemplate>            <TextBlock Text="{Binding Path=Header, RelativeSource={RelativeSource AncestorType=TabItem}}">                <TextBlock.LayoutTransform>                    <RotateTransform Angle="90"/>                </TextBlock.LayoutTransform>            </TextBlock>        </DataTemplate>    </Setter.Value></Setter>

这里面就没有用RenderTransform了,而是用了LayoutTransform,于是就成功了!就是第一个图中第一个红色框框中的效果。

第一个效果成功了之后就开始研究第二个效果……第二个效果最笨的一个方法就是……直接在给Header赋值的时候加上回车符……<TabItem Header="1&#13;2&#13;3" Content="1111111"/>就像这样。

但是感觉这样实现好像不够优雅……尤其是编写界面的时候还要区分当前的TabStripPlacement的值是不是为Right或者Left……为了让界面编程的时候不care换行符,这里同样用一个HeaderTemplate来实现,只是这里实现的时候不对齐进行LayoutTransform操作而是进行一个Converter操作。
XAML代码:

<Setter Property="HeaderTemplate">    <Setter.Value>        <DataTemplate>            <TextBlock Text="{Binding Path=Header, RelativeSource={RelativeSource AncestorType=TabItem} ,Converter={StaticResource tabItemCV}}">            </TextBlock>        </DataTemplate>    </Setter.Value></Setter>

C#代码:

class TabItemConverter:IValueConverter{    //    // 摘要:    //     转换值。    //    // 参数:    //   value:    //     绑定源生成的值。    //    //   targetType:    //     绑定目标属性的类型。    //    //   parameter:    //     要使用的转换器参数。    //    //   culture:    //     要用在转换器中的区域性。    //    // 返回结果:    //     转换后的值。 如果该方法返回 null,则使用有效的 null 值。    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)    {        string str = value.ToString();        StringBuilder sb = new StringBuilder();        int i = 0;        for(i=0; i< str.Length; i++)        {            if(i < (str.Length - 1))            {                sb.Append(str[i]);                sb.Append("/n");            } else            {                sb.Append(str[i]);            }        }        return sb.ToString();    }    //    // 摘要:    //     转换值。    //    // 参数:    //   value:    //     绑定目标生成的值。    //    //   targetType:    //     要转换为的类型。    //    //   parameter:    //     要使用的转换器参数。    //    //   culture:    //     要用在转换器中的区域性。    //    // 返回结果:    //     转换后的值。 如果该方法返回 null,则使用有效的 null 值。    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)    {        throw new NotImplementedException();    }}

其实就是把输入的字符串中间加上回车符再输出而已。

在编写Template的资源字典文件的开头中还需加上:xmlns:re="clr-namespace:testWpf.Resources"<re:TabItemConverter x:Key="tabItemCV"/>

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"                    xmlns:local="clr-namespace:testWpf"                    xmlns:re="clr-namespace:testWpf.Resources"                    xmlns:sys ="clr-namespace:System;assembly=mscorlib">    <re:TabItemConverter x:Key="tabItemCV"/>    ……</ResourceDictionary>

这里,C#文件放置在工程目录的Resuorces文件夹下,所以路径是:testWpf.Resources,中间用”.”号级联。

OK~这样只会,第一个图中的效果就出来了。TabControl的使用方式如下:

<TabControl Grid.Row="1" Margin="0,0,0,0" TabStripPlacement="Right" Style="{DynamicResource TabControlStyleRotate}">    <TabItem Content="111111" Style="{DynamicResource TabItemStyleRotate}" Header="About"/>    <TabItem Header="About" Content="222222" Style="{DynamicResource TabItemStyleRotate2}"/></TabControl>

完成了~!如果有什么错漏希望不吝赐教~谢谢~^_^

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台