WPF Window始末|快速了解、自定义Window控件

8 篇文章 0 订阅
5 篇文章 0 订阅

本文将介绍Window构成、基本属性、使用、以及如何自定义,提供详细的代码示例

概述

Window 类是 WPF 应用程序中用于显示和管理窗口的核心类。

主要功能:

  • 显示窗口:使用 Show 方法显示窗口。
  • 配置窗口:设置窗口的大小、位置和外观。
  • 托管内容:在窗口的客户端区域内添加应用程序特定的内容。
  • 管理生命周期:处理窗口的打开、关闭和其他生命周期事件。

窗口的组成部分

窗口分为两个区域:非工作区和工作区。

窗口的非工作区由 WPF实现,它包括大多数窗口所共有的窗口部分,包括:

  • 非工作区:包括标题栏、图标、最小化/最大化/关闭按钮、系统菜单和边框。
  • 工作区:用于添加应用程序特定的内容,如菜单栏、工具栏和控件。

下图展示了窗口的构成部分:

来自https://learn.microsoft.com/

  • 标题栏 (1-5)。
  • 图标 (1)。
  • 标题 (2)。
  • 最小化 (3)、最大化 (4) 和关闭 (5) 按钮。
  • 包含菜单项的系统菜单 (6)。 单击图标 (1) 时出现。
  • 边框 (7)。
  • 工作区 (8)。
  • 大小调整手柄 (9)。 这是添加到工作区 (8) 的控件。

窗口属性速览

1. 外观属性

  • Title:窗口的标题。
  • Icon:窗口的图标。
  • WindowStyle:窗口的样式(如单一边框、无边框等)
  1. WindowStyle.None
    在这里插入图片描述
    2.WindowStyle.SingleBorderWindow 、ThreeDBorderWindow
    在win11看不出区别
    在这里插入图片描述

3.WindowStyle.ToolWindow :不可最大化
在这里插入图片描述

  • ResizeMode:窗口的调整模式(如可调整大小、不可调整大小等)。

    • NoResize 无法调整大小
      在这里插入图片描述

    • Can Resize Mode Window
      在这里插入图片描述

    • Can Minimize Mode Window
      在这里插入图片描述

    • ResizeMode.CanResizeWithGrip
      在这里插入图片描述

    • AllowsTransparency:是否允许窗口透明。

      打开会消耗性能,一般用于异形窗口,设置为true时,必须设置WindowStyle = None,否则报错

2. 位置属性

  • Left:窗口左边缘相对于屏幕左边缘的距离。
  • Top:窗口上边缘相对于屏幕上边缘的距离。
  • WindowStartupLocation:窗口启动位置(如手动、屏幕中心等)。

3. 大小属性

  • Width:窗口的宽度。

  • Height:窗口的高度。

  • MinWidth:窗口的最小宽度。

  • MinHeight:窗口的最小高度。

  • MaxWidth:窗口的最大宽度。

  • MaxHeight:窗口的最大高度。

  • SizeToContent:窗口大小是否根据内容自动调整。

    4. 可见性属性

    • Visibility:窗口的可见性(如可见、隐藏等)。
    • ShowInTaskbar:窗口是否显示在任务栏中。
    • Topmost:窗口是否总是位于其他窗口之上。

5. 生命周期属性

  • 创建:使用 new 关键字创建窗口实例。
  • 显示:使用 ShowShowDialog 方法显示窗口。
  • 关闭:使用 Close 方法关闭窗口。
  • 事件处理
  • IsActive:窗口是否为活动窗口。
  • Activated:窗口激活事件。
  • Deactivated:窗口失活事件。
  • Closing:窗口关闭前事件。
  • Closed:窗口关闭后事件。

示例

using System.Windows;

namespace WpfApp
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.Loaded += MainWindow_Loaded;
            this.Activated += MainWindow_Activated;
            this.Deactivated += MainWindow_Deactivated;
            this.Closing += MainWindow_Closing;
            this.Closed += MainWindow_Closed;
        }

        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            // 窗口加载完成
        }

        private void MainWindow_Activated(object sender, EventArgs e)
        {
            // 窗口激活
        }

        private void MainWindow_Deactivated(object sender, EventArgs e)
        {
            // 窗口失去焦点
        }

        private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            // 窗口关闭前
        }

        private void MainWindow_Closed(object sender, EventArgs e)
        {
            // 窗口关闭后
        }
    }
}

