
WinUI NumberBox 控件实战指南数值输入、表达式计算、步进与格式化【免费下载链接】microsoft-ui-xamlWinUI: a modern UI framework with a rich set of controls and styles to build dynamic and high-performing Windows applications.项目地址: https://gitcode.com/GitHub_Trending/mi/microsoft-ui-xaml导读本文基于本仓库的 NumberBox 设计规范 与 控件实现源码系统讲解 WinUI 3 中NumberBox控件的完整用法从基础的数值绑定、Header/PlaceholderText 标签到增量步进SpinButton、内联表达式求值、输入验证与区域感知的数字格式化。读完本文你将掌握 NumberBox 全部核心属性Value、SmallChange、LargeChange、ValidationMode、AcceptsExpression、NumberFormatter等的语义、默认值与底层实现行为并能直接写出可运行的 XAML/C# 代码。一、背景为什么需要 NumberBoxXAML 自带的TextBox虽可承担文本输入但纯数字输入场景往往需要更贴合的交互上下按钮步进、鼠标滚轮调值、键盘方向键增减、甚至直接输入算式求值。本仓库中的NumberBox正是为此设计的专用数值控件。按 规范 的说明NumberBox作为 WinUI 包 的一部分随包发布而不是作为 Windows 操作系统的一部分因此它能够在所有支持 WinUI 3 的目标平台上提供一致体验。它表示一个可用于显示和编辑数字的控件支持校验、增量步进并能计算基本的数学表达式乘、除、加、减。二、这是不是你要找的控件规范的 Is this the right control? 一节给出了清晰的选型指南场景应使用的控件捕获、展示数学/数值输入NumberBox需要接受非数字内容的可编辑文本框TextBox密码等敏感输入PasswordBox搜索词输入AutoSuggestBox格式化富文本输入/编辑RichEditBox三、快速开始创建一个 NumberBox最基本的用法是绑定Value属性。规范建议使用x:Bind而非传统Binding以保证界面与数据同步这一点在源码中也有印证NumberBox Value{x:Bind PathViewModel.NumberBoxValue, ModeTwoWay} /Value 与 Text 的语义从 NumberBox.idl 可以看到Value与Text两个关键属性的默认值差异属性默认值IDL 标注说明Valuequiet_NaN()未设置数值时为 NaNText空字符串与 TextBox 一致的文本层源码层面有几个值得注意的行为NumberBox.cpp当 NumberBox 被清空时Value会被设置为NaN以表示当前没有数值Value的 setter 对NaN做了特殊处理当新旧值都为 NaN 时不触发赋值这是为了避免nan ! nan在x:Bind双向绑定下造成栈溢出在初始化阶段Value会覆盖Text初始化之后两者任一变化都会传播到另一个见OnTextPropertyChanged与UpdateTextToValue。规范建议程序化修改走 Value规范的 Recommendations 一节明确建议通过Value属性进行程序化赋值。虽然 NumberBox 继承了 TextBox 的Text属性但 NumberBox 本身只接受数字与算式持续通过Value修改可以避免NumberBox 能接受非数字字符的误解。四、给 NumberBox 加标签Header 与 PlaceholderText当 NumberBox 的用途不直观时可以用Header或PlaceholderText说明NumberBox HeaderEnter a number: Value{x:Bind PathViewModel.NumberBoxValue, ModeTwoWay} /Header无论 NumberBox 是否有值都可见。PlaceholderText则显示在控件内部仅在Value为 NaN 或用户清空输入时出现NumberBox PlaceholderText12^2 Value{x:Bind PathViewModel.NumberBoxValue, ModeTwoWay} /源码中Header支持字符串与HeaderTemplateUpdateHeaderPresenterState只有设置了非空字符串 Header 或提供 HeaderTemplate 时才显示 Header 区域这一尽量晚加载的策略同时服务于轻量化样式lightweight styling。当 Header 不是字符串时NumberBox 自身的 UIA Name 会被转发给内部 TextBox 作为辅助功能名称。五、增量步进SmallChange / LargeChange 与 SpinButton触发步进的方式规范明确了SmallChange与LargeChange的触发场景SmallChangeNumberBox 获得焦点时通过滚动滚轮、按 ↑/↓ 方向键每次增减SmallChangeLargeChangeNumberBox 获得焦点时按PageUp/PageDown键每次增减LargeChange。对应的键盘处理见 OnNumberBoxKeyDownUp → StepValue(SmallChange)、Down → StepValue(-SmallChange)、PageUp → StepValue(LargeChange)、PageDown → StepValue(-LargeChange)滚轮处理见 OnNumberBoxScroll且滚轮步进要求控件处于焦点状态以免在可滚动表面如 ScrollViewer上降低使用体验。默认值IDLSmallChange 1LargeChange 10。SpinButtonPlacementMode三种呈现方式SpinButtonPlacementMode决定上下步进按钮的呈现方式枚举定义见 NumberBox.idl枚举值行为Hidden不显示按钮默认值Inline按钮显示在控件旁边Compact按钮以 Flyout 形式在获得焦点时浮出Inline 模式示例NumberBox Value{x:Bind PathViewModel.NumberBoxValue, ModeTwoWay} SmallChange10 LargeChange100 SpinButtonPlacementModeInline /Compact 模式示例NumberBox Value{x:Bind PathViewModel.NumberBoxValue, ModeTwoWay} SmallChange10 LargeChange100 SpinButtonPlacementModeCompact /从源码看UpdateSpinButtonPlacement 通过视觉状态切换三种形态SpinButtonsCollapsed、SpinButtonsVisible、SpinButtonsPopupCompact 模式的弹出由 OnNumberBoxGotFocus / OnNumberBoxLostFocus 控制Popup.IsOpen。按钮禁用逻辑规范指出当再步进一步会越过Maximum/Minimum时对应按钮会被禁用。源码 UpdateSpinButtonEnabled 实现了这一点当value Maximum()时启用加按钮value Minimum()时启用减按钮若启用了IsWrapEnabled环绕或ValidationMode ! InvalidInputOverwritten则两个按钮始终启用。IsWrapEnabled环绕步进IsWrapEnabled默认false改变步进到边界时的行为——不再停在 Minimum/Maximum而是环绕。规范给出示例Minimum0, Maximum100, SmallChange5, Value98, IsWrapEnabledTrue时向上步进一步得到Value3。实现见 StepValuenewVal max → newVal minnewVal min → newVal max。六、表达式计算AcceptsExpression将AcceptsExpression设为true默认falseNumberBox 即可求值基本的内联表达式如乘法、除法、加法、减法遵循标准运算优先级NumberBox Value{x:Bind PathViewModel.NumberBoxValue, ModeTwoWay} AcceptsExpressionTrue /求值触发时机失去焦点或按下 Enter 键。表达式求值完成后原始表达式形式不会被保留文本会被替换为计算结果。底层求值引擎NumberBox 内建了一个独立的中缀表达式解析器 NumberBoxParser其求值流程为词法分析 → 中缀转后缀 → 后缀求值三步GetTokens词法分析。跳过空白识别数字、运算符 - * / ^与左右括号非法字符或括号不匹配时返回空向量解析失败ConvertInfixToPostfix调度场算法shunting-yard中缀转后缀ComputePostfixExpression后缀求值。除法时除数为 0 返回 NaN对应 V2 规划中的 Division by 0 unsupported 提示幂运算使用std::pow。优先级由 GetPrecedenceValue 定义与规范 Remark 完全一致优先级高→低运算符3^幂2*、/1、-括号可覆盖上述优先级。规范同时注明 NumberBox 使用中缀记法合法字符集为[ 0-9()-/* ]及^。测试用例佐证仓库的交互测试 BasicExpressionTest 覆盖了大量边界表达式可作为能力清单直接参考5 3 → 8 9 - 2 * 6 / 4 → 6 9 - -7 → 16 9-3*2 → 3 // 无空格 10 * 6 → 60 // 多余空格 10 /( 2 3 ) → 2 // 括号 5 * -40 → -200 // 一元负号 3 * ((4 8) / 2) → 18 // 嵌套括号 2 - 2 ^ 3 → -6 // 幂优先于减 2 ^ 2 ^ 2 / 2 9 → 17 // 结合性与优先级 5 ^ -2 → 0.04 // 负指数 (-9) → -9 0^0 → 1同一测试还验证了AcceptsExpressionfalse时输入5 3不会求值结果保持原值。七、输入验证ValidationModeValidationMode是一个两值枚举NumberBox.idl枚举值行为InvalidInputOverwritten在失焦或按 Enter 触发求值时将既非数字也非合法算式的非法输入覆盖为上一次合法值Disabled不做自动验证允许开发者自行实现自定义校验NumberBox HeaderQuantity Value{x:Bind PathViewModel.NumberBoxValue, ModeTwoWay} ValidationModeInvalidInputOverwritten /验证的底层实现验证核心在 ValidateInput文本为空 →Value设为 NaN非空 → 若AcceptsExpression为 true 则走NumberBoxParser::Compute否则用NumberFormatter其本身须是INumberParser解析纯数字解析失败且ValidationMode InvalidInputOverwritten时回写上一次合法值UpdateTextToValue。此外Minimum/Maximum越界也属于验证范畴CoerceValue 在InvalidInputOverwritten模式下会把越界值强制收敛到边界内而 CoerceMinimum/CoerceMaximum 保证Minimum Maximum的约束始终成立。关于小数点和逗号规范特别说明用户输入中使用的小数点/逗号格式会被 NumberBox 配置的格式化规则替换且不会触发输入验证错误这正是 NumberFormatter 同时充当 INumberParser 的原因见下节。八、格式化输入NumberFormatter数字格式化由Windows.Globalization.NumberFormatting命名空间提供配置一个格式化类实例并赋给NumberFormatter属性即可。可用的格式化类包括DecimalFormatter小数、CurrencyFormatter货币、PercentFormatter百分比、SignificantDigitsNumberRounder有效数字等。取整行为同样由格式化属性决定。规范示例使用DecimalFormatter让值显示为 1 位整数 2 位小数并按 0.25 的增量向上取整NumberBox x:NameFormattedNumberBox Value{x:Bind PathViewModel.NumberBoxValue, ModeTwoWay} /private void SetNumberBoxNumberFormatter() { IncrementNumberRounder rounder new IncrementNumberRounder(); rounder.Increment 0.25; rounder.RoundingAlgorithm RoundingAlgorithm.RoundUp; DecimalFormatter formatter new DecimalFormatter(); formatter.IntegerDigits 1; formatter.FractionDigits 2; formatter.NumberRounder rounder; FormattedNumberBox.NumberFormatter formatter; }两个关键实现约束必须同时实现 INumberParserNumberFormatter的赋值回调 ValidateNumberFormatter 会校验该对象是否也能try_asINumberParser否则抛出E_INVALIDARG。这是为了保证格式化与解析使用同一套区域规则从而让用户输入与显示格式保持一致区域感知默认值构造函数 GetRegionalSettingsAwareDecimalFormatter 会基于当前用户区域设置创建默认的DecimalFormatterIntegerDigits1, FractionDigits0并兼容处理区域名中的排序后缀下划线后的字符被裁剪。显示舍入UpdateTextToValue 在把Value渲染为文本时先用m_displayRounderSignificantDigits10的显示用 rounder做一次舍入以避免浮点精度带来的尾数显示问题然后再交给NumberFormatter().FormatDouble输出。九、输入范围与辅助功能InputScopeNumberBox 默认使用Number输入范围面向 0-9 数字由 SetDefaultInputScope 在构造时设置。规范注明开发者可以覆盖该值但其他 InputScope 类型不会被显式支持。IDL 中InputScope属性标注为[MUX_PREVIEW]预览性质。键盘导航规范附录给出了完整的 Tab 停靠顺序行为状态动作焦点在 NumberBox 之前的 Tab 项Tab 将焦点移入 NumberBox 的可编辑文本框焦点在可编辑文本框Tab 触发求值若有校验错误则移焦到错误消息否则移到减号 SpinButton若可见或移出控件到下一 Tab 项焦点在校验错误消息Tab 移到减号 SpinButton焦点在减号 SpinButtonTab 移到加号 SpinButton焦点在加号 SpinButtonTab 移出 NumberBox 到下一 Tab 项另外Enter 触发求值、Escape 回写上一次合法文本OnNumberBoxKeyUp。讲述人Narrator焦点进入文本框朗读AutomationProperty.Name、Header、Text属性触发求值播报求值结果返回校验错误消息播报错误消息焦点移到加减按钮播报按钮属性名。源码层面ReevaluateForwardedUIAProperties 会把 NumberBox 的 Name/Header以及非字符串 Header 时的 LabeledBy转发给内部 TextBox当设置了Minimum/Maximum时还会将边界值拼接到 UIA Name 中帮助辅助功能用户了解取值范围。ValueChanged也会同步通过 NumberBoxAutomationPeer 触发 UIA 的数值变化事件。Gamepad空间导航可在 SpinButton、文本框与控件外部之间移动焦点文本框内按 A 进入输入模式退出输入模式时触发计算按 B 触发求值并退出输入模式焦点在加减按钮时按 A 执行对应的步进动作。十、API 速览枚举与事件NumberBox.idlenum NumberBoxSpinButtonPlacementMode { Hidden, Compact, Inline }; enum NumberBoxValidationMode { InvalidInputOverwritten, Disabled }; runtimeclass NumberBoxValueChangedEventArgs { Double OldValue{ get; }; Double NewValue{ get; }; };ValueChanged事件携带新旧值NumberBoxValueChangedEventArgs当Value变化且新旧值不同、非两个 NaN时触发见 OnValuePropertyChanged。属性总表属性默认值用途ValueNaN当前数值DoubleMinimum/Maximum-max double/max double取值范围边界用于校验与按钮禁用SmallChange1方向键/滚轮/步进按钮的单次增减量LargeChange10PageUp/PageDown 的单次增减量Header/HeaderTemplate空标签及其模板Text空文本表示与 Value 互相传播PlaceholderText空无值时的占位提示ValidationModeInvalidInputOverwritten输入验证模式AcceptsExpressionfalse是否启用算式求值SpinButtonPlacementModeHidden步进按钮呈现方式IsWrapEnabledfalse步进到边界是否环绕NumberFormatter区域感知 DecimalFormatter数值格式化/解析器InputScope预览Number软键盘输入范围十一、最佳实践小结绑定用Value而非TextValue是数值层Text是字符串层两者会自动同步程序化改动统一走Value可避免混淆需要算式能力时显式开启AcceptsExpression并注意表达式求值后原始文本会被结果覆盖越界控制依赖Minimum/MaximumInvalidInputOverwritten若需自定义校验可将ValidationMode设为Disabled自定义格式化时务必使用同时实现INumberParser的格式化类如DecimalFormatter系否则赋值会抛异常步进与滚轮交互仅在获得焦点时生效在可滚动容器内嵌套 NumberBox 时这是预期行为不会抢占父级滚动规范中列为V2 规划当前尚未实现的能力包括ValidationMode扩展出TextBlockMessage/IconMessage消息模式依赖 WinUI Input Validation 工作、以及拖拽drag步进交互。十二、深入仓库设计规范specs/NumberBox/NumberBox.md接口定义默认值/预览属性controls/dev/NumberBox/NumberBox.idl控件实现controls/dev/NumberBox/NumberBox.cpp、controls/dev/NumberBox/NumberBox.h表达式引擎controls/dev/NumberBox/NumberBoxParser.cpp、controls/dev/NumberBox/NumberBoxParser.h默认模板与主题资源controls/dev/NumberBox/NumberBox.xaml、controls/dev/NumberBox/NumberBox_themeresources.xaml交互测试含大量表达式用例controls/dev/NumberBox/InteractionTests/NumberBoxTests.csAPI 测试controls/dev/NumberBox/APITests/NumberBoxTests.cs手工验证页面controls/dev/NumberBox/TestUI/NumberBoxPage.xaml【免费下载链接】microsoft-ui-xamlWinUI: a modern UI framework with a rich set of controls and styles to build dynamic and high-performing Windows applications.项目地址: https://gitcode.com/GitHub_Trending/mi/microsoft-ui-xaml创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考