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

一、窗体的空间范围:

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

二、透明窗体:

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

<Window x:Class="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">
<Grid>
</Grid>
</Window>

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

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

三、添加阴影效果:

<Window x:Class="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"> <Border Margin="20" Background="White">
<Border.Effect>
<DropShadowEffect Direction="0" BlurRadius="20" ShadowDepth="0" Color="#FF585252" />
</Border.Effect>
</Border>
</Window>

 

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

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

四、添加功能:

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

Xaml代码:

<Window x:Class="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"> <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>
</Window> 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代码:
<!-- 设置最外层Border的Name属性 -->
<Border Name="bd" Margin="20" Background="White">
……
</Border> 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”即可。

WPF学习 - 自定义窗体(二)的更多相关文章

  1. WPF 之 自定义窗体标题栏

    在WPF中自定义窗体标题栏,首先需要将窗体的WindowStyle属性设置为None,隐藏掉WPF窗体的自带标题栏.然后可以在窗体内部自定义一个标题栏. 例如,标题栏如下: <WrapPanel ...

  2. WPF学习拾遗(二)TextBlock换行

    原文:WPF学习拾遗(二)TextBlock换行 下午在帮组里的同事解决一个小问题,为了以后方便,把就把它收集一下吧. 新建一个TextBlock作为最基础的一个控件,他所携带的功能相对于其他的控件要 ...

  3. WPF 创建自定义窗体

    在前面的一篇博客"WPF 自定义Metro Style窗体",展示了如何创建一个类似于Metro Style的Window,并在程序中使用.但是这个窗体不能够自由的改变大小.今天的 ...

  4. WPF设计の自定义窗体

    效果图如下: 实现思路: 1.继承Window类 2.为自定义的CustomWindow类设计窗体样式(使用Blend很方便!) 3.为窗体增加最大最小化和关闭按钮,并实现鼠标拖拽改变窗体大小(使用D ...

  5. WPF学习系列之二 (依赖项属性)

    依赖属性;(dependency property)  它是专门针对WPF创建的,但是WPF库中的依赖项属性都使用普通的.NET属性过程进行了包装.从而可能通过常规的方式使用它们,即使使用他们的代码不 ...

  6. WPF学习里程(二) XAML基础

    1.什么是XAML? 官方语言: XAML是eXtensible Application Markup Language的英文缩写,相应的中文名称为可扩展应用程序标记语言,它是微软公司为构建应用程序用 ...

  7. wpf学习

    http://www.jikexueyuan.com/course/1231_3.html?ss=1 WPF入门教程系列二——Application介绍 http://www.cnblogs.com/ ...

  8. WPF自定义控件与样式(13)-自定义窗体Window & 自适应内容大小消息框MessageBox

    一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. 本文主要内容: 自定义 ...

  9. WPF中自定义标题栏时窗体最大化处理之WindowChrome

    注意: 本文方法基础是WindowChrome,而WindowChrome在.NET Framework 4.5之后才集成发布的.见:WindowChrome Class 在.NET Framewor ...

  10. 【转】WPF自定义控件与样式(13)-自定义窗体Window & 自适应内容大小消息框MessageBox

    一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等. 本文主要内容: 自定义Window窗体样式: 基于自定义窗体实现自定义MessageB ...

随机推荐

  1. drf——登录功能、认证、权限、频率组件(Django转换器、配置文件作用)

    Django转换器.配置文件作用 # django转换器 2.x以后 为了取代re_path int path('books/<int:pk>')--->/books/1---> ...

  2. C++温故补缺(二十一):杂项补充2

    杂记2 explicit 在 C++ 中,explicit 是一个关键字,用于修饰类的构造函数,其作用是禁止编译器将一个参数构造函数用于隐式类型转换.具体来说,当一个构造函数被 explicit 修饰 ...

  3. Spring 核心概念之一 IoC

    前言 欢迎来到本篇文章!通过上一篇什么是 Spring?为什么学它?的学习,我们知道了 Spring 的基本概念,知道什么是 Spring,以及为什么学习 Spring.今天,这篇就来说说 Sprin ...

  4. [数据分析与可视化] Python绘制数据地图3-GeoPandas使用要点

    本文主要介绍GeoPandas的使用要点.GeoPandas是一个Python开源项目,旨在提供丰富而简单的地理空间数据处理接口.GeoPandas扩展了Pandas的数据类型,并使用matplotl ...

  5. 解决redis从服务器未配置主服务器密码导致数据未同步&磁盘饱满问题

    问题前置场景 本人前几天买了一台2核4G+40G磁盘空间的云服务器用来学习使用,在服务器上安装了docker.为了学习redis主从架构,使用docker-compose部署了一主二从三台redis服 ...

  6. 重新初始化k8s集群

    执行如下命令,所有节点都执行 kubeadm reset 初始化集群,仅在master(centos01)上执行 [root@centos01 opt]# kubeadm init --apiserv ...

  7. 基于DSP的设备振动信号的采集技术方案综述

    前记  在能源领域,由于很多地方都是无人值守,设备故障检测是一个必须面对的问题.笔者通过几个行业案例了解到,由于很多设备发生故障时候会产生特定频谱的声音,所以该行业对振动监测的需求特别强烈,由于涉及到 ...

  8. 大语言模型的开发利器langchain

    目录 简介 什么是langchain langchain的安装 langchain快速使用 构建应用 聊天模式 Prompt的模板 Chains Agents Memory 总结 简介 最近随着cha ...

  9. Linux 图形栈从入门到放弃 --- Linux 图形相关概念简介

    PS:要转载请注明出处,本人版权所有. PS: 这个只是基于<我自己>的理解, 如果和你的原则及想法相冲突,请谅解,勿喷. 环境说明   无 前言   在日常生活中,像我们常用的ubunt ...

  10. 【笔试实战】LeetCode题单刷题-编程基础 0 到 1【三】

    682. 棒球比赛 题目链接 682. 棒球比赛 题目描述 你现在是一场采用特殊赛制棒球比赛的记录员.这场比赛由若干回合组成,过去几回合的得分可能会影响以后几回合的得分. 比赛开始时,记录是空白的.你 ...