目录

介绍

背景

细节决定成败

功能要求

将Smarts放入ViewModel

树视图配置

将TreeViewItem变成ToggleButton

Aero主题中的复选框错误


介绍

本文回顾了一个WPF TreeView,其项目包含复选框。每个项目都绑定到一个ViewModel对象。当ViewModel对象的检查状态发生变化时,它会将简单的规则应用于其父项和子项的检查状态。本文还展示了如何使用附加行为概念将一个TreeViewItem变为虚拟ToggleButton,这有助于使TreeView的键盘交互简单直观。

本文假设读者已经熟悉数据绑定和模板、使用ViewModel模式简化WPF TreeView及附加属性。

背景

TreeView的项是复选框,这是很常见的,比如在向用户展示一组要选择的分层选项时。在某些UI平台(例如WinForms)中,标准TreeView控件提供了对在其项中显示复选框的内置支持。由于元素组合和丰富的数据绑定是WPF的两个核心方面,因此WPF不提供对显示复选框的内在支持。在TreeView的ItemTemplate中声明一个CheckBox控件是很容易的,突然间树中的每一项都包含了一个CheckBox。向IsChecked属性添加一个简单的{Binding}表达式,这些框的复选状态就会被绑定到底层数据对象的某些属性上。对于WPF TreeView来说,拥有一个特定于在其项目中显示复选框的API是多余的。

细节决定成败

这听起来好得令人难以置信,而且确实如此。从键盘导航的角度来看,让TreeView“感觉正确”并不是那么简单。基本问题是,当您通过箭头键导航树时,TreeViewItem将首先获取输入焦点,然后它包含的CheckBox将焦点放在下一次击键上。TreeViewItem和CheckBox控件都是可聚焦的。结果是您必须按两次箭头键才能在树中从一个项目导航到另一个项目。这绝对不是一种可接受的用户体验,并且您无法设置任何简单的属性来使其正常工作。我已经将这个问题提请微软WPF团队的某个关键成员注意,因此他们可能会在该平台的未来版本中解决这个问题。

功能要求

在我们开始检查这个演示程序是如何工作的之前,首先我们将回顾它的作用。这是演示应用程序的屏幕截图:

现在让我们看看功能需求是什么:

要求 1:树中的每个项目都必须显示一个复选框,以显示底层数据对象的文本和检查状态。

要求2:当一个项目被选中或取消选中时,它的所有子项目应该分别被选中或取消选中。

要求 3:如果一个项目的后代并非都具有相同的检查状态,则该项目的检查状态必须是“不确定的”。

要求 4:从一个项目导航到另一个项目应该只需要按一次箭头键。

要求 5:按空格键或Enter键应切换所选项目的检查状态。

要求 6:单击项目的复选框应切换其检查状态,但不选择项目。

要求 7:单击项目的显示文本应选择项目,但不能切换其检查状态。

要求 8:默认情况下,树中的所有项目都应处于展开状态。

我建议您复制这些要求并将它们粘贴到您最喜欢的文本编辑器中,例如记事本,因为我们将在文章的其余部分按编号引用它们。

将Smarts放入ViewModel

正如我在“ 使用ViewModel模式简化WPF TreeView”一文中解释的那样,TreeView实际上被设计为与 ViewModel 结合使用。本文更进一步,展示了我们如何使用ViewModel来封装与树中项目的检查状态相关的特定于应用程序的逻辑。在本文中,我们将检查我的FooViewModel类,以下接口描述了该类:

interface IFooViewModel : INotifyPropertyChanged
{List<FooViewModel> Children { get; }bool? IsChecked { get; set; }bool IsInitiallySelected { get; }string Name { get; }
}

这个ViewModel类最有趣的方面是IsChecked属性背后的逻辑。这个逻辑满足前面看到的要求23FooViewModel的IsChecked逻辑如下:

/// <summary>
/// Gets/sets the state of the associated UI toggle (ex. CheckBox).
/// The return value is calculated based on the check state of all
/// child FooViewModels.  Setting this property to true or false
/// will set all children to the same check state, and setting it
/// to any value will cause the parent to verify its check state.
/// </summary>
public bool? IsChecked
{get { return _isChecked; }set { this.SetIsChecked(value, true, true); }
}void SetIsChecked(bool? value, bool updateChildren, bool updateParent)
{if (value == _isChecked)return;_isChecked = value;if (updateChildren && _isChecked.HasValue)this.Children.ForEach(c => c.SetIsChecked(_isChecked, true, false));if (updateParent && _parent != null)_parent.VerifyCheckState();this.OnPropertyChanged("IsChecked");
}void VerifyCheckState()
{bool? state = null;for (int i = 0; i < this.Children.Count; ++i){bool? current = this.Children[i].IsChecked;if (i == 0){state = current;}else if (state != current){state = null;break;}}this.SetIsChecked(state, false, true);
}

