C# WPF 中的只读复选框
Posted
技术标签:
【中文标题】C# WPF 中的只读复选框【英文标题】:A read-only CheckBox in C# WPF 【发布时间】:2010-10-29 15:30:05 【问题描述】:我遇到了一个棘手的问题,我想从复选框中获得一些稍微不寻常的行为,但似乎无法弄清楚。任何建议都将受到欢迎。我想要的行为是:
-
CheckBox 已启用并可供用户单击,IsChecked 表示存储在数据结构中的绑定布尔值
用户单击 CheckBox 导致 click 事件触发,但数据结构中的绑定值未更新,并且 CheckBox 的可视化表示未更新但已禁用以停止进一步单击
点击事件触发消息发送到远程设备,需要一些时间才能响应
远程设备响应导致数据结构更新为新值,然后绑定更新 isChecked 状态并重新启用 CheckBox 以供进一步点击
我遇到的问题是,虽然 OneWay 数据绑定在单击 CheckBox 时不会更新数据结构,但视觉表示确实发生了变化(我认为这很奇怪,IsChecked 现在不应该像指向数据结构中的值)。
我可以反转 Click() 事件中的更改并在那里进行禁用,但这很麻烦。我还可以使用数据结构值的 set 属性来设置 isEnabled 值,该值也必然会重新启用 CheckBox,但这似乎也很混乱。
有没有一种干净的方法可以做到这一点?也许使用派生的 CheckBox 类?如何停止更新视觉表示?
谢谢
埃德
【问题讨论】:
【参考方案1】:我认为没有必要为此创建一个完整的控件。 您遇到的问题来自这样一个事实,即您看到“支票”的地方实际上并不是复选框,而是一颗子弹。如果我们查看 ControlTemplate 的 CheckBox,我们可以看到这是如何发生的(尽管我更喜欢 Blend 模板)。作为其中的一部分,即使您对 IsChecked 属性的绑定设置为 OneWay,它仍会在 UI 中更新,即使它没有设置绑定值。
因此,解决此问题的一个非常简单的方法是修改相关复选框的 ControlTemplate。
如果我们使用 Blend 来抓取控件模板,我们可以在 ControlTemplate 中看到表示实际复选框区域的 Bullet。
<BulletDecorator SnapsToDevicePixels="true"
Background="Transparent">
<BulletDecorator.Bullet>
<Microsoft_Windows_Themes:BulletChrome Background="TemplateBinding Background"
BorderBrush="TemplateBinding BorderBrush"
IsChecked="TemplateBinding IsChecked"
RenderMouseOver="TemplateBinding IsMouseOver"
RenderPressed="TemplateBinding IsPressed" />
</BulletDecorator.Bullet>
<ContentPresenter SnapsToDevicePixels="TemplateBinding SnapsToDevicePixels"
HorizontalAlignment="TemplateBinding HorizontalContentAlignment"
Margin="TemplateBinding Padding"
VerticalAlignment="TemplateBinding VerticalContentAlignment"
RecognizesAccessKey="True" />
</BulletDecorator>
在这里,IsChecked 和 RenderPressed 是真正使“Check”出现的原因,因此要修复它,我们可以从 ComboBox 上的 IsChecked 属性中删除绑定,并使用它来替换 IsChecked 属性上的 TemplateBinding子弹。
这是一个演示所需效果的小示例,请注意,要保持 Vista CheckBox 外观,需要将 PresentationFramework.Aero dll 添加到项目中。
<Window x:Class="Sample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
Title="Window1"
Height="300"
Width="300">
<Window.Resources>
<SolidColorBrush x:Key="CheckBoxFillNormal"
Color="#F4F4F4" />
<SolidColorBrush x:Key="CheckBoxStroke"
Color="#8E8F8F" />
<Style x:Key="EmptyCheckBoxFocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle SnapsToDevicePixels="true"
Margin="1"
Stroke="Black"
StrokeDashArray="1 2"
StrokeThickness="1" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="CheckRadioFocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle SnapsToDevicePixels="true"
Margin="14,0,0,0"
Stroke="Black"
StrokeDashArray="1 2"
StrokeThickness="1" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="CheckBoxStyle1"
TargetType="x:Type CheckBox">
<Setter Property="Foreground"
Value="DynamicResource x:Static SystemColors.ControlTextBrushKey" />
<Setter Property="Background"
Value="StaticResource CheckBoxFillNormal" />
<Setter Property="BorderBrush"
Value="StaticResource CheckBoxStroke" />
<Setter Property="BorderThickness"
Value="1" />
<Setter Property="FocusVisualStyle"
Value="StaticResource EmptyCheckBoxFocusVisual" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="x:Type CheckBox">
<BulletDecorator SnapsToDevicePixels="true"
Background="Transparent">
<BulletDecorator.Bullet>
<Microsoft_Windows_Themes:BulletChrome Background="TemplateBinding Background"
BorderBrush="TemplateBinding BorderBrush"
RenderMouseOver="TemplateBinding IsMouseOver" />
</BulletDecorator.Bullet>
<ContentPresenter SnapsToDevicePixels="TemplateBinding SnapsToDevicePixels"
HorizontalAlignment="TemplateBinding HorizontalContentAlignment"
Margin="TemplateBinding Padding"
VerticalAlignment="TemplateBinding VerticalContentAlignment"
RecognizesAccessKey="True" />
</BulletDecorator>
<ControlTemplate.Triggers>
<Trigger Property="HasContent"
Value="true">
<Setter Property="FocusVisualStyle"
Value="StaticResource CheckRadioFocusVisual" />
<Setter Property="Padding"
Value="4,0,0,0" />
</Trigger>
<Trigger Property="IsEnabled"
Value="false">
<Setter Property="Foreground"
Value="DynamicResource x:Static SystemColors.GrayTextBrushKey" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<StackPanel>
<CheckBox x:Name="uiComboBox"
Content="Does not set the backing property, but responds to it.">
<CheckBox.Style>
<Style TargetType="x:Type CheckBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="x:Type CheckBox">
<BulletDecorator SnapsToDevicePixels="true"
Background="Transparent">
<BulletDecorator.Bullet>
<Microsoft_Windows_Themes:BulletChrome Background="TemplateBinding Background"
BorderBrush="TemplateBinding BorderBrush"
RenderMouseOver="TemplateBinding IsMouseOver"
IsChecked="Binding MyBoolean">
</Microsoft_Windows_Themes:BulletChrome>
</BulletDecorator.Bullet>
<ContentPresenter SnapsToDevicePixels="TemplateBinding SnapsToDevicePixels"
HorizontalAlignment="TemplateBinding HorizontalContentAlignment"
Margin="TemplateBinding Padding"
VerticalAlignment="TemplateBinding VerticalContentAlignment"
RecognizesAccessKey="True" />
</BulletDecorator>
<ControlTemplate.Triggers>
<Trigger Property="HasContent"
Value="true">
<Setter Property="FocusVisualStyle"
Value="StaticResource CheckRadioFocusVisual" />
<Setter Property="Padding"
Value="4,0,0,0" />
</Trigger>
<Trigger Property="IsEnabled"
Value="false">
<Setter Property="Foreground"
Value="DynamicResource x:Static SystemColors.GrayTextBrushKey" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</CheckBox.Style>
</CheckBox>
<TextBlock Text="Binding MyBoolean, StringFormat=Backing property:0" />
<CheckBox IsChecked="Binding MyBoolean"
Content="Sets the backing property." />
</StackPanel>
</Grid>
</Window>
以及后面的代码,以及我们支持的布尔值:
public partial class Window1 : Window, INotifyPropertyChanged
public Window1()
InitializeComponent();
this.DataContext = this;
private bool myBoolean;
public bool MyBoolean
get
return this.myBoolean;
set
this.myBoolean = value;
this.NotifyPropertyChanged("MyBoolean");
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
#endregion
【讨论】:
谢谢,这是一个很好的答案,我会尝试阅读 ControlTemplates,有没有办法可以在单独的文件中创建模板,这样我就可以#using 它并应用它?我会去的。干杯编辑 首选做法是应用任何具有样式的 ControlTemplate,如上所示。您可以做的是在单独的 ResourceDictionary 中创建样式,然后通过设置 Style="StaticResource MyStyleName" 将其应用于 CheckBox 如果您想走这条路,您还应该查看 ResourceDictionary.MergedDictionaries 以便您可以在 xaml 而不是 C# 中合并资源。 我经常惊讶于这么简单的东西在 wpf 中能做多少工作。我选择禁用 IsHitTestVisible 和 Focusable。【参考方案2】:当您不希望布尔值被切换时,使用验证来阻止它被切换 - http://www.codeproject.com/KB/WPF/wpfvalidation.aspx
这比其他答案或挂钩 Clicked 更可怕
【讨论】:
虽然这对于新手来说更简单更容易理解,但这不会阻止当鼠标按下 CheckBox 时呈现 CheckMark,我觉得这是一个不完整的 UX。跨度> 【参考方案3】:我一直在尝试创建我的通用 ReadOnlyCheckBox 样式/模板,但我在绑定到数据时遇到了问题。在示例中,您直接绑定到 ControlTemplate 定义中的数据,但这当然不是我真正想要的,因为我希望能够像这样声明新的复选框:
<CheckBox x:Name="uiComboBox" Content="Does not set the backing property, but responds to it."
Style="StaticResource ReadOnlyCheckBoxStyle" IsChecked="Binding MyBoolean" Click="uiComboBox_Click"/>
当然,当我这样做然后将项目符号上的事件触发器设置为 IsChecked 的 TemplateBinding 时,我完全拥有我开始的内容!我想我不明白为什么直接在项目符号中设置绑定与设置 IsChecked 然后绑定到它不同,TemplateBinding 不只是引用正在创建的控件的属性中设置的内容的一种方式吗?即使数据没有更新,点击如何触发 UI 更新?是否有 Click I can override 的触发器来停止更新?
我得到了所有 DictionaryResource 的东西工作正常,所以我很高兴,为指针欢呼。
我好奇的另一件事是,如果可以通过在样式中使用 BasedOn 参数来减少我的 Control/Style 模板,那么我只会覆盖我实际需要更改的东西,而不是声明很多东西我认为无论如何都是标准模板的一部分。我可能会玩这个。
干杯
编辑
【讨论】:
【参考方案4】:如何将数据绑定到IsHitTestVisible 属性?
例如,假设一个 MVVM 方法:
-
将 IsReadOnly 属性添加到您的视图模型,最初设置为 true 以允许点击。
将此属性绑定到 CheckBox.IsHitTestVisible。
第一次单击后,更新您的视图模型以将此值设置为 false,以防止任何进一步的单击。
我没有这个确切的要求,我只需要一个始终只读的复选框,它似乎很好地解决了这个问题。另请注意下面 Goran 关于 Focusable 属性的评论。
【讨论】:
在这种情况下,您确实应该将 Focusable 设置为 false 以防止用户使用 tab 选中复选框。【参考方案5】:这是我编写的用于执行类似操作的类,出于类似的原因(仍然正常引发所有 Click 和 Command 事件,但默认情况下不会更改绑定源并且不会自动切换。不幸的是它确实如此点击时仍然有动画淡入淡出,如果点击处理代码最终没有改变 IsChecked,这有点奇怪。
public class OneWayCheckBox : CheckBox
private class CancelTwoWayMetadata : FrameworkPropertyMetadata
protected override void Merge(PropertyMetadata baseMetadata,
DependencyProperty dp)
base.Merge(baseMetadata, dp);
BindsTwoWayByDefault = false;
static OneWayCheckBox()
// Remove BindsTwoWayByDefault
IsCheckedProperty.OverrideMetadata(typeof(OneWayCheckBox),
new CancelTwoWayMetadata());
protected override void OnToggle()
// Do nothing.
用法:
<yourns:OneWayCheckBox IsChecked="Binding SomeValue"
Command="x:Static yourns:YourApp.YourCommand"
Content="Click me!" />
(请注意,IsChecked 绑定现在默认是单向的;如果需要,您可以将其声明为 TwoWay,但这会破坏部分观点。)
【讨论】:
【参考方案6】:如果它可以帮助任何人,我发现的一个快速而优雅的解决方案是挂钩 Checked 和 Unchecked 事件并根据您的标志操纵值。
public bool readOnly = false; private bool lastValidValue = false; public MyConstructor() InitializeComponent(); contentCheckBox.Checked += new RoutedEventHandler(contentCheckBox_Checked); contentCheckBox.Unchecked += new RoutedEventHandler(contentCheckBox_Checked); void contentCheckBox_Checked(object sender, RoutedEventArgs e) if (readOnly) contentCheckBox.IsChecked = lastValidValue; else lastValidValue = (bool)contentCheckBox.IsChecked;
【讨论】:
这是否也会阻止代码对基础值的更改?【参考方案7】:迟到的答案,但我只是在寻找其他问题时遇到了这个问题。 你想要的根本不是一个行为异常的复选框,你想要的是一个外观不寻常的按钮。更改控件的外观总是比更改其行为更容易。 应该按照这些思路做一些事情(未经测试)
<Button Command="Binding CommandThatStartsTask">
<Button.Template>
<ControlTemplate>
<CheckBox IsChecked="Binding PropertySetByTask, Mode=OneWay" />
</ControlTemplate>
</Button.Template>
</Button>
【讨论】:
【参考方案8】:将 CheckBox 包装在 ContentControl 中。使 ContentControl IsEnabled=False
【讨论】:
这与在复选框本身上设置“IsEnabled=false”具有相同的问题 - 它被禁用,而不是只读(视觉风格不同)【参考方案9】:我需要关闭 AutoCheck 功能以及 Checkbox/RadioButton,我想在不自动检查控件的情况下处理 Click 事件。我在这里和其他线程上尝试了各种解决方案,但对结果不满意。
所以我深入研究了 WPF 正在做什么(使用反射),我注意到:
-
CheckBox 和 RadioButton 都继承自 ToggleButton 控件原语。它们都没有 OnClick 功能。
ToggleButton 继承自 ButtonBase 控件原语。
ToggleButton 覆盖 OnClick 函数并执行以下操作:this.OnToggle(); base.OnClick();
ButtonBase.OnClick 执行 'base.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent, this));'
所以基本上,我需要做的就是覆盖 OnClick 事件,不调用 OnToggle,然后执行 base.RaiseEvent
这是完整的代码(请注意,这也可以很容易地修改为 RadioButtons):
using System.Windows.Controls.Primitives;
public class AutoCheckBox : CheckBox
private bool _autoCheck = false;
public bool AutoCheck
get return _autoCheck;
set _autoCheck = value;
protected override void OnClick()
if (_autoCheck)
base.OnClick();
else
base.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent, this));
我现在有一个不会自动检查的 CheckBox,但仍会触发 Click 事件。另外,当我以编程方式更改 IsChecked 属性时,我仍然可以订阅 Checked/Unchecked 事件并在那里处理事情。
最后一点:与在 Click 事件中执行 IsChecked != IsChecked 之类的其他解决方案不同,在您以编程方式设置 IsChecked 属性之前,这不会导致 Checked/Unchecked/Indeterminate 事件触发。
【讨论】:
【参考方案10】:这两个属性是 – IsHitTestVisible 和 Focusable 将这两个属性设为 False。这使得 WPF 中的只读复选框。 因此 WPF 中的只读复选框的最终 XAML 语句将如下所示 -
【讨论】:
【参考方案11】:这个答案不是你的问题,但它回答了标题中的问题。
WPF 中的复选框没有IsReadOnly
属性。
但是,使用属性可以实现类似的行为
IsHitTestVisible="False"
和 Focusable="False"
<CheckBox IsHitTestVisible="False"
Focusable="False"/>
【讨论】:
正是我需要的,谢谢。获得只读复选框的一个很好的简单解决方案。以上是关于C# WPF 中的只读复选框的主要内容,如果未能解决你的问题,请参考以下文章