从"图形状态切换"理解 Parse 与状态驱动模式
面向初学者 | 无代码,纯思路
目录
- 先看一个场景
- [什么是 Parse](#什么是 Parse)
- 为什么图形能自动变色
- 一个数字装下很多个状态
- [为什么不用一个状态属性,而是散落的 9 个](#为什么不用一个状态属性,而是散落的 9 个)
- 三个转换器,各管一摊
- 从头到尾走一遍
- 总结
先看一个场景
想象你在操作一个检测软件,画布上有很多矩形图形。当发生以下情况时,图形外观会变化:
- 鼠标点击图形 → 边框变虚线
- 检测结果 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"的代码,全部由转换器自动处理。
总结
-
Parse = 读懂原始数据,翻译成结构化信息。 像体温计把水银柱翻译成"38.5 度"。
-
MultiBinding = 把多个属性同时打包送给一个处理函数。 像快递员一次性取走 9 个包裹。
-
位标记 = 一个整数装下多个"是/否"。 每个状态占一个二进制位,加一起就是一个整数。
-
转换器 = 查表决策。 输入状态 → 按优先级查表 → 输出对应的颜色/线型。
-
散落的 9 个 bool 是历史选择。 不是最优,但改动成本高于收益。
整个模式的本质:
散落的 bool 值(数据)
↓ Parse
结构化状态(语义)
↓ Converter
视觉表现(颜色/线型)
数据 → 语义 → 表现,三层分离,各不越界。