拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

02-常用控件

02-常用控件

常用控件:从基础到熟练

上一篇把窗口分成了状态、曲线、配方、报警。这一篇把占位换成真正的控件。数据先写死在 XAML 里。Binding 是下一篇,Command 是第 5 篇。

熟练的标准:配方区能选配方、改上下限;报警区是一张只能看、不能改的表;说得清显示用TextBlock,输入用TextBox,一组记录用ListBox还是DataGrid。


1. 基础:文本、输入、按钮

TextBlock:只显示

状态、温度、标签都用它。操作员不能在上面打字。

<TextBlockText="25.0"FontSize="20"VerticalAlignment="Center"/>

要显示的是Text。不要为了显示一个数去用只读TextBox,只读输入框仍然像能编辑。

TextBox:给操作员改的值

配方上下限用它。

<TextBoxText="30"Width="120"HorizontalAlignment="Left"/>

不设Width且放在会拉伸的格子里时,输入框会拉满整列。配方区这样是合适的。状态栏里不要放会拉满的输入框。

上限、下限是单行数值。AcceptsReturn默认就是 false,回车不会在框里换行。不要为了配方输入去打开它。报警备注那种需要多行的文本才设AcceptsReturn="True",并加上TextWrapping="Wrap"。数字是否合法留给转换器,这一篇先允许框里出现任意字符。

Text在这一篇是写死的字。下一篇改成{Binding HighLimit}之后,界面和数据才连得上。

Button:点一下做一件事

<ButtonContent="启动"Padding="12,4"/>

按钮上的字是Content,不是Text。Content也可以是别的元素,例如一个圆点加文字。现在先写字符串。Button、ListBoxItem、ComboBoxItem都是ContentControl,所以内容都放在Content上,可以是文字,也可以是更复杂的界面。模板那篇再讲这块内部怎么画。

Click能进 code-behind。本篇若要确认按钮是活的,可以临时在Click里改某个TextBlock的Text。启动设备、写寄存器不要写在Click里,那是 Command 那篇的事。

三个控件的属性不要混:

控件字写在哪谁来改
TextBlockText程序
TextBoxText操作员,稍后由绑定写回
ButtonContent一般不改

2. 熟练:选择一项,和列出很多行

ComboBox:多套配方里选一套

<ComboBoxSelectedIndex="0"HorizontalAlignment="Stretch"><ComboBoxItemContent="默认配方"/><ComboBoxItemContent="高温"/><ComboBoxItemContent="低温"/></ComboBox>

SelectedIndex="0"是第一项。操作员展开后只能选中一项。上位机里它代表「当前编辑的是哪套配方」,不是用来显示报警的。

IsEditable默认是 false,操作员不能在框里打出列表中没有的名字。配方选择保持默认。设成 true 时,框变成可输入,容易写出一套不存在的配方。

项很多、或项是后面从 SQLite 读出来的,就不要在 XAML 里逐条写ComboBoxItem。下一篇起改为ItemsSource绑定到集合。

ListBox:一列记录,突出当前选中

适合先把报警「列出来」。每项这一篇先是纯文本。

<ListBox><ListBoxItemContent="12:01 温度超上限 31.2"/><ListBoxItemContent="12:04 已恢复"/></ListBox>

点某一行,它成为选中项。SelectionMode默认是Single,报警表保持单选即可。ListBox自己带滚动,不必再包一层ScrollViewer。前提是父布局给了它一块有限高度,例如 Grid 里固定 180 的那一行。放进竖直的 StackPanel 时,可用高度是无穷的,列表会按全部内容一直变高,滚动条不会出现。这和布局篇里 StackPanel 不把剩余空间分给子元素是同一件事。

ListBox的一项后期会换成时间、内容、数值三列,那是数据模板那篇。这一篇用一行字就能练习选中。

DataGrid:已经知道列的表

报警最终更适合表,因为列是固定的:时间、内容、当时的值。

