前言
在style中定义了一些样式,比如容器的外部样式(比如自定义窗口标题栏,此时窗口是容器,标题栏是外部样式),使用时只能在容器内部放置控件,此时想要在容器的外部样式中添加控件是不行的。但有时候需要在不同的页面中进行一些个性化定制,但总体框架又是一样的,如果每个页面都写冗余代码显然不科学。在总体界面框架一致的情况下可以做局部布局定制的方法,就是本文要讲的内容。
一、什么是PART可替换控件?
前言中已经有了大概的说明,在style中指定一块区域外部可以自由指定标签替换。下面用两个例子来说明可替换控件。
1.wpf源码例子
wpf的ScrollBar就有这样一个控件,ScrollBar的Track就是一个可替换控件。其使用方式为Template中x:Name
值为PART_Track
的Track标签即会变成ScrollBar的Track绑定相应事件。其源码片段如下:
在设置Template的时候找到PART_Track
。
ScrollBar源码可以参考referencesource
示意图如下:
ScrollBar结构可以参考《C# wpf ScrollBar自定义样式详解》
2.自定义标题栏例子
整体界面框架相同但局部不同的一系列窗口,如下。
解决方法是参考ScrollBar的方式,在style中设置一个PART_Title可替换控件:
二、如何实现?
1.定义附加属性
定义两个附加属性PartFrom
和PartName
,PartFrom
指定来源控件或父控件,PartName
指定控件名称。属性被设置之后,获取PartFrom
指定的控件,查找其内部名称为PartName
的控件,将其移除,添加到指定附加属性的容器控件下即可。
PartFrom
定义如下:
public static DependencyObject GetPartFrom(DependencyObject obj)
{
return (DependencyObject)obj.GetValue(PartFromProperty);
}
public static void SetPartFrom(DependencyObject obj, DependencyObject value)
{
obj.SetValue(PartFromProperty, value);
}
// Using a DependencyProperty as the backing store for PartFrom. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PartFromProperty =
DependencyProperty.RegisterAttached("PartFrom", typeof(DependencyObject), typeof(WindowStyles), new PropertyMetadata(null, PartFrom_PropertyChangedCallback));
PartName
定义如下:
public static string GetPartName(DependencyObject obj)
{
return (string)obj.GetValue(PartNameProperty);
}
public static void SetPartName(DependencyObject obj, string value)
{
obj.SetValue(PartNameProperty, value);
}
// Using a DependencyProperty as the backing store for PartName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PartNameProperty =
DependencyProperty.RegisterAttached("PartName", typeof(string), typeof(WindowStyles), new PropertyMetadata(null, PartName_PropertyChangedCallback));
2.使用附加属性
在style的template中使用上述2个属性
<ControlTemplate>
<StackPanel>
<!--可替换区域,设置CP_Content的PART_Title控件可转移到此容器下-->
<Grid local:WindowStyles.PartFrom="{Binding Content, ElementName=CP_Content }"
local:WindowStyles.PartName="PART_Title"/>
<ContentPresenter Name = "CP_Content" />
</StackPanel>
</ControlTemplate>
三、完整代码
完整的包括示例的项目代码https://download.csdn.net/download/u013113678/39199465
四、使用示例
个性化标题栏
在style的template中定义如下(示例):
<ControlTemplate TargetType = "{x:Type Window}" >
<Grid>
<!--标题栏-->
<Grid>
<!--可替换区域,设置客户区CP_Content的PART_Title控件可转移到此容器下-->
<Grid local:WindowStyles.PartFrom="{Binding Content, ElementName=CP_Content }"
local:WindowStyles.PartName="PART_Title"></Grid>
</Grid>
<!--客户区-->
<ContentPresenter Name = "CP_Content" Grid.Row="1"/>
</Grid>
</ControlTemplate>
菜单窗口代码:
<Window x:Class="WpfApp1.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:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="720" Width="1280"
Style="{DynamicResource WindowStyle_Normal_Gray}"
>
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="WindowStyles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid Background="White">
<!--替换标题栏区域-->
<Button x:Name="PART_Title" HorizontalAlignment="Left" Width="120" Height="50" Content="文件(F)" Background="#666666" Foreground="White" FontSize="24" Cursor="Hand">
<Button.Template >
<ControlTemplate TargetType="Button" >
<Border Background="{TemplateBinding Background}">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="{TemplateBinding Content}" Foreground="{TemplateBinding Foreground}" FontSize="{TemplateBinding FontSize}"></TextBlock>
</Border>
</ControlTemplate>
</Button.Template>
</Button>
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="#999999" FontSize="64" Text="Hello Word!"></TextBlock>
</Grid>
</Window>
效果预览:
标题窗口代码:
<Window x:Class="WpfApp1.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:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="720" Width="1280"
Style="{DynamicResource WindowStyle_Normal_Gray}"
>
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="WindowStyles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid Background="White">
<!--替换标题栏区域-->
<TextBlock x:Name="PART_Title" HorizontalAlignment="Center" VerticalAlignment="Center" Text="Hello Word Window" FontWeight="Bold" Foreground="White" FontSize="24" >
</TextBlock>
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="#999999" FontSize="64" Text="Hello Word!"></TextBlock>
</Grid>
</Window>
效果预览:
总结
本文讲述的方法有效的提升了style的可复用性,使得style的定义更加的灵活,解决了一类代码冗余问题。但是这种方法也是有一定的缺点,由于是运行时调用cs代码进行的控件替换,导致设计器看到的效果与实际运行是不一致的,而且使用此方法后页面代码会对style有依赖,不能随意切换style,除非也是同样使用此方法的style。总得来说,此方法还是有用的,对于实际项目的使用还是可以减少很多的冗余代码工作量的。