现代桌面UI框架科普及WPF入门
文章目录
- 现代桌面UI框架科普及WPF入门
- 桌面应用程序框架介绍
- 过时的UI框架
- MFC (Microsoft Foundation Class)
- 缺点
- 经典的UI框架
- **WinForms**
- **QT**
- **WPF**
- 未来的UI框架
- **MAUI**
- **AvaloniaUI**
- WPF相对于Winform,QT,MFC的独立优势
- WPF的基本概念
- WPF 编程模型
- XAML
- 依赖项和附加属性
- 实操
- 创建项目
- 关键字说明
- 尝试添加控件
- 尝试添加响应事件
- 数据绑定
- 模板
- 丰富的开源UI库支持
桌面应用程序框架介绍
过时的UI框架
MFC
语言:C++
MFC (Microsoft Foundation Class)
以C++类的形式封装了Windows API,并且包含一个应用程序框架。类中包含了大量的windows句柄封装类和很多windows的组件和内建控件的封装类。MFC把Windows SDK API函数包装成了几百个类,MFC给Windows系统提供面向对象的接口。
缺点
- 技术老化:MFC是一个非常老的框架,最早发布于1992年。随着时间的推移,新的技术和框架不断涌现,MFC显得过时
- 开发效率低:相比于现代的开发框架,MFC的开发效率较低。它基于C++,需要编写大量的代码来实现一些基本功能,而现代框架如WPF和WinForms则提供了更高层次的抽象和更丰富的控件库
经典的UI框架
WinForms
语言:C#
WinForms (Windows Forms)
微软在2001年随NET Framework和Visual Studio首次发布的图形用户界面(GUI)类库。它的设计初衷是简化Windows桌面应用程序的开发,使企业开发者无需精通C++也能创建数据驱动的业务应用²。
主要特点
- 易用性:WinForms提供了大量的控件和事件处理机制,简化了用户界面开发。
缺点
-
技术老化:WinForms最早发布于2001年,虽然在当时是一个非常先进的技术,但随着时间的推移,新的技术和框架不断涌现,WinForms显得过时
-
功能限制:相比于现代的框架如WPF和UWP,WinForms在功能和灵活性上存在一定的限制。例如,WinForms在处理复杂的UI和动画效果时显得力不从心
-
跨平台支持不足:WinForms主要用于Windows平台,而现代应用程序往往需要跨平台支持。虽然通过Mono项目可以在其他操作系统上运行WinForms,但其跨平台能力仍然有限
QT
语言:C++
Qt最早由挪威的两位程序员Eirik Chambe-Eng和Haavard Nord于1991年开发。Qt的第一个公众预览版于1995年发布。
应用领域
Qt作为一个跨平台的C++应用程序开发框架,广泛应用于以下领域:
- 桌面应用:支持Windows、macOS和Linux等操作系统,适合开发复杂的桌面应用程序。
- 移动应用:支持Android和iOS,适合开发高性能的移动应用。
- 嵌入式系统:在汽车、医疗设备和工业控制等领域有广泛应用。
- 物联网:在智能家居、智能制造等物联网领域崭露头角。
WPF
语言:C#
Windows Presentation Foundation (WPF) 是由微软开发的用户界面框架,最早发布于2006年,作为 .NET Framework 3.0 的一部分。WPF 的设计初衷是提供一个现代化的、基于矢量图形的渲染引擎,能够充分利用现代图形硬件的加速能力。2018年,微软将WPF开源,并在GitHub上发布,采用MIT许可证。
应用领域
WPF广泛应用于各种Windows桌面应用程序的开发,以下是一些主要的应用领域:
- 企业级应用:WPF在企业级应用开发中非常流行,特别是需要复杂用户界面和数据绑定的应用。
- 多媒体应用:由于其强大的图形和动画支持,WPF适合开发多媒体应用,如视频播放器和图形编辑器。
- 数据可视化:WPF的矢量图形和数据绑定功能使其非常适合用于数据可视化和仪表盘应用。
- 教育和培训软件:WPF的动画和多媒体功能使其成为开发教育和培训软件的理想选择。
WPF的强大功能和灵活性使其成为开发现代化Windows应用程序的首选框架之一。
未来的UI框架
MAUI
语言:C#
MAUI (Multi-platform App UI)
- 功能:支持iOS、Android、macOS和Windows平台,使用单一代码库构建原生用户界面
- 特点:利用每个平台的原生UI工具包,确保应用在各平台上都有原生的外观和感觉
AvaloniaUI
语言:C#
- 功能:支持Windows、macOS、Linux、WebAssembly等平台,使用XAML进行界面设计
- 特点:使用Skia图形引擎进行自定义渲染,提供一致的跨平台用户界面
WPF相对于Winform,QT,MFC的独立优势
- XAML:XAML 是 WPF 的标记语言,它是一种基于 XML 的标记语言,用于定义应用程序的用户界面。XAML 允许开发人员以声明方式创建 UI 元素,并使用数据绑定、命令、样式和模板等特性来实现动态 UI。
- 依赖项和附加属性:依赖项属性是 WPF 的一种属性系统,它允许控件和其他元素通过属性来进行通信,并提供一种简单、一致的属性系统。
- 样式和模板:样式和模板是 WPF 的可视化机制,它们允许开发人员创建一致的外观和感觉。样式可以应用到控件、应用程序、窗口或整个应用程序的范围内,而模板可以应用到控件的各个部分。
- 命令:命令是 WPF 的交互模型,它允许开发人员创建可重用的交互逻辑,并与控件、路由事件和数据绑定相结合。
WPF的基本概念
Windows Presentation Foundation (WPF) 是微软开发的一种用于构建 Windows 桌面应用程序的框架。它提供了丰富的图形功能、数据绑定和自定义控件等特性。以下是 WPF 的一些基本概念:
- 矢量图形引擎:WPF 的核心是一个与分辨率无关且基于矢量的呈现引擎,旨在充分利用现代图形硬件。
- XAML:可扩展应用程序标记语言 (XAML) 是一种基于 XML 的标记语言,用于定义应用程序的用户界面。XAML 允许开发人员以声明方式创建 UI 元素。
- 数据绑定:WPF 提供了强大的数据绑定功能,可以轻松地将 UI 元素与数据源连接起来,实现数据的动态更新。
- 控件:WPF 包含一套丰富的控件库,如按钮、文本框、列表框等,开发人员可以使用这些控件构建复杂的用户界面。
- 布局:WPF 提供了多种布局容器,如 Grid、StackPanel 和 Canvas,帮助开发人员灵活地安排 UI 元素的位置和大小。
- 动画和图形:WPF 支持二维和三维图形,以及动画效果,使得应用程序的界面更加生动。
WPF 编程模型
WPF 编程模型包含以下主要组件:
- 应用程序对象:应用程序对象是 WPF 应用程序的入口点,它负责创建应用程序的主窗口、资源、路由事件和其他应用程序级的设置。
- 窗口对象:窗口对象是 WPF 应用程序的主要 UI 容器,它包含应用程序的主要 UI 元素,如菜单、工具栏、状态栏、标题栏、内容区域等。
- 控件:控件是 WPF 应用程序的主要 UI 元素,它们提供丰富的功能和可视化效果,如按钮、文本框、列表框、菜单、对话框等。
- 资源:资源是 WPF 应用程序的外部数据,如颜色、字体、图片、样式、数据模板等。
- 路由事件:路由事件是 WPF 应用程序的事件模型,它允许控件和应用程序对象之间进行通信,并提供一种简单、一致的事件处理机制。
- 数据绑定:数据绑定是 WPF 应用程序的核心功能,它允许开发人员将 UI 元素与数据源绑定起来,实现数据的动态更新。
- 样式和模板:样式和模板是 WPF 应用程序的可视化机制,它们允许开发人员创建一致的外观和感觉。
- 命令:命令是 WPF 应用程序的交互模型,它允许开发人员创建可重用的交互逻辑,并与控件、路由事件和数据绑定相结合。
XAML
语法:xml
<Window
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"
Title="MainWindow"
Width="800"
Height="450"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<Grid>
<Button Content="Click Me" />
</Grid>
</Window>
依赖项和附加属性
依赖属性:
为什么要有依赖属性?
The main difference is, that the value of a normal .NET property is read directly from a private member in your class, whereas the value of a DependencyProperty is resolved dynamically when calling the GetValue() method that is inherited from DependencyObject.
When you set a value of a dependency property it is not stored in a field of your object, but in a dictionary of keys and values provided by the base class DependencyObject. The key of an entry is the name of the property and the value is the value you want to set.
The advantages of dependency properties are
Reduced memory footprint:
It’s a huge dissipation to store a field for each property when you think that over 90% of the properties of a UI control typically stay at its initial values. Dependency properties solve these problems by only store modified properties in the instance. The default values are stored once within the dependency property.
Value inheritance:
When you access a dependency property the value is resolved by using a value resolution strategy. If no local value is set, the dependency property navigates up the logical tree until it finds a value. When you set the FontSize on the root element it applies to all textblocks below except you override the value.
Change notification:
Dependency properties have a built-in change notification mechanism. By registering a callback in the property metadata you get notified, when the value of the property has been changed. This is also used by the databinding.
check the below url for more details about the magic behid it
大意:依赖属性是一种特殊的属性,它的值是动态计算的,而不是直接存储在对象实例的字段中。依赖属性的值存储在依赖对象基类 DependencyObject
提供的字典中,字典的键是属性的名称,值是要设置的值。依赖属性的优点有:
- 内存占用减少:依赖属性仅存储修改过的值,而不是每个属性都存储一个字段。默认值只存储一次,而不是每个实例都存储。
- 值继承:当访问依赖属性时,值是根据值解析策略进行计算的。如果没有本地值,则依赖属性沿着逻辑树向上搜索,直到找到值。当在根元素上设置
FontSize
时,它将应用于所有文本块,除非您覆盖值。 - 通知更改:依赖属性具有内置的更改通知机制。通过在属性元数据中注册回调,可以获得有关属性值更改的通知。这也用于数据绑定。
具体参考:stackoverflow
主要作用:
- 可以在代码或 XAML 中设置属性。
<Button Content="I am red" Background="Red"/>
或者设置复杂的属性值
<Button Content="I have an image background">
<Button.Background>
<ImageBrush ImageSource="stripes.jpg"/>
</Button.Background>
</Button>
在代码中设置
Button myButton = new();
myButton.Width = 200.0;
- 设置资源
<StackPanel.Resources>
<SolidColorBrush x:Key="MyBrush" Color="Gold"/>
</StackPanel.Resources>
- 绑定数据
<Button Content="{Binding text}"/>
4.绑定样式
<Style x:Key="GreenButtonStyle">
<Setter Property="Control.Background" Value="Green"/>
</Style>
使用
<Button Style="{StaticResource GreenButtonStyle}" Content="I am green"/>
附加属性:
附加属性是一个 Extensible Application Markup Language (XAML) 概念。 附加属性允许为派生自 DependencyObject 的任何 XAML 元素设置额外的属性/值对,即使该元素未在其对象模型中定义这些额外的属性。 额外的属性可进行全局访问。 附加属性通常定义为没有常规属性包装器的依赖属性的专用形式。
附加属性允许子元素为父元素中定义的属性指定唯一值。 一个常见方案是,一个子元素指定它应如何被其父元素呈现在 UI 中。 例如,DockPanel.Dock
是一个附加属性,因为它在 DockPanel
的子元素上设置,而不是在 DockPanel
本身设置。 DockPanel
类定义名为 DockProperty
的静态 DependencyProperty
字段,然后提供 GetDock
和 SetDock
方法作为附加属性的公共访问器。
<DockPanel>
<TextBox DockPanel.Dock="Top">Enter text</TextBox>
</DockPanel>
实操
创建项目
关于Net Framework和Net Core、Net
[详情](.NET Standard - .NET | Microsoft Learn)
运行平台 | 语言版本 | 支持平台 | 著名框架 |
---|---|---|---|
Windows | C# 7.3 | .NET Framework | WPF,WinForms,ASP.NET |
Windows,macOS,Linux | C# 8+ | .NET Core | WPF,WinForms,ASP.NET Core |
Windows,macOS,Linux | C# 9+ | .NET 5,6,7,8,9 | WPF,WinForms,MAUI,Uno Platform,Avalonia UI, ASP.Net Core |
MainWindow.xaml
:
<Window
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"
Title="MainWindow"
Width="800"
Height="450"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<Grid>
</Grid>
</Window>
作用:前端文件,定义了窗口的基本结构,包括窗口的大小、位置、标题、背景色等。
MainWindow.xaml.cs
:
namespace WpfApp1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
作用:后端文件,定义了窗口的行为,包括按钮的点击事件、文本框的输入事件等。
App.xaml
:
<Application
x:Class="WpfApp1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
作用:定义了应用程序的基本结构,包括资源、主题等。
App.xaml.cs
:
namespace WpfApp1
{
public partial class App : Application
{
}
}
作用:定义了应用程序的行为,包括启动事件等。
关键字说明
xmlns
:XML 命名空间,用于定义 XML 文档中的元素、属性和指令。
xmlns:x
:XAML 命名空间,用于定义 XAML 文档中的元素、属性和指令。
xmlns:d
:Blend 命名空间,用于定义 Blend 文档中的元素、属性和指令。
xmlns:local
:本地命名空间,用于定义当前文档中的元素、属性和指令。
xmlns:mc
:标记兼容性命名空间,用于定义标记兼容性文档中的元素、属性和指令。
x:Class
:XAML 类,用于指定当前 XAML 文件的类。
mc:Ignorable
:标记兼容性忽略,用于指定标记兼容性文档中可以忽略的元素。
尝试添加控件
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".3*" />
<ColumnDefinition Width=".7*" />
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Text="UserName" TextAlignment="Right" />
<TextBox Grid.Column="1" Margin="25,0,0,0" />
</Grid>
<Grid Grid.Row="1" Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".3*" />
<ColumnDefinition Width=".7*" />
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Text="Password" TextAlignment="Right" />
<TextBox Grid.Column="1" Margin="25,0,0,0" />
</Grid>
<Button Grid.Row="2" Margin="5,10" Content="Login" />
</Grid>
尝试添加响应事件
为了实现登录功能,我们需要在按钮的 Click
事件中编写代码。
前台代码
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".3*" />
<ColumnDefinition Width=".7*" />
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Text="UserName" TextAlignment="Right" />
<TextBox x:Name="TextBoxUserName" Grid.Column="1" Margin="25,0,0,0" />
</Grid>
<Grid Grid.Row="1" Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".3*" />
<ColumnDefinition Width=".7*" />
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Text="Password" TextAlignment="Right" />
<TextBox x:Name="TextBoxPassword" Grid.Column="1" Margin="25,0,0,0" />
</Grid>
<Button
x:Name="ButtonLogin"
Grid.Row="2"
Margin="5,10"
Click="ButtonLogin_Click"
Content="Login" />
</Grid>
后台代码
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void ButtonLogin_Click(object sender, RoutedEventArgs e)
{
var username = TextBoxUserName.Text;
var password = TextBoxPassword.Text;
if (username == "admin" && password == "password")
{
MessageBox.Show("Login successful!");
}
else
{
MessageBox.Show("Invalid username or password!");
}
}
}
数据绑定
数据绑定是 WPF 应用程序的重要特性之一,它允许开发人员在 UI 元素和数据源之间建立双向绑定。
为什么要数据绑定
请问在Winform或者QT中如何实现以下效果?
假设存在MinValue
,MaxValue
两个值,分别代表最小值和最大值,现在需要实现2个滑动条以及2个输入框分别对应2个值,并且当最小值超过最大值时,可以自动调整最大值,反之亦然。
如果在此基础上,再加上MinRange
和MaxRange
的设置,不使用现代UI框架的阁下又该如何是好?
使用WPF后,我们只需要关心业务,而不用像Winform或者QT一样频繁关注控件的ValueChanged
事件,以及各个事件的互锁
<StackPanel Orientation="Vertical">
<materialDesign:NumericUpDown Margin="5" Maximum="{Binding MaxRange}" Minimum="{Binding MinRange}" Value="{Binding MinValue, Mode=TwoWay}" />
<Slider Margin="5" Maximum="{Binding MaxRange}" Minimum="{Binding MinRange}" Value="{Binding MinValue, Mode=TwoWay}" />
<materialDesign:NumericUpDown Margin="5" Maximum="{Binding MaxRange}" Minimum="{Binding MinRange}" Value="{Binding MaxValue, Mode=TwoWay}" />
<Slider Margin="5" Maximum="{Binding MaxRange}" Minimum="{Binding MinRange}" Value="{Binding MaxValue, Mode=TwoWay}" />
<Separator Margin="5" />
<TextBlock Margin="5,5" Text="最小值设置" />
<materialDesign:NumericUpDown Margin="5,0" Maximum="9999" Minimum="-9999" Value="{Binding MinRange}" />
<TextBlock Margin="5,0" Text="最大值设置" />
<materialDesign:NumericUpDown Margin="5,0" Maximum="9999" Minimum="-9999" Value="{Binding MaxRange}" />
</StackPanel>
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
private int minValue = 50;
public int MinValue
{
get { return minValue; }
set
{
minValue = value;
RaisePropertyChanged();
if (value > maxValue)
MaxValue = value;
}
}
private int maxValue = 128;
public int MaxValue
{
get => maxValue;
set
{
maxValue = value;
RaisePropertyChanged();
if (value < minValue)
MinValue = value;
}
}
private int maxRange = 255;
public int MaxRange
{
get => maxRange;
set
{
maxRange = value;
RaisePropertyChanged();
if (MaxValue < value)
MaxValue = value;
}
}
private int minRange = 0;
public int MinRange
{
get => minRange;
set
{
minRange = value;
RaisePropertyChanged();
if (MinValue < value)
MinValue = value;
}
}
void RaisePropertyChanged(
[System.Runtime.CompilerServices.CallerMemberName] string propertyName = ""
) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
模板
DataTemplate
:用于定义数据的显示方式。
如何使用Winform实现以下表格?
复杂程度不在赘述
public List<Student> Students { get; set; } = new();
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
public string Address { get; set; }
public bool IsMale { get; set; }
}
前端部分
<DataGrid ItemsSource="{Binding Students}" />
甚至是自动适配枚举
public enum EClassType
{
kindergarten,
Elementary,
Junior,
Senior,
University
}
如果使用了第三方库
丰富的开源UI库支持