2009-05-28 35 views
15

Tengo un problema complicado, quiero un comportamiento ligeramente inusual en una casilla de verificación y parece que no puedo resolverlo. Cualquier sugerencia sería bienvenida. El comportamiento que quiero es:Un CheckBox de solo lectura en C# WPF

  1. la casilla de verificación está activada y lista para que el usuario haga clic, IsChecked representa un valor booleano límite almacenado en una estructura de datos
  2. el usuario hace clic en la casilla de verificación haciendo que el evento click para disparar, pero el valor encuadernado en la estructura de datos NO se actualiza y la representación visual del CheckBox NO se actualiza, pero está desactivada para dejar de hacer clic en
  3. El evento click activa un mensaje para enviarlo a un dispositivo remoto que tarda un poco en responder
  4. El dispositivo remoto responde causando que la estructura de datos se actualice con el nuevo valor, th e vinculante a continuación, actualiza el estado isChecked y la casilla de verificación se vuelve a activar para hacer clic en más

El problema que tengo es que aunque un dato de un solo sentido las obras de unión al no actualizar la estructura de datos cuando se hace clic en la casilla de verificación, la representación visual hace el cambio (que creo que es extraño, no debería IsChecked ahora actuar como un puntero al valor en la estructura de datos).

Puedo revertir el cambio en el evento Click() y desactivarlo también, pero esto es bastante desordenado. También puedo tener la propiedad de establecer el valor de la estructura de datos para establecer un valor de isEnabled que también está obligado a reactivar el CheckBox, pero que también parece desordenado.

¿Hay una manera limpia de hacer esto? Tal vez con una clase derivada de CheckBox? ¿Cómo puedo detener la actualización de la representación visual?

Gracias

Ed

Respuesta

4

No creo que la creación de un control conjunto para esto es necesario. El tema que se está ejecutando en viene del hecho de que el lugar donde se ve 'el cheque' no es realmente la casilla de verificación, es una bala. Si miramos el ControlTemplate para un CheckBox podemos ver cómo sucede eso (aunque me gusta más la plantilla Blend). Como parte de eso, aunque su enlace en la propiedad IsChecked está configurado en OneWay, todavía se está actualizando en la interfaz de usuario, incluso si no está estableciendo el valor de enlace.

Como tal, una manera realmente simple de arreglar esto, es simplemente modificar el ControlTemplate para la casilla en cuestión.

Si usamos Blend para agarrar la plantilla de control, podemos ver la viñeta dentro de ControlTemplate que representa el área de la casilla de verificación real.

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

En este caso, el IsChecked y RenderPressed son los que efectivamente están haciendo la 'marcar' aparecerá, por lo que solucionarlo, podemos eliminar la unión de la propiedad IsChecked en el cuadro combinado y lo utilizan para reemplazar el TemplateBinding en la propiedad IsChecked de Bullet.

He aquí una pequeña muestra de lo que demuestra el efecto deseado, tenga en cuenta que para mantener el aspecto de Vista CheckBox la DLL PresentationFramework.Aero necesita ser añadido al proyecto.

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

Y el código detrás, con nuestro respaldo valor 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

Gracias, esa es una gran respuesta, intentaré leer en ControlTemplates, ¿hay alguna manera de poder crear la plantilla en un archivo separado para poder simplemente #utilizarla y aplicarla? Voy a probarlo. vítores ed –

+0

La práctica preferida es aplicar cualquier plantilla de control con un estilo, como se muestra arriba. Lo que puede hacer es crear el estilo en un ResourceDictionary separado y luego aplicarlo al CheckBox configurando Style = "{StaticResource MyStyleName}". Si desea ir por esa ruta, también debe consultar ResourceDictionary.MergedDictionaries para que puede fusionar los recursos en xaml en lugar de C#. – rmoore

+0

Estoy constantemente sorprendido de cuánto trabajo algo tan fácil puede hacer en wpf. Opté por deshabilitar IsHitTestVisible y Focusable en su lugar. – Goran

0

Uso de validación para bloquear el booleano de conseguir alternar cuando no quiere que - http://www.codeproject.com/KB/WPF/wpfvalidation.aspx

Este es mucho menos aterrador que la otra respuesta, o enganchar Clicked

+0

Si bien esto es mucho más simple y fácil de entender para alguien nuevo, esto no evitará la renderización de CheckMark cuando se presiona el mouse sobre el CheckBox, lo cual creo que es un UX incompleto. – rmoore

+0

Puede editar la plantilla para que esto no ocurra –

0

He estado tratando de crear mi estilo/plantilla genérica ReadOnlyCheckBox pero estoy teniendo un problema con la vinculación a los datos. En el ejemplo que se unen directamente a los datos de la definición ControlTemplate, pero por supuesto esto no es realmente lo que quiero, como quiero ser capaz de declarar la nueva casilla algo como esto:

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

