Files
IOT_SIM/web/src/designer/preview.ts
T
xuyueandClaude Opus 5.5 91b39603a2 项目 5 补齐:旧版线程 API(Suspend / Resume / Abort)与 ListBox
- 编译时把 Thread 的 Suspend / Resume / Abort、ThreadState、Sleep、new Thread 改写成平台的
  协作式实现:被挂起或终止的线程在 Sleep、后台改控件、调设备类库、弹提示框时停下或结束;
  ThreadStateException 等行为与 .NET Framework 一致;编辑器不再提示"已过时"
- ListBox:列表项、单选、SelectedIndexChanged 先于 Click、删除选中项后取消选中;
  ComboBox 与 ListBox 共用 ListControl 和列表项集合;设计器工具箱加"列表框"
- 评分规则的"包含"对列表框检查所有列表项
- 示例:项目 5 任务 2(烟雾,线程)、任务 3(推送灾情,二维数组、两个窗体、ListBox),
  参考答案修正书上直接 Abort 挂起线程、烟雾记成"发现火情"的问题
- 一直不换行的 Console 输出攒满 4096 字符先发出,避免占满内存
- 测试:旧版线程 API、ListBox 事件顺序、两个示例的端到端和评分测试

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 20:33:12 +08:00

88 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 把设计器文档转换成与运行时相同格式的控件树,直接用运行时的 ControlView 渲染,
// 保证设计时看到的样子和运行时一致。
import type { ControlNode } from '@iot/shared'
import { controlDef } from './catalog'
import type { DesignerDocument } from './document'
import { childrenOf, findBlock, getItems, listControls } from './edit'
import { parseValue, type Value } from './values'
/** 窗体在预览树里的 id;控件的 id 就是控件名 */
export const FORM_ID = '__form'
/** 运行时控件树里的类型名:System.Windows.Forms.Button → Button,教材控件库保留完整类名 */
function clientType(type: string) {
return type.startsWith('System.Windows.Forms.') ? type.slice('System.Windows.Forms.'.length) : type
}
/** 把 Value 转换成运行时消息里的属性值格式(见 web/src/winforms/format.ts) */
export function runtimeValue(value: Value): unknown {
switch (value.kind) {
case 'string':
case 'char':
return value.value
case 'bool':
case 'int':
case 'float':
return value.value
case 'point':
return { x: value.x, y: value.y }
case 'size':
return { width: value.width, height: value.height }
case 'color':
return value.css
case 'font':
return { family: value.family, size: value.size, bold: value.bold, italic: value.italic, underline: value.underline, strikeout: value.strikeout }
case 'enum':
return value.member
case 'raw':
return undefined
}
}
function propsOf(doc: DesignerDocument, target: string): Record<string, unknown> {
const props: Record<string, unknown> = {}
for (const stmt of findBlock(doc, target)?.stmts ?? []) {
if (stmt.kind !== 'prop' || stmt.path.includes('.')) continue
const value = runtimeValue(parseValue(stmt.value))
if (value !== undefined) props[stmt.path] = value
}
return props
}
/** 教材控件在 Designer.cs 里不写、但运行时构造函数会设置的外观 */
const DEVICE_DEFAULTS: Record<string, Record<string, unknown>> = {
'WinFormsDeviceControlLibrary.LED': { BackColor: '#000000', ForeColor: '#ff0000', Font: { family: '宋体', size: 70, bold: false, italic: false, underline: false, strikeout: false } },
'WinFormsDeviceControlLibrary.Lamp': { BackColor: '#ffffff' },
'WinFormsDeviceControlLibrary.Alarm': { BackColor: '#ffffff' },
}
export function previewTree(doc: DesignerDocument): ControlNode {
const types = new Map(listControls(doc).map((c) => [c.name, c.type]))
const build = (name: string): ControlNode => {
const type = types.get(name)!
const props: Record<string, unknown> = { ...DEVICE_DEFAULTS[type], ...propsOf(doc, name) }
// 设计时即使 Visible = false 也显示出来(和 VS 一样),方便选中和编辑
props.Visible = true
if (type === 'System.Windows.Forms.ComboBox' || type === 'System.Windows.Forms.ListBox') props.Items = getItems(doc, name).map((item) => {
const v = parseValue(item)
return v.kind === 'string' ? v.value : item
})
// 设计器里不需要滚动:LED 只显示黑底
if (type === 'WinFormsDeviceControlLibrary.LED') props.LedText = ''
return { id: name, type: clientType(type), name, props, children: childrenOf(doc, name).filter((c) => types.has(c)).map(build) }
}
const formProps = propsOf(doc, '')
return {
id: FORM_ID,
type: 'Form',
name: doc.formName,
props: { ClientSize: { width: 300, height: 300 }, ...formProps, Visible: true },
children: childrenOf(doc, '').filter((c) => types.has(c)).map(build),
}
}
/** 非可视组件(Timer 等),显示在窗体下方的组件栏 */
export function componentsOf(doc: DesignerDocument) {
return listControls(doc).filter((c) => c.parent === null && controlDef(c.type).component === true)
}