WPF制作无边框窗体、圆角窗体、支持改变大小、拖动分屏等(一)

概述

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

### 回答1: WPF(Windows Presentation Foundation)是一种用于创建Windows上交互式应用程序的框架,它支持窗体和页面的开发。在WPF中,我们可以让页面随窗体大小变化而自适应。 首先,我们需要将页面的布局定义为相对布局(Relative Layout),这样页面中的元素将根据相对位置和比例来排列。可以使用Grid控件或其他容器控件来实现。 其次,我们可以使用控件的布局属性和绑定来实现页面元素的自适应。比如,可以使用HorizontalAlignment和VerticalAlignment属性来控制元素在页面中的水平和垂直位置;使用Width和Height属性来控制元素的大小;使用Margin属性来设置元素与页面边界的距离等等。 另外,我们还可以使用响应式设计的思想来实现页面的自适应。通过在页面中定义样式和触发器,我们可以根据窗体大小改变样式或重新布局页面元素。可以使用VisualStateManager和VisualState来定义不同的视觉状态,并在窗体大小变化时自动切换。 最后,为了确保页面随窗体大小变化而自适应,我们还可以使用事件或命令来处理窗体大小变化的通知。WPF提供了SizeChanged事件,我们可以在事件处理程序中更新页面布局或元素的大小。 总结来说,WPF的页面可以通过相对布局、布局属性和绑定、响应式设计以及事件处理来实现随窗体大小变化而自适应。这样可以确保应用程序在不同的窗体大小下都能提供良好的用户体验。 ### 回答2: WPF页面(Page)随窗体大小的变化可以通过使用WPF自带的布局容器来实现。WPF提供了多种布局容器,常用的有Grid、StackPanel和DockPanel等。 使用Grid布局容器是一种常见的做法。在Grid中,可以将页面的各个元素放置在不同的行和列中,设置宽度和高度的比例或者绝对值等。当窗体大小变化时,Grid会根据行和列的定义自动调整元素的大小和位置,从而实现页面随窗体大小的变化。 另一种常见的做法是使用StackPanel布局容器。StackPanel会根据方向(水平或垂直)将页面的元素一次排列在一起。当窗体大小变化时,StackPanel会自动调整元素的大小和位置,保持元素的排列顺序不变,并根据需要将元素放置在新的行或列中。 DockPanel布局容器是将页面的元素放置在不同的方位(上、下、左、右和中间)中。通过设置元素在DockPanel中的Dock属性,可以确定元素相对于DockPanel的位置。当窗体大小变化时,DockPanel会自动调整元素的大小和位置,保持元素的位置不变。 总之,通过合理使用WPF的布局容器,可以实现WPF页面随窗体大小的变化。无论是使用Grid、StackPanel还是DockPanel等布局容器,都需要根据页面的组成和需要来选择合适的布局容器,并设置元素的大小和位置,以实现页面随窗体大小的变化。 ### 回答3: WPF(Windows Presentation Foundation)是一种用于创建具有丰富用户界面的应用程序的技术。在WPF中,Page是一种用来构建独立页面的控件。当Page随窗体大小变化时,可以采用以下方法进行处理: 1. 动态布局:在设计Page时,可以使用WPF的布局控件(如Grid、StackPanel等)来对页面中的元素进行布局。通过设置不同的布局属性,例如HorizontalAlignment和VerticalAlignment等,可以使页面中的元素根据窗体大小自动调整其位置和大小。 2. 控件的自适应:使用WPF的控件,例如TextBox、Button等,在其属性中可以设置一些适应性的特性,例如Stretch、MinHeight、MinWidth等。通过设置这些属性,可以使控件在窗体大小变化时自动调整自身的大小和显示。 3. 响应窗体大小变化的事件:WPF中的Page可以订阅窗体大小变化事件,例如SizeChanged事件。通过处理该事件,可以在窗体大小变化时执行一些特定的操作,例如重新计算页面中元素的布局、刷新页面内容等。 总结起来,要实现Page随窗体大小变化,可以采用动态布局、控件的自适应和处理窗体大小变化事件等方法。这些方法可以使页面中的元素根据窗体大小自动调整其位置和大小,从而适应不同大小窗体
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

若汝棋茗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值