该策略特定于我对自己施加的功能要求。如果您对项目应如何以及何时更新其检查状态有不同的规则,只需调整这些方法中的逻辑以满足您的需求。

树视图配置

现在是时候看看TreeView如何能够显示复选框并绑定到ViewModel。这完全在XAML中完成。TreeView声明其实很简单,如下所示:

<TreeView x:Name="tree"ItemContainerStyle="{StaticResource TreeViewItemStyle}"ItemsSource="{Binding Mode=OneTime}"ItemTemplate="{StaticResource CheckBoxItemTemplate}"/>

TreeView的ItemsSource属性隐式地绑定到它的DataContext,它从包含窗口继承了一个List<FooViewModel>。该列表只包含一个ViewModel对象,但必须将其放入集合中,因为ItemsSource的类型是IEnumerable。

TreeViewItem是ItemTemplate生成的视觉元素的容器。在这个演示中,我们将下面的HierarchicalDataTemplate赋值给树的ItemTemplate属性:

<HierarchicalDataTemplate x:Key="CheckBoxItemTemplate"ItemsSource="{Binding Children, Mode=OneTime}"><StackPanel Orientation="Horizontal"><!-- These elements are bound to a FooViewModel object. --><CheckBoxFocusable="False" IsChecked="{Binding IsChecked}" VerticalAlignment="Center"/><ContentPresenter Content="{Binding Name, Mode=OneTime}" Margin="2,0"/></StackPanel>
</HierarchicalDataTemplate>

该模板有几个有趣的地方。该模板包括一个其Focusable属性设置为false的CheckBox。这可以防止CheckBox接收输入焦点,这有助于满足要求4。您可能想知道如果CheckBox从来没有输入焦点,我们将如何满足要求5当我们研究如何将ToggleButton的行为附加到TreeViewItem时,我们将在本文后面讨论这个问题。

CheckBox的IsChecked属性绑定到FooViewModel对象的IsChecked属性,但请注意其Content属性设置为任何值。相反,它旁边有一个ContentPresenter,它的Content绑定到一个FooViewModel对象的Name属性。默认情况下,单击CheckBox上的任意位置会使其切换其检查状态。通过使用单独的ContentPresenter,而不是设置CheckBox的Content属性,我们可以避免这种默认行为。这有助于我们满足要求67。单击其中的CheckBox框元素将导致其检查状态发生变化,但单击相邻的显示文本不会。同样,单击CheckBox不会选择该项目,但单击相邻的显示文本会。

我们将在下一节中检查TreeView的ItemContainerStyle。

将TreeViewItem变成ToggleButton

在上一节中,我们快速考虑了一个有趣的问题。如果在TreeViewItem中的CheckBox的Focusable属性设置为false,它如何切换其检查状态以响应空格键或Enter键?由于元素只有在具有键盘焦点时才会接收击键,因此似乎不可能满足要求5。记住, 我们必须将CheckBox的Focusable属性设置为false,以便在树中从一个项目导航到另一个项目不需要多次击键。

这是一个棘手的问题:我们不能让CheckBox输入焦点,因为它会对键盘导航产生负面影响,但是,当它的包含项被选中时,它必须以某种方式切换其检查状态以响应某些击键。这些似乎是相互排斥的要求。当我碰到这个问题时,我决定向WPF门徒寻求帮助,并开始了这个线程。令我惊讶的是,WPF医生已经遇到了这类问题,并设计了一个非常接近天才的解决方案,该解决方案很容易插入到我的应用程序中。好医生给我发了一个VirtualToggleButton类的代码,并且很友好地允许我在这篇文章中发布它。

医生的解决方案使用了约翰·戈斯曼所说的“附加行为”。这个想法是您在元素上设置附加属性,以便您可以从公开附加属性的类中访问该元素。一旦该类可以访问该元素,它就可以在其上挂钩事件,并响应这些事件触发,使该元素执行它通常不会执行的操作。它是创建和使用子类的一种非常方便的替代方法,并且对XAML非常友好。

在本文中,我们将了解如何提供TreeViewItem一个附加IsChecked属性,该属性在用户按下空格键或Enter键时进行切换。附加的IsChecked属性绑定到FooViewModel对象的IsChecked属性,而FooViewModel对象也绑定到TreeViewItem中CheckBox的IsChecked属性。该解决方案看起来CheckBox正在切换其检查状态以响应空格键或Enter键,但实际上,它的IsChecked属性会更新以响应TreeViewItem通过数据绑定将新值推送到ViewModel的IsChecked属性。

