WPF前端基本容器

初步工作:
1、确认页面的容器布局,根据需求选择使用以下哪个盒子模型,全部一层一层套好,分好行列
2、确认好宽高的3种撑开方式和设置宽高值
3、给所有块上色、添加margin,border等初步效果,利于区分和美观

Grid :适合于子元素需要分行列的情况

1、Grid中不标识行列的元素默认坐标为0,0
2、设置宽高有:auto由内容撑开(默认),绝对值,比例值*
3、Grid.RowSpan设置元素所占的列数

StackPanel:使用于子元素需要flex布局

1、类似于Vue中的Flex布局,可以通过Orientation属性设置布局方向
2、不会自动换行

WrapPanel:使用于子元素需要flex布局

1、和StackPanel一样,但是会自动换行

UniformGrid :适用于

1、自动根据元素分行列,而且是会换行的flex布局

Border:适用于作为子元素,因为可以设置比较多的边框效果

DockPanel:适用于子元素需要动态填充空间??类似吸边的功能

1、LastChildFill设置最后一个元素是否填充剩余空间
2、DockPanel.Dock对它的子容器设置设置贴哪个边

默认容器的一些基本属性

Content文字、FontSize文字大小、Foregrond文字颜色、Background背景颜色

自定义style:用于管理同类的容器

1、可以定义在Window.Resources中,使用TargetType来指定目标容器类型
2、通过 子属性来设置目标样式
3、Style方通过x:Key = “标识符” 属性来标识自己,容器方通过Style=”{StaticResource 标识符}”来标识需要绑定的Style
4、Style可以通过BaseOn={“StaticResource 标识符”} 继承Style属性,做到多级属性

样式的绑定

ControlTemplate:控件模版,定义给其他内容绑定

数据的绑定

Binding 成员变量名

需要指定上下文

DataTemplate :数据模版,多个同类型数据和界面的绑定

1、DataTemplate x:Key = “标识符” 设置标识,下面定义模版,将类的成员变量绑定到模版的不同控件中
2、在某个容器中使用ItemsSource = “{Binding 集合对象名}”ItemTemplate = “{StaticResource 标识符}”
3、在后端代码中类构造函数中创建对象的集合并且给值,然后在viewmodel层后端创建类对象并指定为上下文DataContext = new MainViewModel(); 或者

Binding ElementName= slider , Path = Value , Mode = Twoway 控件之间的数据相互绑定

用ICommad代替事件驱动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class MyCommand : ICommand
{
private readonly Action executeAction; //创建委托
private readonly Func<bool> canExecuteFunc;

public MyCommand(Action action, Func<bool> canExecute = null)
{
executeAction = action ?? throw new ArgumentNullException(nameof(action));
canExecuteFunc = canExecute ?? (() => true); // 如果未提供 `canExecute`,则默认返回 `true`
}

public event EventHandler CanExecuteChanged;

public bool CanExecute(object parameter)
{
return canExecuteFunc();
}

public void Execute(object parameter)
{
executeAction();
}

// 手动触发 CanExecuteChanged 事件
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}

然后在vm层将创建Command对象,并且传入委托的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MainViewModel
{
public MyCommand ShowCommand { get; }

public string Name{
get{return name;}
set{name = value; PropertyChanged?.Invoke(.............)}} //这个变量会被前端绑定

public MainViewModel()
{
// 将 ShowCommand 初始化为调用 Show 方法的命令
ShowCommand = new MyCommand(Show);
}

private void Show()
{

//这里可以写绑定的变量的改变逻辑
MessageBox.Show("点击了按钮!");
}
}
1
2
3
4
5
6
7
<Window.Resources>
<local:MainViewModel x:Key="MainViewModel" />
</Window.Resources>

<Window.DataContext>
<StaticResource ResourceKey="MainViewModel" />
</Window.DataContext>

MVVM的典型写法

Model层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class UserService
{
// 模拟从数据库获取用户信息
public User GetUserInfo()
{
// 这里应该有实际的数据库查询逻辑
return new User { Name = "Jane Smith", Age = 25 }; // 从数据库中获取的数据
}

// 其他数据库操作方法(如更新用户信息)
public void UpdateUserInfo(User user)
{
// 实际的数据库更新逻辑
}
}

