完善和擴展標準控制元件的方法:
- 樣式:可使用樣式方便地重用控制元件屬性的集合,甚至可以使用觸發器應用效果
- 內容控制元件:所有繼承自ContentControl類的控制元件都支持嵌套的內容,使用內容控制元件,可以快速創建聚集其他元素的復合控制元件(按鈕變成影像按鈕,串列變成影像串列)
- 控制元件模板:所有WPF控制元件都是無外觀的,這意味著他們具有硬編碼的功能,但是他們的外觀是通過控制元件模板單獨定義的,使用新的控制元件模板替代默認模板,可重新構建基本控制元件
- 資料模板:所有派生自ItemsControl的類都支持資料模板,通過資料模板可創建某些資料物件的富串列顯示,通過恰當的資料模板,可使用許多元素組合顯示每個項,這些組合可以是文本,影像甚至是可編輯控制元件,
理解自定義元素
創建自定義元素需要繼承的基類:
| 名稱 | 說明 |
|---|---|
| FrameworkElement | 最低級的基類,只有當希望重寫OnRender()方法并使用DrawContext從頭繪制內容時,才使用此方法 |
| Control | 當從頭開始創建控制元件時,這是最常用的起點,該類是所有用戶互動小組件的基類,Control類添加了用于設定背景、前景、字體和內容對齊方式等屬性,控制元件類自身設定了Tab順序,引入了滑鼠雙擊功能(MouseDoubleClick和PreviewMouseDoubleClick事件),最重要的是定義了Template屬性,為了無限靈活性,該屬性允許使用自定義元素樹替換其外觀 |
| ContentControl | 這是能夠顯示任意單一內容控制元件的基類,顯示的內容可以是元素或者結合使用模板的自定義物件(內容通過Content屬性設定,并且可以通過ContentTemplate屬性提供可選的模板), |
| UserControl | 這是可以用視圖配置的內容控制元件,盡管用戶控制元件和普通的內容控制元件是不同的,但是當希望對個視窗中快速重用用戶界面中的不變模塊時(而不是創建真正的能在不同應用程式之間轉移的獨立控制元件),通常使用該基類 |
| ItemsControl或Selector | 是封裝串列類控制元件的基類,但是不支持選擇,而Selector類是支持選擇的控制元件更具體的基類,創建自定義控制元件不經常使用這些類,因為ListBox、ListView、TreeView控制元件的資料系結特性提供了更大的靈活性 |
| Panel | 具有布局邏輯控制元件的基類,布局控制元件可以包含多個子元素,并根據特定的布局語意安排這些子元素,通常,面板提供了用于設定子元素的附加屬性,配置如何安排子元素, |
| Decorator | 封裝其他元素的元素的基類,并且提供了一種影像效果或特定的功能,兩個例子:Border、Viewbox,Border在元素周圍繪制線條,Viewbox控制元件使用動態縮放其內容, |
| 特殊控制元件類 | 如果希望改進現有控制元件,可以直接繼承該控制元件,比如:可以創建基友內置驗證邏輯的TextBox控制元件, |
創建基本的用戶控制元件
創建自定義顏色拾取器進行演示創建控制元件的各種重要概念,
定義依賴項屬性
添加自定義用戶控制元件之后,就是設計用戶控制元件對外界公開的公共介面,也就是說,設計控制元件的使用者使用的與顏色拾取器進行互動的屬性,方法和事件,
/// <summary>
/// ColorPicker.xaml 的互動邏輯
/// </summary>
public partial class ColorPicker : UserControl
{
public Color Color { get => (Color)GetValue(ColorProperty); set => SetValue(ColorProperty, value); }
public static readonly DependencyProperty ColorProperty = DependencyProperty.Register("Color", typeof(Color), typeof(ColorPicker), new FrameworkPropertyMetadata(Colors.Black, propertyChangedCallback: ColorChangedCallback));
private static void ColorChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var colorPicker = (ColorPicker)d;
var colorNew = (Color)e.NewValue;
colorPicker.Red = colorNew.R;
colorPicker.Green = colorNew.G;
colorPicker.Blue = colorNew.B;
}
public byte Red { get => (byte)GetValue(RedProperty); set => SetValue(RedProperty, value); }
public static readonly DependencyProperty RedProperty = DependencyProperty.Register("Red", typeof(byte), typeof(ColorPicker), new FrameworkPropertyMetadata(0, propertyChangedCallback: ColorRGBChangedCallback));
public byte Green { get => (byte)GetValue(GreenProperty); set => SetValue(GreenProperty, value); }
public static readonly DependencyProperty GreenProperty = DependencyProperty.Register("Green", typeof(byte), typeof(ColorPicker), new FrameworkPropertyMetadata(0, propertyChangedCallback: ColorRGBChangedCallback));
public byte Blue { get => (byte)GetValue(BlueProperty); set => SetValue(BlueProperty, value); }
public static readonly DependencyProperty BlueProperty = DependencyProperty.Register("Blue", typeof(byte), typeof(ColorPicker), new FrameworkPropertyMetadata(0, propertyChangedCallback: ColorRGBChangedCallback));
private static void ColorRGBChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var colorPicker = (ColorPicker)d;
var color = colorPicker.Color;
if (e.Property == RedProperty)
{
color.R = (byte)e.NewValue;
}
if (e.Property == GreenProperty)
{
color.G = (byte)e.NewValue;
}
if (e.Property == BlueProperty)
{
color.B = (byte)e.NewValue;
}
colorPicker.Color = color;
}
public ColorPicker()
{
InitializeComponent();
}
}
屬性變化回呼函式負責使Color屬性與Red,Green,Blue屬性保持一致,無論何時改變Red,Green,Blue屬性時,都會調整Color屬性,當設定Color屬性時,也會更新Red,Green,Blue的值,上述代碼不會引起一系列無休止的呼叫,WPF不允許重新進入屬性變化回呼函式,
定義路由事件
無論何時修改Color屬性,不管是直接修改還是通過修改Red、Green、Blue成分,都會觸發ColorChangedCallback事件,進而觸發ColorChangedEvent路由事件,
//////////////////路由事件/////////////////
public static readonly RoutedEvent ColorChangedEvent = EventManager.RegisterRoutedEvent("ColorChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<Color>), typeof(ColorPicker));
public event RoutedPropertyChangedEventHandler<Color> ColorChanged
{
add { AddHandler(ColorChangedEvent, value); }
remove { RemoveHandler(ColorChangedEvent, value); }
}
private static void ColorChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var colorPicker = (ColorPicker)d;
var colorNew = (Color)e.NewValue;
colorPicker.Red = colorNew.R;
colorPicker.Green = colorNew.G;
colorPicker.Blue = colorNew.B;
//觸發路由事件
RoutedPropertyChangedEventArgs<Color> args = new RoutedPropertyChangedEventArgs<Color>((Color)e.OldValue, colorNew);
args.RoutedEvent = ColorPicker.ColorChangedEvent;
colorPicker.RaiseEvent(args);
}
添加標記
<UserControl
x:
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Padding="5">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Slider x:Name="sliderRed" Grid.Row="0" Margin="{Binding Path=Padding, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}, AncestorLevel=1}}" Maximum="255" Minimum="0" Value="https://www.cnblogs.com/vigorous/p/{Binding Path=Red, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}, AncestorLevel=1}}" />
<Slider x:Name="sliderGreen" Grid.Row="1" Margin="{Binding Path=Padding, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}, AncestorLevel=1}}" Maximum="255" Minimum="0" Value="https://www.cnblogs.com/vigorous/p/{Binding Path=Green, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}, AncestorLevel=1}}" />
<Slider x:Name="sliderBlue" Grid.Row="2" Margin="{Binding Path=Padding, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}, AncestorLevel=1}}" Maximum="255" Minimum="0" Value="https://www.cnblogs.com/vigorous/p/{Binding Path=Blue, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}, AncestorLevel=1}}" />
<Rectangle Grid.RowSpan="3" Grid.Column="1" Width="50" Stroke="Black" StrokeThickness="1">
<Rectangle.Fill>
<SolidColorBrush Color="{Binding Path=Color, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}, AncestorLevel=1}}" />
</Rectangle.Fill>
</Rectangle>
</Grid>
</UserControl>
使用控制元件
<Window
x:
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:local="clr-namespace:Course05"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<Grid>
<local:ColorPicker Width="500" Height="Auto" VerticalAlignment="Top" ColorChanged="ColorPicker_ColorChanged" Color="BurlyWood" />
<TextBlock x:Name="txtColor" Margin="0,200,0,0" HorizontalAlignment="Center" VerticalAlignment="Top" Text="TextBlock" TextWrapping="Wrap" />
</Grid>
</Window>
private void ColorPicker_ColorChanged(object sender, RoutedPropertyChangedEventArgs<Color> e)
{
if (e != null && this.txtColor != null)
txtColor.Text = e.NewValue.ToString();
}
命令支持
通過下面兩種方法為自定義控制元件添加命令支持:
- 添加將控制元件鏈接到特定命令的命令系結,通過這種方法,控制元件可以相應命令,而且不需要借助任何外部代碼,
- 為命令創建新的RoutedUICommand物件,作為自定義控制元件的靜態欄位,然后為這個命令物件添加系結,這種方法可使自定義控制元件支持沒有在基本命令集合中定義命令,
public ColorPicker()
{
InitializeComponent();
SetupCommands();
}
private Color? previousColor;
private void SetupCommands()
{
CommandBinding binding = new CommandBinding(ApplicationCommands.Undo, UndoCommandExecuted, UndoCommandCanExecuted);
this.CommandBindings.Add(binding);
}
private void UndoCommandCanExecuted(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = previousColor.HasValue;
}
private void UndoCommandExecuted(object sender, ExecutedRoutedEventArgs e)
{
this.Color = this.previousColor.Value;
}
更可靠的命令,使用CommandManager.RegisterClassCommandBinding方法關聯靜態的命令處理程式,
CommandManager.RegisterClassCommandBinding(typeof(ColorPicker), new CommandBinding(ApplicationCommands.Undo, UndoCommandExecuted, UndoCommandCanExecuted));
private static void UndoCommandStatisCanExecuted(object sender, CanExecuteRoutedEventArgs e)
{
var colorPicker = (ColorPicker)sender;
e.CanExecute = colorPicker.previousColor.HasValue;
}
private static void UndoCommandStatisExecuted(object sender, ExecutedRoutedEventArgs e)
{
var colorPicker = (ColorPicker)sender;
colorPicker.Color = colorPicker.previousColor.Value;
}
深入分析用戶控制元件
在后臺,UserControl類的作業方式和父類ContentControl非常相似,只有下面幾個區別:
- UserControl改變了一些默認值,將IsTabStop和Focusable屬性設定為false,并將水平垂直對齊設定成Stretch,從而填充整個空間,
- UserControl類應用了一個新的控制元件模板,該模板由包含ContentPresenter元素的Border元素組成,
- UserControl類改變了路由事件的源,當事件從用戶控制元件內的控制元件向以外的元素冒泡或者隧道路由時,事件源變為指向用戶控制元件而不是原始元素,
從技術角度看,可改變用戶控制元件的模板,實際上,只需要進行很少的調整,就可以將所有模板移到模板中,
創建無外觀控制元件
創建無外觀控制元件需要繼承自控制元件基類,但是沒有設計表面的控制元件,相反,這個控制元件將其標記到默認模板中,可替換模板而不會影響控制元件邏輯,
修改顏色提取器的代碼
public class ColorPicker2 : System.Windows.Controls.Control
{
//////////////////依賴項屬性/////////////////
public Color Color { get => (Color)GetValue(ColorProperty); set => SetValue(ColorProperty, value); }
public static readonly DependencyProperty ColorProperty = DependencyProperty.Register("Color", typeof(Color), typeof(ColorPicker2), new FrameworkPropertyMetadata(Colors.Black, propertyChangedCallback: ColorChangedCallback));
private static void ColorChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var colorPicker = (ColorPicker2)d;
var colorNew = (Color)e.NewValue;
colorPicker.previousColor = (Color)e.OldValue;
colorPicker.Red = colorNew.R;
colorPicker.Green = colorNew.G;
colorPicker.Blue = colorNew.B;
//觸發路由事件
RoutedPropertyChangedEventArgs<Color> args = new RoutedPropertyChangedEventArgs<Color>((Color)e.OldValue, colorNew);
args.RoutedEvent = ColorPicker2.ColorChangedEvent;
colorPicker.RaiseEvent(args);
}
public byte Red { get => (byte)GetValue(RedProperty); set => SetValue(RedProperty, value); }
public static readonly DependencyProperty RedProperty = DependencyProperty.Register("Red", typeof(byte), typeof(ColorPicker2), new FrameworkPropertyMetadata((byte)0, propertyChangedCallback: ColorRGBChangedCallback));
public byte Green { get => (byte)GetValue(GreenProperty); set => SetValue(GreenProperty, value); }
public static readonly DependencyProperty GreenProperty = DependencyProperty.Register("Green", typeof(byte), typeof(ColorPicker2), new FrameworkPropertyMetadata((byte)0, propertyChangedCallback: ColorRGBChangedCallback));
public byte Blue { get => (byte)GetValue(BlueProperty); set => SetValue(BlueProperty, value); }
public static readonly DependencyProperty BlueProperty = DependencyProperty.Register("Blue", typeof(byte), typeof(ColorPicker2), new FrameworkPropertyMetadata((byte)0, propertyChangedCallback: ColorRGBChangedCallback));
private static void ColorRGBChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var colorPicker = (ColorPicker2)d;
var color = colorPicker.Color;
if (e.Property == RedProperty)
{
color.R = (byte)e.NewValue;
}
if (e.Property == GreenProperty)
{
color.G = (byte)e.NewValue;
}
if (e.Property == BlueProperty)
{
color.B = (byte)e.NewValue;
}
colorPicker.Color = color;
}
//////////////////路由事件/////////////////
public static readonly RoutedEvent ColorChangedEvent = EventManager.RegisterRoutedEvent("ColorChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<Color>), typeof(ColorPicker2));
public event RoutedPropertyChangedEventHandler<Color> ColorChanged
{
add { AddHandler(ColorChangedEvent, value); }
remove { RemoveHandler(ColorChangedEvent, value); }
}
public ColorPicker2()
{
SetupCommands();
DefaultStyleKeyProperty.OverrideMetadata(typeof(ColorPicker2), new FrameworkPropertyMetadata(typeof(ColorPicker2)));
}
private Color? previousColor;
private void SetupCommands()
{
CommandManager.RegisterClassCommandBinding(typeof(ColorPicker2), new CommandBinding(ApplicationCommands.Undo, UndoCommandStatisExecuted, UndoCommandStatisCanExecuted));
}
private static void UndoCommandStatisCanExecuted(object sender, CanExecuteRoutedEventArgs e)
{
var colorPicker = (ColorPicker2)sender;
e.CanExecute = colorPicker.previousColor.HasValue;
}
private static void UndoCommandStatisExecuted(object sender, ExecutedRoutedEventArgs e)
{
var colorPicker = (ColorPicker2)sender;
colorPicker.Color = colorPicker.previousColor.Value;
}
}
修改了繼承類,去掉了建構式的 InitializeComponent();方法,增加了通知WPF為控制元件提供新的樣式,
修改顏色提取器的標記
增加顏色提取器的樣式:
<Style TargetType="{x:Type local:ColorPicker2}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ColorPicker2}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Slider x:Name="sliderRed" Grid.Row="0" Margin="{TemplateBinding Padding}" Maximum="255" Minimum="0" Value="https://www.cnblogs.com/vigorous/p/{Binding Path=Red, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
<Slider x:Name="sliderGreen" Grid.Row="1" Margin="{TemplateBinding Padding}" Maximum="255" Minimum="0" Value="https://www.cnblogs.com/vigorous/p/{Binding Path=Green, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
<Slider x:Name="sliderBlue" Grid.Row="2" Margin="{TemplateBinding Padding}" Maximum="255" Minimum="0" Value="https://www.cnblogs.com/vigorous/p/{Binding Path=Blue, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
<Rectangle Grid.RowSpan="3" Grid.Column="1" Width="50" Stroke="Black" StrokeThickness="1">
<Rectangle.Fill>
<SolidColorBrush Color="{Binding Path=Color, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
</Rectangle.Fill>
</Rectangle>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
注意此處系結的擴招,一部分使用TemplateBinding,一部分使用Binding(將RelativeSource設定為指向模板的父元素,也就是自定義控制元件),這兩種形式原理基本一致,但是如果需要雙向系結或者系結到繼承自Freezable類的屬性時(SolidColorBrush),模板系結就失效了,
精簡控制元件模板
- 添加部件名稱
<Style TargetType="{x:Type local:ColorPicker3}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ColorPicker3}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Slider x:Name="PART_RedSlider" Grid.Row="0" Margin="{TemplateBinding Padding}" Maximum="255" Minimum="0" />
<Slider x:Name="PART_GreenSlider" Grid.Row="1" Margin="{TemplateBinding Padding}" Maximum="255" Minimum="0" />
<Slider x:Name="PART_BlueSlider" Grid.Row="2" Margin="{TemplateBinding Padding}" Maximum="255" Minimum="0" />
<Rectangle Grid.RowSpan="3" Grid.Column="1" Width="50" Stroke="Black" StrokeThickness="1">
<Rectangle.Fill>
<SolidColorBrush x:Name="PART_PreviewBrush" />
</Rectangle.Fill>
</Rectangle>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
洗掉系結,賦予名稱,這些名稱根據約定,都以PART_開頭,
- 操作模板控制元件
重寫方法OnApplyTemplate:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
{
RangeBase silder = GetTemplateChild("PART_RedSlider") as RangeBase;
if (silder != null)
{
Binding binding = new Binding("Red");
binding.Source = this;
binding.Mode = BindingMode.TwoWay;
silder.SetBinding(RangeBase.ValueProperty, binding);
}
}
{
RangeBase silder = GetTemplateChild("PART_GreenSlider") as RangeBase;
if (silder != null)
{
Binding binding = new Binding("Green");
binding.Source = this;
binding.Mode = BindingMode.TwoWay;
silder.SetBinding(RangeBase.ValueProperty, binding);
}
}
{
RangeBase silder = GetTemplateChild("PART_BlueSlider") as RangeBase;
if (silder != null)
{
Binding binding = new Binding("Blue");
binding.Source = this;
binding.Mode = BindingMode.TwoWay;
silder.SetBinding(RangeBase.ValueProperty, binding);
}
}
{
SolidColorBrush brush = GetTemplateChild("PART_PreviewBrush") as SolidColorBrush;
if (brush != null)
{
Binding binding = new Binding("Color");
binding.Source = brush;
binding.Mode = BindingMode.OneWayToSource;
this.SetBinding(ColorPicker3.ColorProperty, binding);
}
}
}
- 記錄模板部件
[TemplatePart(Name = "PART_RedSlider", Type =typeof(RangeBase))]
[TemplatePart(Name = "PART_GreenSlider", Type = typeof(RangeBase))]
[TemplatePart(Name = "PART_BlueSlider", Type = typeof(RangeBase))]
[TemplatePart(Name = "PART_PreviewBrush", Type = typeof(SolidColorBrush))]
public class ColorPicker3 : System.Windows.Controls.Control
支持可視化狀態
上面例子中的ColorPicker控制元件設計相對簡單,是因為它不涉及狀態(不具有焦點,滑鼠是否在上面懸停,是否禁用狀態來區分其可視化外觀),
下面的例子中FlipPanel,通過翻轉效果來切換兩種表面,可通過代碼執行翻轉(通過設定名為IsFlipped的屬性),也可以使用一個便捷的按鈕來翻轉面板(除非控制元件使用者從模板中移除了此按鈕),控制元件模板需要制定兩個獨立部分:FlipPanel控制元件的前后內容區域,需要一個方法在兩個狀態之間切換:翻轉狀態和不翻轉狀態,可通過模板添加觸發器來完成該作業,
- 開始撰寫FlipPanel類
public class FlipPanel : Control
{
public Object FrontContent { get => (Object)GetValue(FrontContentProperty); set => SetValue(FrontContentProperty, value); }
public static readonly DependencyProperty FrontContentProperty = DependencyProperty.Register("FrontContent", typeof(Object), typeof(FlipPanel), new FrameworkPropertyMetadata(null));
public Object BackContent { get => (Object)GetValue(BackContentProperty); set => SetValue(BackContentProperty, value); }
public static readonly DependencyProperty BackContentProperty = DependencyProperty.Register("BackContent", typeof(Object), typeof(FlipPanel), new FrameworkPropertyMetadata(null));
public bool IsFlipped { get => (bool)GetValue(IsFlippedProperty); set { SetValue(IsFlippedProperty, value); ChangeVisualStatus(true); } }
public static readonly DependencyProperty IsFlippedProperty = DependencyProperty.Register("IsFlipped", typeof(bool), typeof(FlipPanel), new FrameworkPropertyMetadata(false));
private void ChangeVisualStatus(bool isFlipped)
{
}
public CornerRadius CornerRadius { get => (CornerRadius)GetValue(CornerRadiusProperty); set => SetValue(CornerRadiusProperty, value); }
public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(FlipPanel), null);
public FlipPanel()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(FlipPanel), new FrameworkPropertyMetadata(typeof(FlipPanel)));
}
}
默認樣式的輪廓:
<Style TargetType="{x:Type local:FlipPanel}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:FlipPanel}">
<Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
- 選擇部件和狀態
FlipPanel需要兩個狀態:
- 正常狀態:只有前面的內容可見,后面的內容被翻轉、淡化或者被移出視圖
- 翻轉狀態:只有后面的內容可見,前面的內容被影片移出視圖
需要兩個部件:
FlipButton:單擊按鈕時,視圖從前面改到后面(或者從后面改到前面),FlipPanel通過處理按鈕事件來提供該服務
FlipPanelAlternate:這是一個可選元素,與FlipButton的作業方式相同,允許控制元件使用者在自定義模板中使用兩種不同的方法,一種選擇時使用可翻轉區域外的單個翻轉按鈕,另一種是選擇在可翻轉的兩側放置獨立的翻轉按鈕,
[TemplateVisualState(Name = "Normal", GroupName = "ViewStatus")]
[TemplateVisualState(Name = "Flipped", GroupName = "ViewStatus")]
[TemplatePart(Name = "FlipButton", Type = typeof(ToggleButton))]
[TemplatePart(Name = "FlipButtonAlternate", Type = typeof(ToggleButton))]
public class FlipPanel : Control
- 默認控制元件模板
<Style TargetType="{x:Type local:FlipPanel}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:FlipPanel}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border x:Name="FrontContent" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="{TemplateBinding CornerRadius}">
<ContentPresenter Content="{TemplateBinding FrontContent}" />
</Border>
<Border x:Name="BackContent" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="{TemplateBinding CornerRadius}">
<ContentPresenter Content="{TemplateBinding BackContent}" />
</Border>
<ToggleButton x:Name="FlipButton" Grid.Row="1" Width="20" Height="20" Margin="0,10,0,0" RenderTransformOrigin="0.5,0.5">
<ToggleButton.Template>
<ControlTemplate>
<Grid>
<Ellipse Fill="AliceBlue" Stroke="#FFA9A9A9" />
<Path HorizontalAlignment="Center" VerticalAlignment="Center" Data="https://www.cnblogs.com/vigorous/p/M1,1.5L4.5,5 8,1.5" Stroke="#FF666666" StrokeThickness="2" />
</Grid>
</ControlTemplate>
</ToggleButton.Template>
<ToggleButton.RenderTransform>
<RotateTransform x:Name="FlipButtonTransform" Angle="-90" />
</ToggleButton.RenderTransform>
</ToggleButton>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ViewStatus">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:0.7" To="Flipped">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="FlipButtonTransform" Storyboard.TargetProperty="Angle" To="90" Duration="0:0:0.2" />
</Storyboard>
</VisualTransition>
<VisualTransition GeneratedDuration="0:0:0.7" To="Normal">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="FlipButtonTransform" Storyboard.TargetProperty="Angle" To="-90" Duration="0:0:0.2" />
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="Normal">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="BackContent" Storyboard.TargetProperty="Opacity" To="0" Duration="0" />
<DoubleAnimation Storyboard.TargetName="FrontContent" Storyboard.TargetProperty="Opacity" To="1" Duration="0" />
</Storyboard>
</VisualState>
<VisualState x:Name="Flipped">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="FlipButtonTransform" Storyboard.TargetProperty="Angle" To="90" Duration="0" />
<DoubleAnimation Storyboard.TargetName="FrontContent" Storyboard.TargetProperty="Opacity" To="0" Duration="0" />
<DoubleAnimation Storyboard.TargetName="BackContent" Storyboard.TargetProperty="Opacity" To="1" Duration="0" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
- 使用FlipPanel控制元件
<Grid >
<local:FlipPanel x:Name="panel" HorizontalAlignment="Left" Margin="128,33,0,0" VerticalAlignment="Top" Height="216" Width="536">
<local:FlipPanel.FrontContent>
<StackPanel>
<TextBlock Text="FrontContext"/>
</StackPanel>
</local:FlipPanel.FrontContent>
<local:FlipPanel.BackContent>
<StackPanel>
<TextBlock Text="BackContent"/>
<Button Content="123" Width="100" Height="20" Click="Button_Click"/>
</StackPanel>
</local:FlipPanel.BackContent>
</local:FlipPanel>
</Grid>
private void Button_Click(object sender, RoutedEventArgs e)
{
panel.IsFlipped = !panel.IsFlipped;
}
- 使用不同的控制元件模板
已經設計好的自定義控制元件極其靈活,可以使用新模板來修改ToggleButton按鈕的外觀和位置,并修改當在前后內容區域之間進行切換時應用的影片效果,
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/295836.html
標籤:WPF
下一篇:六、從GitHub瀏覽Prism示例代碼的方式入門WPF下的Prism之MVVM中的FilteringEvents
