目錄: WPF自定義Window樣式(1) WPF自定義Window樣式(2) 1. 引言 WPF是製作界面的一大利器。最近在做一個項目,用的就是WPF。既然使用了WPF了,那麼理所當然的,需要自定義窗體樣式。所使用的代碼是在網上查到的,遺憾的是,整理完畢後,再找那篇帖子卻怎麼也找不到了,僅僅在下載 ...
目錄:
1. 引言
WPF是製作界面的一大利器。最近在做一個項目,用的就是WPF。既然使用了WPF了,那麼理所當然的,需要自定義窗體樣式。所使用的代碼是在網上查到的,遺憾的是,整理完畢後,再找那篇帖子卻怎麼也找不到了,僅僅在下載記錄裡面找到了作者的git地址,DinoChan。原帖作者看到後可以聯繫我,以便我加上原文鏈接。
首先上原始源碼。
2. 創建項目
創建空白項目stonemqy.CustomWindow,添加WPF項目stonemqy.CustomWindow.Main。在stonemqy.CustomWindow.Main中添加文件夾Themes,併在其中添加資源字典Generic.xaml,註意這裡的Themes文件夾和Generic.xaml資源字典的名字不可更改。併在項目下依次添加類VisualStates、TransitioningContentControl和CustomWindow。
3. VisualStates
using System.Diagnostics; using System.Linq; using System.Windows; using System.Windows.Media; namespace stonemqy.CustomWindow.Main { public static class VisualStates { /// <summary> /// This method tries to get the named VisualStateGroup for the /// dependency object. The provided object's ImplementationRoot will be /// looked up in this call. /// </summary> /// <param name="dependencyObject">The dependency object.</param> /// <param name="groupName">The visual state group's name.</param> /// <returns>Returns null or the VisualStateGroup object.</returns> public static VisualStateGroup TryGetVisualStateGroup(DependencyObject dependencyObject, string groupName) { FrameworkElement root = GetImplementationRoot(dependencyObject); if (root == null) { return null; } return VisualStateManager.GetVisualStateGroups(root) .OfType<VisualStateGroup>() .Where(group => string.CompareOrdinal(groupName, group.Name) == 0) .FirstOrDefault(); } /// <summary> /// Gets the implementation root of the Control. /// </summary> /// <param name="dependencyObject">The DependencyObject.</param> /// <remarks> /// Implements Silverlight's corresponding internal property on Control. /// </remarks> /// <returns>Returns the implementation root or null.</returns> public static FrameworkElement GetImplementationRoot(DependencyObject dependencyObject) { Debug.Assert(dependencyObject != null, "DependencyObject should not be null."); return (1 == VisualTreeHelper.GetChildrenCount(dependencyObject)) ? VisualTreeHelper.GetChild(dependencyObject, 0) as FrameworkElement : null; } } }
4. TransitioningContentControl
using System; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Media.Animation; namespace stonemqy.CustomWindow.Main { /// <summary> /// Represents a control with a single piece of content and when that content /// changes performs a transition animation. /// </summary> /// <QualityBand>Experimental</QualityBand> /// <remarks>The API for this control will change considerably in the future.</remarks> [TemplateVisualState(GroupName = PresentationGroup, Name = NormalState)] [TemplateVisualState(GroupName = PresentationGroup, Name = DefaultTransitionState)] [TemplatePart(Name = PreviousContentPresentationSitePartName, Type = typeof(ContentControl))] [TemplatePart(Name = CurrentContentPresentationSitePartName, Type = typeof(ContentControl))] public class TransitioningContentControl : ContentControl { #region Visual state names /// <summary> /// The name of the group that holds the presentation states. /// </summary> private const string PresentationGroup = "PresentationStates"; /// <summary> /// The name of the state that represents a normal situation where no /// transition is currently being used. /// </summary> private const string NormalState = "Normal"; /// <summary> /// The name of the state that represents the default transition. /// </summary> public const string DefaultTransitionState = "DefaultTransition"; #endregion Visual state names #region Template part names /// <summary> /// The name of the control that will display the previous content. /// </summary> internal const string PreviousContentPresentationSitePartName = "PreviousContentPresentationSite"; /// <summary> /// The name of the control that will display the current content. /// </summary> internal const string CurrentContentPresentationSitePartName = "CurrentContentPresentationSite"; #endregion Template part names #region TemplateParts /// <summary> /// Gets or sets the current content presentation site. /// </summary> /// <value>The current content presentation site.</value> private ContentPresenter CurrentContentPresentationSite { get; set; } /// <summary> /// Gets or sets the previous content presentation site. /// </summary> /// <value>The previous content presentation site.</value> private ContentPresenter PreviousContentPresentationSite { get; set; } #endregion TemplateParts #region public bool IsTransitioning /// <summary> /// Indicates whether the control allows writing IsTransitioning. /// </summary> private bool _allowIsTransitioningWrite; /// <summary> /// Gets a value indicating whether this instance is currently performing /// a transition. /// </summary> public bool IsTransitioning { get { return (bool)GetValue(IsTransitioningProperty); } private set { _allowIsTransitioningWrite = true; SetValue(IsTransitioningProperty, value); _allowIsTransitioningWrite = false; } } /// <summary> /// Identifies the IsTransitioning dependency property. /// </summary> public static readonly DependencyProperty IsTransitioningProperty = DependencyProperty.Register( "IsTransitioning", typeof(bool), typeof(TransitioningContentControl), new PropertyMetadata(OnIsTransitioningPropertyChanged)); /// <summary> /// IsTransitioningProperty property changed handler. /// </summary> /// <param name="d">TransitioningContentControl that changed its IsTransitioning.</param> /// <param name="e">Event arguments.</param> private static void OnIsTransitioningPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { TransitioningContentControl source = (TransitioningContentControl)d; if (!source._allowIsTransitioningWrite) { source.IsTransitioning = (bool)e.OldValue; throw new InvalidOperationException("TransitiotioningContentControl_IsTransitioningReadOnly"); } } #endregion public bool IsTransitioning /// <summary> /// The storyboard that is used to transition old and new content. /// </summary> private Storyboard _currentTransition; /// <summary> /// Gets or sets the storyboard that is used to transition old and new content. /// </summary> private Storyboard CurrentTransition { get { return _currentTransition; } set { // decouple event if (_currentTransition != null) { _currentTransition.Completed -= OnTransitionCompleted; } _currentTransition = value; if (_currentTransition != null) { _currentTransition.Completed += OnTransitionCompleted; } } } #region public string Transition /// <summary> /// Gets or sets the name of the transition to use. These correspond /// directly to the VisualStates inside the PresentationStates group. /// </summary> public string Transition { get { return GetValue(TransitionProperty) as string; } set { SetValue(TransitionProperty, value); } } /// <summary> /// Identifies the Transition dependency property. /// </summary> public static readonly DependencyProperty TransitionProperty = DependencyProperty.Register( "Transition", typeof(string), typeof(TransitioningContentControl), new PropertyMetadata(DefaultTransitionState, OnTransitionPropertyChanged)); /// <summary> /// TransitionProperty property changed handler. /// </summary> /// <param name="d">TransitioningContentControl that changed its Transition.</param> /// <param name="e">Event arguments.</param> private static void OnTransitionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { TransitioningContentControl source = (TransitioningContentControl)d; string oldTransition = e.OldValue as string; string newTransition = e.NewValue as string; if (source.IsTransitioning) { source.AbortTransition(); } // find new transition Storyboard newStoryboard = source.GetStoryboard(newTransition); // unable to find the transition. if (newStoryboard == null) { // could be during initialization of xaml that presentationgroups was not yet defined if (VisualStates.TryGetVisualStateGroup(source, PresentationGroup) == null) { // will delay check source.CurrentTransition = null; } else { // revert to old value source.SetValue(TransitionProperty, oldTransition); throw new ArgumentException( "TransitioningContentControl_TransitionNotFound"); } } else { source.CurrentTransition = newStoryboard; } } #endregion public string Transition #region public bool RestartTransitionOnContentChange /// <summary> /// Gets or sets a value indicating whether the current transition /// will be aborted when setting new content during a transition. /// </summary> public bool RestartTransitionOnContentChange { get { return (bool)GetValue(RestartTransitionOnContentChangeProperty); } set { SetValue(RestartTransitionOnContentChangeProperty, value); } } /// <summary> /// Identifies the RestartTransitionOnContentChange dependency property. /// </summary> public static readonly DependencyProperty RestartTransitionOnContentChangeProperty = DependencyProperty.Register( "RestartTransitionOnContentChange", typeof(bool), typeof(TransitioningContentControl), new PropertyMetadata(false, OnRestartTransitionOnContentChangePropertyChanged)); /// <summary> /// RestartTransitionOnContentChangeProperty property changed handler. /// </summary> /// <param name="d">TransitioningContentControl that changed its RestartTransitionOnContentChange.</param> /// <param name="e">Event arguments.</param> private static void OnRestartTransitionOnContentChangePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((TransitioningContentControl)d).OnRestartTransitionOnContentChangeChanged((bool)e.OldValue, (bool)e.NewValue); } /// <summary> /// Called when the RestartTransitionOnContentChangeProperty changes. /// </summary> /// <param name="oldValue">The old value of RestartTransitionOnContentChange.</param> /// <param name="newValue">The new value of RestartTransitionOnContentChange.</param> protected virtual void OnRestartTransitionOnContentChangeChanged(bool oldValue, bool newValue) { } #endregion public bool RestartTransitionOnContentChange #region Events /// <summary> /// Occurs when the current transition has completed. /// </summary> public event RoutedEventHandler TransitionCompleted; #endregion Events /// <summary> /// Initializes a new instance of the <see cref="TransitioningContentControl"/> class. /// </summary> public TransitioningContentControl() { DefaultStyleKey = typeof(TransitioningContentControl); } /// <summary> /// Builds the visual tree for the TransitioningContentControl control /// when a new template is applied. /// </summary> public override void OnApplyTemplate() { if (IsTransitioning) { AbortTransition(); } base.OnApplyTemplate(); PreviousContentPresentationSite = GetTemplateChild(PreviousContentPresentationSitePartName) as ContentPresenter; CurrentContentPresentationSite = GetTemplateChild(CurrentContentPresentationSitePartName) as ContentPresenter; if (CurrentContentPresentationSite != null) { CurrentContentPresentationSite.Content = Content; } // hookup currenttransition Storyboard transition = GetStoryboard(Transition); CurrentTransition = transition; if (transition == null) { string invalidTransition = Transition; // revert to default Transition = DefaultTransitionState; throw new ArgumentException( "TransitioningContentControl_TransitionNotFound"); } VisualStateManager.GoToState(this, NormalState, false); VisualStateManager.GoToState(this, Transition, true); } /// <summary> /// Called when the value of the <see cref="P:System.Windows.Controls.ContentControl.Content"/> property changes. /// </summary> /// <param name="oldContent">The old value of the <see cref="P:System.Windows.Controls.ContentControl.Content"/> property.</param> /// <param name="newContent">The new value of the <see cref="P:System.Windows.Controls.ContentControl.Content"/> property.</param> protected override void OnContentChanged(object oldContent, object newContent) { base.OnContentChanged(oldContent, newContent); StartTransition(oldContent, newContent); } /// <summary> /// Starts the transition. /// </summary> /// <param name="oldContent">The old content.</param> /// <param name="newContent">The new content.</param> [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "newContent", Justification = "Should be used in the future.")] private void StartTransition(object oldContent, object newContent) { // both presenters must be available, otherwise a transition is useless. if (CurrentContentPresentationSite != null && PreviousContentPresentationSite != null) { CurrentContentPresentationSite.Content = newContent; PreviousContentPresentationSite.Content = oldContent; // and start a new transition if (!IsTransitioning || RestartTransitionOnContentChange) { IsTransitioning = true; VisualStateManager.GoToState(this, NormalState, false); VisualStateManager.GoToState(this, Transition, true); } } } /// <summary> /// Handles the Completed event of the transition storyboard. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param> private void OnTransitionCompleted(object sender, EventArgs e) { AbortTransition(); RoutedEventHandler handler = TransitionCompleted; if (handler != null) { handler(this, new RoutedEventArgs()); } } /// <summary> /// Aborts the transition and releases the previous content. /// </summary> public void AbortTransition() { // go to normal state and release our hold on the old content. VisualStateManager.GoToState(this, NormalState, false); IsTransitioning = false; if (PreviousContentPresentationSite != null) { PreviousContentPresentationSite.Content = null; } } /// <summary> /// Attempts to find a storyboard that matches the newTransition name. /// </summary> /// <param name="newTransition">The new transition.</param> /// <returns>A storyboard or null, if no storyboard was found.</returns> private Storyboard GetStoryboard(string newTransition) { VisualStateGroup presentationGroup = VisualStates.TryGetVisualStateGroup(this, PresentationGroup); Storyboard newStoryboard = null; if (presentationGroup != null) { newStoryboard = presentationGroup.States .OfType<VisualState>() .Where(state => state.Name == newTransition) .Select(state => state.Storyboard) .FirstOrDefault(); } return newStoryboard; } } }
5. CustomWindow
此處原來有關於CustomWindow的一些註釋,但是經驗證,註釋有誤,故貼代碼時將註釋刪除了。
using System; using System.Windows; using System.Windows.Input; namespace stonemqy.CustomWindow.Main { public class CustomWindow : Window { public CustomWindow() { DefaultStyleKey = typeof(CustomWindow); CommandBindings.Add(new CommandBinding(SystemCommands.CloseWindowCommand, CloseWindow)); CommandBindings.Add(new CommandBinding(SystemCommands.MaximizeWindowCommand, MaximizeWindow, CanResizeWindow)); CommandBindings.Add(new CommandBinding(SystemCommands.MinimizeWindowCommand, MinimizeWindow, CanMinimizeWindow)); CommandBindings.Add(new CommandBinding(SystemCommands.RestoreWindowCommand, RestoreWindow, CanResizeWindow)); CommandBindings.Add(new CommandBinding(SystemCommands.ShowSystemMenuCommand, ShowSystemMenu)); } protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) { base.OnMouseLeftButtonDown(e); if (e.ButtonState == MouseButtonState.Pressed) DragMove(); } protected override void OnContentRendered(EventArgs e) { base.OnContentRendered(e); if (SizeToContent == SizeToContent.WidthAndHeight) InvalidateMeasure(); } #region Window Commands private void CanResizeWindow(object sender, CanExecuteRoutedEventArgs e) { e.CanExecute = ResizeMode == ResizeMode.CanResize || ResizeMode == ResizeMode.CanResizeWithGrip; } private void CanMinimizeWindow(object sender, CanExecuteRoutedEventArgs e) { e.CanExecute = ResizeMode != ResizeMode.NoResize; } private void CloseWindow(object sender, ExecutedRoutedEventArgs e) { this.Close(); //SystemCommands.CloseWindow(this); } private void MaximizeWindow(object sender, ExecutedRoutedEventArgs e) { SystemCommands.MaximizeWindow(this); } private void MinimizeWindow(object sender, ExecutedRoutedEventArgs e) { SystemCommands.MinimizeWindow(this); } private void RestoreWindow(object sender, ExecutedRoutedEventArgs e) { SystemCommands.RestoreWindow(this); } private void ShowSystemMenu(object sender, ExecutedRoutedEventArgs e) { var element = e.OriginalSource as FrameworkElement; if (element == null) return; var point = WindowState == WindowState.Maximized ? new Point(0, element.ActualHeight) : new Point(Left + BorderThickness.Left, element.ActualHeight + Top + BorderThickness.Top); point = element.TransformToAncestor(this).Transform(point); SystemCommands.ShowSystemMenu(this, point); } #endregion } }
6. Generic.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:stonemqy.CustomWindow.Main" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"> <DataTemplate x:Key="RestoreWhite"> <Grid UseLayoutRounding="True" RenderTransform="1,0,0,1,.5,.5"> <Path Data="M2,0 L8,0 L8,6 M0,3 L6,3 M0,2 L6,2 L6,8 L0,8 Z" Width="8" Height="8" VerticalAlignment="Center" HorizontalAlignment="Center" Stroke="White" StrokeThickness="1" /> </Grid> </DataTemplate> <DataTemplate x:Key="CloseWhite"> <Grid Margin="1,0,0,0"> <Rectangle Stroke="White" Height="2" RenderTransformOrigin="0.5,0.5" Width="11" UseLayoutRounding="True"> <Rectangle.RenderTransform> <TransformGroup> <ScaleTransform /> <SkewTransform /> <RotateTransform Angle="45" /> <TranslateTransform /> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> <Rectangle Stroke="White" Height="2" RenderTransformOrigin="0.5,0.5" Width="11" UseLayoutRounding="True"> <Rectangle.RenderTransform> <TransformGroup> <ScaleTransform /> <SkewTransform /> <RotateTransform Angle="-45" /> <TranslateTransform /> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> </Grid> </DataTemplate> <DataTemplate x:Key="MaximizeWhite"> <Grid> <Path Data="M0,1 L9,1 L9,8 L0,8 Z" Width="9" Height="8" Margin="0,2,0,0" VerticalAlignment="Center" HorizontalAlignment="Center" Stroke="White" StrokeThickness="2" /> </Grid> </DataTemplate> <DataTemplate x:Key="MinimizeWhite"> <Grid> <Path Data="M0,6 L8,6 Z" Width="8" Height="7" VerticalAlignment="Center" HorizontalAlignment="Center" Stroke="White" StrokeThickness="2" /> </Grid> </DataTemplate> <Style