// 把设计器文档转换成与运行时相同格式的控件树,直接用运行时的 ControlView 渲染, // 保证设计时看到的样子和运行时一致。 import type { ControlNode } from '@iot/shared' import { controlDef } from './catalog' import { CHART_TYPE, previewChart, readChart } from './chart' import type { DesignerDocument } from './document' import { childrenOf, findBlock, getItems, listControls } from './edit' import { resourceNameOf, resourceUrl } from './resources' import { parseValue, type Value } from './values' /** 窗体在预览树里的 id;控件的 id 就是控件名 */ export const FORM_ID = '__form' /** 运行时控件树里的类型名:System.Windows.Forms.Button → Button,教材控件库保留完整类名 */ function clientType(type: string) { if (type === 'AxWMPLib.AxWindowsMediaPlayer') return 'AxWindowsMediaPlayer' if (type === 'System.Windows.Forms.DataVisualization.Charting.Chart') return 'Chart' 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 { const props: Record = {} for (const stmt of findBlock(doc, target)?.stmts ?? []) { if (stmt.kind !== 'prop' || stmt.path.includes('.')) continue // 项目资源里的图片:预览时直接显示素材文件 const resource = resourceNameOf(stmt.value) if (resource) { props[stmt.path] = { key: '', url: resourceUrl(resource), width: 0, height: 0 } continue } const value = runtimeValue(parseValue(stmt.value)) if (value !== undefined) props[stmt.path] = value } return props } /** 教材控件在 Designer.cs 里不写、但运行时构造函数会设置的外观 */ const DEVICE_DEFAULTS: Record> = { '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 = { ...DEVICE_DEFAULTS[type], ...propsOf(doc, name) } // 设计时即使 Visible = false 也显示出来、Enabled = false 也不画成灰色(和 VS 一样,运行后才生效),方便选中和编辑 props.Visible = true props.Enabled = 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 = '' // 图表和 VS 一样用示例数据画出各个系列 if (type === CHART_TYPE) props.Chart = previewChart(readChart(doc, name)) // 和 VS 一样显示今天的日期 if (type === 'System.Windows.Forms.DateTimePicker') props.Text = previewDate(String(props.Format ?? 'Long')) 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), } } function previewDate(format: string) { const now = new Date() const [y, m, d] = [now.getFullYear(), now.getMonth() + 1, now.getDate()] const time = `${now.getHours()}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}` return format === 'Short' ? `${y}/${m}/${d}` : format === 'Time' ? time : `${y}年${m}月${d}日` } /** 非可视组件(Timer 等),显示在窗体下方的组件栏 */ export function componentsOf(doc: DesignerDocument) { return listControls(doc).filter((c) => c.parent === null && controlDef(c.type).component === true) }