在开发wpf中少不了自定义标题栏的需求,so我整理了我最近几天的成功,分享出来。

首先看看windowchrome的几个参数解释

首先我们需要在 MainWindow 也就是我们的主窗口中的 Window.Resources 中实现 WindowChrome 的基本样式:WindowChrome.ResizeBorderThickness 设置不可见边框宽度WindowChrome.CaptionHeight> 设置属于标题栏的范围——高度WindowChrome.UseAeroCaptionButtons 是否启用默认系统按钮功能——三大金刚键WindowChrome.NonClientFrameEdges 设置客户区域,使用 bottom 可以实现加载时空白窗口而不显示默认窗口,提升用户体验

为了抽象为公用的代码,我们直接使用模版的方式,不需要在 每个window下面去写样式了。

新建Themes/windowControlTemp.xml 文件

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:sys="clr-namespace:System;assembly=mscorlib"
                     xmlns:wbw="clr-namespace:Main.view;assembly=Main"
                     xmlns:convert="clr-namespace:Main.widget.converter;assembly=Main"
                     xmlns:shell="clr-namespace:System.Windows.Shell;assembly=PresentationFramework">
            <convert:WorkAreaWidth x:Key="workAreaWidth" />
            <convert:WorkAreaHeight x:Key="workAreaHeight" />
    <ControlTemplate x:Key="BaseWindowControlTemplate" TargetType="{x:Type wbw:BaseWindow}">
        <Border SnapsToDevicePixels="True"  Background="{StaticResource BorderBackgroundColor}" >
            <Grid x:Name="RootGrid" Margin="{TemplateBinding Padding}">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="*" />
                </Grid.RowDefinitions>
                <!--  标题栏  -->
                <Grid Height="{Binding NonClientAreaHeight,Mode=TwoWay}" Background="#4b95f3"   x:Name="PART_NonClientArea">
                    <StackPanel HorizontalAlignment="Left" VerticalAlignment="Center"  Orientation="Horizontal" x:Name="stackPanelTitleLeft">
                        <Image  Name="iconLogo" Source="/Assets/Img/logo.png"  Stretch="Uniform" Width="20" Height="20" VerticalAlignment="Center" Margin="5,0,0,0"/>
                        <TextBlock x:Name="textBlockTitle" Text="{Binding Title,Mode=OneTime}" FontSize="14" VerticalAlignment="Center" Foreground="White" Margin="5,0,0,0" />
                    </StackPanel>
                    <StackPanel  HorizontalAlignment="Right"  Orientation="Horizontal">
                        <Button
                             Command="SystemCommands.MinimizeWindowCommand" 
                                     WindowChrome.IsHitTestVisibleInChrome="True"
                                     Padding="0"
                                     Style="{StaticResource imgButtonStyle}"
                                    x:Name="BtMin">
                            <Image Margin="8,0" x:Name="iconSmall" Source="/Assets/Img/icon_minimize.png"  Stretch="Uniform" Width="18" Height="18"/>
                        </Button>
                        <Button
                            Command="SystemCommands.RestoreWindowCommand"
                                    WindowChrome.IsHitTestVisibleInChrome="True"
                                    Style="{StaticResource imgButtonStyle}"
                                    Padding="0"
                                    x:Name="BtMax" >
                            <Image   Margin="8,0"  x:Name="iconMax" Source="/Assets/Img/icon_small.png"  Stretch="Uniform"  Width="18" Height="18"/>
                        </Button>
                        <Button
                            Command="SystemCommands.MaximizeWindowCommand"
                                WindowChrome.IsHitTestVisibleInChrome="True"
                                Style="{StaticResource imgButtonStyle}"
                                Padding="0"
                            Visibility="Collapsed"
                                x:Name="BtMax_min" >
                            <Image   Margin="8,0"  x:Name="iconMax_min" Source="/Assets/Img/icon_max.png"  Stretch="Uniform"  Width="18" Height="18"/>
                        </Button>
                        <Button
                            Command="SystemCommands.CloseWindowCommand"
                                     WindowChrome.IsHitTestVisibleInChrome="True"
                                     Style="{StaticResource imgButtonCloseStyle}"
                                     x:Name="BtClose">
                            <Image Margin="8,0"  x:Name="iconClose" Source="/Assets/Img/icon_close_bar.png"  Stretch="Uniform"  Width="18" Height="18"/>
                        </Button>
                    </StackPanel>
                </Grid>
                <AdornerDecorator   Grid.Row="1" >
                    <ContentPresenter  ClipToBounds="True" />
                </AdornerDecorator>
            </Grid>
        </Border>
        <ControlTemplate.Triggers>

            <Trigger Property="WindowState" Value="Maximized">
                <!--<Setter TargetName="RootGrid" Property="Margin" Value="6" />-->
                <Setter Property="Visibility" Value="Visible" TargetName="BtMax" />
                <Setter Property="Visibility" Value="Collapsed" TargetName="BtMax_min" />
                <Setter TargetName="RootGrid" Property="MaxHeight" Value="{Binding Converter={StaticResource workAreaHeight}}" />
                <Setter TargetName="RootGrid" Property="MaxWidth" Value="{Binding Converter={StaticResource workAreaWidth}}" />

            </Trigger>
            <Trigger Property="WindowState" Value="Normal">
                <Setter Property="Visibility" Value="Collapsed" TargetName="BtMax" />
                <Setter Property="Visibility" Value="Visible" TargetName="BtMax_min" />
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>
    <Style x:Key="BaseWindowStyle" TargetType="{x:Type wbw:BaseWindow}" >
        <Setter Property="BorderThickness" Value="1" />
        <Setter Property="Background" Value="Transparent" />
        <Setter Property="BorderBrush" Value="#262e2f" />
        <Setter Property="UseLayoutRounding" Value="True" />
        <Setter Property="SnapsToDevicePixels" Value="True" />
        <Setter Property="Template" Value="{StaticResource BaseWindowControlTemplate}"/>
    </Style>

