2009-05-28 17 views
15

Ho un problema complesso, voglio un comportamento leggermente insolito da una casella di controllo e non riesco a capirlo. Qualsiasi suggerimento sarebbe il benvenuto. Il comportamento che voglio è:Un CheckBox di sola lettura in C# WPF

  1. la casella di controllo è abilitato e pronto per l'utente a cliccare, IsChecked rappresenta un valore booleano limite memorizzato in una struttura dati
  2. L'utente fa clic sul CheckBox causando l'evento click per sparare, ma il valore associato nella struttura dati NON è aggiornato e la rappresentazione visiva del CheckBox NON è aggiornata ma è disabilitata per interrompere ulteriormente cliccando
  3. L'evento click innesca un messaggio da inviare a un dispositivo remoto che richiede del tempo per rispondere
  4. Il dispositivo remoto risponde provocando l'aggiornamento della struttura dati con il nuovo valore, th e vincolante quindi aggiorna lo stato isChecked e CheckBox viene riattivato per cliccando ulteriori

Il problema che ho è che, anche se un dato OneWay Lavori di rilegatura a non aggiornare la struttura dei dati quando la casella è cliccato, la rappresentazione visiva non cambia (che penso sia strano, IsChecked non dovrebbe agire come un puntatore al valore nella struttura dei dati).

Posso invertire la modifica nell'evento Click() e fare la disabilitazione anche qui, ma questo è abbastanza disordinato. Posso anche avere la proprietà set del valore della struttura dei dati per impostare un valore isEnabled che è anche destinato a riattivare il CheckBox ma che sembra anche disordinato.

C'è un modo pulito per farlo? Forse con una classe CheckBox derivata? Come posso interrompere l'aggiornamento della rappresentazione visiva?

Grazie

Ed

risposta

4

Non credo che la creazione di un insieme di controllo per la necessità. Il problema che stai incontrando deriva dal fatto che il posto dove vedi 'il controllo' non è proprio la casella di controllo, è un proiettile. Se guardiamo allo ControlTemplate per un CheckBox possiamo vedere come ciò accade (anche se mi piace il modello di Blend meglio). Come parte di ciò, anche se l'associazione sulla proprietà IsChecked è impostata su OneWay, viene comunque aggiornata nell'IU, anche se non sta impostando il valore di binding.

Come tale, un modo molto semplice per risolvere questo problema, è modificare semplicemente il ControlTemplate per la casella di controllo in questione.

Se utilizziamo Blend per afferrare il modello di controllo, possiamo vedere il Bullet all'interno del ControlTemplate che rappresenta l'effettiva area di checkbox.

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

Qui, l'IsChecked e RenderPressed sono ciò che sono in realtà facendo il 'Il check' apparire, in modo da risolvere il problema, siamo in grado di rimuovere l'associazione dalla proprietà IsChecked sulla ComboBox e utilizzarlo per sostituire il TemplateBinding su la proprietà IsChecked del Bullet.

Ecco un piccolo esempio che dimostra l'effetto desiderato, si noti che per mantenere il Vista CheckBox guardare il PresentationFramework.Aero dll deve essere aggiunto al progetto.

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

E il codice dietro, con il nostro supporto valore booleano:

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

Grazie, questa è un'ottima risposta, cercherò di avere una lettura su ControlTemplates, c'è un modo per creare il modello in un file separato in modo da poterlo solo #utilizzare e applicarlo? I'll go. evviva ed –

+0

La pratica preferita è applicare qualsiasi ControlTemplate con uno stile, come mostrato sopra. Quello che puoi fare è creare lo stile in un ResourceDictionary separato, quindi applicarlo al CheckBox impostando Style = "{StaticResource MyStyleName}" Se vuoi seguire questa strada, dovresti anche cercare ResourceDictionary.MergedDictionaries in modo che puoi unire le risorse in xaml anziché in C#. – rmoore

+0

Sono costantemente stupito di quanto lavoro possa essere facile in wpf. Ho optato per disabilitare IsHitTestVisible e Focusable invece. – Goran

0

utilizzare la convalida per bloccare il booleano dall'ottenere attivata quando non si desidera che - http://www.codeproject.com/KB/WPF/wpfvalidation.aspx

Questo è molto meno spaventoso dell'altra risposta, o aggancio Cliccato

+0

Mentre questo è molto più semplice e facile per qualcuno di nuovo capire questo non impedirà il rendering del CheckMark quando il mouse viene premuto sul CheckBox, che ritengo sia un UX incompleto. – rmoore

+0

È possibile modificare il modello per evitare che ciò accada –

0

Sto provando a creare il mio stile/modello generico ReadOnlyCheckBox ma sto avendo un problema con l'associazione ai dati. Nell'esempio si associa direttamente ai dati dalla definizione ControlTemplate, ma naturalmente questo non è davvero quello che voglio, come voglio essere in grado di dichiarare la nuova casella di spunta qualcosa di simile:

 <CheckBox x:Name="uiComboBox" Content="Does not set the backing property, but responds to it." 
Style="{StaticResource ReadOnlyCheckBoxStyle}" IsChecked="{Binding MyBoolean}" Click="uiComboBox_Click"/> 

