C#WPF中的只读CheckBox

时间:2009-05-28 16:41:25

标签: c# wpf binding checkbox

我遇到了一个棘手的问题,我希望从复选框中略微出现一些不寻常的行为,似乎无法弄明白。任何建议都是最受欢迎的。我想要的行为是:

  1. CheckBox已启用且已准备好供用户单击,IsChecked表示存储在数据结构中的绑定布尔值
  2. 用户单击CheckBox会导致单击事件触发但数据结构中的绑定值未更新且CheckBox的可视化表示未更新但已禁用以停止进一步单击
  3. 点击事件会触发要发送到远程设备的消息,需要一些时间才能响应
  4. 远程设备响应导致使用新值更新数据结构,然后绑定更新isChecked状态,并重新启用CheckBox以进一步单击
  5. 我遇到的问题是,虽然OneWay数据绑定在单击CheckBox时不会更新数据结构,但是可视化表示确实发生了变化(我认为这很奇怪,现在IsChecked不应该像指向数据结构中的值。)

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

    有干净的方法吗?也许使用派生的CheckBox类?如何阻止直观表示更新?

    由于

11 个答案:

答案 0 :(得分:17)

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

例如,假设采用MVVM方法:

  1. IsReadOnly 属性添加到您的视图模型中,最初设置为true以允许点击。
  2. 将此属性绑定到CheckBox.IsHitTestVisible。
  3. 首次点击后,更新您的视图模型以将此值设置为false,从而阻止任何进一步的点击。
  4. 我没有这个确切的要求,我只需要一个永远只读复选框,它似乎很好地解决了问题。另请注意Goran关于 Focusable 属性的评论。

答案 1 :(得分:4)

我不认为为此创建一个完整的控件是必要的。 您遇到的问题来自于您看到“支票”的地方不是真正的复选框,它是一颗子弹。如果我们查看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属性中删除绑定并使用它来替换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
}

答案 2 :(得分:3)

我还需要关闭一个Checkbox / RadioButton的AutoCheck功能,我想在没有控件自动检查的情况下处理Click事件。我在这里和其他线程上尝试了各种解决方案,但对结果不满意。

所以我挖掘了WPF正在做的事情(使用Reflection),我注意到了:

  1. CheckBox&amp; RadioButton继承自ToggleButton控件原语。它们都没有OnClick功能。
  2. ToggleButton继承自ButtonBase控件原语。
  3. ToggleButton会覆盖OnClick函数并执行:this.OnToggle(); base.OnClick();
  4. ButtonBase.OnClick确实'base.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent,this));'
  5. 基本上,我需要做的就是覆盖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属性。

答案 3 :(得分:1)

这是我为了类似的原因而写的类似的东西(仍然像往常一样提升所有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,但这会使部分失败。)

答案 4 :(得分:1)

迟到的答案,但我刚刚遇到了寻找其他问题的问题。 您想要的不是具有异常行为的复选框,您想要的是具有不寻常外观的按钮。改变控件的外观总是比它的行为更容易。 这些方面的东西应该做(未经测试)

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

答案 5 :(得分:0)

使用验证来阻止布尔值在您不想要时切换 - http://www.codeproject.com/KB/WPF/wpfvalidation.aspx

这比其他答案要小得多,或挂钩Clicked

答案 6 :(得分:0)

我一直在尝试创建我的通用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更新?是否有触发Click我可以覆盖以停止更新?

我得到了所有DictionaryResource的工作正常,所以我很满意,为指针欢呼。

我很好奇的另一件事是,如果可以通过使用样式中的BasedOn参数来减少我的Control / Style模板,那么我只会覆盖我实际需要更改的内容而不是声明很多东西我认为无论如何都是标准模板的一部分。我可能会玩这个。

干杯

答案 7 :(得分:0)

如果它对任何人有帮助,我发现一个快速而优雅的解决方案是勾选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;
    }

答案 8 :(得分:0)

此答案不是您的问题,但会从标题中回答问题。

WPF中的复选框不具有IsReadOnly属性。 但是,使用属性可以实现类似的行为 IsHitTestVisible="False"Focusable="False"

      <CheckBox IsChecked="True"
                IsHitTestVisible="False"
                Focusable="False"
                Content="Some Text"/>

      <CheckBox IsChecked="{Binding IsEnabled}"
                IsHitTestVisible="False"
                Focusable="False"
                Content="Some Text"/>

答案 9 :(得分:-1)

将CheckBox包装在ContentControl中。使ContentControl IsEnabled = False

答案 10 :(得分:-1)

这两个属性是– IsHitTestVisible和Focusable 使这两个属性为False。这使WPF中的只读复选框。 因此,最终的XAML语句将对WPF中的“只读”复选框如下所示-