
當(dāng)現(xiàn)有控件不滿(mǎn)足業(yè)務(wù)需要時(shí)WPF也支持自定義控件。以下提供一個(gè)評(píng)分控件以供學(xué)習(xí)!--StarRating.xaml-- ResourceDictionary xmlnshttp://schemas.microsoft.com/winfx/2006/xaml/presentation xmlns:xhttp://schemas.microsoft.com/winfx/2006/xaml xmlns:ctrlsclr-namespace:CustomControlDemo.Controls !--沒(méi)有 x:Key默認(rèn)樣式自動(dòng)應(yīng)用到所有 StarRating-- Style TargetType{x:Type ctrls:StarRating} Setter PropertyHorizontalAlignment ValueLeft/ Setter PropertyTemplate Setter.Value ControlTemplate TargetType{x:Type ctrls:StarRating} !--①I(mǎi)temsControl ItemsSource{Binding Stars, RelativeSource{RelativeSource AncestorTypectrls:StarRating}}-- !--②ItemsControl ItemsSource{TemplateBinding Stars}-- !--①②與下面這行等價(jià)不過(guò)用TemplateBinding和TemplatedParent時(shí)要求父節(jié)點(diǎn)為ControlTemplate-- ItemsControl ItemsSource{Binding Stars, RelativeSource{RelativeSource TemplatedParent}} !--ItemsSource要求Stars是一個(gè)集合比如List,Dictionary,ObservableCollection等-- ItemsControl.ItemsPanel ItemsPanelTemplate StackPanel OrientationHorizontal/ /ItemsPanelTemplate /ItemsControl.ItemsPanel ItemsControl.ItemTemplate DataTemplate !--Path data用于描述控件形狀本例中11個(gè)點(diǎn)坐標(biāo)圍成五角星-- Path DataM12,17.27L18.18,21L16.54,13.97L22,9.24L14.81,8.62L12,2L9.19,8.62L2,9.24L7.45,13.97L5.82,21L12,17.27Z Width{Binding StarSize, RelativeSource{RelativeSource AncestorTypectrls:StarRating}} Height{Binding StarSize, RelativeSource{RelativeSource AncestorTypectrls:StarRating}} Margin2 CursorHand Path.Style Style TargetTypePath Setter PropertyFill Value{Binding EmptyStarBrush, RelativeSource{RelativeSource AncestorTypectrls:StarRating}}/ Style.Triggers DataTrigger Binding{Binding} ValueTrue Setter PropertyFill Value{Binding FilledStarBrush, RelativeSource{RelativeSource AncestorTypectrls:StarRating}}/ /DataTrigger /Style.Triggers /Style /Path.Style /Path /DataTemplate /ItemsControl.ItemTemplate /ItemsControl /ControlTemplate /Setter.Value /Setter /Style Style x:KeyBlueTheme TargetType{x:Type ctrls:StarRating} BasedOn{StaticResource {x:Type ctrls:StarRating}} Setter PropertyFilledStarBrush Value#2196F3/ Setter PropertyEmptyStarBrush Value#BBDEFB/ /Style Style x:KeyRedLargeTheme TargetType{x:Type ctrls:StarRating} BasedOn{StaticResource {x:Type ctrls:StarRating}} Setter PropertyFilledStarBrush Value#F44336/ Setter PropertyEmptyStarBrush Value#FFCDD2/ Setter PropertyStarSize Value50/ /Style Style x:KeyCircleTheme TargetType{x:Type ctrls:StarRating} Setter PropertyHorizontalAlignment ValueLeft/ Setter PropertyTemplate Setter.Value ControlTemplate TargetType{x:Type ctrls:StarRating} ItemsControl ItemsSource{Binding Stars, RelativeSource{RelativeSource TemplatedParent}} ItemsControl.ItemsPanel ItemsPanelTemplate StackPanel OrientationHorizontal/ /ItemsPanelTemplate /ItemsControl.ItemsPanel ItemsControl.ItemTemplate DataTemplate Ellipse Width{Binding StarSize, RelativeSource{RelativeSource AncestorTypectrls:StarRating}} Height{Binding StarSize, RelativeSource{RelativeSource AncestorTypectrls:StarRating}} Margin3 CursorHand Ellipse.Style Style TargetTypeEllipse Setter PropertyFill Value{Binding EmptyStarBrush, RelativeSource{RelativeSource AncestorTypectrls:StarRating}}/ Setter PropertyStroke ValueGray/ Setter PropertyStrokeThickness Value2/ Style.Triggers !--Binding{Binding}意思是綁定到當(dāng)前默認(rèn)的DataContext在此例中就是Stars中的每一個(gè)Bool元素-- DataTrigger Binding{Binding} ValueTrue Setter PropertyFill Value{Binding FilledStarBrush, RelativeSource{RelativeSource AncestorTypectrls:StarRating}}/ /DataTrigger /Style.Triggers /Style /Ellipse.Style /Ellipse /DataTemplate /ItemsControl.ItemTemplate /ItemsControl /ControlTemplate /Setter.Value /Setter /Style /ResourceDictionary //StarRating.cs using System; using System.Collections.ObjectModel; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; namespace CustomControlDemo.Controls { public class StarRating : Control { public static readonly DependencyProperty RatingProperty DependencyProperty.Register( Rating, typeof(int), typeof(StarRating), new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnRatingChanged)); public static readonly DependencyProperty MaxRatingProperty DependencyProperty.Register( MaxRating, typeof(int), typeof(StarRating), new PropertyMetadata(1, OnRatingChanged)); public static readonly DependencyProperty FilledStarBrushProperty DependencyProperty.Register( FilledStarBrush, typeof(Brush), typeof(StarRating), new PropertyMetadata(Brushes.Gold)); public static readonly DependencyProperty EmptyStarBrushProperty DependencyProperty.Register( EmptyStarBrush, typeof(Brush), typeof(StarRating), new PropertyMetadata(Brushes.LightGray)); public static readonly DependencyProperty StarSizeProperty DependencyProperty.Register( StarSize, typeof(double), typeof(StarRating), new PropertyMetadata(30.0)); // 星星列表直接供 ItemsControl 綁定 public ObservableCollectionbool Stars { get; } new ObservableCollectionbool(); public event EventHandlerint RatingChanged; static StarRating() { //注冊(cè)StarRating的默認(rèn)Style和ControlTemplate, WPF將會(huì)去Generic.xaml中尋找 DefaultStyleKeyProperty.OverrideMetadata( typeof(StarRating), new FrameworkPropertyMetadata(typeof(StarRating))); } public StarRating() { UpdateStars(); } public int Rating { get (int)GetValue(RatingProperty); set SetValue(RatingProperty, value); } public int MaxRating { get (int)GetValue(MaxRatingProperty); set SetValue(MaxRatingProperty, value); } public Brush FilledStarBrush { get (Brush)GetValue(FilledStarBrushProperty); set SetValue(FilledStarBrushProperty, value); } public Brush EmptyStarBrush { get (Brush)GetValue(EmptyStarBrushProperty); set SetValue(EmptyStarBrushProperty, value); } public double StarSize { get (double)GetValue(StarSizeProperty); set SetValue(StarSizeProperty, value); } private static void OnRatingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var control (StarRating)d; control.UpdateStars(); } // Rating 或 MaxRating 變化時(shí)更新星星列表 private void UpdateStars() { Stars.Clear(); for (int i 1; i MaxRating; i) { Stars.Add(i Rating); } } protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) { base.OnMouseLeftButtonDown(e); var position e.GetPosition(this); var starWidth ActualWidth / MaxRating; var clickedStar (int)(position.X / starWidth) 1; if (clickedStar 1 clickedStar MaxRating) { Rating clickedStar; RatingChanged?.Invoke(this, clickedStar); } } } } !--Mainwindow.xaml-- Window x:ClassCustomControlDemo.MainWindow xmlnshttp://schemas.microsoft.com/winfx/2006/xaml/presentation xmlns:xhttp://schemas.microsoft.com/winfx/2006/xaml xmlns:ctrlsclr-namespace:CustomControlDemo.Controls xmlns:localclr-namespace:CustomControlDemo TitleCustomControl 示例 Height400 Width500 StackPanel Margin20 TextBlock TextCustomControl 演示 - 可以換膚 FontSize20 FontWeightBold Margin0,0,0,20/ TextBlock Text默認(rèn)金色星星 Margin0,0,0,5/ ctrls:StarRating Rating{Binding Rating1, ModeTwoWay} MaxRating{Binding MaxRating} Margin0,0,0,15/ TextBlock Text藍(lán)色主題 Margin0,0,0,5/ ctrls:StarRating Rating{Binding Rating2, ModeTwoWay} MaxRating{Binding MaxRating} Style{StaticResource BlueTheme} Margin0,0,0,15/ TextBlock Text紅色大尺寸主題 Margin0,0,0,5/ ctrls:StarRating Rating{Binding Rating3, ModeTwoWay} MaxRating{Binding MaxRating} Style{StaticResource RedLargeTheme} Margin0,0,0,15/ TextBlock Text圓形主題 Margin0,0,0,5/ ctrls:StarRating Rating{Binding Rating4, ModeTwoWay} MaxRating{Binding MaxRating} Style{StaticResource CircleTheme} Margin0,0,0,15/ TextBlock Text{Binding ResultText} FontSize14 ForegroundBlue Margin0,10,0,0/ /StackPanel /Window !--App.xaml-- Application x:ClassCustomControlDemo.App xmlnshttp://schemas.microsoft.com/winfx/2006/xaml/presentation xmlns:xhttp://schemas.microsoft.com/winfx/2006/xaml Application.Resources ResourceDictionary ResourceDictionary.MergedDictionaries ResourceDictionary Sourcepack://application:,,,/CustomControlDemo;Component/Themes/Generic.xaml/ /ResourceDictionary.MergedDictionaries /ResourceDictionary /Application.Resources /Application //App.xaml.cs using System; using System.Windows; namespace CustomControlDemo { public partial class App : Application { protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); var window new MainWindow();//創(chuàng)建UI窗口 window.DataContext new MainViewModel();//注入ViewModel window.Show();//顯示窗口 } } } !--Generic.xaml-- ResourceDictionary xmlnshttp://schemas.microsoft.com/winfx/2006/xaml/presentation xmlns:xhttp://schemas.microsoft.com/winfx/2006/xaml ResourceDictionary.MergedDictionaries ResourceDictionary Sourcepack://application:,,,/CustomControlDemo;Component/Controls/Styles/StarRating.xaml/ /ResourceDictionary.MergedDictionaries /ResourceDictionary //MainViewModel.cs using System.ComponentModel; using System.Runtime.CompilerServices; namespace CustomControlDemo { public class MainViewModel : INotifyPropertyChanged { private int _rating1 3; private int _rating2 4; private int _rating3 2; private int _rating4 3; private int _maxRating 6; public int Rating1 { get _rating1; set { if (_rating1 ! value) { _rating1 value; OnPropertyChanged(); OnPropertyChanged(nameof(ResultText)); } } } public int Rating2 { get _rating2; set { if (_rating2 ! value) { _rating2 value; OnPropertyChanged(); } } } public int Rating3 { get _rating3; set { if (_rating3 ! value) { _rating3 value; OnPropertyChanged(); } } } public int Rating4 { get _rating4; set { if (_rating4 ! value) { _rating4 value; OnPropertyChanged(); } } } public int MaxRating { get _maxRating; set { if (_maxRating ! value) { _maxRating value; OnPropertyChanged(); OnPropertyChanged(nameof(ResultText)); } } } public string ResultText $當(dāng)前評(píng)分: {Rating1} / {MaxRating}; public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }說(shuō)明1.ControlTemplate用法所有繼承Control的控件有一個(gè)ControlTemplate類(lèi)型的屬性Template 可用于掛載ControlTemplate模板定義控件整體的外觀和布局。(整體外觀由ControlTemplate控制)2. Datatemplate用法xaml中寫(xiě)的Datatemplate被用于賦值給ContentControl控件的ContentTemplate或ItemsControl控件的ItemTemplate(二者都是Datatemplate類(lèi)型的依賴(lài)屬性), 當(dāng)wpf要去渲染控件的Content或ItemsSource時(shí)會(huì)按照ContentTemplate/ItemTemplate 中定義的樣子對(duì)content/ItemsSource數(shù)據(jù)進(jìn)行繪制(內(nèi)部個(gè)體外觀由DataTemplate控制)。這樣擁有Content/ItemsSource屬性的控件被稱(chēng)為數(shù)據(jù)呈現(xiàn)控件可以設(shè)置DataTemplate讓它顯示復(fù)雜的業(yè)務(wù)對(duì)象。而像布局類(lèi)控件(Grid, StackPanelBorder等)和純視覺(jué)控件(TextBlock等)沒(méi)有Content或ItemsSource這樣的數(shù)據(jù)承載屬性因此無(wú)法設(shè)置Datatemplate。3. ControlTemplate和Datatemplate 內(nèi)部都只能放一個(gè)根控件如果想要放多個(gè)則需要先放布局控件作為根再在布局控件里面放置多個(gè)。4. 上述StarRating控件繼承自Control是利用ControlTemplate完全重繪的一個(gè)自包含型控件它默認(rèn)不支持DataTemplate但它在ControlTemplate內(nèi)部硬編碼實(shí)例化了ItemsControl, 并在ItemsControl內(nèi)部使用DataTemplate這是合法的。5. 繼承ContentControl或ItemsControl時(shí)控件制作方只需負(fù)責(zé)邊框、陰影、標(biāo)題欄、布局等整體設(shè)計(jì)控件內(nèi)部的具體內(nèi)容由用戶(hù)確定用戶(hù)可以在該控件內(nèi)放任意UI。如果不需要將內(nèi)部UI開(kāi)放給用戶(hù)則應(yīng)該直接繼承Control由控件的提供者確定控件內(nèi)外的全部形態(tài)。6. 在繼承ContentControl或ItemsControl實(shí)現(xiàn)自定義控件時(shí)需要在Controltemplate模板中使用ContentPresenter或ItemsPresenter占位引導(dǎo)ContentContentTemplate或ItemsPanel在控件指定位置展開(kāi)。ContentPresenter可顯式綁定Content和ContentTemplate(默認(rèn)隱式從父控件找)而ItemsPresenter不能顯示綁定ItemsPanel。