窗口使用

创建

在 WPF 中,可以使用 XAML 和代码来实现窗口的外观和行为。通常,窗口的外观使用 XAML 标记实现,而行为使用代码隐藏实现。

示例

<Window x:Class="MyApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <!-- 在此处添加窗口的内容 -->
using System.Windows;

namespace MyApp
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

打开窗口

窗口的打开模式主要分为两种:模态窗口(Modal Window)和非模态窗口(Modeless Window)。

模态(Modal)这个词在编程和用户界面设计中,模态通常指的是一种状态或模式。

// 我想你应该听说过莫代尔棉*,就是这个单词Modal *

模态窗口

在这种模式下,打开模态窗口后,调用窗口会被锁定,无法进行其他操作。

用户必须完成某个特定任务或操作,才能返回到主程序或其他任务。

常见场景例如,打开一个文件对话框选择文件时,必须选择文件或取消对话框,才能返回到主窗口。

var window = new Margins();
window.Owner = this;
window.ShowDialog();

非模态窗口

非模态模式下,用户可以自由切换和操作其他窗口或任务,而不必先完成当前窗口的任务。

常见场景例如,打开一个工具窗口(如调色板或属性窗口)时,你可以继续操作主窗口,而不必先关闭工具窗口。

使用 Show 方法打开非模态窗口。以下是一个示例代码:

var window = new Margins();
window.Owner = this;
window.Show();

启动窗口

WPF 应用程序启动窗口的两种方式

  • XAML中快速启动

使用 StartupUri 来指定应用程序中 XAML 文件的路径。 应用程序将自动创建并显示由该属性指定的窗口。

<Application x:Class="WindowsOverview.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WindowsOverview"
             StartupUri="ClippedWindow.xaml">
    <Application.Resources>
    </Application.Resources>
</Application>
  • 后端代码中启动
<Application
    x:Class="WPFTopMost.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WPFTopMost"
    Startup="Application_Startup">
    <Application.Resources />
</Application>

using System.Configuration;
using System.Data;
using System.Windows;

namespace WPFTopMost
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        private void Application_Startup(object sender, StartupEventArgs e)
        {
            // 创建并显示主窗口
            MainWindow mainWindow = new MainWindow();
            mainWindow.Show();
        }
    }

}

激活窗口与防止激活

窗口激活

窗口激活是指将一个窗口设置为当前活动窗口,使其获得用户输入焦点。

使用 Activate 方法激活

Window window = new Window();
window.Show();
window.Activate(); // 激活窗口

**IsActive **属性 检查窗口是否为当前活动窗口。

if (window.IsActive)
{
    // 窗口是活动窗口
}

防止窗口激活

  1. 通知窗口:例如电子邮件或聊天应用程序的通知窗口,这些窗口弹出时不应打断用户当前的操作
  2. 工具提示或帮助窗口:这些窗口提供额外信息或帮助,但不应抢占用户的焦点
  3. 后台任务窗口:例如下载进度窗口或后台处理任务的状态窗口,这些窗口显示信息但不需要用户立即互动

使用 ShowActivated 属性

ShowActivated 属性用于控制窗口在显示时是否自动激活。

默认情况下,此属性为 true,表示窗口在显示时会自动激活。

如果将其设置为 false,则窗口在显示时不会自动激活。


        private void OpenWindowButton_Click(object sender, RoutedEventArgs e)
        {
            Window nonActiveWindow = new Window
            {
                Title = "非激活窗口",
                Width = 300,
                Height = 200,
                ShowInTaskbar = false,
                ShowActivated = false,
            };
            nonActiveWindow.Show();
            nonActiveWindow.Content = string.Format("非激活窗口,IsActive={0}.主窗口仍保持激活状态", nonActiveWindow.IsActive);
        }

        private void OpenActiveWindowButton_Click(object sender, RoutedEventArgs e)
        {
            Window activeWindow = new Window
            {
                Title = "激活窗口",
                Width = 300,
                Height = 200,
                ShowInTaskbar = false,
                ShowActivated = true,
            };
            activeWindow.Show();
            activeWindow.Content = string.Format("激活窗口,IsActive={0},主窗口变为激活状态", activeWindow.IsActive);
        }
  • 可以看到,当打开激活窗口,被打开的窗口状态为激活,主程序窗口状态变为未激活
    在这里插入图片描述

  • 当打开非激活窗口,被打开的窗口状态为未激活,主程序窗口状态保持为激活
    在这里插入图片描述

