A read-only CheckBox in C# WPF
Asked Answered
K

11

22

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

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

I can reverse the change in the Click() event and do the disable there as well but this is pretty messy. 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.

Is there a clean way to do this? Perhaps with a derived CheckBox class? How can I stop the visual representation getting updated?

Thanks

Ed

Kletter answered 28/5, 2009 at 16:41 Comment(0)
P
21

What about data binding to the IsHitTestVisible property?

For example, assuming an MVVM approach:

  1. Add a IsReadOnly property to your view model, initially set as true to allow click.
  2. Binding this property to CheckBox.IsHitTestVisible.
  3. After the first click, update your view model to set this value to false, preventing any further clicks.

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.

Papist answered 6/9, 2010 at 4:2 Comment(1)
In this case, you really should set Focusable to false to prevent users from checking the checkbox using tab.Slashing
M
17

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"/>
Mcclish answered 21/11, 2019 at 10:56 Comment(1)
Exactly what I needed, thanks. A nice simple solution to get a readonly checkbox.Inspirit
D
5

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
}
Downcast answered 28/5, 2009 at 17:24 Comment(3)
Thanks, that's a great answer, I'll try and have a read up on ControlTemplates, is there a way I can create the template in a seperate file so I can just #using it in and apply it? I'll have a go. cheers edKletter
The preferred practice is to apply any ControlTemplate with a style, as shown above. What you can do is create the style in a seperate ResourceDictionary, and then apply it to the CheckBox by setting Style="{StaticResource MyStyleName}" If you'd like to go that route, you should also look at ResourceDictionary.MergedDictionaries so that you can merge the resources in xaml instead of C#.Downcast
I'm constantly amazed how much work something this easy can take in wpf. I've opted for disabling IsHitTestVisible and Focusable instead.Slashing
I
4

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.

Inhalator answered 23/1, 2016 at 19:15 Comment(0)
B
2

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.

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

Bendwise answered 16/9, 2010 at 5:7 Comment(0)
R
1

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>
Rattler answered 3/9, 2012 at 8:23 Comment(0)
M
0

Use Validation to block the boolean from getting toggled when you don't want it to - http://www.codeproject.com/KB/WPF/wpfvalidation.aspx

This is much less scary than the other answer, or hooking Clicked

Modestine answered 29/5, 2009 at 5:13 Comment(1)
While this is a lot simpler and easier for someone new to understand this won't prevent the rendering of the CheckMark when the mouse is pressed over the CheckBox, which I feel is an incomplete UX.Downcast
K
0

I've been trying to create my generic ReadOnlyCheckBox style/template but I'm having a problem with the binding to the data. 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:

        <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! 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? How is the Click triggering the UI update even tho the data does not get updated? 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.

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. I might have a play with this.

Cheers

ed

Kletter answered 7/6, 2009 at 18:45 Comment(0)
K
0

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;
    }
Kynewulf answered 31/8, 2011 at 13:1 Comment(1)
Wouldnt that also block changes to the underlying value from code as well?Rattler
B
-1

Wrap the CheckBox in a ContentControl. Make the ContentControl IsEnabled=False

Bosomy answered 21/3, 2013 at 15:43 Comment(1)
This has the same problem as setting "IsEnabled=false" on the checkbox itself - it get's disabled, rather than read-only (difference in visual style)Monarski
P
-1

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 –

Polymerous answered 20/8, 2018 at 7:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.