ViewModel层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76

using System.ComponentModel;
using System.Windows.Input;

public class UserViewModel : INotifyPropertyChanged
{
private readonly UserService _userService;
private User _user;

public UserViewModel()
{
_userService = new UserService();
LoadUserInfoCommand = new RelayCommand(LoadUserInfo); //
UpdateCommand = new RelayCommand(UpdateUserInfo); // 将按键和本层的函数LoadUserInfo绑定
}

public string Name
{
get => _user?.Name;
set
{
if (_user.Name != value)
{
_user.Name = value;
OnPropertyChanged(nameof(Name));
}
}
}

public int Age
{
get => _user?.Age ?? 0;
set
{
if (_user.Age != value)
{
_user.Age = value;
OnPropertyChanged(nameof(Age));
}
}
}

// 加载用户信息的命令
public ICommand LoadUserInfoCommand { get; }

private void LoadUserInfo()
{
_user = _userService.GetUserInfo();
OnPropertyChanged(nameof(Name));
OnPropertyChanged(nameof(Age));
}

// 更新用户信息命令
public ICommand UpdateCommand { get; }

private void UpdateUserInfo()
{
if (_user == null)
{
Console.WriteLine("User information is not loaded yet.");
return;
}

// 将 ViewModel 中的用户信息传递给 Model 层进行更新
_user.Name = Name;
_user.Age = Age;
_userService.UpdateUserInfo(_user);
}

public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

View层

1
2
3
4
5
6
7
8
9
10
11
12
<StackPanel>
<!-- 绑定加载用户信息的命令 -->
<Button Content="Load User Info" Command="{Binding LoadUserInfoCommand}" />

<!-- TextBox 控件绑定到 Name 和 Age 属性 -->
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" Width="200" />
<TextBox Text="{Binding Age, UpdateSourceTrigger=PropertyChanged}" Width="200" />

<!-- 绑定更新用户信息的命令 -->
<Button Content="Update User Info" Command="{Binding UpdateCommand}" />
</StackPanel>

委托

为异步操作提供结束操作

Task、async 和 await机制已经代替其操作

策略委托

可以动态改变算法策略达到不同的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
using System;

public class PaymentContext
{
private Func<decimal, decimal> _paymentStrategy;

// 设置支付策略
public void SetPaymentStrategy(Func<decimal, decimal> paymentStrategy)
{
_paymentStrategy = paymentStrategy;
}

// 执行支付操作
public void ExecutePayment(decimal amount)
{
var finalAmount = _paymentStrategy(amount);
Console.WriteLine($"Payment processed. Amount: {finalAmount}");
}
}

class Program
{
static void Main()
{
var context = new PaymentContext();

// 设置策略为信用卡支付
context.SetPaymentStrategy(amount => amount * 0.95m); // 假设信用卡支付有5%折扣
context.ExecutePayment(100); // 输出:Payment processed. Amount: 95

// 更改策略为支付宝支付
context.SetPaymentStrategy(amount => amount * 0.90m); // 假设支付宝支付有10%折扣
context.ExecutePayment(100); // 输出:Payment processed. Amount: 90
}
}

有了策略委托,我们就可以在前端界面很方便地对测试和实际运行模式进行上下文切换

事件+委托

委托只适用于单播,只由一个触发者调用一个委托。
当我们需要多播,一个触发源可能需要引发多个委托被调用(UI事件、日志、系统通知)时,就需要通过事件来达到效果。另外,事件比委托更安全,因为多了一个订阅来统一管理。

特性(Attribute)和反射(Reflection)

特性

特性是一种 描述类型、方法、属性等成员的声明方式,即给它们声明时附加了设定好的“特性”。
• 避免繁琐的手写逻辑(例如:手动判断哪些字段需要序列化)
• 让框架或库可以自动读取额外的元数据(比如 ORM 框架读取数据库表名)
• 提供更好的代码解耦方式(比如 ASP.NET MVC 路由绑定)

反射

解析特性
• 动态加载程序集(Plugins 机制)
• 动态调用方法(如通过字符串调用方法,而不是硬编码方法名)
• 框架自动注册(如 ASP.NET Core 依赖注入基于反射解析对象)
• 序列化与反序列化(如 JSON 库根据对象的属性动态解析)

设计模式

单例设计模式

只需要创建一次的例子,可以统一放在单例中

工厂方法设计模式

C# 届的多态,即可以先将抽象接口类实例创建出来,再根据不同情况给他赋值不同的真正的类型,进行相应类型的操作

原型设计模式

C# 届的深拷贝和浅拷贝

建造者设计模式

当需要逐步构建一个对象时,并且希望能做到解耦合,开闭原则时,可以使用这个模式。例如用于逐步构建复杂的GUI。让构建一个完整类的过程可以更灵活可视化,比如装电脑,建造者可以使用不同的函数来插入电脑零件。

装饰器设计模式

可以动态的为某个对象动态添加功能,这些功能不是必须的。比如为一杯奶茶添加不同的小料。

适配器设计模式

将不兼容的接口转换为可兼容接口,提高接口的兼容性

异步

异步在不同线程的行为

异步方法(UI线程触发的除外)都会交由线程池处理,异步方法中执行到await 异步方法()时,将会暂停代码执行,直到异步方法()结束,接下来的代码可能不在原线程继续执行。

UI触发的异步方法由UI线程处理,执行到await 异步方法()时,将会暂停代码执行,直到异步方法()结束,接下来的代码会交由原线程继续执行。这个就叫同步上下文,这是个耗时操作,但是UI线程规定UI触发的方法(控件)必须要在UI线程访问它(线程安全),因此必须同步上下文。

📡通讯协议

##🔹 串行通信

###🔸 USART 协议

####⚡ 电气标准(物理层)
• TTL (Transistor-Transistor Logic)
• 逻辑电平:0V(低),3.3V / 5V(高)
• 传输模式:单端
• 传输距离:短(一般在 PCB 板内)
• 传输速率:高
• 设备支持数:点对点
• 抗干扰能力:弱
• 应用场景:MCU 之间短距离通信(STM32、ESP8266)
• RS232 (Recommended Standard 232)
• 逻辑电平:-12V~-3V(低),+3V~+12V(高)
• 传输模式:单端
• 传输距离:最远 15m
• 传输速率:较低(115200bps 以内)
• 设备支持数:点对点
• 抗干扰能力:弱
• 应用场景:PC 机串口通信、工业控制设备(POS 机、PLC)
• RS485 (Recommended Standard 485)
• 逻辑电平:-2.5V~+2.5V(差分信号)
• 传输模式:差分
• 传输距离:可达 1200m
• 传输速率:高
• 设备支持数:支持多点(最多 32 设备)
• 抗干扰能力:强
• 应用场景:工业自动化、楼宇控制(Modbus 协议,远程传感器)

###🔸 I²C(Inter-Integrated Circuit)
• 特点:
• 双线协议:SCL(时钟) 和 SDA(数据)
• 主从结构:支持多个从设备,但只有一个主设备
• 数据格式:
• 起始位(S) → 7-bit 设备地址 → 读/写位 → 应答位(ACK/NACK) → 数据传输 → 停止位(P)
• 速率:
• 标准模式(100kbps)
• 快速模式(400kbps)
• 高速模式(3.4Mbps)
• 通信方式:半双工
• 抗干扰能力:一般
• 应用场景:
• 传感器(MPU6050、BH1750)
• EEPROM 存储器
• 显示屏(OLED)

###🔸 SPI(Serial Peripheral Interface)
• 特点:
• 全双工通信
• 主从模式:一个主设备,多个从设备(用 CS 片选)
• 4 线协议:
• SCLK(时钟)
• MOSI(主输出从输入)
• MISO(主输入从输出)
• CS(片选)
• 数据格式:
• 数据按字节传输(8/16/32 位)
• 时钟极性(CPOL)和时钟相位(CPHA)可配置
• 速率:比 I²C 高(可达几十 MHz)
• 通信方式:全双工
• 抗干扰能力:强
• 应用场景:
• FLASH 存储器(W25Qxx)
• 屏幕(TFT 显示)
• SD 卡
• 高速传感器(ADXL345)

##🔹 并行通信
• 特点:
• 数据通过多个数据线同时传输
• 速率更快,但需要更多引脚
• 典型传输方式:地址总线 + 数据总线
• 常见协议:
• PCI(Peripheral Component Interconnect)
• PCIe(PCI Express)(高速串行)
• Parallel ATA(PATA)(旧式 IDE 硬盘接口)
• VGA / DVI(并行视频接口)

C#知识

数据类型

C# 分为值类型和引用类型,Class是引用类型,Struct是值类型。
new出来的内容才会放在堆上,不是new的内容则放在栈上。
new出来的内容中的值类型也会放在堆上。

类修饰符

访问修饰符

顶级类只有public和internal
internal:程序集访问权限,别的程序调用API时无法访问它
private:上层类访问权限
protected:上层类和派生类访问权限

行为修饰符

abstract:不可实例化,只能单继承,并且需要重写抽象方法
sealed:不可继承,防止扩展
static:不可实例化,不可继承,看作单例?
partial:告诉编译器在编译时合并

三大特性

封装

将数据和行为打包,隐藏内部细节,控制暴露的接口
提供API黑盒子(复杂度下降)、
修改逻辑无需修改API(维护性提高)、
防止意外修改(安全性)

继承

允许一个类从另一个类获取属性和方法
提高代码复用性
提高层次性
提供扩展能力,符合开闭原则

多态

同一种操作可以作用于不同类型的对象,并根据实际对象类型产生不同的行为,提供运行时决策能力
可以用同一个接口处理不同对象,简化接口
提高扩展能力,可以方便引入新子类
符合现实世界的模型,同操作不同的具体实现

装箱和拆箱

装即将值类型包装成类,具备面向对象的特性,并借助CLR管理生命周期
兼容性问题,有些接口只能处理引用类型

各业务之间解耦合:IoC Inversion of Control 依赖反转

IoC 是一种设计原则,它将对象创建、依赖管理的控制权从内部转移到外部,实现方式有DI Dependency Injection依赖注入(对象属性赋值)

CI/CD Continuous Integration/Continuous Delivery 持续集成与持续交付/部署

提升开发效率、降低集成风险并加速软件发布周期。
具体实现:将项目git到云端,jenkins自动对云端项目进行编译,发布,运行

解决数据库并发带来的问题:

读写分离

数据库的并发能力是有限的,读需求往往并发量很大,读过多可能会让写需求无法进行,因此需要将数据库分为读库和写库。
具体实现:写需求给到写库,完成后将数据同步到读库

多级缓存

将数据缓存到中间层,查询时先检查中间层再检查数据库
具体实现:部署MemoryCache到本地端,Redis在服务端做两个中间件,逐级查找

解决单个服务实例处理请求能力有限的问题:多实例附载均衡

具体实现:nginx均衡分配请求到不同实例(集群)

分布式系统中消费者并发能力小于生产者:RabbitMQ消息队列

具体实现:将并发的HTTP请求先放到消息队列中,根据处理能力逐步处理,达到异步的效果

提供多端Web调用:Web API

具体实现,将前端使用Web技术构建,向后端发送HTTP请求完成业务逻辑

责任链模式对请求的处理:中间件MiddleWare

一个请求需要通过多个中间处理,这些中间处理的部分叫做中间件

分布式锁

进程间的锁,当我们多个实例并发访问数据库时是非原子操作,需要使用分布式锁提供原子操作,防止超卖现象。

微服务

分布式文件系统

将文件的存储放在一个独立的微服务中,防止各个微服务要独立存储自己产生的文件
具体实现:将磁盘的文件上传到Minio中

数据库

分库分表

库的并发能力有限,当并发需求过高,需要分库,将原本在同一个库的内容分到别的库
库的数据过大会影响性能,需要分表,将原本在同一个表的内容分出另一个表

泛型(延迟类型声明)

1、满足不同的类型参数可以做相同的事
2、避免了值类型的装箱拆箱操作
策略是生成dll、exe时使用占位符代替,运行时环境