• WPF学习 - 自定义窗体(二)


    上一篇文章写了如何创建自定义窗体:使用 WindowChrome 或者 WindowStyle=“None”这两种方式。本文将讲述如何设置窗体的效果(以阴影效果为例),以及在效果模式下,窗体各功能的配合。

    一、窗体的空间范围:

    窗体的范围,就是白色区域部分:包括窗体的边框,标题栏,以及内部的空白部分。出了白色范围,不再属于窗体,且窗体也不能影响到白色区域意外的地方。理解这一点很重要!

    二、透明窗体:

    要设置透明窗体,比较简单,要同时设置三个属性:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    "ControlTest.EffectNoneWindow"
            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:ControlTest"
            mc:Ignorable="d"
            Title="EffectNoneWindow" Height="450" Width="800"
            AllowsTransparency="True" Background="Transparent" WindowStyle ="None">
        
        

     注意:当AllowTransparency = “True”时,WindowStyle的值必须为None。

    这个截图就是窗体。因为它是透明的,所以直接看到了桌面(注意看顶部的调试工具条)。

    三、添加阴影效果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    "ControlTest.EffectNoneWindow"
            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:ControlTest"
            mc:Ignorable="d"
            Title="EffectNoneWindow" Height="450" Width="800"
            AllowsTransparency="True" Background="Transparent" WindowStyle ="None">
     
        "20" Background="White">
            
                "0" BlurRadius="20" ShadowDepth="0" Color="#FF585252" />
            
        

     

     注意,最外圈的灰色部分(图中标1的地方)是桌面(它是微信截图的时候产生的);图中标2的青色框,是窗体的实际边框,它与白色部分(图中标3的地方)之间的空间,就是Border的Margin。为什么要这个Margin?

    因为Border有一个Effect。如果没有这个Margin,Effect没有地方放置(因为任何窗体的效果、元素,都不能超出窗体的区域)。

    四、添加功能:

    你以为到这里文章就结束了?No No No,让我们添加功能之后再看看。把上一篇文章中,关于标题栏,按钮的功能加上。

    复制代码
    Xaml代码:
    
    "ControlTest.EffectNoneWindow"
            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:ControlTest"
            mc:Ignorable="d"
            Title="EffectNoneWindow" Height="450" Width="800"
            AllowsTransparency="True" Background="Transparent" WindowStyle ="None">
    
        "20" Background="White">
            
                "0" BlurRadius="20" ShadowDepth="0" Color="#FF585252" />
            
    
            
                
                    "auto"/>
                    "*"/>
                
                "30" Background="YellowGreen"
                MouseDown="TitleMove">
                    
                        
                            
                        
                        "Horizontal" WindowChrome.IsHitTestVisibleInChrome="True">
                            
                            "Center" Margin="3,0" Text="{Binding Title, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>
                        
    
                        "Horizontal" HorizontalAlignment="Right" WindowChrome.IsHitTestVisibleInChrome="True">
                            
    复制代码

    大部分功能完好。可以用鼠标在标题上拖动窗体,双击标题栏放大、缩小,最小化、关闭按钮工作正常。唯独最大化按钮的效果不是我们想要的!

    问题:1. 最大化之后,它的四周与屏幕边缘还有空白;2. 它遮住了桌面的任务栏。

    这才是要解决的问题的关键!这个问题的产生,还是“窗体的空间范围”引起的:

    1. 因为最大化的是窗体,而窗体包括了最外层Border的Margin,所以最大化之后,Margin依然是存在的,四周与屏幕之间自然就有了空白。

    2. 因为没有操作系统赋予的“窗口边框”,窗体也就不知道了屏幕可用范围大小,只能是尽可能占据全部屏幕,因此就遮住了桌面的任务栏。

    要解决这两个问题,只需要做两个设置。且看如下代码(最重要的是OnStateChanged()方法):

    复制代码
    Xaml代码:
    
    Name="bd" Margin="20" Background="White">
        ……
    
    
    C#代码:
    public partial class EffectNoneWindow : Window
    {
        Thickness originMargin;              // 记录bd的初始Margin值
        Thickness zeroMargin = new Thickness(5);    // 设置当窗体最大化之后,bd的Margin值。该值本应该为0,但是实际上最大化之后会有点“吃”窗体,因此设置为5或者其他数值,以求更好的视觉效果。
    
    
        public EffectNoneWindow()
        {
            InitializeComponent();
            
         // 注册事件。如此当拖动鼠标到屏幕的边缘时,Windows系统会将窗体最大化,程序也能正常工作。
    this.StateChanged += OnStateChanged;               // 设置窗体最大化时的最大高度 this.MaxHeight = SystemParameters.MaximizedPrimaryScreenHeight; this.originMargin = this.bd.Margin; } private void OnStateChanged(object? sender, EventArgs e) { if(this.WindowState == WindowState.Normal) { // Normal状态时,将bd的Margin设置为初始值 this.bd.Margin = this.originMargin; } else { // 最大化时,将bd的Margin设置为0; this.bd.Margin = zeroMargin; } } // 窗体移动 private void TitleMove(object sender, MouseButtonEventArgs e) { if (e.ChangedButton != MouseButton.Left) return; // 非左键点击,退出 if (e.ClickCount == 1) { this.DragMove(); // 拖动窗体 } else { WindowMax(); // 双击时,最大化或者还原窗体 } } // 最小化 private void Btn_Min(object sender, RoutedEventArgs e) { this.WindowState = WindowState.Minimized; } // 关闭窗体 private void Btn_Close(object sender, RoutedEventArgs e) { this.Close(); } // 最大化、还原 private void Btn_Max(object sender, RoutedEventArgs e) { WindowMax(); } private void WindowMax() { if (this.WindowState == WindowState.Normal) { this.WindowState = WindowState.Maximized; } else { this.WindowState = WindowState.Normal; } } }
    复制代码

    你以为这就完事了?太天真了,微软的东西哪有这么简单?它总是会给你埋坑的!

    五、分屏:

    以上代码,在点击“最大化”按钮、用鼠标把窗体拖到屏幕顶端(系统赋予的最大化操作)时,都OK,没问题。但如果把窗体用鼠标拖到屏幕的左侧、右侧、屏幕的四个角时,系统会让窗体占据部分屏幕(类似于分屏)时,窗体呈现的效果如下:

    窗体高度OK了,但是Margin错了!

     因此,还要修改。代码如下:

    复制代码
        public partial class EffectNoneWindow : Window
        {
            Thickness originMargin;
            Thickness margin5 = new Thickness(8);
            Thickness marginTopLeft;
            Thickness marginTopRight;
            Thickness marginBottomLeft;
            Thickness marginBottomRight;
            Thickness marginLeft;
            Thickness marginRight;
    
            double maxWidth = SystemParameters.MaximizedPrimaryScreenWidth;
            double maxHeight = SystemParameters.MaximizedPrimaryScreenHeight;
    
    
            public EffectNoneWindow()
            {
                InitializeComponent();
    
                //不再使用StateChanged事件,而使用SizeChanged事件
                this.SizeChanged += EffectNoneWindow_SizeChanged;
    
                // 设置窗体最大化时的最大高度
                MaxHeight = maxHeight;
                originMargin = bd.Margin;
                marginTopLeft = new Thickness(0, 0, originMargin.Right, originMargin.Bottom);
                marginTopRight = new Thickness(originMargin.Left, 0, 0, originMargin.Bottom);
                marginBottomLeft = new Thickness(0, originMargin.Top, originMargin.Right, 0);
                marginBottomRight = new Thickness(originMargin.Left, originMargin.Top, 0, 0);
                marginLeft = new Thickness(0, 0, originMargin.Right, 0);
                marginRight = new Thickness(originMargin.Left, 0, 0, 0);
            }
    
            // 尺寸发生改变时:
            private void EffectNoneWindow_SizeChanged(object sender, SizeChangedEventArgs e)
            {
                // 1. 判断是否最大化:
                if(Math.Abs(this.ActualWidth- maxWidth)<4)          // 此处用绝对值判断,是因为窗体的大小,不会刚刚好是最大值。下同。
                {
                    bd.Margin = margin5;
                    return;
                }
    
                // 2. 判断是否分屏:
                if(Math.Abs(this.Height-maxHeight)<20)
                {
                    // 在窗体的左侧:
                    bd.Margin = Left == 0 ? marginLeft : marginRight;
                    //Height = maxHeight;
                    return;
                }
    
                // 3. 判断是否四分屏:
                if(Math.Abs(this.Width - maxWidth/2)<20 && Math.Abs(this.Height - maxHeight/2)<20)
                {
                    if(Left == 0)
                    {
                        if(Top ==0)
                        {
                            // 左上角
                            bd.Margin = marginTopLeft;
                        }
                        else
                        {
                            bd.Margin = marginBottomLeft;
                        }
                    }
                    else
                    {
                        if(Top == 0)
                        {
                            bd.Margin = marginTopRight;
                        }
                        else
                        {
                            bd.Margin = marginBottomRight;
                        }
                    }
                    return;
                }
    
                this.bd.Margin = originMargin;
            }
    
            private void OnStateChanged(object? sender, EventArgs e)
            {
                if(this.WindowState == WindowState.Normal)
                {
                    // Normal状态时,将bd的Margin设置为初始值
                    this.bd.Margin = this.originMargin;
                }
                else
                {
                    // 最大化时,将bd的Margin设置为0;
                    this.bd.Margin = margin5;
                }
            }
    
            // 窗体移动
            private void TitleMove(object sender, MouseButtonEventArgs e)
            {
                if (e.ChangedButton != MouseButton.Left) return;            // 非左键点击,退出
                if (e.ClickCount == 1)
                {
                    this.DragMove();                                        // 拖动窗体
                }
                else
                {
                    WindowMax();                                            // 双击时,最大化或者还原窗体
                }
            }
    
            // 最小化
            private void Btn_Min(object sender, RoutedEventArgs e)
            {
                this.WindowState = WindowState.Minimized;
            }
    
    
    
            // 关闭窗体
            private void Btn_Close(object sender, RoutedEventArgs e)
            {
                this.Close();
            }
    
            // 最大化、还原
            private void Btn_Max(object sender, RoutedEventArgs e)
            {
                WindowMax();
            }
    
            private void WindowMax()
            {
                if (this.WindowState == WindowState.Normal)
                {
                    this.WindowState = WindowState.Maximized;
                }
                else
                {
                    this.WindowState = WindowState.Normal;
                }
            }
        }
    复制代码

    此处,最主要的是EffectNoneWindow_SizeChanged()方法。当窗体的尺寸发生改变时,它会判断窗体在哪个位置,是最大化、两分屏,还是四分屏,并对Margin进行相应的赋值。

    至此,一个功能齐全的自定义窗体就搞定了。要改换效果,则只需要将Xaml代码中的Effect替换即可。

    六、禁止调整窗体大小:

    有些时候,我们不希望窗体能调整大小,比如初始窗口,仅仅只是在加载的时候显示,程序加载完之后就关闭了。

    如果不希望窗体能调整大小,只需设置Windows的ResizeMode = “NoResize”即可。

  • 相关阅读:
    在openSUSE-Leap-15.4-DVD-x86_64下安装网易云音乐linux客户端
    Spring Expression Language (SpEL) 介绍与使用方法
    go gin 自定义验证
    HC32L110 系列 M0 MCU 介绍和Win10下DAP-Link, ST-Link, J-Link方式的烧录
    【Java集合框架】22 ——SortedMap 接口
    C++中TCP socket传输文件
    Java项目:ssm物业管理系统
    EXPLIAN查询type
    基于Python的高校教务系统设计与实现
    服务器配置tomcat
  • 原文地址:https://www.cnblogs.com/raynado/p/17662002.html