窗口所有权

窗口所有权(Window Ownership)是指一个窗口(子窗口)由另一个窗口(父窗口)拥有的关系。
通过设置窗口的 Owner 属性,可以建立这种所有权关系。

设置窗口所有权

使用 ShowDialog 方法

当使用 ShowDialog 方法打开子窗口时,父窗口和子窗口之间会自动建立所有权关系。此时,子窗口是模式窗口,用户必须关闭子窗口才能返回父窗口。

Window ownedWindow = new Window();
ownedWindow.Owner = this; // 设置所有者
ownedWindow.ShowDialog(); // 以模式窗口方式显示

使用 Show 方法

当使用 Show 方法打开子窗口时,需要手动设置 Owner 属性来建立所有权关系。

Window ownedWindow = new Window();
ownedWindow.Owner = this; // 设置所有者
ownedWindow.Show(); // 以非模式窗口方式显示

窗口所有权的行为

1 最小化行为

  • 如果所有者窗口最小化,则所有拥有的窗口也会最小化。
  • 如果拥有的窗口最小化,所有者窗口不会最小化。

2 关闭行为

  • 如果关闭所有者窗口,则所有拥有的窗口也会关闭。
  • 如果关闭拥有的窗口,所有者窗口不会关闭。

3 窗口层级

  • 所有者窗口永远不会被拥有的窗口覆盖。拥有的窗口总是在所有者窗口之上。

示例

主窗口代码

using System.Windows;

namespace WpfApp
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void OpenOwnedWindow_Click(object sender, RoutedEventArgs e)
        {
            Window ownedWindow = new Window();
            ownedWindow.Owner = this; // 设置所有者
            ownedWindow.Show(); // 显示子窗口
        }
    }
}

主窗口XAML

<Window x:Class="WpfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="200" Width="300">
    <StackPanel>
        <Button Content="打开子窗口" Click="OpenOwnedWindow_Click" Margin="10"/>
    </StackPanel>
</Window>

注意事项

  • 在浏览器中托管的窗口无法设置或获取 Owner 属性。
  • 如果使用 ShowDialog 打开子窗口,建议同时设置 Owner 属性,以确保正确的行为。

关闭/取消关闭窗口

关闭窗口

1. 使用 Close 方法

在 WPF 中,最常见的关闭窗口的方法是调用窗口的 Close 方法。这会触发窗口的 ClosingClosed 事件。

private void closeButton_Click(object sender, RoutedEventArgs e)
{
    this.Close();
}

2 使用 DialogResult 属性

对于模态窗口,可以通过设置 DialogResult 属性来关闭窗口。

private void okButton_Click(object sender, RoutedEventArgs e)
{
    this.DialogResult = true;
}

取消关闭窗口

1 订阅 Closing 事件

要取消关闭窗口,可以订阅窗口的 Closing 事件,并在事件处理程序中设置 CancelEventArgs.Canceltrue

public MainWindow()
{
    InitializeComponent();
    this.Closing += MainWindow_Closing;
}

private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
    if (MessageBox.Show("确定要关闭窗口吗?", "提示", MessageBoxButton.YesNo) == MessageBoxResult.No)
    {
        e.Cancel = true;
    }
}

2 隐藏窗口而非关闭

有时可能希望隐藏窗口而不是关闭它。可以在 Closing 事件中调用 Hide 方法。

private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
    e.Cancel = true;
    this.Hide();
}

其他关闭窗口的方法

1 使用 IsCancel 属性

可以将按钮的 IsCancel 属性设置为 true,以便按下 ESC 键时关闭窗口。这仅在使用 ShowDialog 方法打开窗口时有效。

private void CreateCancelButtonWindow_Click(object sender, RoutedEventArgs e)
{
    Window cancelButtonWindow = new Window
    {
        Title = "Cancel Button Window",
        Width = 300,
        Height = 200
    };

    Button cancelButton = new Button
    {
        Content = "Cancel",
        Width = 100,
        Height = 30,
        IsCancel = true,
        HorizontalAlignment = HorizontalAlignment.Center,
        VerticalAlignment = VerticalAlignment.Center
    };

    // 将取消按钮添加到窗口的内容中
    cancelButtonWindow.Content = cancelButton;

    // 显示窗口
    cancelButtonWindow.ShowDialog();
}

