简体   繁体   English

C# WPF 中的只读复选框

[英]A read-only CheckBox in C# WPF

I am having a tricky problem, I want some slightly unusual behaviour from a checkbox and can't seem to figure it out.我遇到了一个棘手的问题,我想从复选框中获得一些稍微不寻常的行为,但似乎无法弄清楚。 Any suggestions would be most welcome.任何建议将是最受欢迎的。 The behaviour I want is:我想要的行为是:

  1. The CheckBox is enabled and ready for the user to click, IsChecked represents a bound boolean value stored in a data structure CheckBox 已启用并准备好供用户单击,IsChecked 表示存储在数据结构中的绑定布尔值
  2. The user clicks the CheckBox causing the click event to fire but the bound value in the data structure is NOT updated and the visual representation of the CheckBox is NOT updated but it is disabled to stop further clicking用户单击 CheckBox 导致 click 事件触发,但数据结构中的绑定值未更新,CheckBox 的视觉表示未更新但已禁用以停止进一步单击
  3. The click event triggers a message to be sent to a remote device which takes some time to respond点击事件触发一条消息发送到远程设备,该设备需要一些时间来响应
  4. The remote device responds causing the data structure to be updated with the new value, the binding then updates the isChecked status and the CheckBox gets reenabled for further clicking远程设备响应导致数据结构更新为新值,然后绑定更新 isChecked 状态,并重新启用 CheckBox 以进一步单击

The problem I have is that although a OneWay data binding works at not updating the data structure when the CheckBox is clicked, the visual representation does change (which I think is odd, shouldn't IsChecked now act like a pointer to the value in the data structure).我遇到的问题是,尽管 OneWay 数据绑定在单击 CheckBox 时不会更新数据结构,但视觉表示确实发生了变化(我认为这很奇怪,IsChecked 现在不应该像指向数据结构)。

I can reverse the change in the Click() event and do the disable there as well but this is pretty messy.我可以反转 Click() 事件中的更改并在那里禁用,但这非常混乱。 I can also have the set property of the data structure value to set an isEnabled value which is also bound to reenable the CheckBox but that seems messy too.我还可以使用数据结构值的 set 属性来设置 isEnabled 值,该值也绑定到重新启用 CheckBox 但这似乎也很混乱。

Is there a clean way to do this?有没有干净的方法来做到这一点? Perhaps with a derived CheckBox class?也许使用派生的 CheckBox 类? How can I stop the visual representation getting updated?如何停止更新视觉表示?

Thanks谢谢

Ed埃德

What about data binding to the IsHitTestVisible property?绑定到IsHitTestVisible属性的数据怎么样?

For example, assuming an MVVM approach:例如,假设采用 MVVM 方法:

  1. Add a IsReadOnly property to your view model, initially set as true to allow click.IsReadOnly属性添加到您的视图模型,最初设置为 true 以允许单击。
  2. Binding this property to CheckBox.IsHitTestVisible.将此属性绑定到 CheckBox.IsHitTestVisible。
  3. After the first click, update your view model to set this value to false, preventing any further clicks.第一次单击后,更新您的视图模型以将此值设置为 false,以防止任何进一步的单击。

I don't have this exact requirement, I just needed an always read only checkbox, and it seems to solve the problem nicely.我没有这个确切的要求,我只需要一个始终只读的复选框,它似乎很好地解决了问题。 Also note Goran's comment below about the Focusable property.还要注意下面 Goran 关于Focusable属性的评论。

This answer is not your question, but it answers the question from the title.这个答案不是你的问题,但它回答了标题中的问题。

Checkbox in WPF does not have the IsReadOnly property. WPF 中的复选框没有IsReadOnly属性。 But, similar behavior is achieved using properties IsHitTestVisible="False" and Focusable="False"但是,使用属性IsHitTestVisible="False"Focusable="False"可以实现类似的行为

      <CheckBox IsHitTestVisible="False"
                Focusable="False"/>

I don't think that creating a whole control for this is necessary.我认为没有必要为此创建一个完整的控件。 The issue that you're running into comes from the fact that the place where you see 'the check' isn't really the checkbox, it's a bullet.您遇到的问题来自这样一个事实,即您看到“支票”的地方并不是真正的复选框,而是一颗子弹。 If we look at the ControlTemplate for a CheckBox we can see how that happens (Though I like the Blend template better).如果我们查看 CheckBox 的ControlTemplate ,我们可以看到它是如何发生的(尽管我更喜欢 Blend 模板)。 As a part of that, even though your binding on the IsChecked property is set to OneWay it is still being updated in the UI, even if it is not setting the binding value.作为其中的一部分,即使您对 IsChecked 属性的绑定设置为 OneWay,它仍会在 UI 中更新,即使它没有设置绑定值。

As such, a really simple way to fix this, is to just modify the ControlTemplate for the checkbox in question.因此,解决此问题的一个非常简单的方法是修改相关复选框的 ControlTemplate。

If we use Blend to grab the control template we can see the Bullet inside the ControlTemplate that represents the actual checkbox area.如果我们使用 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>

In here, the IsChecked and RenderPressed are what are actually making the 'Check' appear, so to fix it, we can remove the binding from the IsChecked property on the ComboBox and use it to replace the TemplateBinding on the IsChecked property of the Bullet.在这里,IsChecked 和 RenderPressed 实际上是使“Check”出现的原因,因此要修复它,我们可以从 ComboBox 上的 IsChecked 属性中删除绑定,并使用它来替换 Bullet 的 IsChecked 属性上的 TemplateBinding。

Here's a small sample demonstrating the desired effect, do note that to maintain the Vista CheckBox look the PresentationFramework.Aero dll needs to be added to the project.这是一个演示所需效果的小示例,请注意,要保持 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>

And the code behind, with our backing Boolean value:以及后面的代码,以及我们支持的布尔值:

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
}

