基于C#语言的GUI开发,主要介绍WPF框架
框架
简介
下面是几种常见基于.Net的界面开发框架,都是Microsoft提供,下面简介由GPT生成
WinForms (Windows Forms
WPF (Windows Presentation Foundation)
UWP (Universal Windows Platform)
MAUI (Multi-platform App UI)
- WinForms(Windows Forms):
- 介绍: WinForms是由Microsoft提供的用于开发Windows桌面应用程序的UI框架。它使用简单的拖放界面进行用户界面的构建,并基于.NET框架。
- 主要特点:
- 使用可视化设计器进行快速应用程序开发(RAD)。
- 事件驱动的编程模型。
- 与Windows API直接集成。
- WPF(Windows Presentation Foundation):
- 介绍: WPF是用于构建具有丰富用户界面的Windows桌面应用程序的框架。与WinForms相比,它提供了更现代和灵活的UI开发方法。
- 主要特点:
- 强大的数据绑定和模板系统。
- 分辨率独立的矢量图形。
- 可扩展和可定制的控件。
- UI和业务逻辑的分离。
- UWP(Universal Windows Platform):
- 介绍: UWP是由Microsoft引入的平台,用于创建可以在各种Windows设备上运行的通用应用程序,包括PC、平板、手机、Xbox、HoloLens等。它是Windows 10生态系统的一部分。
- 主要特点:
- 自适应用户界面,适应不同设备形状。
- 安全性和性能改进。
- 与Windows 10功能的集成,如Cortana和Live Tiles。
- MAUI(Multi-platform App UI):
- 介绍: MAUI是由Microsoft推出的现代跨平台框架,用于从单一代码库构建适用于iOS、Android、Windows和macOS的本机应用程序。它是Xamarin.Forms的演进,是.NET MAUI堆栈的一部分。
- 主要特点:
- 单一代码库适用于多个平台。
- 支持每个平台的本机UI控件。
- 与.NET 6及更高版本集成。
- 面向各种设备,包括桌面和移动设备。
选择
winform过于古早
uwp的话,我们就用windows电脑,没必要
maui因为跨平台,导致了其臃肿
所以我们选择WPF,下面文章也是主要讲WPF的
UI
设计思路
先根据功能,设计布局
然后就是如果项目比较大,要考虑设计模式的选取,让前后端分离
控件
因为UI的开发都是基于界面和控件的,而我们常用的控件都大同小异,需要的时候自行查阅文档就好了
常用
信息类 label
编辑类 文本编辑 textbox 状态编辑 checkbox
按钮 button
添加方式
拖拽添加
代码添加
布局
定位
anchor,锚定某个边缘
dock,停靠,按弹出的选项进行设置
重写Onlayout函数(布局改变时触发)
分组
panel,多层次布局,将控件绑定到panel上,便于组合整体控制
事件
即界面获取操作,传到后端进行相应
winform
项目结构
VS选择基于C#的和.Net的窗体应用创建
直接运行,可以看到一个空的界面
然后可以看解决方案管理,可以看到有
- Form1.cs这个是界面设计文件,直接打开展示UI,可以右键查看代码
- Form1.Designer.cs,由我们的设计自动生成的底层代码,由设计文件调用
- 上面两个是同一个类分在不同的文件,前者处理事件,后者处理布局
- Form.resx
- program.cs 程序入口,
new Form1()
来示例化我们的窗口
通过拖拽添加控件,通过属性添加事件.界面的主要代码都在designer中,我们最好不要动它,需要的改动都在Form1.cs中
因为应用场景少,所以不深入学习
WPF
项目结构
- App.xaml
StartupUri
设置启动的窗口,Resources
样式模板 - MainWindow.xaml 拖拽布局,可实时渲染
然后我们直接运行项目,可以看到一个空白窗口
xaml
xaml是一种基于xml的标记语言,用于定义UI和属性,这里面都会根据xaml生成对应的cs文件
就下面这个语法
<CMD ..... />
等价于<CMD>.....</CMD>
我们看``MainWindow.xaml`
<Window x:Class="WPF.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPF"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
</Grid>
</Window>
解释如下
<Window>
元素:x:Class="WPF.MainWindow"
:这个属性指定了窗口的类名。在这里,窗口的类名是MainWindow
,它与代码中的类名相对应。这个属性通常用于将XAML文件与后端代码(例如C#)中的类关联起来。
- 命名空间声明:
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
:指定默认的XML命名空间,该命名空间用于定义WPF的基本呈现元素,如窗口、面板、按钮等。xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
:指定XAML专用命名空间,其中包含一些用于XAML文件的特殊属性。
- 附加的命名空间声明:
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
:用于Blend工具的命名空间,该工具通常用于设计和交互式开发。xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
:定义了一些用于在不同XAML编译器版本之间保持兼容性的属性。
- 本地命名空间声明:
xmlns:local="clr-namespace:WPF"
:指定了一个本地的命名空间,它与命名空间WPF
相关联。这使得在XAML文件中可以引用WPF
命名空间中定义的类和资源。
mc:Ignorable="d"
:- 这个属性告诉编译器在处理未知的
d
前缀时忽略错误。在这里,d
是Blend工具的命名空间,而Ignorable
属性允许在不影响编译的情况下忽略这个未知的前缀。
- 这个属性告诉编译器在处理未知的
- 窗口的属性设置:
Title="MainWindow"
:设置窗口的标题为 “MainWindow”。Height="450"
和Width="800"
:设置窗口的高度和宽度分别为450和800。
布局
所有东西都可以通过属性来改变
-
StackPanel
StackPanel 主要用于垂直或水平排列元素、在容器的可用尺寸内放置有限个元素,元素的 尺寸总和(长/高)不允许超过 StackPanel 的尺寸, 否则超出的部分不可见。
-
WrapPanel
WrapPanel 默认排列方向与 StackPanel 相反、WrapPanel 的 Orientation 默认为 Horizontal。 WrapPanel 具备 StackPanel 的功能基础上具备在尺寸变更后自动适应容器的宽高进行换行换列处理。
-
DockPanel
停靠式布局
内部控件通过DockPanel.Dock 属性来布局,默认添加 Left。
DockPanel 有一个 LastChildFill 属性, 该属性默认为 true, 该属性作用为, 当容器中的最后一个元素时, 默认该元素填充 DockPanel 所有空间。
-
Grid
Grid 具备分割空间的能力。
RowDefinitions / ColumnDefinitions 用于给 Grid 分配行与列。
Row / Column 来确定控件位置
ColumnSpan / RowSpan来设置控件长宽
如下来进行分割与分配
尺寸中auto表示跟网格内的控件一样,*表示比例划分auto剩下的
<Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="37*" /> <RowDefinition Height="138*" /> <RowDefinition Height="175*" /> <RowDefinition Height="175*" /> <RowDefinition Height="175*" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="58*" /> <ColumnDefinition Width="58*" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="362*" /> <ColumnDefinition Width="71*" /> </Grid.ColumnDefinitions> <Label Grid.Column="1" Content="Label" Grid.ColumnSpan="1" Grid.RowSpan="3" HorizontalAlignment="Center" Grid.Row="2" VerticalAlignment="Center"/>
控件
-
ContentControl 类
内容属性为 Content,只能设置一次,但可以嵌套
-
ItemsControl 类
此类控件大多数属于显示列表类的数据、设置数据源的方式一般通过 ItemSource 设置。
-
HeaderedContentControl 类
相对于 ContentControl 来说、这类控件即可设置 Content, 还有带标题的 Header。 像比较常见的分组控件 GroupBox、TabControl 子元素 TabItem、它们都是具备标题和内容的控件。
-
常用控件
- TextBlock: 用于显示文本, 不允许编辑的静态文本。
- TextBox: 用于输入/编辑内容的控件、作用与 winform 中 TextBox 类似, Text 设置输入显示的内容。
- Button: 简单按钮、Content 显示文本、Click 可设置点击事件、Command 可设置后台的绑定命令。
- ComboBox: 下拉框控件, ItemSource 设置下拉列表的数据源, 也可以显示设置。
事件
在MainWindow.xaml里面设置,用于触发函数
namespace WPF
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();//界面显示
}
//添加的事件函数
private void Button_Click(object sender, RoutedEventArgs e)
{
}
}
}
样式
可设置样式
- 字体(FontFamily)
- 字体大小(FontSize)
- 背景颜色(Background)
- 字体颜色(Foreground)
- 边距(Margin)
- 水平位置(HorizontalAlignment)
- 垂直位置(VerticalAlignment)
全局的在app中的<Application.Resources>中添加
局部的在当前xaml里面添加<Window.Resources>(与grid同级)
<Style x:Key="TextBlockStyle" TargetType="TextBlock">
<Setter Property="Text" Value="TextBlock" />
<Setter Property="Height" Value="30" />
<Setter Property="Width" Value="60" />
<Setter Property="Background" Value="Red" />
</Style>
使用
<TextBlock TextWrapping="Wrap" Style="{StaticResource TextBlockStyle}"/>
<!--这里也可以再修改样式,优先级高于Style-->
触发器
一样是style,在对应事件触发时执行
<Style x:Key="TextBlockStyle" TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Blue" />
</Trigger>
</Style.Triggers>
</Style>
模板
控件模板
我们在样式中所能做的修改有限,
于是可以右键控件,编辑副本来创建新的控件
如给按钮添加圆角
在编辑副本后,在生成的副本中,给Border标签加上CornerRadius="10"
然后设置按钮Style="{StaticResource ButtonStyle1}"
数据模板
CellTemplate
表格实现,利用grid
模板直接放在里,这个表格设置了三列,并在第四列设置了两个按钮
<DataGrid Name="gd" AutoGenerateColumns="False" CanUserSortColumns="True" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding UserName}" Width="100" Header="学生姓名"/>
<DataGridTextColumn Binding="{Binding ClassName}" Width="100" Header="班级名称"/>
<DataGridTextColumn Binding="{Binding Address}" Width="200" Header="地址"/>
<DataGridTemplateColumn Header="操作" Width="100" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Left">
<Button Content="编辑"/>
<Button Margin="8 0 0 0" Content="删除" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
绑定数据,在InitializeComponent();后面
public class Student
{
public string UserName { get; set; }
public string ClassName { get; set; }
public string Address { get; set; }
}
List<Student> students = new List<Student>();
students.Add(new Student() { UserName = "小王", ClassName = "高二三班", Address = "广州市" });
students.Add(new Student() { UserName = "小李", ClassName = "高三六班", Address = "清远市" });
students.Add(new Student() { UserName = "小张", ClassName = "高一一班", Address = "深圳市" });
students.Add(new Student() { UserName = "小黑", ClassName = "高一三班", Address = "赣州市" });
gd.ItemsSource = students;
ItemTemplate
用来绑定控件内容,如下
<Window.Resources>
<DataTemplate x:Key="comTemplate">
<StackPanel Orientation="Horizontal" Margin="5,0">
<Border Width="10" Height="10" Background="{Binding Code}"/>
<TextBlock Text="{Binding Code}" Margin="5,0"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<ComboBox Name="cob" Width="120" Height="30" ItemTemplate="{StaticResource comTemplate}"/>
<ListBox Name="lib" Width="120" Height="100" Margin="5,0" ItemTemplate="{StaticResource comTemplate}"/>
</StackPanel>
</Grid>
绑定数据
public class Color
{
public string? Code { get; set; }
}
List<Color> ColorList = new List<Color>();
ColorList.Add(new Color() { Code = "#FF8C00" });
ColorList.Add(new Color() { Code = "#FF7F50" });
ColorList.Add(new Color() { Code = "#FF6EB4" });
ColorList.Add(new Color() { Code = "#FF4500" });
ColorList.Add(new Color() { Code = "#FF3030" });
ColorList.Add(new Color() { Code = "#CD5B45" });
cob.ItemsSource = ColorList;
lib.ItemsSource = ColorList;
ContentTemplate
用得很少,不提了
绑定
前端显示后后端数据的关联
绑定控件
通过给控件取名,然后根据名字将text的值绑定为上面slider控件的value
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<Slider Name="slider" Width="200" />
<TextBlock Text="{Binding ElementName=slider, Path=Value}" HorizontalAlignment="Center" />
</StackPanel>
绑定模式
如下进行设置
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<Slider Name="slider" Width="200" />
<TextBox Width="200" Text="{Binding ElementName=slider, Path=Value, Mode=OneWay}" HorizontalAlignment="Center" />
</StackPanel>
常用模式
OneWay : 单向绑定,绑定对象会决定自己
TwoWay : 双向绑定,下面输入后tab即可显示
OneTime : 只绑定第一次的值,这里直接绑定启动的值了
OneWayToSource : 与OneTime一样,对象相反
Default : 默认单项或双向
绑定数据
Source
<Window.Resources>
<TextBox x:Key="txt1">ABC</TextBox>
</Window.Resources>
<Grid>
<TextBox Text="{Binding Source={StaticResource txt1}, Path=Text}" />
</Grid>
RelativeSource
查看相关标签的数据
<StackPanel Width="20">
<StackPanel Width="60">
<TextBlock Text="{Binding Path=Width,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type StackPanel}}}" />
</StackPanel>
</StackPanel>
DataContext
与代码数据绑定
<Grid>
<DataGrid Grid.Row="1" ItemsSource="{Binding Students}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}" Header="名称" />
<DataGridTextColumn Binding="{Binding Age}" Header="年龄" />
<DataGridTextColumn Binding="{Binding Sex}" Header="性别" />
</DataGrid.Columns>
</DataGrid>
</Grid>
绑定
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
PageModel page = new PageModel();
page.Students = new List<Student>();
page.Students.Add(new Student() { Name = "张三", Age = "18", Sex = "男" });
page.Students.Add(new Student() { Name = "李四", Age = "19", Sex = "男" });
page.Students.Add(new Student() { Name = "王二", Age = "20", Sex = "女" });
this.DataContext = page;
}
}
public class PageModel
{
public List<Student> ?Students { get; set; }
}
public class Student
{
public string? Name { get; set; }
public string? Age { get; set; }
public string? Sex { get; set; }
}
MVVM
在开发大型项目时收益高
我们的页面设计xaml和我们的数据分开,利用框架,可以二者完全分离
在MainWindow.xaml中设置界面和绑定(数据,函数(函数参数))
在MainViewModel.cs中来实现数据的处理和提供函数接口,(替换原生在MainWindow.xaml.cs里来设置响应)
让前后端的工作分开,专心处理一种逻辑
有很多MVVM框架,这里使用prism
实战
把页面划分为区域,用区域便于设置
安装框架 : 项目->管理NuGet程序包->Prism.DryIoc(Prism Dry Inversion of Control)
NuGet(发音为"New Get")是一个用于.NET平台的包管理系统
使用Prism
对App.xaml进行操作
cs
修改继承并重写方法
public partial class App : PrismApplication
{
protected override Window CreateShell()
{//初始页
return Container.Resolve<MainWindow>();
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{//用于注册要实现的页面
}
}
xaml
添加命名空间prism并删除StartupUri
<prism:PrismApplication x:Class="WPF.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPF"
xmlns:prism ="http://prismlibrary.com/">
<Application.Resources>
</Application.Resources>
</prism:PrismApplication>
项目模板
扩展->管理扩展->Prism Template Pack->然后新建该模板的项目,就会自动配置好上面使用框架的内容
框架自动生成两个文件夹 :
Views用来存放我们的界面
ViewModels来存放我们的指令
只要放在这两个文件夹下就会自动关联,不需要麻烦的指定
然后就是了解region,在框架中,界面通过region来控制.
在MainWindow.xaml
代码中定义region
<ContentControl prism:RegionManager.RegionName="ContentRegion" Grid.ColumnSpan="2" />
如果需要显示,在MainWindowViewModel.cs
中注册区域,
下面将显示ViewA这个界面
public class MainWindowViewModel : BindableBase
{
private IRegionManager regionManager;
public MainWindowViewModel(IRegionManager regionManager)
{
this.regionManager = regionManager;
regionManager.RegisterViewWithRegion("ContentRegion", typeof(ViewA));
}
}
模块
创建 -> 模块
模块的ModuleAModule.cs
文件
public class ModuleAModule : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
var regionManager = containerProvider.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion("ContentRegion", typeof(ViewA));
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
}
}
然后主程序的App
public partial class App : PrismApplication
{
protected override Window CreateShell()
{
return Container.Resolve<MainWindow>();
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
}
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
moduleCatalog.AddModule<ModuleA.ModuleAModule>();
}
}
MAUI
需要下载.NET Multi_platform App UI开发
新建->.NET MAUI->.net6.0->运行
因为跨平台,没办法像WPF那样实时渲染,得自己运行来看效果,不过确实方便
因为跨平台,开发成本低,运行成本高
项目结构
- App是MAUI应用的入口点,它们定义了一个App类,继承自Application类;App.xaml是用
XAML语言声明App类的属性和资源,如主题颜色,字体大小等;App.xaml.cs是用C#语言
编写App类的逻辑,如初始化应用,设置主窗口,处理生命周期事件等。 - AppShell是MAUI应用的导航框架,它们定义了一个AppShell类,继承自Shell类;
AppShell.xaml是用XAML语言声明AppShell类的内容和结构,如导航菜单,标签页,飞出
页等;AppShell.xaml.cs是用C#语言编写AppShell类的逻辑,如注册路由,处理导航事件
等。 - MainPage是MAUI应用的主页面,它们定义了一个MainPage类,继承自ContentPage类;
MainPage.xaml是用XAML语言声明MainPage类的用户界面,如控件,布局,样式等;
MainPage.xaml.cs是用C#语言编写MainPage类的逻辑,如事件处理,数据绑定,页面生命
周期等。 - MauiProgram.cs是应用的跨平台入口点,它用来配置和启动应用。模板启动代码指向了
App.xaml文件中定义的App类。
安卓环境
用虚拟机或真机
- 模拟器是一种虚拟设备,它可以在你的开发机上模拟不同平台的设备,如Android手机或iOS手机。配置方式请参考:管理虚拟设备
- 真机是一种实体设备,它可以通过USB或无线连接到你的开发机上,如Android手机或iOS手机。配置方式请参考:设置设备进行开发
安全
对所有输入一定要谨慎,(正则匹配),防止胡乱的输入让程序崩溃,即提高鲁棒性
如在计算器中输入1***************1*