WPF学习 - 自定义窗体(二)
阅读原文时间:2023年09月05日阅读:5

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

一、窗体的空间范围:

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

二、透明窗体:

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

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

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

三、添加阴影效果:

<Border Margin="20" Background="White">  
    <Border.Effect>  
        <DropShadowEffect Direction="0" BlurRadius="20" ShadowDepth="0" Color="#FF585252" />  
    </Border.Effect>  
</Border>  

 

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

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

四、添加功能:

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

Xaml代码:

<Border Margin="20" Background="White">  
    <Border.Effect>  
        <DropShadowEffect Direction="0" BlurRadius="20" ShadowDepth="0" Color="#FF585252" />  
    </Border.Effect>

    <Grid>  
        <Grid.RowDefinitions>  
            <RowDefinition Height="auto"/>  
            <RowDefinition Height="\*"/>  
        </Grid.RowDefinitions>  
        <Border Height="30" Background="YellowGreen"  
        MouseDown="TitleMove">  
            <Grid>  
                <Grid.Resources>  
                    <Style TargetType="Button">  
                        <Setter Property="Width" Value="30"/>  
                        <Setter Property="Background" Value="Transparent"/>  
                        <Setter Property="BorderThickness" Value="0"/>  
                    </Style>  
                </Grid.Resources>  
                <StackPanel Orientation="Horizontal" WindowChrome.IsHitTestVisibleInChrome="True">  
                    <Image />  
                    <TextBlock VerticalAlignment="Center" Margin="3,0" Text="{Binding Title, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>  
                </StackPanel>

                <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" WindowChrome.IsHitTestVisibleInChrome="True">  
                    <Button Content="\_" Click="Btn\_Min"/>  
                    <Button Content="Max" Click="Btn\_Max"/>  
                    <Button Content="X" Click="Btn\_Close"/>  
                </StackPanel>  
            </Grid>  
        </Border>  
        <TabControl Grid.Row="1" Margin="10">  
            <TabItem Header="项目"/>  
            <TabItem Header="代码"/>  
        </TabControl>  
    </Grid>  
</Border>  

C#代码:(此处不包含调整窗体大小的代码。具体请看上一篇文章的末尾)

// 窗体移动
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;
}
}

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

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

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

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

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

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

Xaml代码:


……

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”即可。

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章