窗体显示和 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

+32 -7
View File
@@ -11,7 +11,7 @@ namespace System.Windows.Forms;
/// <summary>
/// 仿真控件的基类。属性保存在本进程里;控件所在窗体显示后,属性变化以 patch 消息发给浏览器渲染。
/// Font、BackColor、ForeColor 是"环境属性":没有显式设置时沿用父控件的值,只把显式设置的值发给浏览器。
/// Font、BackColor、ForeColor 是"环境属性":没有显式设置时沿用父控件的值(文本框等的颜色除外,见 OwnBackColor),只把显式设置的值发给浏览器。
/// </summary>
public class Control : Component, IWin32Window
{
@@ -178,24 +178,42 @@ public class Control : Component, IWin32Window
}
}
/// <summary>和 WinForms 一样:父控件(分组框、面板)禁用时子控件也是禁用的,读出 false、不响应操作;浏览器按同样的规则画成灰色</summary>
public bool Enabled
{
get => _enabled;
get => _enabled && (_parent?.Enabled ?? true);
set
{
if (SetProp(ref _enabled, value, nameof(Enabled))) OnEnabledChanged(EventArgs.Empty);
var old = Enabled;
SetProp(ref _enabled, value, nameof(Enabled));
if (Enabled != old) OnEnabledChanged(EventArgs.Empty);
}
}
/// <summary>
/// 没有显式设置时控件自己的颜色;null 表示沿用父控件。文本框、列表框、下拉框、日期选择是白底黑字(SystemColors.Window / WindowText),
/// 不沿用父控件:分组框设成白字时,里面的文本框还是黑字。
/// </summary>
private protected virtual Color? OwnBackColor => null;
private protected virtual Color? OwnForeColor => null;
/// <summary>能不能把 BackColor 设成透明(含半透明):窗体、文本框、列表框、下拉框、日期选择、数据表格不行,和 WinForms 一样报错</summary>
private protected virtual bool SupportsTransparentBackColor => true;
public virtual Color BackColor
{
get => !_backColor.IsEmpty ? _backColor : _parent?.BackColor ?? DefaultBackColor;
set => SetProp(ref _backColor, value, nameof(BackColor));
get => !_backColor.IsEmpty ? _backColor : OwnBackColor ?? _parent?.BackColor ?? DefaultBackColor;
set
{
if (!value.IsEmpty && value.A < 255 && !SupportsTransparentBackColor) throw new ArgumentException("控件不支持透明的背景色。");
SetProp(ref _backColor, value, nameof(BackColor));
}
}
public virtual Color ForeColor
{
get => !_foreColor.IsEmpty ? _foreColor : _parent?.ForeColor ?? DefaultForeColor;
get => !_foreColor.IsEmpty ? _foreColor : OwnForeColor ?? _parent?.ForeColor ?? DefaultForeColor;
set => SetProp(ref _foreColor, value, nameof(ForeColor));
}
@@ -302,7 +320,14 @@ public class Control : Component, IWin32Window
protected virtual void OnMouseLeave(EventArgs e) => MouseLeave?.Invoke(this, e);
protected virtual void OnTextChanged(EventArgs e) => TextChanged?.Invoke(this, e);
protected virtual void OnVisibleChanged(EventArgs e) => VisibleChanged?.Invoke(this, e);
protected virtual void OnEnabledChanged(EventArgs e) => EnabledChanged?.Invoke(this, e);
protected virtual void OnEnabledChanged(EventArgs e)
{
EnabledChanged?.Invoke(this, e);
// 自己没被禁用的子控件实际状态也跟着变,同样触发 EnabledChanged(WinForms 的 OnParentEnabledChanged)
for (var i = 0; i < Controls.Count; i++)
if (Controls[i]._enabled) Controls[i].OnEnabledChanged(e);
}
protected virtual void OnGotFocus(EventArgs e) => GotFocus?.Invoke(this, e);
protected virtual void OnLostFocus(EventArgs e) => LostFocus?.Invoke(this, e);
protected virtual void OnPaint(PaintEventArgs e) => Paint?.Invoke(this, e);
@@ -468,6 +468,8 @@ public class DataGridView : Control, ISupportInitialize
private bool _initializing;
private bool _rowsDirty;
private protected override bool SupportsTransparentBackColor => false;
public DataGridView()
{
Columns = new DataGridViewColumnCollection(this);
@@ -22,6 +22,10 @@ public class DateTimePicker : Control
protected override Size DefaultSize => new(200, 21);
private protected override Color? OwnBackColor => SystemColors.Window;
private protected override Color? OwnForeColor => SystemColors.WindowText;
private protected override bool SupportsTransparentBackColor => false;
public DateTime Value
{
get => _value;
+2
View File
@@ -23,6 +23,8 @@ public class Form : ContainerControl
private bool _modal;
private DialogResult _dialogResult;
private protected override bool SupportsTransparentBackColor => false;
public Form()
{
base.Visible = false;
@@ -1,5 +1,6 @@
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
namespace System.Windows.Forms;
@@ -10,6 +11,10 @@ public abstract class ListControl : Control
/// <summary>选中项的序号。列表项增删时直接调整(和 WinForms 一样不触发 SelectedIndexChanged)</summary>
private protected int SelectedIndexValue = -1;
private protected override Color? OwnBackColor => SystemColors.Window;
private protected override Color? OwnForeColor => SystemColors.WindowText;
private protected override bool SupportsTransparentBackColor => false;
public abstract int SelectedIndex { get; set; }
public string DisplayMember { get; set; } = "";
+4
View File
@@ -10,6 +10,10 @@ public abstract class TextBoxBase : Control
private bool _readOnly;
private BorderStyle _borderStyle = BorderStyle.Fixed3D;
private protected override Color? OwnBackColor => _readOnly ? SystemColors.Control : SystemColors.Window;
private protected override Color? OwnForeColor => SystemColors.WindowText;
private protected override bool SupportsTransparentBackColor => false;
public bool Multiline
{
get => _multiline;
+81
View File
@@ -672,6 +672,87 @@ test('虚拟时钟:拨时间、倍速后 DateTime.Now / Today 跟着走,恢
expect(runner.messages.some((m) => m.type === 'console' && m.text.includes('today=2024-01-16'))).toBe(true)
}, 30000)
test('颜色:分组框设成白字时,里面的标签沿用白字,文本框、列表框、下拉框还是白底黑字;只发显式设置的颜色', async () => {
const { runner } = await runMain(
'colors',
`
var form = new Form();
var group = new GroupBox { ForeColor = Color.White, BackColor = Color.Navy };
var label = new Label();
var text = new TextBox { Name = "text" };
var readOnly = new TextBox { ReadOnly = true };
var list = new ListBox();
var combo = new ComboBox();
var red = new TextBox { Name = "red", ForeColor = Color.Red };
group.Controls.AddRange(new Control[] { label, text, readOnly, list, combo, red });
form.Controls.Add(group);
Console.WriteLine(string.Join(",", new Control[] { label, text, list, combo, red }.Select(c => c.ForeColor.Name)));
Console.WriteLine(string.Join(",", new Control[] { label, text, readOnly, list, combo }.Select(c => c.BackColor.Name)));
Application.Run(form);`,
'using System.Drawing;\nusing System.Windows.Forms;',
)
const { form } = await runner.waitFor('formShown')
expect(findByName(form, 'text').props.ForeColor).toBeNull()
expect(findByName(form, 'red').props.ForeColor).toBe('#ff0000')
const output = runner.messages.flatMap((m) => (m.type === 'console' ? [m.text] : [])).join('')
expect(output).toBe('White,WindowText,WindowText,WindowText,Red\nNavy,Window,Control,Window,Window\n')
}, 30000)
test('禁用:分组框禁用后里面的控件 Enabled 读出 false、触发 EnabledChanged、点击不响应;重新启用后恢复', async () => {
const { runner } = await runMain(
'enabled',
`
var form = new Form();
var group = new GroupBox { Name = "group" };
var button = new Button { Name = "button" };
var off = new Button { Enabled = false };
group.Controls.Add(button);
group.Controls.Add(off);
form.Controls.Add(group);
button.EnabledChanged += (s, e) => Console.WriteLine("button changed=" + button.Enabled);
off.EnabledChanged += (s, e) => Console.WriteLine("off changed");
button.Click += (s, e) => Console.WriteLine("click");
var toggle = new Button { Name = "toggle" };
toggle.Click += (s, e) => { group.Enabled = !group.Enabled; Console.WriteLine("group=" + group.Enabled + " button=" + button.Enabled + " off=" + off.Enabled); };
form.Controls.Add(toggle);
Application.Run(form);`,
'using System.Windows.Forms;',
)
const { form } = await runner.waitFor('formShown')
const click = (name: string) => runner.send({ type: 'action', id: findByName(form, name).id, action: 'click' })
click('toggle')
await runner.waitFor('console', (m) => m.text.startsWith('group=False'))
click('button')
click('toggle')
await runner.waitFor('console', (m) => m.text.startsWith('group=True'))
click('button')
await runner.waitFor('console', (m) => m.text === 'click\n')
const output = runner.messages.flatMap((m) => (m.type === 'console' ? [m.text] : [])).join('')
// 浏览器只收到分组框自己的 Enabled,子控件按父控件画成禁用
expect(findByName(form, 'button').props.Enabled).toBe(true)
expect(output).toBe(
'button changed=False\ngroup=False button=False off=False\nbutton changed=True\ngroup=True button=True off=False\nclick\n',
)
}, 30000)
test('透明背景:文本框、窗体等设透明背景色和 WinForms 一样报错,标签、面板可以', async () => {
const { runner } = await runMain(
'transparent',
`
foreach (var control in new Control[] { new TextBox(), new ComboBox(), new ListBox(), new Form(), new Label(), new Panel() })
{
try { control.BackColor = Color.Transparent; Console.WriteLine(control.GetType().Name + " ok"); }
catch (ArgumentException ex) { Console.WriteLine(control.GetType().Name + " " + ex.Message); }
}`,
'using System.Drawing;\nusing System.Windows.Forms;',
)
await runner.finished()
const output = runner.messages.flatMap((m) => (m.type === 'console' ? [m.text] : [])).join('')
expect(output).toBe(
'TextBox 控件不支持透明的背景色。\nComboBox 控件不支持透明的背景色。\nListBox 控件不支持透明的背景色。\nForm 控件不支持透明的背景色。\nLabel ok\nPanel ok\n',
)
}, 30000)
test('TrackBar:拖动时先 ValueChanged 再 Scroll,超出范围的值截到两端;代码设超出范围的 Value 报错', async () => {
const { runner } = await runMain(
'trackbar',
+9 -2
View File
@@ -1,7 +1,7 @@
<script setup lang="ts">
// 属性窗口和事件窗口(仿 VS):显示选中控件的常用属性;改动立即写回 Designer.cs。
import { computed, ref, watch } from 'vue'
import { type ControlDef, FORM_DEF, type PropDef, controlDef } from './catalog'
import { type ControlDef, FORM_DEF, NO_TRANSPARENT_BACK_COLOR, type PropDef, controlDef } from './catalog'
import { type ChartModel, readChart } from './chart'
import ChartDialog from './ChartDialog.vue'
import type { DesignerDocument } from './document'
@@ -47,6 +47,13 @@ function current(prop: PropDef): Value | undefined {
return text === undefined ? prop.default : parseValue(text)
}
/** 颜色下拉框的选项:文本框等的背景色没有"透明"(已经设成透明的旧窗体照样显示出来) */
function colorChoices(prop: PropDef) {
const names = Object.keys(NAMED_COLORS)
if (prop.name !== 'BackColor' || !NO_TRANSPARENT_BACK_COLOR.has(typeName.value) || color(prop)?.name === 'Transparent') return names
return names.filter((name) => name !== 'Transparent')
}
/** 设成默认值时删除这条属性语句(VS 不写默认值) */
function set(prop: PropDef, value: Value) {
const isDefault = prop.default && JSON.stringify(prop.default) === JSON.stringify(value)
@@ -289,7 +296,7 @@ function renameHandler(event: string, value: string) {
<i class="swatch" :style="{ background: color(prop)?.css ?? 'transparent' }" />
<select :value="color(prop)?.name ?? (color(prop) ? 'custom' : '')" @change="setColor(prop, ($event.target as HTMLSelectElement).value)">
<option value="">(默认)</option>
<option v-for="(_, name) in NAMED_COLORS" :key="name" :value="name" :title="name">{{ COLOR_LABELS[name] ?? name }}</option>
<option v-for="name in colorChoices(prop)" :key="name" :value="name" :title="name">{{ COLOR_LABELS[name] ?? name }}</option>
<option v-if="color(prop) && !color(prop)!.name" value="custom" disabled>{{ color(prop)!.css }}</option>
</select>
<input
+3
View File
@@ -22,6 +22,9 @@ export interface PropDef {
category: '外观' | '行为' | '布局' | '数据'
}
/** BackColor 不能选透明的控件(WinForms 里设透明会报"控件不支持透明的背景色",VS 的属性窗口也不让选) */
export const NO_TRANSPARENT_BACK_COLOR = new Set(['Form', 'TextBox', 'ComboBox', 'ListBox', 'DateTimePicker', 'DataGridView'])
export interface EventDef {
/** WinForms 的事件名,事件处理方法的名字也用它(如 button1_Click) */
name: string
+2 -1
View File
@@ -75,8 +75,9 @@ export function previewTree(doc: DesignerDocument): ControlNode {
const build = (name: string): ControlNode => {
const type = types.get(name)!
const props: Record<string, unknown> = { ...DEVICE_DEFAULTS[type], ...propsOf(doc, name) }
// 设计时即使 Visible = false 也显示出来(和 VS 一样),方便选中和编辑
// 设计时即使 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)
+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>
+6 -3
View File
@@ -69,9 +69,12 @@ function close() {
>
<div v-if="!borderless" class="wf-titlebar" @pointerdown="startDrag" @pointermove="drag" @pointerup="dragStart = undefined">
<span class="wf-title">{{ displayText(p.Text) }}</span>
<span v-if="p.MinimizeBox !== false" class="wf-titlebar-button">─</span>
<span v-if="p.MaximizeBox !== false" class="wf-titlebar-button">☐</span>
<span class="wf-titlebar-button wf-titlebar-close" @pointerdown.stop @click="close">✕</span>
<!-- ControlBox = false 时标题栏右边的三个按钮都不显示(和 WinForms 一样) -->
<template v-if="p.ControlBox !== false">
<span v-if="p.MinimizeBox !== false" class="wf-titlebar-button">─</span>
<span v-if="p.MaximizeBox !== false" class="wf-titlebar-button">☐</span>
<span class="wf-titlebar-button wf-titlebar-close" @pointerdown.stop @click="close">✕</span>
</template>
</div>
<div
class="wf-client"
+11
View File
@@ -0,0 +1,11 @@
import { describe, expect, test } from 'bun:test'
import { contentAlignCss } from './format'
describe('Label 的 TextAlign', () => {
test('默认左上不加样式,其他位置用 flex 对齐', () => {
expect(contentAlignCss('TopLeft')).toEqual({})
expect(contentAlignCss(undefined)).toEqual({})
expect(contentAlignCss('MiddleCenter')).toEqual({ display: 'flex', alignItems: 'center', justifyContent: 'center', textAlign: 'center' })
expect(contentAlignCss('BottomRight')).toEqual({ display: 'flex', alignItems: 'flex-end', justifyContent: 'flex-end', textAlign: 'right' })
})
})
+12
View File
@@ -53,6 +53,18 @@ export function fontCss(font: FontValue | null | undefined): CSSProperties {
export const AMBIENT_BACK_COLOR: InjectionKey<Readonly<Ref<string>>> = Symbol('ambientBackColor')
export const DEFAULT_BACK_COLOR = '#f0f0f0'
/** 父控件实际是否可用:分组框、面板禁用时里面的控件也是禁用的(变灰、不响应操作),和 WinForms 一样 */
export const PARENT_ENABLED: InjectionKey<Readonly<Ref<boolean>>> = Symbol('parentEnabled')
/** Label 的 TextAlign(ContentAlignment,如 MiddleCenter)对应的 CSS:AutoSize 为 false 时文字在控件里的位置 */
export function contentAlignCss(align: unknown): CSSProperties {
const match = /^(Top|Middle|Bottom)(Left|Center|Right)$/.exec(String(align ?? ''))
if (!match || align === 'TopLeft') return {}
const flex = { Top: 'flex-start', Middle: 'center', Bottom: 'flex-end', Left: 'flex-start', Center: 'center', Right: 'flex-end' } as const
const [, vertical, horizontal] = match as unknown as [string, keyof typeof flex, keyof typeof flex]
return { display: 'flex', alignItems: flex[vertical], justifyContent: flex[horizontal], textAlign: horizontal.toLowerCase() as 'left' | 'center' | 'right' }
}
/** 背景色沿用父控件的控件(按钮、文本框等有自己的默认底色) */
export const AMBIENT_BACK_TYPES = new Set(['Label', 'Panel', 'GroupBox', 'CheckBox', 'RadioButton', 'PictureBox', 'TrackBar'])
+55 -6
View File
@@ -74,9 +74,22 @@
border-color: #005499;
}
/*
* 文本框、下拉框、列表框、日期选择的文字默认是黑色(SystemColors.WindowText),不沿用父控件的 ForeColor:
* 教材里分组框设成白字时,里面白底的文本框还是黑字。控件自己设了 ForeColor 时写在 style 上,优先于这里。
* 数据表格不在这里:它的单元格文字和 WinForms 一样跟着 ForeColor 走(没设置时沿用父控件)。
*/
.wf-textbox,
.wf-combobox,
.wf-listbox,
.wf-dtp {
color: #000;
}
/* 禁用的控件不管设了什么 ForeColor 都画成灰字(WinForms 画禁用文字用系统灰色),所以要压过 style 上的颜色 */
.wf-button:disabled,
.wf-disabled {
color: #a0a0a0;
color: #a0a0a0 !important;
}
.wf-label {
@@ -84,6 +97,16 @@
overflow: hidden;
}
/* Label 的 BorderStyle:单线是黑色细边框,立体是凹下去的边框 */
.wf-label.wf-border-FixedSingle {
border: 1px solid #000;
}
.wf-label.wf-border-Fixed3D {
border: 1px solid;
border-color: #a0a0a0 #fff #fff #a0a0a0;
}
.wf-textbox input,
.wf-textbox textarea,
.wf-combobox input {
@@ -105,8 +128,9 @@
border-color: #0078d7;
}
/* 文本框的背景色画在外层(控件的 BackColor 写在外层的 style 上),输入框本身透明 */
.wf-textbox {
/* 文本框、下拉框的背景色画在外层(控件的 BackColor 写在外层的 style 上),输入框本身透明 */
.wf-textbox,
.wf-combobox {
background: #fff;
}
@@ -114,8 +138,28 @@
background: #f0f0f0;
}
/* 禁用时和 WinForms 一样灰字(设了 ForeColor 也是灰字);没设背景色时是灰底 */
.wf-textbox:has(> :disabled),
.wf-combobox:has(> :disabled) {
background: #f0f0f0;
color: #6d6d6d !important;
}
/* 多行文本框的滚动条和 WinForms 一样由 ScrollBars 决定:默认没有,Vertical / Both 一直显示竖直滚动条(自动换行时没有水平滚动条) */
.wf-textbox textarea.wf-scrollbars-None,
.wf-textbox textarea.wf-scrollbars-Horizontal {
overflow: hidden;
}
.wf-textbox textarea.wf-scrollbars-Vertical,
.wf-textbox textarea.wf-scrollbars-Both {
overflow-x: hidden;
overflow-y: scroll;
}
.wf-textbox input,
.wf-textbox textarea {
.wf-textbox textarea,
.wf-combobox input {
background: transparent;
}
@@ -133,10 +177,9 @@
align-items: center;
justify-content: center;
font-size: 10px;
background: #fff;
}
/* 挂在 body 下,位置、宽度、缩放和字体由 ControlView.vue 按下拉框算好 */
/* 挂在 body 下,位置、宽度、缩放、字体和颜色由 ControlView.vue 按下拉框算好 */
.wf-combobox-list {
position: fixed;
z-index: 2000;
@@ -464,6 +507,12 @@
color: #333;
}
/* 不确定状态(CheckState.Indeterminate):框里一个实心小方块 */
.wf-checkbox-box[data-indeterminate='true'] {
background: #333;
box-shadow: inset 0 0 0 2px #fff;
}
.wf-radio-dot[data-checked='true'] {
background: radial-gradient(circle, #333 0 3px, #fff 3.5px);
}