簡體   English   中英

C# WPF 中的只讀復選框

[英]A read-only CheckBox in C# WPF

我遇到了一個棘手的問題,我想從復選框中獲得一些稍微不尋常的行為,但似乎無法弄清楚。 任何建議將是最受歡迎的。 我想要的行為是:

  1. CheckBox 已啟用並准備好供用戶單擊,IsChecked 表示存儲在數據結構中的綁定布爾值
  2. 用戶單擊 CheckBox 導致 click 事件觸發,但數據結構中的綁定值未更新,CheckBox 的視覺表示未更新但已禁用以停止進一步單擊
  3. 點擊事件觸發一條消息發送到遠程設備,該設備需要一些時間來響應
  4. 遠程設備響應導致數據結構更新為新值,然后綁定更新 isChecked 狀態,並重新啟用 CheckBox 以進一步單擊

我遇到的問題是,盡管 OneWay 數據綁定在單擊 CheckBox 時不會更新數據結構,但視覺表示確實發生了變化(我認為這很奇怪,IsChecked 現在不應該像指向數據結構)。

我可以反轉 Click() 事件中的更改並在那里禁用,但這非常混亂。 我還可以使用數據結構值的 set 屬性來設置 isEnabled 值,該值也綁定到重新啟用 CheckBox 但這似乎也很混亂。

有沒有干凈的方法來做到這一點? 也許使用派生的 CheckBox 類? 如何停止更新視覺表示?

謝謝

埃德

綁定到IsHitTestVisible屬性的數據怎么樣?

例如,假設采用 MVVM 方法:

  1. IsReadOnly屬性添加到您的視圖模型,最初設置為 true 以允許單擊。
  2. 將此屬性綁定到 CheckBox.IsHitTestVisible。
  3. 第一次單擊后,更新您的視圖模型以將此值設置為 false,以防止任何進一步的單擊。

我沒有這個確切的要求,我只需要一個始終只讀的復選框,它似乎很好地解決了問題。 還要注意下面 Goran 關於Focusable屬性的評論。

這個答案不是你的問題,但它回答了標題中的問題。

WPF 中的復選框沒有IsReadOnly屬性。 但是,使用屬性IsHitTestVisible="False"Focusable="False"可以實現類似的行為

      <CheckBox IsHitTestVisible="False"
                Focusable="False"/>

我認為沒有必要為此創建一個完整的控件。 您遇到的問題來自這樣一個事實,即您看到“支票”的地方並不是真正的復選框,而是一顆子彈。 如果我們查看 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 屬性中刪除綁定,並使用它來替換 Bullet 的 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
}

我需要關閉自動檢查功能以及復選框/單選按鈕,我想在沒有控件自動檢查的情況下處理 Click 事件。 我在這里和其他線程上嘗試了各種解決方案,但對結果不滿意。

所以我深入研究了 WPF 正在做什么(使用反射),我注意到:

  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 事件。 另外,當我以編程方式更改 IsChecked 屬性時,我仍然可以訂閱 Checked/Unchecked 事件並處理那里的事情。

最后一點:與在 Click 事件中執行 IsChecked != IsChecked 之類的其他解決方案不同,這不會導致 Checked/Unchecked/Indeterminate 事件觸發,直到您以編程方式設置 IsChecked 屬性。

這是我編寫的類,用於執行類似的操作,出於類似的原因(仍然像往常一樣引發所有 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,但這會部分說明問題。)

遲到的答案,但我只是遇到了這個問題,正在尋找其他東西。 你想要的根本不是一個行為異常的復選框,你想要的是一個外觀異常的按鈕。 更改控件的外觀總是比更改其行為更容易。 沿着這些路線應該做的事情(未經測試)

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

如果它對任何人有幫助,我發現的一個快速而優雅的解決方案是掛鈎 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; }

使用驗證來阻止布爾值在您不希望它被切換時被切換 - http://www.codeproject.com/KB/WPF/wpfvalidation.aspx

這遠沒有其他答案那么可怕,或者鈎住 Clicked

我一直在嘗試創建我的通用 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 更新? 單擊我可以覆蓋以停止更新是否有觸發器?

我讓所有 DictionaryResource 的東西都工作正常,所以我很高興,為指針歡呼。

我好奇的另一件事是,是否可以通過在樣式中使用 BasedOn 參數來減少我的 Control/Style 模板,那么我只會覆蓋我實際需要更改的內容,而不是聲明很多我認為的內容無論如何都是標准模板的一部分。 我可能會玩這個。

干杯

編輯

將 CheckBox 包裝在 ContentControl 中。 使 ContentControl IsEnabled=False

這兩個屬性是 – IsHitTestVisible 和 Focusable 使這兩個屬性為 False。 這使得 WPF 中的只讀復選框。 因此,對於 WPF 中的只讀復選框,最終的 XAML 語句將如下所示 –

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM