WPF TabControl 滚动选项卡
我原本以为是很简单的事情,但是没想到实际做起来还是有很多的基础知识点的。我们平常写TabControl的时候,可能都很习惯了直接写TabControl+TabItem。但是TabControl负责了什么布局,TabItem负责了什么布局,我们都不知道。在《深入浅出WPF》中,我们可以看到TabControl属于ItemsControl我们去看看控件模板样式副本。WPF的xaml的优点是每个控件都是
前言
我原本以为是很简单的事情,但是没想到实际做起来还是有很多的基础知识点的。
相关链接
TabControl是什么东西
我们平常写TabControl的时候,可能都很习惯了直接写TabControl+TabItem。但是TabControl负责了什么布局,TabItem负责了什么布局,我们都不知道。
在《深入浅出WPF》中,我们可以看到TabControl属于ItemsControl
通过模板副本查看样式
我们去看看控件模板样式副本。WPF的xaml的优点是每个控件都是单独的逻辑,耦合低。缺点是写起来麻烦,每次改动约等于重新写一个新的。通过增加自己的工作量来降低了耦合
<SolidColorBrush x:Key="TabItem.Selected.Background" Color="#FFFFFF"/>
<SolidColorBrush x:Key="TabItem.Selected.Border" Color="#ACACAC"/>
<Style x:Key="TabControlStyle1" TargetType="{x:Type TabControl}">
<!--默认基础属性-->
<Setter Property="Padding" Value="2"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Background" Value="{StaticResource TabItem.Selected.Background}"/>
<Setter Property="BorderBrush" Value="{StaticResource TabItem.Selected.Border}"/>
<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>
<!--设置TabItem选项卡的布局-->
<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"/>
<!--设置TabItem的内容-->
<Border x:Name="contentPanel" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" 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>
</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"/>
<Setter Property="Margin" TargetName="headerPanel" Value="2,0,2,2"/>
</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"/>
<Setter Property="Margin" TargetName="headerPanel" Value="2,2,0,2"/>
</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"/>
<Setter Property="Margin" TargetName="headerPanel" Value="0,2,2,2"/>
</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>
我们可以看到中间有个Gird分布,用于声明选项卡和内容之间布局关系。那么哪个代表选项卡,哪个代码【内容】呢?
IsItemsHost
其实选项卡布局还有一个要求,就是得声明【IsItemsHost】这个属性,我们可以看看微软官方文档是怎么说的。
简单来说【IsItemsHost】就是一个可以自动绑定的ItemPanel的声明,ItemPanel是设置ItemControl的布局方式。
如何修改
其实改起来很简单,就是加个【ScrollViewer】就行了。原来的是Panel,我们改成StackPanel这样就不会自动换行了。
<Window x:Class="WpfApp2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp2"
mc:Ignorable="d"
Title="MainWindow"
Height="450"
Width="800">
<Window.Resources>
<SolidColorBrush x:Key="TabItem.Selected.Background"
Color="#FFFFFF" />
<SolidColorBrush x:Key="TabItem.Selected.Border"
Color="#ACACAC" />
<Style x:Key="TabControlStyle1"
TargetType="{x:Type TabControl}">
<!--默认基础属性-->
<Setter Property="Padding"
Value="2" />
<Setter Property="HorizontalContentAlignment"
Value="Center" />
<Setter Property="VerticalContentAlignment"
Value="Center" />
<Setter Property="Background"
Value="{StaticResource TabItem.Selected.Background}" />
<Setter Property="BorderBrush"
Value="{StaticResource TabItem.Selected.Border}" />
<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>
<!--设置TabItem选项卡的布局-->
<ScrollViewer Height="{TemplateBinding Height}" >
<StackPanel x:Name="headerPanel"
Background="Transparent"
Grid.Column="0"
IsItemsHost="True"
Margin="2,2,2,0"
Grid.Row="0"
KeyboardNavigation.TabIndex="1"
Panel.ZIndex="1" />
</ScrollViewer>
<!--设置TabItem的内容-->
<Border x:Name="contentPanel"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
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>
</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" />
<Setter Property="Margin"
TargetName="headerPanel"
Value="2,0,2,2" />
</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" />
<Setter Property="Margin"
TargetName="headerPanel"
Value="2,2,0,2" />
</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" />
<Setter Property="Margin"
TargetName="headerPanel"
Value="0,2,2,2" />
</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>
<Style TargetType="TabItem"
x:Key="default">
<Setter Property="FontSize"
Value="50" />
</Style>
</Window.Resources>
<Grid>
<TabControl Style="{DynamicResource TabControlStyle1}" TabStripPlacement="Left">
<TabItem Header="标题1"
Style="{StaticResource default}">
</TabItem>
<TabItem Header="标题2"
Style="{StaticResource default}">
</TabItem>
<TabItem Header="标题3"
Style="{StaticResource default}">
</TabItem>
<TabItem Header="标题4"
Style="{StaticResource default}">
</TabItem>
<TabItem Header="标题5"
Style="{StaticResource default}">
</TabItem>
<TabItem Header="标题6"
Style="{StaticResource default}">
</TabItem>
<TabItem Header="标题7"
Style="{StaticResource default}">
</TabItem>
<TabItem Header="标题8"
Style="{StaticResource default}">
</TabItem>
</TabControl>
</Grid>
</Window>
HandyControl的TabControl 滚动
我们看看HandyControl的代码
我们进到代码里面,可以看到他实现了ContentControl,这里我们就不展开说明了
总结
控件模板是必须要深入了解的。WPF我认为就3+1个难点。控件模板,依赖属性,Binding+WPF 动画。前三个掌握了,基本的静态页面就可以写了。动画属于附加需求,不用动画也可以写的很好看
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)