窗体显示和 WinForms 对齐:文本框、下拉框、列表框、日期选择默认白底黑字,不沿用父控件的颜色(项目 8 白字分组框里的文本框填了字看不见);下拉框的背景色能显示、展开的列表跟着下拉框的颜色和字体;分组框、面板禁用时里面的控件也禁用(变灰、不响应操作,Enabled 读出 false 并触发 EnabledChanged);禁用的控件设了颜色也画灰字;窗体、文本框等设透明背景色和 WinForms 一样报错,设计器里也不提供"透明";多行文本框按 ScrollBars 显示滚动条;标签的边框和 TextAlign、窗体的 ControlBox、复选框的不确定状态能显示;设计器里 Enabled = false 的控件和 VS 一样照常显示
CI/CD / test (push) Canceled after 0s
CI/CD / deploy (push) Canceled after 0s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-27 16:41:46 +08:00
1 parent a2f03ee57f
commit 50946467f6
15 files changed
+247 -26

No files matched your search

+19 -7
View File
@@ -3,7 +3,7 @@
// 用户操作只通过 act() 上报,由运行器按真实 WinForms 的顺序触发事件。
import { computed, inject, onBeforeUnmount, provide, ref, watch, type CSSProperties } from 'vue'
import type { ControlNode, UserAction } from '@iot/shared'
import { AMBIENT_BACK_COLOR, AMBIENT_BACK_TYPES, boxCss, DEFAULT_BACK_COLOR, displayText } from './format'
import { AMBIENT_BACK_COLOR, AMBIENT_BACK_TYPES, boxCss, contentAlignCss, DEFAULT_BACK_COLOR, displayText, PARENT_ENABLED } from './format'
import { backgroundCss, imageSources, RUN_IMAGES, srcset, type ImageValue } from './images'
import ChartControl from './ChartControl.vue'
import DataGridViewControl from './DataGridViewControl.vue'
@@ -19,7 +19,10 @@ const props = defineProps<{
const p = computed(() => props.node.props)
// WinForms 中先加入 Controls 的控件在最上层,所以倒序渲染(后渲染的 DOM 元素在上面)
const childrenTopLast = computed(() => [...props.node.children].reverse())
const enabled = computed(() => p.value.Enabled !== false)
const parentEnabled = inject(PARENT_ENABLED, ref(true))
/** 实际是否可用:自己和所有父控件都可用(和 WinForms 一样,运行器也按这个规则忽略禁用控件上的操作) */
const enabled = computed(() => p.value.Enabled !== false && parentEnabled.value)
provide(PARENT_ENABLED, enabled)
const runImages = inject(RUN_IMAGES, new Map<string, string>())
const ambient = inject(AMBIENT_BACK_COLOR, ref(DEFAULT_BACK_COLOR))
/** 这个控件实际的背景色,子控件没设置背景色时沿用 */
@@ -65,12 +68,16 @@ function openDropDown() {
const el = combo.value
if (!el) return
const rect = el.getBoundingClientRect()
// 列表挂在页面最外层,字体、颜色从下拉框本身取(和 WinForms 一样跟着下拉框的 Font、ForeColor、BackColor)
const css = getComputedStyle(el)
listStyle.value = {
left: `${rect.left}px`,
top: `${rect.bottom}px`,
width: `${el.offsetWidth}px`,
transform: `scale(${rect.width / el.offsetWidth || 1})`,
font: getComputedStyle(el).font,
font: css.font,
color: css.color,
backgroundColor: css.backgroundColor,
}
// 先触发 Click(教材在 Click 里填充串口列表),列表随后按最新的 Items 显示
act({ action: 'dropDown' })
@@ -131,8 +138,8 @@ const itemHeight = computed(() => `${(p.value.ItemHeight as number | undefined)
<div
v-else-if="node.type === 'Label'"
class="wf-control wf-label"
:class="{ 'wf-autosize': p.AutoSize === true, 'wf-disabled': !enabled }"
:style="style"
:class="[`wf-border-${p.BorderStyle ?? 'None'}`, { 'wf-autosize': p.AutoSize === true, 'wf-disabled': !enabled }]"
:style="[style, p.AutoSize === true ? {} : contentAlignCss(p.TextAlign)]"
@click="act({ action: 'click' })"
>
{{ displayText(p.Text) }}
@@ -142,6 +149,7 @@ const itemHeight = computed(() => `${(p.value.ItemHeight as number | undefined)
<div v-else-if="node.type === 'TextBox'" class="wf-control wf-textbox" :style="style">
<textarea
v-if="p.Multiline"
:class="`wf-scrollbars-${p.ScrollBars ?? 'None'}`"
:value="String(p.Text ?? '')"
:readonly="p.ReadOnly === true"
:disabled="!enabled"
@@ -232,7 +240,11 @@ const itemHeight = computed(() => `${(p.value.ItemHeight as number | undefined)
:style="style"
@click="act({ action: 'click' })"
>
<span :class="node.type === 'CheckBox' ? 'wf-checkbox-box' : 'wf-radio-dot'" :data-checked="p.Checked === true" />
<span
:class="node.type === 'CheckBox' ? 'wf-checkbox-box' : 'wf-radio-dot'"
:data-checked="p.Checked === true && p.CheckState !== 'Indeterminate'"
:data-indeterminate="p.CheckState === 'Indeterminate'"
/>
<span class="wf-check-text">{{ displayText(p.Text) }}</span>
</div>
@@ -245,7 +257,7 @@ const itemHeight = computed(() => `${(p.value.ItemHeight as number | undefined)
</div>
<!-- GroupBox -->
<div v-else-if="node.type === 'GroupBox'" class="wf-control wf-groupbox" :style="style">
<div v-else-if="node.type === 'GroupBox'" class="wf-control wf-groupbox" :class="{ 'wf-disabled': !enabled }" :style="style">
<!-- 标题处的边框断开、透出底下的背景(教材项目 8 的分组框放在背景图上,白色标题) -->
<fieldset class="wf-groupbox-frame">
<legend v-if="p.Text" class="wf-groupbox-caption">{{ displayText(p.Text) }}</legend>