在我看来,这是WPF v3.5中实现TreeView复选框的最干净的方式,这表明微软需要简化平台的这方面。对我来说,微软需要简化平台的这一方面。但是,在他们这样做之前,这可能是实现该功能的最佳方式。

在此演示中,我们没有使用WPF医生的VirtualToggleButton类中的所有功能。它支持一些我们不需要的东西,比如处理鼠标点击和提供三态复选框。我们只需要利用它对附加IsVirtualToggleButton和IsChecked属性的支持以及它提供的键盘交互行为。

这是附加IsVirtualToggleButton属性的属性更改回调方法,它使此类能够访问树中的TreeViewItem:

/// <summary>
/// Handles changes to the IsVirtualToggleButton property.
/// </summary>
private static void OnIsVirtualToggleButtonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{IInputElement element = d as IInputElement;if (element != null){if ((bool)e.NewValue){element.MouseLeftButtonDown += OnMouseLeftButtonDown;element.KeyDown += OnKeyDown;}else{element.MouseLeftButtonDown -= OnMouseLeftButtonDown;element.KeyDown -= OnKeyDown;}}
}

当TreeViewItem引发其KeyDown事件时,此逻辑将执行:

private static void OnKeyDown(object sender, KeyEventArgs e)
{if (e.OriginalSource == sender){if (e.Key == Key.Space){// ignore alt+space which invokes the system menuif ((Keyboard.Modifiers & ModifierKeys.Alt) == ModifierKeys.Alt) return;UpdateIsChecked(sender as DependencyObject);e.Handled = true;}else if (e.Key == Key.Enter && (bool)(sender as DependencyObject).GetValue(KeyboardNavigation.AcceptsReturnProperty)){UpdateIsChecked(sender as DependencyObject);e.Handled = true;}}
}private static void UpdateIsChecked(DependencyObject d)
{Nullable<bool> isChecked = GetIsChecked(d);if (isChecked == true){SetIsChecked(d, GetIsThreeState(d) ? (Nullable<bool>)null : (Nullable<bool>)false);}else{SetIsChecked(d, isChecked.HasValue);}
}

该UpdateIsChecked方法在一个元素上设置附加属性IsChecked,在这个演示中是一个TreeViewItem。在TreeViewItem上设置附加属性本身没有任何影响。为了让应用程序使用该属性值,它必须绑定到某些东西。在此应用程序中,它绑定到FooViewModel对象的IsChecked属性。以下Style分配给TreeView的ItemContainerStyle属性。它将TreeViewItem绑定到一个FooViewModel对象并添加我们刚刚检查过的虚拟ToggleButton行为。

<Style x:Key="TreeViewItemStyle" TargetType="TreeViewItem"><Setter Property="IsExpanded" Value="True" /><Setter Property="IsSelected" Value="{Binding IsInitiallySelected, Mode=OneTime}" /><Setter Property="KeyboardNavigation.AcceptsReturn" Value="True" /><Setter Property="dw:VirtualToggleButton.IsVirtualToggleButton" Value="True" /><Setter Property="dw:VirtualToggleButton.IsChecked" Value="{Binding IsChecked}" />
</Style>

这件作品将整个拼图联系在一起。请注意,在每个TreeViewItem上附加的KeyboardNavigation.AcceptsReturn属性被设置为true,这样VirtualToggleButton将切换其检查状态以响应Enter键。Style中的第一个Setter将每个项的IsExpanded属性的初始值设置为true,确保满足要求8

Aero主题中的复选框错误

我必须指出一个奇怪且令人失望的问题。WPF CheckBox控件的Aero主题在.NET 3.5中存在问题。当它从“不确定”状态移动到“已检查”状态时,框的背景不会正确更新,直到您将鼠标光标移到它上面。您可以在下面的屏幕截图中看到这一点:

为了解决这个问题,我将Royale主题合并到窗口的Resources集合中。使用CheckBox Royale主题时不会出现此缺陷。我真的希望微软在下一版本的WPF中解决这个问题。

https://www.codeproject.com/Articles/28306/Working-with-Checkboxes-in-the-WPF-TreeView

在WPF TreeView中使用复选框相关推荐

  1. 获取jQuery中的复选框值

    如何在jQuery中获取复选框的值? #1楼 尝试这个小解决方案: $("#some_id").attr("checked") ? 1 : 0; 要么 $(&q ...

  2. 在reader中勾选pdf复选框_绝对可勾选的在WORD 2003中加入复选框的方法

    绝对可勾选的在 word 2003 中加入复选框的方法 方法一: 要在 word 2003 中加入复选框,最好是使用"控件工具箱"来完成. 具体方法是: 打开 Word ,依次点击 ...

  3. 在word 2007中插入复选框

    在Word2007中插入复选框Checkbox 2008-10-31 12:06 要在Office 2007中插入复选框,需要先点击"Office按钮"->"Wor ...

  4. php表单复选传值,jQuery+SpringMVC中的复选框选择与传值实例_jquery

    下面我就为大家分享一篇jQuery+SpringMVC中的复选框选择与传值实例,具有很好的参考价值,希望对大家有所帮助. 一.checkbox选择 在jQuery中,选中checkbox通用的两种方式 ...

  5. 中添加复选框_Word/excel中在方框中打钩/叉的符号,简单方便快捷

    方法1:插入特殊符号 将光标定位于需要打钩的地方,选择[插入]--[符号]--[其他符号]--更改字体为[Windings2] 方法2:使用快捷键 从上面的截图中可以看到设置快捷键,所以在插入特殊符号 ...

  6. 【Qt】QTableView中嵌入复选框CheckBox 的四种方法总结

    搜索了一下,QTableView中嵌入复选框CheckBox方法有四种: 第一种不能之前显示,必须双击/选中后才能显示,不适用. 第二种比较简单,通常用这种方法. 第三种只适合静态显示静态数据用 第四 ...

  7. 【Apache Poi】如何使用poi在word中生成复选框

    如何使用poi在word中生成复选框 应用场景 解决方式 代码示例 结语 应用场景 我们经常会在开发中遇到需要通过Poi来生成类似下面这样的复选框 解决方式 我们可以通过unicode编码:\u25A ...

  8. winform中TreeView控件复选框联动时鼠标点击过快导致的显示不正确的问题

    今天我在试着做一个C#树形视图(TreeView)控件,要求在每个节点前面添加一个可用于打勾的复选框,并要求复选框有上下级联动的效果.现在在网上能查到挺多满足这类功能的代码,原本我也以为这是一件挺简单 ...

  9. 用python实现复选框树_如何使用Python中的复选框创建树视图

    我一直在使用Tkinter和Tix来编写一个小程序. 我在需要一个树状视图的复选框(检查按钮),所以我可以从树视图中选择项目. 有没有办法做到这一点? 我一直在看ttk.Treeview(),它看起来 ...

最新文章

  1. python用函数绘制椭圆_python - 如何使用python从3个点找到椭圆的方程 - SO中文参考 - www.soinside.com...
  2. Codeforces Round #552 (Div. 3)D、E题解
  3. [HNOI2003]操作系统
  4. 刘邦韩信java_刘邦为啥非要杀韩信,800多年后李世民给出了答案,原来如此
  5. 事务管理基础:数据库的并发控制相关知识笔记
  6. java ee架构_与Java EE和Spring的集成架构
  7. 关于 Error: No PostCSS Config found in 的错误
  8. PHP-php://(类型)访问各个输入/输出流以及全局变量$HTTP_RAW_POST_DATA讲解
  9. mysql数据库优化语句_MySQL优化之三:SQL语句优化
  10. BZOJ3674: 可持久化并查集加强版
  11. java递归求和 1 n_Java递归求和1+2+3+...+n实例详解
  12. C盘满了怎么办?最强清理工具来了
  13. 从Solidworks模型到UG制工程图
  14. html设置浮动框架的位置,网页浮动窗口怎么设置 怎么让链接网页在浮动框架中显示...
  15. Basic认证方式的配置
  16. 计算机专业知识教学,2016计算机专业知识:计算机的分类(一)
  17. 七星配资沪指低频震荡
  18. powershell入门教程-v0.3版
  19. 被称为“核弹级别”的OpenSSL漏洞
  20. Masonry介绍与使用实践:快速上手Autolayout

热门文章

  1. python 菜单 阻塞 其它程序_Python subprocess.call阻塞
  2. zipkin使用_我的Spring Cloud(十):Zipkin 服务跟踪
  3. 使用android开发移动学习平台_移动学习平台有几种开发方法,你造吗?
  4. 尽显中国风 | 高品质海报背景,PSD分层,智能替换展示商品
  5. 美妆海报不会做? PSD分层模板带你轻松掌握!
  6. 可临摹的PSD分层模板,拆解上线,高逼格电商设计竟如此简单?
  7. 计算机与应用化学ppt,应用化学专用课件.ppt
  8. maya 阿诺德水晶材质_Maya教程之Arnold材质
  9. CPUID — CPU Identification
  10. 物理层上/下行每个功能块需要的信息