Excepto, por supuesto cuando hago esto y luego configuro el desencadenador de evento en la viñeta para que sea un Enlace de plantilla de IsChecked, ¡tengo exactamente con lo que comencé! Supongo que no entiendo por qué configurar el enlace directamente en la viñeta es diferente de establecer IsChecked y luego vincularlo a eso, ¿no es el TemplateBinding solo una forma de hacer referencia a lo que se establece en las propiedades del control que se está creando? ¿Cómo está haciendo clic para activar la actualización de la interfaz de usuario aunque los datos no se actualicen? ¿Hay un disparador para Click que puedo anular para detener la actualización?

Obtuve todo el material de DictionaryResource funcionando bien, así que estoy contento con eso, saludos para el puntero.

La otra cosa por la que sentía curiosidad era si es posible reducir mi plantilla de Control/Estilo utilizando el parámetro BasedOn en el estilo, entonces solo anularía las cosas que realmente necesito cambiar en lugar de declarar una gran cantidad de cosas que creo que es parte de la plantilla estándar de todos modos. Podría tener una jugada con esto.

Saludos

ed

16

¿Qué pasa con la unión a la propiedad IsHitTestVisible datos?

Por ejemplo, asumiendo un enfoque MVVM:

  1. añadir un alojamiento IsReadOnly a su modelo de vista, establecido inicialmente como true para permitir clic.
  2. Enlazando esta propiedad a CheckBox.IsHitTestVisible.
  3. Después del primer clic, actualice su modelo de vista para establecer este valor en falso, evitando cualquier otro clic.

No tengo este requisito exacto, solo necesitaba una casilla de verificación siempre de solo lectura, y parece resolver el problema muy bien. También tenga en cuenta el comentario de Goran a continuación sobre la propiedad Focusable.

+3

En este caso, debe establecer Focusable en falso para evitar que los usuarios marquen la casilla de verificación usando tabulación. – Goran

1

Esta es la clase que he escrito para hacer algo similar, por motivos similares (aún plantea todos los eventos de clic y comando de forma normal, pero no altera la fuente de enlace de manera predeterminada y no cambia automáticamente. Desafortunadamente todavía tiene el fade-in-out animado al hacer clic, lo cual es un poco extraño si el código de manejo de clics no termina cambiando 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. 
    } 
} 

Uso:

<yourns:OneWayCheckBox IsChecked="{Binding SomeValue}" 
         Command="{x:Static yourns:YourApp.YourCommand}" 
         Content="Click me!" /> 

(. Tenga en cuenta que la IsChecked unión es ahora un solo sentido por defecto, se puede declarar como TwoWay si quieres, pero eso iría en contra de parte del punto)

0

Si ayuda a alguien, una solución rápida y elegante que he encontrado es enganchar con los eventos Controlados y No Controlados y manipular el valor basado en su bandera.

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

¿No bloquearía eso los cambios en el valor subyacente del código también? – PaulMolloy

1

respuesta tardía, pero me encontré con la cuestión buscando otra cosa. Lo que quiere no es una casilla con comportamiento inusual, lo que quiere es un botón con apariencia inusual. Siempre es más fácil cambiar la apariencia de un control que su comportamiento. algo en este sentido debería hacer (no probado)

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

Wrap la casilla de verificación en un ContentControl. Hacer ContentControl IsEnabled = False

+0

Esto tiene el mismo problema que configurar "IsEnabled = false" en la casilla de verificación en sí - se desactiva, en lugar de solo lectura (diferencia en el estilo visual) –

1

Necesitaba que la función AutoCheck también estuviera desactivada para una casilla de verificación/RadioButton, donde quería manejar el evento Click sin tener el control automático. He intentado varias soluciones aquí y en otros hilos y no estaba contento con los resultados.

Y cavé en lo que está haciendo WPF (utilizando la reflexión), y me di cuenta:

  1. Tanto CheckBox & RadioButton heredar del control ToggleButton primitiva. Ninguno de ellos tiene una función OnClick.
  2. El ToggleButton hereda de la primitiva de control ButtonBase.
  3. El ToggleButton anula la función OnClick y lo hace: this.OnToggle(); base.OnClick();
  4. ButtonBase.OnClick hace 'base.RaiseEvent (nuevo RoutedEventArgs (ButtonBase.ClickEvent, this));'

Así que, básicamente, todo lo que tenía que hacer era anular el evento OnClick, no llame OnToggle, y hacer base.RaiseEvent

Aquí está el código completo (tenga en cuenta que esto puede ser fácilmente revisado a fin de hacer RadioButtons también):

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

} 

ahora tengo una casilla de verificación que no se auto-comprobación, y todavía desencadena el evento Click. Además, aún puedo suscribirme a los eventos Controlados/No Controlados y manejarlos allí cuando cambie programáticamente la propiedad IsChecked.

Una última nota: a diferencia de otras soluciones que hacen algo como IsChecked! = IsChecked en un evento Click, esto no hará que los eventos Checked/Unchecked/Indeterminate se disparen hasta que establezca programáticamente la propiedad IsChecked.

Cuestiones relacionadas