繁体   English   中英

C# WPF 中的只读复选框

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

我遇到了一个棘手的问题,我想从复选框中获得一些稍微不寻常的行为,但似乎无法弄清楚。 任何建议将是最受欢迎的。 我想要的行为是:

  1. CheckBox 已启用并准备好供用户单击,IsChecked 表示存储在数据结构中的绑定布尔值
  2. 用户单击 CheckBox 导致 click 事件触发,但数据结构中的绑定值未更新,CheckBox 的视觉表示未更新但已禁用以停止进一步单击
  3. 点击事件触发一条消息发送到远程设备,该设备需要一些时间来响应
  4. 远程设备响应导致数据结构更新为新值,然后绑定更新 isChecked 状态,并重新启用 CheckBox 以进一步单击

我遇到的问题是,尽管 OneWay 数据绑定在单击 CheckBox 时不会更新数据结构,但视觉表示确实发生了变化(我认为这很奇怪,IsChecked 现在不应该像指向数据结构)。

我可以反转 Click() 事件中的更改并在那里禁用,但这非常混乱。 我还可以使用数据结构值的 set 属性来设置 isEnabled 值,该值也绑定到重新启用 CheckBox 但这似乎也很混乱。

有没有干净的方法来做到这一点? 也许使用派生的 CheckBox 类? 如何停止更新视觉表示?

谢谢

埃德

绑定到IsHitTestVisible属性的数据怎么样?

例如,假设采用 MVVM 方法:

  1. IsReadOnly属性添加到您的视图模型,最初设置为 true 以允许单击。
  2. 将此属性绑定到 CheckBox.IsHitTestVisible。
  3. 第一次单击后,更新您的视图模型以将此值设置为 false,以防止任何进一步的单击。

我没有这个确切的要求,我只需要一个始终只读的复选框,它似乎很好地解决了问题。 还要注意下面 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 正在做什么(使用反射),我注意到:

  1. CheckBox 和 RadioButton 都继承自 ToggleButton 控件原语。 它们都没有 OnClick 功能。
  2. ToggleButton 继承自 ButtonBase 控件原语。
  3. ToggleButton 覆盖 OnClick 函数并执行: this.OnToggle(); base.OnClick();
  4. 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 之类的其他解决方案不同,这不会导致 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.

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