</ResourceDictionary>

代码中用到了wbw:BaseWindow自定义基类 和 转换器workAreaHeight、workAreaWidth,WindowChrome我是写到基类中了,就没在模板中体现。

workAreaHeight,workAreaWidth 是为了解决最大化的时候边框会溢出问题,所以这里为内容设置最大的高宽来解决此问题,代码如下:


namespace Main.widget.converter
{
    public class WorkAreaHeight : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return System.Windows.SystemParameters.WorkArea.Height;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}
namespace Main.widget.converter
{
    public class WorkAreaWidth : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return System.Windows.SystemParameters.WorkArea.Width;
        }
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

BaseWindow基类


namespace Main.view
{
    [TemplatePart(Name = ElementNonClientArea, Type = typeof(UIElement))]
    public  class BaseWindow : System.Windows.Window
    {
        private const string ElementNonClientArea = "PART_NonClientArea";
        //定义一个高宽的属性
        public double ContainerWidth { get; set; } = 0;
        public double ContainerHeight { get; set; } = 0;
        public bool isShowTitleBar { get; set; } = true;
        public bool isCloseHint { get; set; } = false;
        private Thickness _actualBorderThickness;
        public static readonly DependencyProperty NonClientAreaHeightProperty = DependencyProperty.Register(
            nameof(NonClientAreaHeight), typeof(double), typeof(BaseWindow),
            new PropertyMetadata(35.0));
        public double NonClientAreaHeight
        {
            get => (double)GetValue(NonClientAreaHeightProperty);
            set => SetValue(NonClientAreaHeightProperty, value);
        }
        static BaseWindow(){
            StyleProperty.OverrideMetadata(typeof(BaseWindow), new FrameworkPropertyMetadata(ResourceHelper.GetResourceInternal<Style>(“BaseWindowStyle”)));
        }
        public BaseWindow()
        {
           
            InitializeBaseBehavior();
            //布局加载完成
            this.Loaded += initLoad;
            this.Unloaded += BaseWindow_Unloaded;
        }

