概述
Windows Presentation Foundation (WPF) 是一个可创建桌面客户端应用程序的 UI 框架。 WPF 开发平台支持广泛的应用开发功能,包括应用模型、资源、控件、图形、布局、数据绑定、文档和安全性。 此框架是 .NET Framework 的一部分,因此,在未来如果想快速的开发桌面软件,WPF腚是你的不二选择。
需求说明
WPF既然是桌面软件UI,那一定绕不开桌面软件的多元化(chan pin jing li de chou pi)显示方式,最常见的就是无边框窗体
、圆角窗体
、窗体阴影
等,同时还需要支持改变窗口大小
、响应拖动分屏
等。本文也会从这几个需求入手,打造一个让各位猿友满意的窗体。
开整(分割线搞个仪式)
制作无边框窗体
思路: 无边框窗体比较好办,思路就是设置窗体属性WindowStyle为None,这时无边框窗体就创建好了,而且支持改变尺寸,但是为了普适性,需要对窗体进行装饰,比如:窗体拖动、拖动分屏、窗体功能按钮、窗体是否支持改变大小等,这些都需要代码参与。
窗体拖动、拖动分屏
拖动操作一般是在窗体头操作的,所以需要对窗体进行Grid分行。然后在title的Grid里注册鼠标移动事件,然后在后台控制窗体拖动(title的Grid背景色必须赋值,哪怕是透明色,不然不会命中测试)。实际上在调用DragMove方法时,窗体就已经支持了左右分屏以及全屏,但是问题就是:全屏是满屏,把任务栏也遮住了,其次满屏后不能再拖动为正常尺寸。
<Window x:Class="WpfApp1.Window1"
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:WpfApp1"
mc:Ignorable="d"
WindowStyle="None"
Title="Window1" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid x:Name="title" Background="Transparent" MouseMove="title_MouseMove">
</Grid>
<Grid x:Name="body" Grid.Row="1">
</Grid>
</Grid>
</Window>
namespace WpfApp1
{
/// <summary>
/// Window1.xaml 的交互逻辑
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void title_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton== MouseButtonState.Pressed)
{
this.DragMove();
}
}
}
}
解决拖动问题
(1)首先就是最大化遮挡任务栏问题,可以通过重写OnStateChanged事件,在最大化时设置窗体边框以及最大允许尺寸(最大尺寸由工作区域+16组成,16的数字来源于经验),但是由于该事件需要激活,所以需要在Loaded空调用一次。
namespace WpfApp1
{
/// <summary>
/// Window1.xaml 的交互逻辑
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.Loaded += this.Window1_Loaded;
}
private void Window1_Loaded(object sender, RoutedEventArgs e)
{
OnStateChanged(null);
}
private void title_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton== MouseButtonState.Pressed)
{
this.DragMove();
}
}
protected override void OnStateChanged(EventArgs e)
{
switch (this.WindowState)
{
case WindowState.Maximized:
this.MaxWidth = SystemParameters.WorkArea.Width + 16;
this.MaxHeight = SystemParameters.WorkArea.Height + 16;
this.BorderThickness = new Thickness(5); //最大化后需要调整
break;
case WindowState.Normal:
this.BorderThickness = new Thickness(0);
this.MaxWidth = SystemParameters.WorkArea.Width + 16;
this.MaxHeight = SystemParameters.WorkArea.Height + 16;
break;
}
}
}
}
(2)其次解决最大化时拖动问题,我们可以考虑在最大化时拖动时,先设置WindowState为Normal,但是直接这样做会导致鼠标和拖动点之间存在很大间距,所以还需要设置窗体位置。
namespace WpfApp1
{
/// <summary>
/// Window1.xaml 的交互逻辑
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.Loaded += this.Window1_Loaded;
}
private void Window1_Loaded(object sender, RoutedEventArgs e)
{
OnStateChanged(null);
}
private void title_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
if (this.WindowState == WindowState.Maximized)
{
WindowState = WindowState.Normal;
Point point = e.MouseDevice.GetPosition(this);
Left = point.X - this.title.ActualWidth * point.X / SystemParameters.WorkArea.Width;
Top = point.Y - this.title.ActualHeight * point.Y / SystemParameters.WorkArea.Height;
}
this.DragMove();
}
}
protected override void OnStateChanged(EventArgs e)
{
switch (this.WindowState)
{
case WindowState.Maximized:
this.MaxWidth = SystemParameters.WorkArea.Width + 16;
this.MaxHeight = SystemParameters.WorkArea.Height + 16;
this.BorderThickness = new Thickness(5); //最大化后需要调整
break;
case WindowState.Normal:
this.BorderThickness = new Thickness(0);
this.MaxWidth = SystemParameters.WorkArea.Width + 16;
this.MaxHeight = SystemParameters.WorkArea.Height + 16;
break;
}
}
}
}
窗体功能按钮
窗体的功能按钮是控制窗体的最重要控件。一般的有最小化、正常化切换、最大化三种功能按钮。但是不能直接在title里面直接部署这三个按钮,因为这样的话按钮也会响应拖动命令,这是我们所不希望的。所以必须重新对窗体头布局。然后对按钮注册click事件。
<Window x:Class="WpfApp1.Window1"
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:WpfApp1"
mc:Ignorable="d"
WindowStyle="None"
Title="Window1" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="90"/>
</Grid.ColumnDefinitions>
<Grid x:Name="title" Background="Transparent" MouseMove="title_MouseMove">
</Grid>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<Button Content="一" Width="30" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Click="Button_Click"/>
<Button Content="口" Width="30" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Click="Button_Click_1"/>
<Button Content="X" Width="30" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Click="Button_Click_2"/>
</StackPanel>
</Grid>
<Grid x:Name="body" Grid.Row="1">
</Grid>
</Grid>
</Window>
namespace WpfApp1
{
/// <summary>
/// Window1.xaml 的交互逻辑
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.Loaded += this.Window1_Loaded;
}
private void Window1_Loaded(object sender, RoutedEventArgs e)
{
OnStateChanged(null);
}
private void title_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
if (this.WindowState == WindowState.Maximized)
{
WindowState = WindowState.Normal;
Point point = e.MouseDevice.GetPosition(this);
Left = point.X - this.title.ActualWidth * point.X / SystemParameters.WorkArea.Width;
Top = point.Y - this.title.ActualHeight * point.Y / SystemParameters.WorkArea.Height;
}
this.DragMove();
}
}
protected override void OnStateChanged(EventArgs e)
{
switch (this.WindowState)
{
case WindowState.Maximized:
this.MaxWidth = SystemParameters.WorkArea.Width + 16;
this.MaxHeight = SystemParameters.WorkArea.Height + 16;
this.BorderThickness = new Thickness(5); //最大化后需要调整
break;
case WindowState.Normal:
this.BorderThickness = new Thickness(0);
this.MaxWidth = SystemParameters.WorkArea.Width + 16;
this.MaxHeight = SystemParameters.WorkArea.Height + 16;
break;
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.WindowState = WindowState.Minimized;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
if (this.WindowState == WindowState.Normal)
{
this.WindowState = WindowState.Maximized;
}
else
{
this.WindowState = WindowState.Normal;
}
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
this.Close();
}
}
}
使用RRQMSkin创建无边框窗体
实际上以上代码只能实现比较简单的功能,并不完全具备普适性,所以无边框窗体已经被本猿封装到了RRQMSkin中,如果大家想直接使用的话直接在Nuget搜索RRQMSkin即可,具体操作如下:
添加好引用后,在主窗体替换继承类为RRQMWindow即可(包括后台代码)。
<window:RRQMWindow x:Class="WpfApp1.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:local="clr-namespace:WpfApp1"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:window="若汝棋茗_Windows"
Height="450"
Width="800"
mc:Ignorable="d">
<Grid />
</window:RRQMWindow>
namespace WpfApp1
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : RRQMSkin.Windows.RRQMWindow
{
public MainWindow()
{
InitializeComponent();
}
}
}
结束
OK,一个无边框窗体就创建完成了,下节说创建圆角窗体。
最后希望这篇博客能帮到各位猿友,当然如果还有什么不明白的,可以私信本猿哦,QQ群:234762506