• Wpf 自定义窗体


          用Wpf做客户端界面也有一段时间了,一直都直接使用的Window显示窗体,这几天闲来没事情,整理了下,自己做了一个自定义窗体。我自定义的窗体需要达到的细节效果包括:

          1、自定义边框粗细、颜色,窗体顶端不要有边框线,也就是说只有窗体左、右和底有边框,顶部是标题栏;

          2、实现圆角窗体,当具有圆角时,关闭按钮离窗体右侧边距为圆角值;

          3、标题栏有logo图标和标题栏文字,右侧有最小化、最大化和关闭按钮,需使用fontawesome字体图标,最大化按钮有切换图标效果;

          4、窗体最大化后不遮挡系统任务栏;

          网上度娘的文章基本都只针对某一个方面来说,我总结下做为我学习研究的一个小结,最终实现的效果如下图所示:

    1

          资源字典

          我们先来看一下窗体的自定义资源xaml文件的代码,注意我是使用“自定义控件”创建这个自定义窗体,如下图所示,而不是“用户控件”,2者之间的差异是,“自定义控件”将xaml和cs代码分离,xaml文件名称为Generic.xaml,该文件被自动存放在一个叫做”Themes”的文件夹中,如下面第2张图所示。而通过“用户控件”选项创建的控件xaml和cs代码是归并在一起的,cs是后台代码。

    1 2

          Generic.xaml代码

          代码首先通过xmlns:local="clr-namespace:youplus.OA.WpfApp"引入名称空间,该空间下我们定义了WindowBase.cs的代码;通过xmlns:converter="clr-namespace:youplus.OA.WpfApp.Converter"引入值转换器。

    然后定义了3个值转换器用于转换边框粗细、圆角半径、关闭按钮右侧边距的值。然后引入了FontAwesome字体,最小化、最大化、关闭按钮是使用的该字体里的对应项。然后定义了这几个按钮所使用的样式。最后是WindowBase窗体的自定义模板。

    <ResourceDictionary
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:youplus.OA.WpfApp"
        xmlns:converter="clr-namespace:youplus.OA.WpfApp.Converter">
        
        <converter:WindowBaseBorderThicknessConverter x:Key="BorderThicknessConverter"/>
        <converter:WindowBaseCornerRadiusConverter x:Key="CornerRadiusConverter"/>
        <converter:WindowBaseCloseMarginRightConverter x:Key="CloseMarginRightConverter"/>    
        
        <Style x:Key="FontAwesome" >
            <Setter Property="TextElement.FontFamily" Value="pack://application:,,,/Resources/#FontAwesome" />
            <Setter Property="TextElement.FontSize" Value="11" />
        </Style>
        
        <Style x:Key="WindowBaseButton" TargetType="{x:Type Button}">
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="Foreground" Value="Black"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type Button}">
                        <Border BorderThickness="{TemplateBinding BorderThickness}"
                                Background="{TemplateBinding Background}"
                                >
                            <ContentPresenter HorizontalAlignment="Center"
                                              VerticalAlignment="Center"
                                              Margin="{TemplateBinding Padding}"
                                               />
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsMouseOver" Value="true">
                                <Setter Property="Background" Value="#c75050"/>
                                <Setter Property="Foreground" Value="White"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style> 
        
        <Style TargetType="{x:Type local:WindowBase}">
            <Setter Property="AllowsTransparency" Value="True" />
            <Setter Property="WindowStyle" Value="None"/>
            <Setter Property="ResizeMode" Value="CanMinimize"/>
            <Setter Property="BorderBrush" Value="#6fbdd1" />
            <Setter Property="CornerRadius" Value="2" />
            <Setter Property="BorderThickness" Value="4"/>
            <Setter Property="Background" Value="White"/>
            <Setter Property="HeaderHeight" Value="40"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type local:WindowBase}">                   
                        <Grid Name="root" Style="{StaticResource FontAwesome}">
                            <Grid.RowDefinitions>
                                <RowDefinition Height="{Binding RelativeSource={RelativeSource TemplatedParent},Path=HeaderHeight}"/>
                                <RowDefinition/>
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition/>
                            </Grid.ColumnDefinitions>
                            <Border Name="header" Background="{TemplateBinding BorderBrush}"
                                    CornerRadius="{Binding Path=CornerRadius, RelativeSource={RelativeSource TemplatedParent},
                                        Converter={StaticResource CornerRadiusConverter}, ConverterParameter=header}"
                                    BorderThickness="0">
                                <DockPanel Height="Auto">
                                    <StackPanel VerticalAlignment="Center" Orientation="Horizontal" DockPanel.Dock="Left">
                                        <Image Source="{TemplateBinding Icon}" MaxHeight="20" MaxWidth="20" Margin="10,0,0,0"/>
                                        <TextBlock Text="{TemplateBinding Title}" FontSize="14" FontFamily="Microsoft Yihi" VerticalAlignment="Center" Margin="6,0,0,0"></TextBlock>
                                    </StackPanel>
                                    <StackPanel DockPanel.Dock="Right" Height="32" HorizontalAlignment="Right" VerticalAlignment="Top" Orientation="Horizontal">
                                        <Button x:Name="btnMin" Width="32" Content="&#xf2d1;" Style="{StaticResource WindowBaseButton}" Padding="0,0,0,7"/>
                                        <Button x:Name="btnMax" Width="32" Content="&#xf2d0;" Style="{StaticResource WindowBaseButton}"/>
                                        <Button Content="&#xf00d;" x:Name="btnClose" Width="32" 
                                                Margin="{Binding Path=CornerRadius,RelativeSource={RelativeSource TemplatedParent},
                                                    Converter={StaticResource CloseMarginRightConverter}}"
                                                 Style="{StaticResource WindowBaseButton}"/>
                                    </StackPanel>
                                </DockPanel>
                            </Border>
                            <Border Grid.Row="1" CornerRadius="{Binding Path=CornerRadius,RelativeSource={RelativeSource TemplatedParent},
                                        Converter={StaticResource CornerRadiusConverter}, ConverterParameter=content}"
                                    BorderThickness="{TemplateBinding BorderThickness,Converter={StaticResource BorderThicknessConverter}}"
                                    BorderBrush="{TemplateBinding BorderBrush}"
                                    Background="{TemplateBinding Background}"
                                    DockPanel.Dock="Top" Height="Auto">
                                <AdornerDecorator>
                                    <ContentPresenter />
                                </AdornerDecorator>
                            </Border>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </ResourceDictionary>

    WindowBase.cs

    接下来我们看看WindowBase的代码,它继承自Window,自定义了HeaderHeight和CornerRadius两个依赖项属性,从而可以在以上的xaml代码中配置2个属性。在静态WindowBase构造函数中我们要完成依赖项属性的注册,在实例WindowBase构造函数中我们监听SystemParameters.StaticPropertyChanged事件,从而可以使窗体最大化时不覆盖系统任务栏。最后通过覆盖父类的OnApplyTemplate事件代码,来为几个按钮配置状态和事件。

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    
    namespace youplus.OA.WpfApp
    {
        public class WindowBase : Window
        {
            private static DependencyProperty HeaderHeightProperty;
            public int HeaderHeight
            {
                get => (int)GetValue(HeaderHeightProperty);
                set => SetValue(HeaderHeightProperty, value);
            }
    
            private static int maxCornerRadius = 10;
            public static DependencyProperty CornerRadiusProperty;
            public int CornerRadius
            {
                get => (int)GetValue(CornerRadiusProperty);
                set => SetValue(CornerRadiusProperty, value);
            }
    
            static WindowBase()
            {
                DefaultStyleKeyProperty.OverrideMetadata(typeof(WindowBase), new FrameworkPropertyMetadata(typeof(WindowBase)));
    
                FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata();
                metadata.Inherits = true;
                metadata.DefaultValue = 2;
                metadata.AffectsMeasure = true;
                metadata.PropertyChangedCallback += (d,e)=> { };
                CornerRadiusProperty = DependencyProperty.Register("CornerRadius",
                    typeof(int), typeof(WindowBase), metadata,
                    o => {
                        int radius = (int)o;
                        if (radius >= 0 && radius <= maxCornerRadius) return true;
                        return false;
                    });
    
                metadata = new FrameworkPropertyMetadata();
                metadata.Inherits = true;
                metadata.DefaultValue = 40;
                metadata.AffectsMeasure = true;
                metadata.PropertyChangedCallback += (d, e) => { };
                HeaderHeightProperty = DependencyProperty.Register("HeaderHeight",
                    typeof(int), typeof(WindowBase), metadata,
                    o => {
                        int radius = (int)o;
                        if (radius >= 0 && radius <= 1000) return true;
                        return false;
                    });
            }
    
            public WindowBase() : base()
            {
                SystemParameters.StaticPropertyChanged -= SystemParameters_StaticPropertyChanged;
                SystemParameters.StaticPropertyChanged += SystemParameters_StaticPropertyChanged;
            }
    
            private void SystemParameters_StaticPropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                if (e.PropertyName == "WorkArea")
                {
                    if (this.WindowState == WindowState.Maximized)
                    {
                        double top = SystemParameters.WorkArea.Top;
                        double left = SystemParameters.WorkArea.Left;
                        double right = SystemParameters.PrimaryScreenWidth - SystemParameters.WorkArea.Right;
                        double bottom = SystemParameters.PrimaryScreenHeight - SystemParameters.WorkArea.Bottom;
                        root.Margin = new Thickness(left, top, right, bottom);
                    }
                }
            }
    
            private double normaltop;
            private double normalleft;
            private double normalwidth;
            private double normalheight;
            private Grid root;
            private Button minBtn;
            private Button maxBtn;
            private Button closeBtn;
            private Border header;
    
            public override void OnApplyTemplate()
            {
                base.OnApplyTemplate();
    
                minBtn = (Button)Template.FindName("btnMin", this);
                minBtn.Click += (o, e) => WindowState = WindowState.Minimized;
    
                maxBtn = (Button)Template.FindName("btnMax", this);
                root = (Grid)Template.FindName("root",this);
                maxBtn.Click += (o, e) =>
                {
                    if (WindowState == WindowState.Normal)
                    {
                        normaltop = this.Top;
                        normalleft = this.Left;
                        normalwidth = this.Width;
                        normalheight = this.Height;
    
                        double top = SystemParameters.WorkArea.Top;
                        double left = SystemParameters.WorkArea.Left;
                        double right = SystemParameters.PrimaryScreenWidth - SystemParameters.WorkArea.Right;
                        double bottom = SystemParameters.PrimaryScreenHeight - SystemParameters.WorkArea.Bottom;
                        root.Margin = new Thickness(left, top, right, bottom);
    
                        WindowState = WindowState.Maximized;
                        maxBtn.Content = "xf2d2";
                    }
                    else
                    {
                        WindowState = WindowState.Normal;
                        maxBtn.Content = "xf2d0";
    
                        Top = 0;
                        Left = 0;
                        Width = 0;
                        Height = 0;
    
                        this.Top = normaltop;
                        this.Left = normalleft;
                        this.Width = normalwidth;
                        this.Height = normalheight;
    
                        root.Margin = new Thickness(0);
                    }
                };
    
                closeBtn = (Button)Template.FindName("btnClose", this);
                closeBtn.Click += (o, e) => Close();
    
                header = (Border)Template.FindName("header", this);
                header.MouseMove += (o, e) =>
                {
                    if (e.LeftButton == MouseButtonState.Pressed)
                    {
                        this.DragMove();
                    }
                };
                header.MouseLeftButtonDown += (o, e) =>
                {
                    if (e.ClickCount >= 2)
                    {
                        maxBtn.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
                    }
                };
            }
        }
    }

           值转换器

          接下来我们看看值转换器的代码,值转换器有三个,1、窗体的边框只有左、下、右三面有,我们需要将配置给窗体的边框设置去掉顶部的边框设置后,配置给WindowBase内部的Border元素,该转换操作通过WindowBaseBorderThicknessConverter完成。2、窗体可能具有圆角,关闭按钮需要与窗体右边缘保持圆角指定值的边距,此时需要从Int型的圆角值转换为Thickness类型的边距,这是通过WindowBaseCloseMarginRightConverter实现的。3、最后一个转换器将Int型的圆角值转换为各个内部Border控件的CornerRadius。

    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Data;
    
    namespace youplus.OA.WpfApp.Converter
    {
        [ValueConversion(typeof(Thickness),typeof(Thickness))]
        public class WindowBaseBorderThicknessConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            {
                Thickness t = (Thickness)value;
                return new Thickness(t.Left,0,t.Right,t.Bottom);
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            {
                throw new NotImplementedException();
            }
        }
    
        [ValueConversion(typeof(int), typeof(Thickness))]
        public class WindowBaseCloseMarginRightConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            {
                int v = (int)value;
                return new Thickness(0, 0, v, 0);
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            {
                throw new NotImplementedException();
            }
        }
    
        [ValueConversion(typeof(int), typeof(CornerRadius))]
        public class WindowBaseCornerRadiusConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            {
                int v = (int)value;
                string p = parameter.ToString().Trim().ToLower();
                if (p == "header")
                    return new CornerRadius(v, v, 0, 0);
                else if(p== "btnclose")
                    return new CornerRadius(0, v, 0, 0);
                else
                    return new CornerRadius(0, 0, v, v);
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            {
                throw new NotImplementedException();
            }
        }
    }

    WindowBase的使用

    接下来我们就需要将以上的自定义窗体应用到我们的MainWindow窗体上了,实例xaml代码如下所示

    <local:WindowBase x:Class="youplus.OA.WpfApp.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:youplus.OA.WpfApp"
            mc:Ignorable="d"
            Title="自定义窗体测试" CornerRadius="10"  Height="311" Width="493" Icon="Resources/logo.ico" WindowStartupLocation="CenterScreen">
        <Grid>
    
        </Grid>
    </local:WindowBase>
  • 相关阅读:
    灵魂有香气的女子IOS版本APP,近期将考虑开放源代码
    PHP中$_SERVER获取当前页面的完整URL地址
    zabbix监控报错zabbix server is not running解决方法
    Linux重启inotify配置max_user_watches无效被恢复默认值8192的正确修改方法
    centos在yum install报错:Another app is currently holding the yum lock解决方法
    nginx去掉单个目录和多个目录PHP执行权限方法
    express搭建权限管理系统
    在express项目中使用formidable & multiparty实现文件上传
    vue生成图片验证码
    第0步:OracleRAC软件准备
  • 原文地址:https://www.cnblogs.com/alexywt/p/9968082.html
Copyright © 2020-2023  润新知