2 强制退出

即使有其他线程未结束,也可以使用 Application.Current.Shutdown 方法强制退出应用程序。

Application.Current.Shutdown();

自定义窗口

对窗口的样式进行自定义,主要是修改窗口的非工作区的样式——包括标题栏、图标、最小化/最大化/关闭按钮、系统菜单和边框。

通过设置WindowStyle=None,并重写标题栏、图标、最小化/最大化/关闭按钮、系统菜单和边框样式即可。

问题1:即便WindowStyle=None,还是存在一定的边框。

在这里插入图片描述
在之前的文章WPF 窗体增加置顶按钮Demo_wpf 弹出窗口 置顶-CSDN博客中,当时我误以为是因为设置了WindowStyle=None,导致不能调整窗口大小,我还重新实现了调整窗口大小的功能。

在后来才发现是因为我设置了AllowsTransparency =true 。而之所以会这样是因为:

AllowsTransparency 是为了方便创建非矩形窗口,因此,当AllowsTransparency 设置为true 时,非工作区将不起效果,所以需要自己实现。而且注意此时窗口的WindowStyle 属性必须设置为None。当启用透明度时,窗口的渲染需要处理透明像素,这与标准窗口的渲染机制不同,会增加渲染的复杂性和计算量。所以一般的窗口无需设置为True。

问题2:该如何去掉边框呢?那该如何实现高性能且自定义的Window样式呢?

使用WindowChrome

WindowChrome

允许开发者自定义这些非客户区的元素,从而实现更灵活和个性化的窗口设计。位于 System.Windows.Shell 命名空间中。

  • CaptionHeight: 标题栏的高度。
  • CornerRadius: 窗口角的圆角半径。
  • GlassFrameThickness: 玻璃框架的厚度。
  • ResizeBorderThickness: 可调整大小的边框厚度。
  • UseAeroCaptionButtons: 是否使用 Aero 风格的系统按钮。
何为 Chrome

在计算机图形学和用户界面设计中,“chrome”指的是应用程序窗口的非客户区部分。

这些部分包括标题栏、边框和系统按钮(如最小化、最大化和关闭按钮)。

这些元素通常不包含应用程序的主要内容,但它们提供了窗口的控制和装饰功能。

“Chrome”这个术语的使用源于其在用户界面中的装饰性和功能性作用,就像汽车的镀铬装饰一样,既美观又实用。

//一个奢侈品品牌——克罗心Chrome Hearts

示例

这里就以WPF 窗体增加置顶按钮Demo为例,将其封装为一个自定义控件。

效果

