A read-only CheckBox in C# WPF

后端 未结 11 2030
南方客
南方客 2020-12-06 04:37

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

相关标签:
11条回答
  • 2020-12-06 05:05

    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). 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.

    As such, a really simple way to fix this, is to just modify the ControlTemplate for the checkbox in question.

    If we use Blend to grab the control template we can see the Bullet inside the ControlTemplate that represents the actual checkbox area.

            <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.

    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.

    <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
    }
    
    0 讨论(0)
  • 2020-12-06 05:06

    This answer is not your question, but it answers the question from the title.

    Checkbox in WPF does not have the IsReadOnly property. But, similar behavior is achieved using properties IsHitTestVisible="False" and Focusable="False"

          <CheckBox IsHitTestVisible="False"
                    Focusable="False"/>
    
    0 讨论(0)
  • 2020-12-06 05:12

    Those two Properties are – IsHitTestVisible and Focusable Make thse two properties to False. This makes the readonly checkbox in WPF. So final XAML statement will be as follows for readonly checkbox in WPF –

    0 讨论(0)
  • 2020-12-06 05:15

    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.

        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;
        }
    
    0 讨论(0)
  • 2020-12-06 05:16

    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. 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:

    1. Both CheckBox & RadioButton inherit from the ToggleButton control primitive. Neither of them have a OnClick function.
    2. The ToggleButton inherits from the ButtonBase control primitive.
    3. The ToggleButton overrides the OnClick function and does: this.OnToggle(); base.OnClick();
    4. ButtonBase.OnClick does '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

    Here's the complete code (note that this can easily be reworked to do RadioButtons as well):

    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. Plus I can still subscribe to the Checked/Unchecked events and handle things there when I programmatically change the IsChecked property.

    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.

    0 讨论(0)
  • 2020-12-06 05:17

    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>
    
    0 讨论(0)
提交回复
热议问题