CheckBox только для чтения в C # WPF - PullRequest
18 голосов
/ 28 мая 2009

У меня сложная проблема, я хочу немного необычного поведения из-за флажка и не могу понять это. Любые предложения будут приветствоваться. Поведение, которое я хочу, это:

  1. CheckBox включен и готов для нажатия пользователем, IsChecked представляет связанное логическое значение, сохраненное в структуре данных
  2. Пользователь щелкает CheckBox, вызывая событие click, но связанное значение в структуре данных НЕ обновляется, и визуальное представление CheckBox НЕ обновляется, но отключается, чтобы прекратить дальнейшее нажатие
  3. Событие щелчка инициирует отправку сообщения на удаленное устройство, на ответ которого требуется некоторое время
  4. Удаленное устройство отвечает, вызывая обновление структуры данных новым значением, затем привязка обновляет состояние isChecked, и CheckBox снова включается для дальнейшего нажатия

Проблема, с которой я столкнулся, заключается в том, что, хотя привязка данных OneWay не обновляет структуру данных при нажатии кнопки CheckBox, визуальное представление меняется (что, на мой взгляд, странно, теперь IsChecked не должен действовать как указатель на значение в структуре данных).

Я могу отменить изменение в событии Click () и отключить его там, но это довольно грязно. У меня также может быть свойство set для значения структуры данных, чтобы установить значение isEnabled, которое также обязано повторно включить CheckBox, но это тоже выглядит грязно.

Есть ли чистый способ сделать это? Возможно, с производным классом CheckBox? Как я могу остановить обновление визуального представления?

Спасибо

Ed

Ответы [ 10 ]

18 голосов
/ 06 сентября 2010

Как насчет привязки данных к свойству IsHitTestVisible ?

Например, предполагая подход MVVM:

  1. Добавьте свойство IsReadOnly к вашей модели представления, изначально установленное как true, чтобы разрешить щелчок.
  2. Привязка этого свойства к CheckBox.IsHitTestVisible.
  3. После первого щелчка обновите модель представления, чтобы установить для этого значения значение false, предотвращая дальнейшие щелчки.

У меня нет этого точного требования, мне просто был нужен флажок всегда только для чтения, и, похоже, это хорошо решает проблему. Также обратите внимание на комментарий Горана о свойствах Focusable .

5 голосов
/ 28 мая 2009

Я не думаю, что создание целого контроля для этого необходимо. Проблема, с которой вы сталкиваетесь, связана с тем, что место, где вы видите «чек», на самом деле не флажок, а пуля. Если мы посмотрим на ControlTemplate для CheckBox, мы увидим, как это происходит (хотя мне больше нравится шаблон Blend). В связи с этим, даже если для вашей привязки свойства IsChecked установлено значение OneWay, оно все еще обновляется в пользовательском интерфейсе, даже если оно не устанавливает значение привязки.

Таким образом, действительно простой способ исправить это - просто изменить ControlTemplate для соответствующего флажка.

Если мы используем Blend для захвата шаблона элемента управления, мы можем увидеть Bullet внутри ControlTemplate, который представляет фактическую область флажка.

        <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' появляться, поэтому, чтобы исправить это, мы можем удалить привязку из свойства IsChecked в ComboBox и использовать его для замены TemplateBinding для свойства IsChecked Пуля.

Вот небольшой пример, демонстрирующий желаемый эффект, обратите внимание, что для поддержания 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
}
3 голосов
/ 23 января 2016

Мне нужно было отключить функцию AutoCheck для Checkbox / RadioButton, где я хотел обработать событие Click без автоматической проверки элемента управления. Я пробовал разные решения здесь и в других темах и был недоволен результатами.

Итак, я углубился в то, что делает WPF (используя Reflection), и заметил:

  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. Кроме того, я все еще могу подписаться на события Checked / Unchecked и обрабатывать их там, когда я программно изменяю свойство IsChecked.

Последнее замечание: в отличие от других решений, которые делают что-то вроде IsChecked! = IsChecked в событии Click, это не вызовет события Checked / Unchecked / Indeterminate, пока вы не программно установите свойство IsChecked.

1 голос
/ 03 сентября 2012

Поздний ответ, но я только что наткнулся на вопрос в поисках чего-то другого. То, что вы хотите, это вовсе не флажок с необычным поведением, а кнопка с необычным внешним видом. Всегда легче изменить внешний вид элемента управления, чем его поведение. Что-то в этом духе должно быть сделано (не проверено)

<Button Command="{Binding CommandThatStartsTask}">
    <Button.Template>
        <ControlTemplate>
            <CheckBox IsChecked="{Binding PropertySetByTask, Mode=OneWay}" />
        </ControlTemplate>
    </Button.Template>
</Button>
1 голос
/ 16 сентября 2010

Это класс, который я написал, чтобы сделать что-то похожее по тем же причинам (по-прежнему вызывает все события 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, но это лишит части смысла.)

0 голосов
/ 20 августа 2018

Эти два свойства - IsHitTestVisible и Focusable Сделайте эти два свойства ложными. Это делает флажок только для чтения в WPF. Таким образом, окончательное утверждение XAML будет следующим для флажка только для чтения в WPF -

0 голосов
/ 21 марта 2013

Обернуть CheckBox в ContentControl. Сделать ContentControl IsEnabled = False

0 голосов
/ 31 августа 2011

Если это кому-нибудь поможет, быстрое и элегантное решение, которое я нашел, - это подключиться к событиям 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;
    }
0 голосов
/ 07 июня 2009

Я пытался создать свой общий стиль / шаблон 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"/>

За исключением, конечно, когда я делаю это и затем устанавливаю триггер события на маркере как TemplateBinding of IsChecked, у меня есть именно то, с чего я начал! Думаю, я не понимаю, почему установка привязки непосредственно в маркере отличается от установки IsChecked, а затем привязки к ней, не является ли TemplateBinding просто способом ссылки на то, что установлено в свойствах создаваемого элемента управления? Как Click запускает обновление пользовательского интерфейса, даже если данные не обновляются? Есть ли триггер для Click I, который можно переопределить, чтобы остановить обновление?

У меня все отлично работает в DictionaryResource, так что я доволен этим, поболела за указатель.

Другая вещь, которая меня интересовала, заключалась в том, что если можно уменьшить шаблон элемента управления / стиля с помощью параметра BasedOn в стиле, тогда я переопределю только то, что мне нужно изменить, вместо того, чтобы объявлять много вещей. в любом случае это часть стандартного шаблона. Я мог бы поиграть с этим.

Приветствия

1012 * изд *

0 голосов
/ 29 мая 2009

Используйте Validation, чтобы заблокировать переключение логического значения, когда вы этого не хотите - http://www.codeproject.com/KB/WPF/wpfvalidation.aspx

Это гораздо менее страшно, чем другой ответ или перехват нажал

...