在这里插入图片描述

  • TopMostWindow.xaml
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
    xmlns:local="clr-namespace:WindowDemo">

    <Style TargetType="{x:Type local:TopMostWindow}">
        <Setter Property="WindowStyle" Value="None" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:TopMostWindow}">
                    <Border
                        BorderBrush="White"
                        BorderThickness="0"
                        CornerRadius="5">
                        <Grid
                            x:Name="PART_RootGrid"
                            Background="White">
                            <Grid.RowDefinitions>
                                <RowDefinition Height="30" />
                                <RowDefinition Height="*" />
                            </Grid.RowDefinitions>
                            <Border
                                Grid.Row="0"
                                Background="DarkGray"
                                CornerRadius="5">
                                <Grid>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="*" />
                                        <ColumnDefinition Width="Auto" />
                                        <ColumnDefinition Width="Auto" />
                                        <ColumnDefinition Width="Auto" />
                                        <ColumnDefinition Width="Auto" />
                                    </Grid.ColumnDefinitions>
                                    <TextBlock
                                        Grid.Column="0"
                                        Margin="10,0,0,0"
                                        VerticalAlignment="Center"
                                        Text="{TemplateBinding Title}" />
                                    <ToggleButton
                                        x:Name="PART_TopMostButton"
                                        Grid.Column="1"
                                        Width="30"
                                        Height="30"
                                        Content="📌">
                                        <i:Interaction.Triggers>
                                            <i:EventTrigger EventName="Checked">
                                                <i:InvokeCommandAction Command="{Binding TopMostWindowViewModel.TopMostCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TopMostWindow}}}" />
                                            </i:EventTrigger>
                                        </i:Interaction.Triggers>
                                    </ToggleButton>
                                    <Button
                                        Grid.Column="2"
                                        Width="30"
                                        Height="30"
                                        Command="{Binding MinimizeCommand}"
                                        Content="-">
                                        <i:Interaction.Triggers>
                                            <i:EventTrigger EventName="Click">
                                                <i:InvokeCommandAction Command="{Binding TopMostWindowViewModel.MinimizeCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TopMostWindow}}}" />
                                            </i:EventTrigger>
                                        </i:Interaction.Triggers>
                                    </Button>
                                    <Button
                                        Grid.Column="3"
                                        Width="30"
                                        Height="30"
                                        Command="{Binding MaximizeCommand}"
                                        Content="[]">
                                        <i:Interaction.Triggers>
                                            <i:EventTrigger EventName="Click">
                                                <i:InvokeCommandAction Command="{Binding TopMostWindowViewModel.MaximizeCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TopMostWindow}}}" />
                                            </i:EventTrigger>
                                        </i:Interaction.Triggers>
                                    </Button>
                                    <Button
                                        Grid.Column="4"
                                        Width="30"
                                        Height="30"
                                        Command="{Binding CloseCommand}"
                                        Content="X">
                                        <i:Interaction.Triggers>
                                            <i:EventTrigger EventName="Click">
                                                <i:InvokeCommandAction Command="{Binding TopMostWindowViewModel.CloseCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TopMostWindow}}}" />
                                            </i:EventTrigger>
                                        </i:Interaction.Triggers>
                                    </Button>
                                </Grid>
                            </Border>
                            <ContentPresenter Grid.Row="1" />
                            <i:Interaction.Triggers>
                                <i:EventTrigger EventName="MouseLeftButtonDown">
                                    <i:InvokeCommandAction Command="{Binding TopMostWindowViewModel.DragMoveCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TopMostWindow}}}" />
                                </i:EventTrigger>
                            </i:Interaction.Triggers>
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>


  • TopMostWindow.cs
using System.Windows;
using System.Windows.Shell;

namespace WindowDemo
{
    public class TopMostWindow : Window
    {
        static TopMostWindow()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(TopMostWindow), new FrameworkPropertyMetadata(typeof(TopMostWindow)));
        }
        public TopMostWindow()
        {
            this.Loaded += (sender, e) =>
            {
                TopMostWindowViewModel = new TopMostWindowViewModel(this);
                this.MaxHeight = SystemParameters.PrimaryScreenHeight;//防止最大化时系统任务栏被遮盖
            };

           WindowChrome.SetWindowChrome(this, new WindowChrome()
            {
                CaptionHeight = 0,
                UseAeroCaptionButtons = false,
                NonClientFrameEdges = NonClientFrameEdges.None
            });
        }

        public TopMostWindowViewModel TopMostWindowViewModel
        {
            get { return (TopMostWindowViewModel)GetValue(TopMostWindowViewModelProperty); }
            set { SetValue(TopMostWindowViewModelProperty, value); }
        }

        public static readonly DependencyProperty TopMostWindowViewModelProperty =
            DependencyProperty.Register("TopMostWindowViewModel", typeof(TopMostWindowViewModel), 
                typeof(TopMostWindow), new PropertyMetadata(null));
    }
}

  • ViewModel
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Windows;

namespace WindowDemo
{
    public partial class TopMostWindowViewModel: ObservableObject
    {
        public TopMostWindowViewModel(TopMostWindow window)
        {
            _window = window;
        }

        public TopMostWindow _window;

        [RelayCommand]
        private void Close()
        {
            if (_window == null) return;
            _window.Close();
        }

        [RelayCommand]
        private void Maximize()
        {
            if (_window == null) return;
            if (_window.WindowState == WindowState.Maximized)
                _window.WindowState = WindowState.Normal;
            else
                _window.WindowState = WindowState.Maximized;
        }

        [RelayCommand]
        private void Minimize()
        {
            if (_window == null) return;
            _window.WindowState = WindowState.Minimized;
        }

        [RelayCommand]
        private void DragMove()
        {
            if (_window == null) return;
            _window.DragMove();
        }

        [RelayCommand]
        private void TopMost()
        {
            if (_window == null) return;
            _window.Topmost = true;
        }
    }
}


参考资料

  • 24
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值