[英]A read-only CheckBox in C# WPF
我遇到了一个棘手的问题,我想从复选框中获得一些稍微不寻常的行为,但似乎无法弄清楚。 任何建议将是最受欢迎的。 我想要的行为是:
我遇到的问题是,尽管 OneWay 数据绑定在单击 CheckBox 时不会更新数据结构,但视觉表示确实发生了变化(我认为这很奇怪,IsChecked 现在不应该像指向数据结构)。
我可以反转 Click() 事件中的更改并在那里禁用,但这非常混乱。 我还可以使用数据结构值的 set 属性来设置 isEnabled 值,该值也绑定到重新启用 CheckBox 但这似乎也很混乱。
有没有干净的方法来做到这一点? 也许使用派生的 CheckBox 类? 如何停止更新视觉表示?
谢谢
埃德
绑定到IsHitTestVisible属性的数据怎么样?
例如,假设采用 MVVM 方法:
我没有这个确切的要求,我只需要一个始终只读的复选框,它似乎很好地解决了问题。 还要注意下面 Goran 关于Focusable属性的评论。
这个答案不是你的问题,但它回答了标题中的问题。
WPF 中的复选框没有IsReadOnly
属性。 但是,使用属性IsHitTestVisible="False"
和Focusable="False"
可以实现类似的行为
<CheckBox IsHitTestVisible="False"
Focusable="False"/>
我认为没有必要为此创建一个完整的控件。 您遇到的问题来自这样一个事实,即您看到“支票”的地方并不是真正的复选框,而是一颗子弹。 如果我们查看 CheckBox 的ControlTemplate ,我们可以看到它是如何发生的(尽管我更喜欢 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 属性中删除绑定,并使用它来替换 Bullet 的 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
}
我需要关闭自动检查功能以及复选框/单选按钮,我想在没有控件自动检查的情况下处理 Click 事件。 我在这里和其他线程上尝试了各种解决方案,但对结果不满意。
所以我深入研究了 WPF 正在做什么(使用反射),我注意到:
所以基本上,我需要做的就是覆盖 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 之类的其他解决方案不同,这不会导致 Checked/Unchecked/Indeterminate 事件触发,直到您以编程方式设置 IsChecked 属性。
这是我编写的类,用于执行类似的操作,出于类似的原因(仍然像往常一样引发所有 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,但这会部分说明问题。)
迟到的答案,但我只是遇到了这个问题,正在寻找其他东西。 你想要的根本不是一个行为异常的复选框,你想要的是一个外观异常的按钮。 更改控件的外观总是比更改其行为更容易。 沿着这些路线应该做的事情(未经测试)
<Button Command="{Binding CommandThatStartsTask}">
<Button.Template>
<ControlTemplate>
<CheckBox IsChecked="{Binding PropertySetByTask, Mode=OneWay}" />
</ControlTemplate>
</Button.Template>
</Button>
如果它对任何人有帮助,我发现的一个快速而优雅的解决方案是挂钩 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; }
使用验证来阻止布尔值在您不希望它被切换时被切换 - http://www.codeproject.com/KB/WPF/wpfvalidation.aspx
这远没有其他答案那么可怕,或者钩住 Clicked
我一直在尝试创建我的通用 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 不只是一种引用正在创建的控件的属性中设置的内容的方法吗? 即使数据没有更新,Click 如何触发 UI 更新? 单击我可以覆盖以停止更新是否有触发器?
我让所有 DictionaryResource 的东西都工作正常,所以我很高兴,为指针欢呼。
我好奇的另一件事是,是否可以通过在样式中使用 BasedOn 参数来减少我的 Control/Style 模板,那么我只会覆盖我实际需要更改的内容,而不是声明很多我认为的内容无论如何都是标准模板的一部分。 我可能会玩这个。
干杯
编辑
将 CheckBox 包装在 ContentControl 中。 使 ContentControl IsEnabled=False
这两个属性是 – IsHitTestVisible 和 Focusable 使这两个属性为 False。 这使得 WPF 中的只读复选框。 因此,对于 WPF 中的只读复选框,最终的 XAML 语句将如下所示 –
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.