I had a need for the AutoCheck functionality be off as well for a Checkbox/RadioButton, where I wanted to handle the Click event without having the control auto-check.我需要关闭自动检查功能以及复选框/单选按钮,我想在没有控件自动检查的情况下处理 Click 事件。 I've tried various solutions here and on other threads and was unhappy with the results.我在这里和其他线程上尝试了各种解决方案,但对结果不满意。

So I dug into what WPF is doing (using Reflection), and I noticed:所以我深入研究了 WPF 正在做什么(使用反射),我注意到:

  1. Both CheckBox & RadioButton inherit from the ToggleButton control primitive. CheckBox 和 RadioButton 都继承自 ToggleButton 控件原语。 Neither of them have a OnClick function.它们都没有 OnClick 功能。
  2. The ToggleButton inherits from the ButtonBase control primitive. ToggleButton 继承自 ButtonBase 控件原语。
  3. The ToggleButton overrides the OnClick function and does: this.OnToggle(); ToggleButton 覆盖 OnClick 函数并执行: this.OnToggle(); base.OnClick(); base.OnClick();
  4. ButtonBase.OnClick does 'base.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent, this));' ButtonBase.OnClick 执行 'base.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent, this));'

So basically, all I needed to do was override the OnClick event, don't call OnToggle, and do base.RaiseEvent所以基本上,我需要做的就是覆盖 OnClick 事件,不要调用 OnToggle,然后执行 base.RaiseEvent

Here's the complete code (note that this can easily be reworked to do RadioButtons as well):这是完整的代码(请注意,这也可以很容易地重新编写以执行 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));
        }
    }

}

I now have a CheckBox that doesn't auto-check, and still fires the Click event.我现在有一个不会自动检查的 CheckBox,并且仍然会触发 Click 事件。 Plus I can still subscribe to the Checked/Unchecked events and handle things there when I programmatically change the IsChecked property.另外,当我以编程方式更改 IsChecked 属性时,我仍然可以订阅 Checked/Unchecked 事件并处理那里的事情。

One final note: unlike other solutions that do something like IsChecked != IsChecked in a Click event, this won't cause the Checked/Unchecked/Indeterminate events to fire until you programmatically set the IsChecked property.最后一点:与在 Click 事件中执行 IsChecked != IsChecked 之类的其他解决方案不同,这不会导致 Checked/Unchecked/Indeterminate 事件触发,直到您以编程方式设置 IsChecked 属性。

This is the class I've written to do something similar, for similar reasons (still raises all the Click and Command events as normal, but does not alter the binding source by default and does not auto-toggle. Unfortunately it does still have the animated fade-in-out on click, which is a bit strange if the click-handling code doesn't end up changing 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.
    }
}

Usage:用法:

<yourns:OneWayCheckBox IsChecked="{Binding SomeValue}"
                       Command="{x:Static yourns:YourApp.YourCommand}"
                       Content="Click me!" />

(Note that the IsChecked binding is now one-way by default; you can declare it as TwoWay if you want, but that would defeat part of the point.) (请注意,IsChecked 绑定现在默认是单向的;如果需要,您可以将其声明为 TwoWay,但这会部分说明问题。)

Late answer, but I just came across the question looking for something else.迟到的答案,但我只是遇到了这个问题,正在寻找其他东西。 What you want is not a checkbox with unusual behaviour at all, what you want is a button with unusual appearance.你想要的根本不是一个行为异常的复选框,你想要的是一个外观异常的按钮。 It's always easier to change the appearance of a control than its behaviour.更改控件的外观总是比更改其行为更容易。 Something along these lines ought to do (untested)沿着这些路线应该做的事情(未经测试)

<Button Command="{Binding CommandThatStartsTask}">
    <Button.Template>
        <ControlTemplate>
            <CheckBox IsChecked="{Binding PropertySetByTask, Mode=OneWay}" />
        </ControlTemplate>
    </Button.Template>
</Button>

If it helps anyone, a fast and elegant solution I've found is to hook with the Checked and Unchecked events and manipulate the value based on your flag.如果它对任何人有帮助,我发现的一个快速而优雅的解决方案是挂钩 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; }

Use Validation to block the boolean from getting toggled when you don't want it to - http://www.codeproject.com/KB/WPF/wpfvalidation.aspx使用验证来阻止布尔值在您不希望它被切换时被切换 - http://www.codeproject.com/KB/WPF/wpfvalidation.aspx

This is much less scary than the other answer, or hooking Clicked这远没有其他答案那么可怕,或者钩住 Clicked

I've been trying to create my generic ReadOnlyCheckBox style/template but I'm having a problem with the binding to the data.我一直在尝试创建我的通用 ReadOnlyCheckBox 样式/模板,但我在绑定到数据时遇到了问题。 In the example you bind directly to the data from the ControlTemplate definition, but of course this is not really what I want, as I want to be able to declare the new checkbox something like this:在示例中,您直接绑定到 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"/>

Except of course when I do this and then set the event trigger on the bullet to be a TemplateBinding of IsChecked I have exactly what I started with!当然,除了当我这样做然后将项目符号上的事件触发器设置为 IsChecked 的 TemplateBinding 时,我完全拥有我开始的内容! I guess I don't understand why setting the binding directly in the bullet is different from setting IsChecked and then binding to that, isn't the TemplateBinding just a way of referencing what is set in the properties of the control being created?我想我不明白为什么直接在项目符号中设置绑定与设置 IsChecked 然后绑定到它不同,TemplateBinding 不只是一种引用正在创建的控件的属性中设置的内容的方法吗? How is the Click triggering the UI update even tho the data does not get updated?即使数据没有更新,Click 如何触发 UI 更新? Is there a trigger for Click I can override to stop the update?单击我可以覆盖以停止更新是否有触发器?

I got all the DictionaryResource stuff working fine so I am happy with that, cheers for the pointer.我让所有 DictionaryResource 的东西都工作正常,所以我很高兴,为指针欢呼。

The other thing I was curious about was if it is possible to reduce my Control/Style template by using the BasedOn parameter in the style, then I would only override the things I actually need to change rather than declaring a lot of stuff that I think is part of the standard template anyway.我好奇的另一件事是,是否可以通过在样式中使用 BasedOn 参数来减少我的 Control/Style 模板,那么我只会覆盖我实际需要更改的内容,而不是声明很多我认为的内容无论如何都是标准模板的一部分。 I might have a play with this.我可能会玩这个。

Cheers干杯

ed编辑

Wrap the CheckBox in a ContentControl.将 CheckBox 包装在 ContentControl 中。 Make the ContentControl IsEnabled=False使 ContentControl IsEnabled=False

Those two Properties are – IsHitTestVisible and Focusable Make thse two properties to False.这两个属性是 – IsHitTestVisible 和 Focusable 使这两个属性为 False。 This makes the readonly checkbox in WPF.这使得 WPF 中的只读复选框。 So final XAML statement will be as follows for readonly checkbox in WPF –因此,对于 WPF 中的只读复选框,最终的 XAML 语句将如下所示 –

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM