Проверка только для чтения CheckBox в С# WPF

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

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

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

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

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

Спасибо

Ed

Ответы

Ответ 1

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

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

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

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

Ответ 2

Я не думаю, что создание целого элемента управления для этого необходимо. Проблема, с которой вы сталкиваетесь, связана с тем, что место, где вы видите "чек", на самом деле не является флажком, это пуля. Если мы посмотрим на 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 должна быть добавлена ​​в проект.

<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

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

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

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

Ответ 5

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

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

  • Оба CheckBox и RadioButton наследуются от примитива управления ToggleButton. Ни одна из них не имеет функции OnClick.
  • ToggleButton наследуется от элемента управления ButtonBase.
  • ToggleButton переопределяет функцию OnClick и выполняет следующие действия: this.OnToggle(); base.OnClick();
  • ButtonBase.OnClick делает 'base.RaiseEvent(новый RoutedEventArgs (ButtonBase.ClickEvent, это));'

В основном, все, что мне нужно было сделать, это переопределить событие 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/Indetermine, пока вы программно не установите свойство IsChecked.

Ответ 6

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

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

Ответ 7

Я пытаюсь создать свой общий стиль/шаблон 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 IsChecked. У меня именно то, с чего я начал! Наверное, я не понимаю, почему установка привязки непосредственно в пуле отличается от установки IsChecked, а затем привязки к ней, а не для TemplateBinding - это просто способ ссылки на то, что установлено в свойствах создаваемого элемента управления? Как клик запускает обновление пользовательского интерфейса, даже если данные не обновляются? Есть ли триггер для Click, который я могу переопределить, чтобы остановить обновление?

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

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

Приветствия

е изд

Ответ 8

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

Ответ 9

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