        public virtual void InitializeBaseBehavior()
        {
            var chrome = new WindowChrome
            {
                UseAeroCaptionButtons = false,//是否启用默认系统按钮功能
                ResizeBorderThickness = new Thickness(0, 0, 0, 0),//置不可见边框宽度 拖动大小
                NonClientFrameEdges = NonClientFrameEdges.Bottom | NonClientFrameEdges.Left | NonClientFrameEdges.Right,
            };
            //chrome.GlassFrameThickness = new Thickness(0,0,0,0); //边框玻璃效果 w10下可禁用
            //chrome.CornerRadius = new CornerRadius();
            WindowChrome.SetResizeGripDirection(this, ResizeGripDirection.None);
            //绑定高度值
            BindingOperations.SetBinding(chrome, WindowChrome.CaptionHeightProperty,
               new Binding(NonClientAreaHeightProperty.Name) { Source = this });
            WindowChrome.SetWindowChrome(this, chrome);
            //MaxHeight = ScreenUtils.GetRealHeight();
            //MaxWidth = ScreenUtils.GetRealWidth();
            MinHeight = ScreenUtils.GetRealHeight() * 0.8;
            MinWidth = ScreenUtils.GetRealWidth() * 0.8;
            this.ResizeMode = System.Windows.ResizeMode.CanResize‌;
            this.WindowStartupLocation = WindowStartupLocation.CenterScreen;
            WindowState = WindowState.Maximized;

       }
        //卸载界面完成
        private void BaseWindow_Unloaded(object sender, RoutedEventArgs e)
        {

        }
        bool isInitialized = false;
        public virtual void initLoad(object sender, RoutedEventArgs re)
        {
            if (isInitialized) return;
            _actualBorderThickness = BorderThickness;
            CommandBindings.Add(new CommandBinding(SystemCommands.MinimizeWindowCommand,
               (s, e) => WindowState = WindowState.Minimized));
            CommandBindings.Add(new CommandBinding(SystemCommands.MaximizeWindowCommand,
                (s, e) => WindowState = WindowState.Maximized));
            CommandBindings.Add(new CommandBinding(SystemCommands.RestoreWindowCommand,
                (s, e) => WindowState = WindowState.Normal));
            CommandBindings.Add(new CommandBinding(SystemCommands.CloseWindowCommand, (s, e) => btn_close_Click()));

            isInitialized = true;
            ///如果当前窗口的 SizeToContent 模式 不是 WidthAndHeight(即不同时自动调整宽度和高度),则直接退出
            if (SizeToContent != SizeToContent.WidthAndHeight)
                return;

            SizeToContent = SizeToContent.Height;
            Dispatcher.BeginInvoke(new Action(() => { SizeToContent = SizeToContent.WidthAndHeight; }));


        }
        //设置窗口的标题
        public void SetTitle(string title)
        {
            this.Title = title;
        }
        public void hideTitleBar() {
            WindowState = WindowState.Maximized;
            WindowStyle = WindowStyle.None;
            ResizeMode = ResizeMode.NoResize;
            Style = null;
        }
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
        }

  
        //激活
        protected override void OnActivated(EventArgs e)
        {
            base.OnActivated(e);
        }
        //失去焦点
        protected override void OnDeactivated(EventArgs e)
        {
            base.OnDeactivated(e);
        }
        //关闭阶段
        protected override void OnClosing(CancelEventArgs e)
        {
            base.OnClosing(e);
        }
        //关闭完成
        protected override void OnClosed(EventArgs e)
        {
            base.OnClosed(e);
            Dispose();
        }
        protected override void OnSourceInitialized(EventArgs e)
        {
            base.OnSourceInitialized(e);
        }
 
        protected override void OnContentRendered(EventArgs e)
        {
            base.OnContentRendered(e);
            if (SizeToContent == SizeToContent.WidthAndHeight)
                InvalidateMeasure();
        }

        protected override void OnStateChanged(EventArgs e)
        {
            base.OnStateChanged(e);
            Console.WriteLine("OnStateChanged我恢复状态了:" + this.WindowState);
           
            if (WindowState == WindowState.Maximized)
            {
                BorderThickness = new Thickness();
            }
            else
            {
                BorderThickness = _actualBorderThickness;
            }
        }
        protected override void OnKeyDown(KeyEventArgs e)
        {
            base.OnKeyDown(e);
        }
        protected override void OnKeyUp(KeyEventArgs e)
        {
            base.OnKeyUp(e);
        }
        public virtual void Dispose()
        {
            this.Loaded -= initLoad;
            this.Unloaded -= BaseWindow_Unloaded;
            baseWindowTemplate = null;

        }

    }
}

其中下面的代码就是加载我们的样式到window样式。

StyleProperty.OverrideMetadata(typeof(BaseWindow), new FrameworkPropertyMetadata(ResourceHelper.GetResourceInternal<Style>(“BaseWindowStyle”)));

helper代码如下:

    public class ResourceHelper
    {
        private static ResourceDictionary _theme;

        /// <summary>
        ///     获取资源
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static T GetResource<T>(string key)
        {
            if (Application.Current.TryFindResource(key) is T resource)
            {
                return resource;
            }

            return default;
        }

        internal static T GetResourceInternal<T>(string key)
        {
            if (GetTheme()[key] is T resource)
            {
                return resource;
            }

            return default;
        }


        /// <summary>
        ///     get Main skin
        /// </summary>
        /// <param name="skin"></param>
        /// <returns></returns>
        public static ResourceDictionary GetSkin(SkinType skin) => new ResourceDictionary()
        {
            Source = new Uri($"pack://application:,,,/Main;component/Themes/Skin{skin}.xaml")
        };

        /// <summary>
        ///     get Main theme
        /// </summary>
        public static ResourceDictionary GetTheme() => _theme ?? GetStandaloneTheme();

        public static ResourceDictionary GetStandaloneTheme()
        {
            return new ResourceDictionary()
            {
                Source = new Uri("pack://application:,,,/Main;component/Themes/WindowControlTemp.xaml")
            };
        }

    }

}

使用基类

<local:BaseWindow x:Class="Main.view.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:Main.view"        mc:Ignorable="d"       >
///内容区域
</local:BaseWindow>

这些代码都是项目中的代码,注意代码中的命名空间。

最后看看效果:

最大化:

0

最小化:

0

ok,以上就是我的应用的解决方案。