<0x00> 我为何选择WPF
(@ 23-08-25)
慢慢学了一个月,总算知道为啥现在用WPF的个人项目不多了
写简单的UI确实非常方便,但如果要用些现代些的设计就很烦了
非常容易出现些神秘问题
这个我也说不明白,大概就是因为之前看到有些项目的窗口就是WPF写的吧
一定要找个理由就是WPF的支持丰富
作为一个老UI框架,教程满天飞,更重要的是,用WPF仍可以设计出现代的UI
(而且相比那些electron框架来说性能更好,只要不搞跨平台)
目前在Windows下,微软这常见的有四套UI框架
分别是:WinFrom、WPF、WinUI3、XAUIWinFrom实在是老,做一些效果动画会比较费力WPF虽然也挺老的,但支持的特性足够做些不错的UI了WinUI3新是新,但教程不多,文档支持也比较神秘XAUI能跨平台,但微软特色,永不推广,而且也比较新,文档比较神秘
从WPF开始,微软的UI框架都是要写.xaml的
所以学会WPF之后,迁移到微软更新的UI框架的学习成本是相对低的
目前我体验下来WPF算是很好入门的了,只需要会一门.net框架下的语言就可以入门xaml语法也不难,可以说看多了也会了
而且微软的看家本领,UI的可视化开发(Visual Studio),这对效率提升真的很有帮助
这篇文章的代码来自我自己随便搞的简易计算器
(整个程序就花了一个小时写完,足见WPF是多方便)
<0x01> WPF的典型结构
我这里创建的是基于C#的WPF项目
创建完我们可以看到如图的项目结构
差不多就是一个.xaml文件下套.cs文件
其中在App.xaml文件中,定义了这个应用的基本信息(主要就是启动窗口是哪一个)MainWindows.xaml定义了窗口的样式(这个的编辑界面就是一半实时预览窗口,一半是代码编辑器)
下面的.cs文件可以说描述程序的行为
简单来说就是.xaml文件管前端(界面),.cs文件管后端(行为)
具体的东西后面再讲
<0x02> XAML的简单语法
xaml这玩意就是参考xml语法的
如果能看懂xml甚至是html也能看懂xaml
<!--MainWindow.xaml-->
<Window x:Class="WPFTest.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:WPFTest"
mc:Ignorable="d"
Title="MainWindow" Height="800" Width="800">
<Window.Resources>
<Style TargetType="Button">
<Setter Property="Margin" Value="5"/>
<Setter Property="FontSize" Value="30"/>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<TextBlock Name="Answer" Text="0" FontSize="30" HorizontalAlignment="Right" Margin="20"/>
</Grid>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Name="Button_MinusSign" Click="Button_MinusSign_Click"
Content="±" Grid.Column="0" Grid.Row="0"/>
<Button Name="Button_Clear" Click="Button_Clear_Click"
Content="CE" Grid.Column="1" Grid.Row="0"/>
<Button Name="Button_Backspace" Click="Button_Backspace_Click"
Content="←" Grid.Column="2" Grid.Row="0"/>
<Button Name="Button_Num9" Click="Button_Num9_Click"
Content="9" Grid.Column="2" Grid.Row="1"/>
<Button Name="Button_Num8" Click="Button_Num8_Click"
Content="8" Grid.Column="1" Grid.Row="1"/>
<Button Name="Button_Num7" Click="Button_Num7_Click"
Content="7" Grid.Column="0" Grid.Row="1"/>
<Button Name="Button_Num6" Click="Button_Num6_Click"
Content="6" Grid.Column="2" Grid.Row="2"/>
<Button Name="Button_Num5" Click="Button_Num5_Click"
Content="5" Grid.Column="1" Grid.Row="2"/>
<Button Name="Button_Num4" Click="Button_Num4_Click"
Content="4" Grid.Column="0" Grid.Row="2"/>
<Button Name="Button_Num3" Click="Button_Num3_Click"
Content="3" Grid.Column="2" Grid.Row="3"/>
<Button Name="Button_Num2" Click="Button_Num2_Click"
Content="2" Grid.Column="1" Grid.Row="3"/>
<Button Name="Button_Num1" Click="Button_Num1_Click"
Content="1" Grid.Column="0" Grid.Row="3"/>
<Button Name="Button_Num0" Click="Button_Num0_Click"
Content="0" Grid.ColumnSpan="2" Grid.Column="0" Grid.Row="4"/>
<Button Name="Button_Dot" Click="Button_Dot_Click"
Content="." Grid.Column="2" Grid.Row="4"/>
<Button Name="Button_Div" Click="Button_Div_Click"
Content="/" Grid.Column="3" Grid.Row="0"/>
<Button Name="Button_Mul" Click="Button_Mul_Click"
Content="*" Grid.Column="3" Grid.Row="1"/>
<Button Name="Button_Sub" Click="Button_Sub_Click"
Content="-" Grid.Column="3" Grid.Row="2"/>
<Button Name="Button_Add" Click="Button_Add_Click"
Content="+" Grid.Column="3" Grid.Row="3"/>
<Button Name="Button_Equal" Click="Button_Equal_Click"
Content="=" Grid.Column="3" Grid.Row="4"/>
</Grid>
</Grid>
</Window>
(为了代码高亮正常工作,这里的代码设置为xml的高亮格式)
一块块掰碎讲
声明部分
就是最上面一坨不知道什么东西的部分,随便写写的话根本不需要知道这些是啥,别动就好
(因为我目前也不是很懂这些)
<Window.Resources>
这里面我就定义了个Button类型的Style
其实就是规定了Button的一些默认参数
在下面写Button的时候就不用一个个都写这些东西了
Style里面定义了按钮到界面边界的距离(Margin)有5单位
字体大小(FontSize)为30个单位
(在WPF里,并不是以像素为单位,而是按DPI来确定实际像素单位大小)
<Style>里面要指定设置的类型(TargetType)
在<Style>块中,用<Setter>标签来设置类型下面的属性<Setter>标签里面Property选择属性,Value选择值
<Grid>
在WPF中,有两种常用的界面布局
分别是:网格布局(<Grid>)和栈布局(<StackPanel>)<Grid>可以把一块区域分割成一个网格,网格里面可以塞控件<StackPanel>就是个栈,所有控件从上到下(从左到右)排列
基本上用这两种布局的互相嵌套能解决绝大多数的布局
我这里的布局选择了<Grid>里面再套一个<Grid>
具体实现
在<Grid.RowDefinitions>块里面塞两个<RowDefinition/>把Grid分成两行
(塞几个<RowDefinition/>就是有几行,列同理)
在一个<RowDefinition/>里加入Height="auto"表示第一行的高度随内容改变
(就是跟着第一行的控件高度走)
然后正式往Grid网格里面塞控件,先第一行
首先是再塞了个<Grid>,然后在里面塞了个<TextBlock>
其中,Grid.Row可以指定控件所在的网格行号,列同理
并定义了一系列的属性参数
(Name属性就是给.cs调用的对象名,HorizontalAlignment是水平对正方式,设置为右对齐)
(不是很懂我当时写的逻辑,其实不用多写个<Grid>,直接塞<TextBlock>就好)
第二行,塞了个<Grid>,并定义出5*4的网格
下面就是一大堆的按键定义,相对比较无趣
值得说道的是Click属性,这个会指向对应.cs的一个具体的方法名,实现前后端的融合
(具体看后端实现就可以了)
还有按钮0多了个Grid.ColumnSpan="2",这个就是让这个按钮可以跨两列显示
由于有之前的<Style>的定义,所有的按钮都自带字体大小30单位,间距5单位的属性
这样,我们成功整出了一个简单的计算器界面
长这样的
<0x03> 与C#后端的结合
(@ 23-08-14)
实际上这样并不是搞后端,这些都是再定义前端的行为
具体搞后端可以看我之后写的讲MVVM设计模型的博客
但如果项目很小的话这样搞也没问题的
与C#后端的结合可以说非常简单,就是调用和更改控件对象的成员值就可以
还记得我们已经在.xaml中为一些控件添加了Name属性吗
这些Name属性是实现前后端融合的关键
//等于号按键点击事件的实现逻辑
private void Button_Equal_Click(object sender, RoutedEventArgs e)
{
if (flag && ans != null)
{
Calculate();
flag = false;
Answer.Text = ans.ToString();
calculate = null;
}
}
别的都不重要,还记得我们有一个<TextBlock>的Name属性设置为Answer吗
通过Answer.Text可以直接访问并修改<TextBlock>的内容,非常方便
(Answer.Text就是String类型)
其他的控件也同理
接下来要处理下按钮的点击事件了
这个也非常简单,我们之前已经设置了每个按钮的Click属性
接下来就是在对应的.cs中实现逻辑就好
//大概的结构
namespace WPFTest
{
public partial class MainWindow : Window
{
//...
private void Button_MinusSign_Click(object sender, RoutedEventArgs e);
//加负号按钮的点击事件
private void Button_Clear_Click(object sender, RoutedEventArgs e);
//清屏键的点击事件
private void Button_Backspace_Click(object sender, RoutedEventArgs e);
//退格键的点击事件
private void Button_Num9_Click(object sender, RoutedEventArgs e);
private void Button_Num8_Click(object sender, RoutedEventArgs e);
private void Button_Num7_Click(object sender, RoutedEventArgs e);
private void Button_Num6_Click(object sender, RoutedEventArgs e);
private void Button_Num5_Click(object sender, RoutedEventArgs e);
private void Button_Num4_Click(object sender, RoutedEventArgs e);
private void Button_Num3_Click(object sender, RoutedEventArgs e);
private void Button_Num2_Click(object sender, RoutedEventArgs e);
private void Button_Num1_Click(object sender, RoutedEventArgs e);
private void Button_Num0_Click(object sender, RoutedEventArgs e);
//各个数字键的点击事件
private void Button_Dot_Click(object sender, RoutedEventArgs e);
//小数点按键的点击事件
private void Button_Div_Click(object sender, RoutedEventArgs e);
private void Button_Mul_Click(object sender, RoutedEventArgs e);
private void Button_Sub_Click(object sender, RoutedEventArgs e);
private void Button_Add_Click(object sender, RoutedEventArgs e);
//+-*/键的点击事件
private void Button_Equal_Click(object sender, RoutedEventArgs e);
//等于号的点击事件
}
}
每个按钮的点击事件都相当于在C#中的一个方法
点击一次就触发一次
附录
完整的MainWindow.xaml已经贴在上文了,这里就不再放了
//MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WPFTest
{
public partial class MainWindow : Window
{
bool flag = false;
int? calculate = null;
double? ans = null;
public MainWindow()
{
InitializeComponent();
}
private void Calculate()
{
switch (calculate)
{
case 1:
ans += Double.Parse(Answer.Text);
break;
case 2:
ans -= Double.Parse(Answer.Text);
break;
case 3:
ans *= Double.Parse(Answer.Text);
break;
case 4:
ans /= Double.Parse(Answer.Text);
break;
}
}
private void Button_MinusSign_Click(object sender, RoutedEventArgs e)
{
if (flag)
{
if (Answer.Text[0]=='-')
{
Answer.Text = Answer.Text.Substring(1, Answer.Text.Length - 1);
}
else
{
Answer.Text = "-" + Answer.Text;
}
}
}
private void Button_Clear_Click(object sender, RoutedEventArgs e)
{
flag = false;
calculate = null;
ans = null;
Answer.Text = "0";
}
private void Button_Backspace_Click(object sender, RoutedEventArgs e)
{
if (flag)
{
Answer.Text = Answer.Text.Substring(0, Answer.Text.Length - 1);
if (Answer.Text.Length == 0)
{
flag = false;
Answer.Text = "0";
}
}
}
private void Button_Num9_Click(object sender, RoutedEventArgs e)
{
if (flag)
{
Answer.Text += "9";
}
else
{
flag = true;
Answer.Text = "9";
}
}
private void Button_Num8_Click(object sender, RoutedEventArgs e)
{
if (flag)
{
Answer.Text += "8";
}
else
{
flag = true;
Answer.Text = "8";
}
}
private void Button_Num7_Click(object sender, RoutedEventArgs e)
{
if (flag)
{
Answer.Text += "7";
}
else
{
flag = true;
Answer.Text = "7";
}
}
private void Button_Num6_Click(object sender, RoutedEventArgs e)
{
if (flag)
{
Answer.Text += "6";
}
else
{
flag = true;
Answer.Text = "6";
}
}
private void Button_Num5_Click(object sender, RoutedEventArgs e)
{
if (flag)
{
Answer.Text += "5";
}
else
{
flag = true;
Answer.Text = "5";
}
}
private void Button_Num4_Click(object sender, RoutedEventArgs e)
{
if (flag)
{
Answer.Text += "4";
}
else
{
flag = true;
Answer.Text = "4";
}
}
private void Button_Num3_Click(object sender, RoutedEventArgs e)
{
if (flag)
{
Answer.Text += "3";
}
else
{
flag = true;
Answer.Text = "3";
}
}
private void Button_Num2_Click(object sender, RoutedEventArgs e)
{
if (flag)
{
Answer.Text += "2";
}
else
{
flag = true;
Answer.Text = "2";
}
}
private void Button_Num1_Click(object sender, RoutedEventArgs e)
{
if (flag)
{
Answer.Text += "1";
}
else
{
flag = true;
Answer.Text = "1";
}
}
private void Button_Num0_Click(object sender, RoutedEventArgs e)
{
if (flag)
{
Answer.Text += "0";
}
else
{
flag = true;
Answer.Text = "0";
}
}
private void Button_Dot_Click(object sender, RoutedEventArgs e)
{
if (flag)
{
Answer.Text += ".";
}
}
private void Button_Div_Click(object sender, RoutedEventArgs e)
{
if (flag)
{
if (ans == null)
{
ans = Double.Parse(Answer.Text);
calculate = 4;
flag = false;
}
else
{
Calculate();
flag = false;
Answer.Text = ans.ToString();
}
}
else
{
calculate = 4;
}
}
private void Button_Mul_Click(object sender, RoutedEventArgs e)
{
if (flag)
{
if (ans == null)
{
ans = Double.Parse(Answer.Text);
calculate = 3;
flag = false;
}
else
{
Calculate();
flag = false;
Answer.Text = ans.ToString();
}
}
else
{
calculate = 3;
}
}
private void Button_Sub_Click(object sender, RoutedEventArgs e)
{
if (flag)
{
if (ans == null)
{
ans = Double.Parse(Answer.Text);
calculate = 2;
flag = false;
}
else
{
Calculate();
flag = false;
Answer.Text = ans.ToString();
}
}
else
{
calculate = 2;
}
}
private void Button_Add_Click(object sender, RoutedEventArgs e)
{
if (flag)
{
if (ans == null)
{
ans = Double.Parse(Answer.Text);
calculate = 1;
flag = false;
}
else
{
Calculate();
flag = false;
Answer.Text = ans.ToString();
}
}
else
{
calculate = 1;
}
}
private void Button_Equal_Click(object sender, RoutedEventArgs e)
{
if (flag && ans != null)
{
Calculate();
flag = false;
Answer.Text = ans.ToString();
calculate = null;
}
}
}
}
逻辑就是随性写的,不保证完全没Bug,应该是没严重Bug的
(所以也没写高精度的运算处理)
(写这篇博客比我写代码的时间都长😂)