Files
IOT_SIM/web/src/winforms/ControlView.vue
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

174 lines
6.2 KiB
Vue
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.
<script setup lang="ts">
// 按控件类型渲染一个 WinForms 控件及其子控件。
// 用户操作只通过 act() 上报,由运行器按真实 WinForms 的顺序触发事件。
import { computed, ref } from 'vue'
import type { ControlNode, UserAction } from '@iot/shared'
import { boxCss, displayText } from './format'
import LedControl from './LedControl.vue'
const props = defineProps<{
node: ControlNode
act: (id: string, action: UserAction) => void
}>()
const p = computed(() => props.node.props)
// WinForms 中先加入 Controls 的控件在最上层,所以倒序渲染(后渲染的 DOM 元素在上面)
const childrenTopLast = computed(() => [...props.node.children].reverse())
const enabled = computed(() => p.value.Enabled !== false)
const style = computed(() => boxCss(p.value, props.node.type === 'Label' && p.value.AutoSize === true))
function act(action: UserAction) {
if (enabled.value) props.act(props.node.id, action)
}
// TextBox
const passwordChar = computed(() => {
if (p.value.UseSystemPasswordChar) return '●'
const ch = p.value.PasswordChar as string | undefined
return ch && ch !== '\0' ? ch : ''
})
// ComboBox
const dropDownOpen = ref(false)
const items = computed(() => (p.value.Items as string[] | undefined) ?? [])
const editable = computed(() => p.value.DropDownStyle !== 'DropDownList')
function openDropDown() {
if (!enabled.value) return
if (dropDownOpen.value) {
dropDownOpen.value = false
return
}
// 先触发 Click(教材在 Click 里填充串口列表),列表随后按最新的 Items 显示
act({ action: 'dropDown' })
dropDownOpen.value = true
}
function selectItem(index: number) {
dropDownOpen.value = false
act({ action: 'select', index })
}
// ListBox
const itemHeight = computed(() => `${(p.value.ItemHeight as number | undefined) ?? 12}px`)
</script>
<template>
<!-- Button -->
<button
v-if="node.type === 'Button'"
class="wf-control wf-button"
:style="style"
:disabled="!enabled"
@click="act({ action: 'click' })"
>
{{ displayText(p.Text) }}
</button>
<!-- Label -->
<div
v-else-if="node.type === 'Label'"
class="wf-control wf-label"
:class="{ 'wf-autosize': p.AutoSize === true, 'wf-disabled': !enabled }"
:style="style"
@click="act({ action: 'click' })"
>
{{ displayText(p.Text) }}
</div>
<!-- TextBox -->
<div v-else-if="node.type === 'TextBox'" class="wf-control wf-textbox" :style="style">
<textarea
v-if="p.Multiline"
:value="String(p.Text ?? '')"
:readonly="p.ReadOnly === true"
:disabled="!enabled"
@input="act({ action: 'text', value: ($event.target as HTMLTextAreaElement).value })"
/>
<input
v-else
:type="passwordChar ? 'password' : 'text'"
:value="String(p.Text ?? '')"
:readonly="p.ReadOnly === true"
:disabled="!enabled"
@input="act({ action: 'text', value: ($event.target as HTMLInputElement).value })"
/>
</div>
<!-- ComboBox -->
<div v-else-if="node.type === 'ComboBox'" class="wf-control wf-combobox" :style="style">
<input
:value="String(p.Text ?? '')"
:readonly="!editable"
:disabled="!enabled"
@mousedown="!editable && openDropDown()"
@input="act({ action: 'text', value: ($event.target as HTMLInputElement).value })"
/>
<span class="wf-combobox-arrow" @mousedown.prevent="openDropDown">▾</span>
<ul v-if="dropDownOpen" class="wf-combobox-list">
<li v-for="(item, index) in items" :key="index" @mousedown.prevent="selectItem(index)">{{ item }}</li>
<li v-if="items.length === 0" class="wf-combobox-empty">&nbsp;</li>
</ul>
</div>
<!-- ListBox:点空白处只触发 Click,点某一项先选中再触发 Click -->
<div
v-else-if="node.type === 'ListBox'"
class="wf-control wf-listbox"
:class="[`wf-listbox-border-${p.BorderStyle ?? 'Fixed3D'}`, { 'wf-disabled': !enabled }]"
:style="style"
@click.self="act({ action: 'click' })"
>
<div
v-for="(item, index) in items"
:key="index"
class="wf-listbox-item"
:class="{ 'wf-listbox-selected': index === p.SelectedIndex }"
:style="{ height: itemHeight, lineHeight: itemHeight }"
@click="act({ action: 'select', index })"
@dblclick="act({ action: 'doubleClick' })"
>
{{ item }}
</div>
</div>
<!-- CheckBox / RadioButton:自绘勾选框,状态完全由运行器决定(点击只上报,不在浏览器里自行切换) -->
<div
v-else-if="node.type === 'CheckBox' || node.type === 'RadioButton'"
class="wf-control wf-check"
:class="{ 'wf-disabled': !enabled, 'wf-autosize': p.AutoSize === true }"
:style="style"
@click="act({ action: 'click' })"
>
<span :class="node.type === 'CheckBox' ? 'wf-checkbox-box' : 'wf-radio-dot'" :data-checked="p.Checked === true" />
<span class="wf-check-text">{{ displayText(p.Text) }}</span>
</div>
<!-- 教材控件库:灯泡、报警灯(自绘图片,尺寸与教材控件一致) -->
<div v-else-if="node.type === 'WinFormsDeviceControlLibrary.Lamp'" class="wf-control wf-device-image" :style="style">
<img :src="p.IsOn ? '/devices/lamp-on.svg' : '/devices/lamp-off.svg'" alt="" draggable="false" />
</div>
<div v-else-if="node.type === 'WinFormsDeviceControlLibrary.Alarm'" class="wf-control wf-device-image" :style="style">
<img :src="p.IsOn ? '/devices/alarm-on.svg' : '/devices/alarm-off.svg'" alt="" draggable="false" />
</div>
<!-- GroupBox -->
<div v-else-if="node.type === 'GroupBox'" class="wf-control wf-groupbox" :style="style">
<div class="wf-groupbox-frame" />
<span v-if="p.Text" class="wf-groupbox-caption">{{ displayText(p.Text) }}</span>
<ControlView v-for="child in childrenTopLast" :key="child.id" :node="child" :act="props.act" />
</div>
<!-- 教材控件库:LED 显示屏 -->
<LedControl
v-else-if="node.type === 'WinFormsDeviceControlLibrary.LED'"
:node="node"
:style="style"
/>
<!-- 其他容器(Panel、UserControl 等):按普通容器渲染 -->
<div v-else class="wf-control wf-panel" :style="style" :title="node.type">
<ControlView v-for="child in childrenTopLast" :key="child.id" :node="child" :act="props.act" />
</div>
</template>