7.28 从“图形状态切换“理解 Parse 与状态驱动模式

从"图形状态切换"理解 Parse 与状态驱动模式

面向初学者 | 无代码,纯思路

目录

  1. 先看一个场景
  2. [什么是 Parse](#什么是 Parse)
  3. 为什么图形能自动变色
  4. 一个数字装下很多个状态
  5. [为什么不用一个状态属性,而是散落的 9 个](#为什么不用一个状态属性,而是散落的 9 个)
  6. 三个转换器,各管一摊
  7. 从头到尾走一遍
  8. 总结

先看一个场景

想象你在操作一个检测软件,画布上有很多矩形图形。当发生以下情况时,图形外观会变化:

  • 鼠标点击图形 → 边框变虚线
  • 检测结果 OK → 背景变绿色
  • 检测结果 NG → 背景变红色
  • 正在扫描 → 背景变橙色
  • 既没选中也没状态 → 默认实线边框

问题来了:程序怎么知道一个图形"当前是什么状态",然后自动切换颜色?


什么是 Parse

Parse = 读懂一份数据,翻译成另一种格式。

生活类比 原始数据 翻译后
看体温计 水银柱 "38.5 度"
看红绿灯 亮红灯 "停"
读成绩单 "张三 数学 85" {名字:"张三", 科目:"数学", 成绩:85}

程序里的 Parse 完全一样:

复制代码
输入:一堆碎片信息 → 输出:一个有意义的整体

为什么图形能自动变色

第一步:ViewModel 告诉外界"我有什么属性"

每个图形对象对外暴露一组属性,全是"是/否":

复制代码
这个图形被选中了吗?    → 是 / 否
这个图形被鼠标悬停了吗?  → 是 / 否
这个图形正在被扫描吗?    → 是 / 否
这个图形扫描完了吗?      → 是 / 否
这个图形正在重建吗?      → 是 / 否
这个图形重建完了吗?      → 是 / 否
这个图形正在检测吗?      → 是 / 否
这个图形检测结果 OK?     → 是 / 否
这个图形检测结果 NG?     → 是 / 否

总共 9 个问题,每个答案不是"是"就是"否"。

第二步:WPF 的"多路绑定"把这些答案打包送出

WPF 有一个机制叫 MultiBinding(多路绑定) ,它能把多个属性同时、按顺序送给一个处理函数:

复制代码
MultiBinding 说:"我把 9 个属性的值打包成一个数组,你按顺序拿------"

第一个位置 → 是否选中
第二个位置 → 是否悬停
第三个位置 → 是否扫描
...
第九个位置 → 是否 NG

第三步:Parse 函数把"是/否"转换成"状态对象"

收到的就是一堆 true / false,比如:

复制代码
[true, false, true, false, false, false, false, false, false]

Parse 的工作:读懂这些 true/false 分别表示什么,把它们合并成一个复合状态。

复制代码
[true, false, true, ...]
 ↓
"这个图形被选中了 + 正在扫描中"

第四步:转换器根据状态决定颜色

Parse 输出的状态交给不同的"转换器":

复制代码
                      Parse 输出的状态
                           │
          ┌────────────────┼────────────────┐
          边框转换器        背景转换器         描边转换器
          决定线型          决定背景色         决定描边色

每个转换器做的事情就是查表:

复制代码
如果状态里有"NG"        → 给红色
如果状态里有"正在检测"    → 给青色
如果状态里有"选中"       → 给虚线
如果什么都没有           → 给实线默认色

有优先级:NG > OK > 检测中 > 扫描中 > 悬停 > 选中 > 默认


一个数字装下很多个状态

你会问:一个图形同时"选中"又"正在扫描"又"结果 NG",怎么存?

用"位标记"技巧

把每个状态分配一个二进制位:

复制代码
选中     → 第 0 位
悬停     → 第 1 位
扫描中   → 第 2 位
检测中   → 第 5 位
结果 OK  → 第 6 位
结果 NG  → 第 7 位
...

一个 int 有 32 位,足够装下所有状态。

复制代码
一个图形"选中 + 扫描中 + NG"
→ 第 0 位置 1、第 2 位置 1、第 7 位置 1
→ 最终存成一个整数

查询时很简单:"状态里有没有 NG?"→ 看看第 7 位是不是 1。

特殊处理:OK 和 NG 互斥

一个检测结果不可能同时 OK 又 NG。所以加一条规则:

复制代码
如果传入"OK" → 先加上 OK 标记,再擦掉 NG 标记
如果传入"NG" → 先加上 NG 标记,再擦掉 OK 标记

后写入的会覆盖前一个。


基础语法课堂

如果你是初学者,下面这些写法可能会让你困惑。我们用人话解释一下。

位运算:||=&~

这组符号用来操控位标记状态。

加标记 |(OR,或)

复制代码
我有一个状态:           选中(第 0 位是 1)
我想再加"正在扫描":     选中 | 正在扫描
结果:                   选中 + 正在扫描(两个 1)

| 的作用是:只要任意一边的位是 1,结果就是 1。 所以适合"往上加"标记。

|=| 的简写

复制代码
状态 = 状态 | 正在扫描   ← 完整写法
状态 |= 正在扫描          ← 简写,效果一样

就像 x = x + 5 可以简写成 x += 5


查标记 &(AND,与)

复制代码
想知道状态里有没有"NG"?
状态 & NG → 如果结果不为 0,说明有 NG

& 的作用是:只有两边的位都是 1,结果才是 1。

复制代码
状态 = 选中 | NG              (第 0 位和第 10 位是 1)
状态 & NG = 非零              → 有 NG
状态 & OK = 0                 → 没有 OK

擦除标记 ~ 配合 &(NOT,取反)

复制代码
想擦掉状态里的"OK"标记?
状态 = 状态 & (~OK)

~ 把 OK 的二进制位全部翻转:原来 OK 对应位是 1,翻转后变成 0,原来别的位是 0,翻转后全是 1。

然后用 & 去"与"一下:OK 对应的位会变成 0(被擦掉),其他位保持不变。

复制代码
OK       = 0001000000    (假设 OK 占第 6 位)
~OK      = 1110111111    (除了第 6 位,其他全是 1)

状态 & ~OK → OK 对应的位被清 0,其他位原样保留

三元表达式:条件 ? 是 : 否

这是 if-else 的迷你版:

复制代码
if (条件) {
    取值A;
} else {
    取值B;
}

等价于:条件 ? 取值A : 取值B

通常用来做防御,防止传入的值是 null 导致崩溃:

复制代码
传入值可能是 null → 如果是 null,用 false 代替
传入值可能是 "abc" → 不是 bool,也用 false 代替
传入值 = true     → 正常使用

is bool 变量名 模式匹配

这个东西同时干了三件事:

复制代码
某个值 is bool s
第几步 干什么 举例
判断这个值是不是 bool 类型 null 不是 / "abc" 不是 / true 是
如果是,把值存到变量 s 里 s = true
如果否,s 不存在,整个表达式为 false ---

变量名没有含义,叫 s、flag、x 都行,用完就扔。

极简写法:

复制代码
某个值 is true

不需要起变量名,直接判断"是不是 true"。null 和 false 都会被视为"不是 true"。


WPF MultiBinding 怎么传参数

WPF 会把 XAML 里绑定的属性按顺序打包成数组

复制代码
XAML 写的顺序:
第 1 个绑定 → 数组[0]
第 2 个绑定 → 数组[1]
第 3 个绑定 → 数组[2]

下标就是绑定的序号。 转换器收到的就是一个 object[],里面每个位置对应一个绑定的属性值。

注意:东西类型是 object,而不是 bool。因为 WPF 的绑定系统在传值时不检查类型,什么值都能塞进去(null、字符串、数字......都可能)。所以转换器里必须做类型判断。


一句话总结这堆语法

符号 作用 一句话
` ` 加标记
` =` 加标记简写
& 查标记 "看看某一位是不是 1"
~ 取反 "除了这一位,其他全变"
& ~ 擦标记 "把某一位清零"
? : 三元 "迷你 if-else"
is bool s 模式匹配 "判断 + 取值 + 防御"
数组下标 取值 "第几个绑定的属性"

为什么不用一个状态属性,而是散落的 9 个

理想的设计是:ViewModel 直接暴露一个"状态"属性,而不是 9 个散落的 true/false。

复制代码
// 理想
图形.当前状态 = 选中 | 扫描中 | 结果OK

// 实际情况
图形.是否选中 = true
图形.是否扫描中 = true
图形.是否结果OK = true
...

为什么没按理想来?

因为这些 true/false 属性不只是为 UI 颜色服务的。它们还要被业务逻辑直接读取:

复制代码
"如果选中了 → 启用删除按钮"
"如果正在扫描 → 禁用编辑"

这些业务判断直接读"是否选中"比查位标记要直观。所以属性先以散落的 bool 形式存在,后来要给 UI 加自动变色功能时,用 MultiBinding 打了个补丁。

这是真实项目中常见的"历史选择"------不是最优方案,但改动成本高于收益。

按长度分组的潜在问题

Parse 函数里有一段按"传了多少个属性"做分组判断的逻辑。这种写法有一个隐患:

如果一个图形只绑了"是否选中"这一个属性(长度=1),那么"是否选中"的状态会被静默跳过,永远解析不到。

现在没出 bug,纯粹因为所有用到这个转换器的图形都恰好绑了至少两个属性,但并没有任何机制保证这一点。


三个转换器,各管一摊

转换器 管什么 怎么判断
边框转换器 实线/虚线/无线 只看"选中"和"悬停"
背景转换器 填充色 按 NG > OK > 检测中 > 扫描 > 悬停 优先级
描边转换器 边框颜色 同背景逻辑,但用不透明颜色

同一个状态值,三个转换器分别读自己关心的部分,互不干扰。


从头到尾走一遍

复制代码
1. 用户点击一个图形
   → 图形.是否选中 = true

2. WPF 检测到属性变化
   → 把 9 个属性值打包:[true, false, false, ...]

3. Parse 处理
   → 读第一个位置:true → 打上"选中"标记
   → 读其他位置:都是 false → 不加标记
   → 输出状态 = "选中"

4. 边框转换器拿到"选中"
   → 查表:选中 → 虚线

5. 背景转换器拿到"选中"
   → 按优先级查表:不是 NG,不是 OK,不是检测...是选中 → 透明色

6. 描边转换器拿到"选中"
   → 选中 → 返回默认描边色

7. 画布刷新
   → 图形边框变虚线,用户看到视觉反馈

整个过程无需手写任何"如果 xx 就把颜色设为 yy"的代码,全部由转换器自动处理。


总结

  1. Parse = 读懂原始数据,翻译成结构化信息。 像体温计把水银柱翻译成"38.5 度"。

  2. MultiBinding = 把多个属性同时打包送给一个处理函数。 像快递员一次性取走 9 个包裹。

  3. 位标记 = 一个整数装下多个"是/否"。 每个状态占一个二进制位,加一起就是一个整数。

  4. 转换器 = 查表决策。 输入状态 → 按优先级查表 → 输出对应的颜色/线型。

  5. 散落的 9 个 bool 是历史选择。 不是最优,但改动成本高于收益。

整个模式的本质:

复制代码
散落的 bool 值(数据)
    ↓ Parse
结构化状态(语义)
    ↓ Converter
视觉表现(颜色/线型)

数据 → 语义 → 表现,三层分离,各不越界。

相关推荐
逻极3 小时前
C# 从入门到精通:现代云原生与AI应用开发实战
ai·云原生·c#·.net
geovindu18 小时前
CSharp: Breadth First Search Algorithm and Depth First Search Algorithm
开发语言·后端·算法·c#·.net·搜索算法
小羊先生car1 天前
RTOS-F429-HAL-(动/静态)任务的创建(2026/7/27)
开发语言·算法·c#
地球驾驶员1 天前
NX二次开发C#-获取体的外表面
开发语言·c#
向夏威夷 梦断明暄1 天前
从 Bun 的 Rust 重写,看 C# 如何重建 AI 基础设施层
人工智能·rust·c#
心平气和量大福大1 天前
C#-WPF-控件-LiveChart图表-线性2(LineSeries)-数据绑定
开发语言·c#·wpf
海盗12341 天前
微软技术周报2026-07-27
microsoft·c#·.net
code bean2 天前
【C#】 `Channel<T>` 深度解析:生产者-消费者模式的现代解法
数据结构·c#
吴可可1232 天前
C# CAD二次开发:合并首尾重合多段线
c#