Tranne ovviamente quando faccio questo e poi setto il trigger dell'evento sul bullet per essere un TemplateBinding di IsChecked, ho esattamente quello che ho iniziato! Immagino di non capire perché impostare l'associazione direttamente nel punto elenco è diverso dall'impostazione di IsChecked e quindi l'associazione a questo, non è il TemplateBinding solo un modo per fare riferimento a ciò che è impostato nelle proprietà del controllo che si sta creando? In che modo Click attiva l'aggiornamento dell'interfaccia utente anche se i dati non vengono aggiornati? Esiste un trigger per Fare clic su Ignora per interrompere l'aggiornamento?

Ho tutte le risorse DictionaryResource che funzionano bene, quindi sono contento di questo, applauso per il puntatore.

L'altra cosa che mi incuriosiva era se è possibile ridurre il mio modello di controllo/stile utilizzando il parametro BasedOn nello stile, quindi ignorerei solo le cose che effettivamente ho bisogno di cambiare piuttosto che dichiarare un sacco di roba che penso sia parte del modello standard comunque. Potrei avere un gioco con questo.

Acclamazioni

ndr

16

Che dire di associazione dati alla proprietà IsHitTestVisible?

Per esempio, assumendo un approccio MVVM:

  1. aggiungere un alloggio IsReadOnly al vostro modello di vista, inizialmente fissata come vero per consentire click.
  2. Associazione di questa proprietà a CheckBox.IsHitTestVisible.
  3. Dopo il primo clic, aggiornare il modello di visualizzazione per impostare questo valore su falso, impedendo ulteriori clic.

Non ho questo esatto requisito, ho solo bisogno di una casella di controllo sempre di sola lettura e sembra che risolva il problema in modo corretto. Nota anche il commento di Goran riportato di seguito sulla proprietà Focusable.

+3

In questo caso, è necessario impostare Focusable su false per impedire agli utenti di selezionare la casella di controllo tramite la scheda. – Goran

1

Questa è la classe che ho scritto per fare qualcosa di simile, per ragioni simili (solleva ancora tutti gli eventi Click e Command normalmente, ma non altera la sorgente di binding per impostazione predefinita e non si attiva automaticamente. ha ancora il fade-in-out animato al clic, che è un po 'strano se il codice di gestione dei clic non finisce per cambiare 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!" /> 

(. Si noti che l'IsChecked legame è ora a senso unico per impostazione predefinita, è possibile dichiarare come TwoWay se si vuole, ma che avrebbe sconfitto parte del punto)

0

Se aiuta qualcuno, una soluzione rapida ed elegante che ho trovato è quella di agganciare gli eventi Controllato e Deselezionato e manipolare il valore in base alla bandiera.

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

Non impedirebbe anche il blocco delle modifiche al valore sottostante dal codice? – PaulMolloy

1

risposta tardi, ma ho appena incontrato la questione alla ricerca di qualcosa di diverso. Quello che vuoi non è una casella di controllo con un comportamento insolito, quello che vuoi è un pulsante con aspetto insolito. È sempre più facile modificare l'aspetto di un controllo rispetto al suo comportamento. Qualcosa in questo senso dovrebbe fare (non testato)

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

Avvolgere la casella di controllo di un ContentControl. Make ContentControl IsEnabled = False

+0

Questo ha lo stesso problema dell'impostazione "IsEnabled = false" sulla casella di controllo stessa - viene disabilitato, piuttosto che di sola lettura (differenza nello stile di visualizzazione) –

1

Avevo bisogno che la funzionalità AutoCheck fosse disattivata anche per un Checkbox/RadioButton, in cui volevo gestire l'evento Click senza avere il controllo automatico del controllo. Ho provato varie soluzioni qui e su altri thread e non ero soddisfatto dei risultati.

Quindi ho scavato in quello che sta facendo WPF (utilizzando la riflessione), e ho notato:

  1. Sia CheckBox RadioButton & ereditano dal controllo ToggleButton primitiva. Nessuno dei due ha una funzione OnClick.
  2. Il ToggleButton eredita dalla primitiva del controllo ButtonBase.
  3. Il ToggleButton sovrascrive la funzione OnClick e fa: this.OnToggle(); base.OnClick();
  4. ButtonBase.OnClick fa 'base.RaiseEvent (new RoutedEventArgs (ButtonBase.ClickEvent, this));'

Quindi, in pratica, tutto quello che dovevo fare era ignorare l'evento OnClick, non chiamare OnToggle, e fare base.RaiseEvent

Ecco il codice completo (si noti che questo può essere facilmente rielaborato per fare RadioButtons pure):

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)); 
     } 
    } 

} 

Ora ho un CheckBox che non controlla automaticamente e spara ancora l'evento Click. Inoltre, posso ancora iscriversi agli eventi selezionati/non selezionati e gestirli quando cambio la proprietà IsChecked al livello di programmazione.

Un'ultima nota: a differenza di altre soluzioni che fanno qualcosa come IsChecked! = IsChecked in un evento Click, questo non causa l'attivazione degli eventi Controllato/Non selezionato/Indeterminato finché non si imposta la proprietà IsChecked al livello di programmazione.