<DataGridAutoGenerateColumns="False"IsReadOnly="True"CanUserAddRows="False"HeadersVisibility="Column"GridLinesVisibility="Horizontal"><DataGrid.Columns><DataGridTextColumnHeader="时间"Binding="{Binding Time}"Width="160"/><DataGridTextColumnHeader="内容"Binding="{Binding Message}"Width="*"/><DataGridTextColumnHeader="值"Binding="{Binding Value}"Width="80"/></DataGrid.Columns></DataGrid>

这一篇先认识这几个开关。列上的Binding只是列定义。没有ItemsSource时不会生成行,所以表现在是空的。行数据下一篇再接。

ListBox和DataGrid都自带滚动,同样要求父布局限制高度。报警表放在 Grid 的固定高度行里。不要放进竖直 StackPanel,否则表会无限变高,内部滚动不会出现。HeadersVisibility="Column"去掉最左边的行头;有的主题下单元格左侧仍会留一条细缝,不影响列内容。

属性作用
AutoGenerateColumns="False"列由自己写,不按对象属性自动长出一堆列
IsReadOnly="True"报警是记录,不允许在格子里改字
CanUserAddRows="False"底部不留一个空白新行
HeadersVisibility="Column"只要列表头,不要最左边的行头
Width="*"这一列吃掉剩余宽度,和 Grid 的*同一思路

怎么选:

  • 配方名称这种「从几项里挑一个」用ComboBox。
  • 先看选中效果、一项就一句话,用ListBox。
  • 时间、内容、数值这种多列记录,用DataGrid,并且只读。

不要用DataGrid做主界面布局。它是表,不是面板。

选中项

ListBox、ComboBox、DataGrid都有SelectedIndex和SelectedItem。

  • SelectedIndex是第几项,从 0 开始。没有选中时是-1。
  • SelectedItem是那一项本身。静态 XAML 里它是ListBoxItem或ComboBoxItem。绑到集合之后,它是集合里的那个对象,例如一条报警。

本篇在设计器或运行界面里点一行,能看到该项高亮,就说明选中在工作。下一篇把SelectedItem绑到 ViewModel,用来删除这一条。

多选删除

报警偶尔要一次删掉几条。SelectionMode还有Multiple和Extended。Extended可以用 Ctrl 和 Shift 连续选,和资源管理器一样,报警表用这个。默认Single保持不变,直到真的要多选。

<DataGridSelectionMode="Extended"/>

SelectedItem仍然只表示最后点中的那一条。多选的集合是SelectedItems。它不是依赖属性,不能写{Binding SelectedItems}。删除前在SelectionChanged里把当前选中抄进 ViewModel 的一份列表,命令删的是这份列表,不是控件上的属性。

privatevoidOnAlarmSelectionChanged(objectsender,SelectionChangedEventArgse){if(DataContextisnotDeviceViewModelvm)return;vm.ReplaceSelection(((DataGrid)sender).SelectedItems.Cast<AlarmRow>());}

没有选中时列表为空,删除命令直接返回。不要用SelectedIndex != -1代替这份列表。

数字过滤和红框校验见08-值转换器.md。表的行虚拟化,以及行样式和数据模板怎么分工,见07-数据模板.md和06-资源与样式.md。在线、运行那种状态灯不是TextBlock也不是Ellipse堆在窗口上,从10-依赖属性.md的StatusLamp做起。


3. 后期应用:放回四块骨架

曲线区仍用Border占位。其余三块换成控件。状态栏的「启动」「停止」先只是按钮,不写Click。

<StackPanelGrid.Row="0"Grid.ColumnSpan="2"Orientation="Horizontal"Margin="12,8"><TextBlockText="离线"VerticalAlignment="Center"/><TextBlockText="待机"Margin="16,0,0,0"VerticalAlignment="Center"/><TextBlockText="25.0"Margin="16,0,0,0"FontSize="20"VerticalAlignment="Center"/><ButtonContent="启动"Margin="16,0,0,0"Padding="12,4"/><ButtonContent="停止"Margin="8,0,0,0"Padding="12,4"/></StackPanel><BorderGrid.Row="1"Grid.Column="1"Margin="6,0,12,8"Padding="12"><StackPanel><TextBlockText="配方"FontSize="16"/><ComboBoxMargin="0,12,0,0"SelectedIndex="0"><ComboBoxItemContent="默认配方"/><ComboBoxItemContent="高温"/><ComboBoxItemContent="低温"/></ComboBox><TextBlockText="上限"Margin="0,12,0,4"/><TextBoxText="30"/><TextBlockText="下限"Margin="0,12,0,4"/><TextBoxText="10"/><ButtonContent="保存"Margin="0,12,0,0"Padding="12,4"HorizontalAlignment="Left"/></StackPanel></Border><DataGridGrid.Row="2"Grid.ColumnSpan="2"Margin="12,0,12,12"AutoGenerateColumns="False"IsReadOnly="True"CanUserAddRows="False"HeadersVisibility="Column"><DataGrid.Columns><DataGridTextColumnHeader="时间"Binding="{Binding Time}"Width="160"/><DataGridTextColumnHeader="内容"Binding="{Binding Message}"Width="*"/><DataGridTextColumnHeader="值"Binding="{Binding Value}"Width="80"/></DataGrid.Columns></DataGrid>

运行后应看到:状态下拉不动、温度是一块文本;配方能切换三项、上下限能打字;报警表只有表头,没有空行可编辑。表是空的是对的,行要等变更通知那篇往集合里加。

若想在这一篇就看到报警文字,同一格先改用ListBox加两条ListBoxItem。换成DataGrid时删掉这些静态项。

按钮变灰这一篇可以写IsEnabled="False"看效果,然后删掉。真正的灰亮跟着待机、运行、离线走,用 Command 的CanExecute,不要在界面上写死。


4. 自测

  1. 温度数值为什么用TextBlock,上限为什么用TextBox?
  2. Button上的字写在Text还是Content?
  3. 三套配方切换用ListBox、ComboBox还是DataGrid?
  4. 报警表为什么要IsReadOnly和CanUserAddRows="False"?
  5. 没有任何选中时,SelectedIndex是多少?

参考答案:

  1. 温度是程序显示给操作员看的。上限是操作员要修改并保存的设定。
  2. Content。
  3. ComboBox。它表示当前这一套,不占一大块列表,也不需要多列。
  4. 报警是历史记录。只读避免改字,关掉新增行避免底部多出一个空行。
  5. -1。

5. 练习

  1. 用第 3 节替换主窗口里的状态栏、配方区和报警区。运行,切换配方,在上下限里打字。
  2. 把报警区临时换成带两条ListBoxItem的ListBox,点其中一行。再换回DataGrid。
  3. 给「启动」加上IsEnabled="False",确认它变灰且点不到。确认完删掉这个属性。

面试可能提问

  1. 温度用 TextBlock,上限用 TextBox,依据是什么?
    答:温度是程序显示给操作员的。上限是操作员要改、并且会写回配方的。只读的数不要放进 TextBox。

  2. 报警记录为什么用只读 DataGrid,而不让操作员在格子里改?
    答:报警是已经发生的记录。IsReadOnly禁止改字,CanUserAddRows="False"去掉底部空行。列用AutoGenerateColumns="False"自己定,避免按对象属性长出多余列。

  3. 三套配方的切换为什么用 ComboBox?
    答:操作员只需要选定当前这一套。ListBox 占一块列表,DataGrid 适合多列记录,都不适合「从几项里挑一个」。

  4. SelectedIndex为 -1 表示什么?删除当前报警前要先看什么?
    答:表示没有选中项。删除前看SelectedItem是不是 null。不要只判断SelectedIndex != -1就去取第 0 项。两种判断大多同步,业务代码以SelectedItem这份对象为准,没有选中就是 null。

返回列表