Un CheckBox de solo lectura en C#WPF
radio button wpf (10)
Tengo un problema difícil, quiero un comportamiento un poco inusual en una casilla de verificación y parece que no puedo resolverlo. Cualquier sugerencia sería bienvenida. El comportamiento que quiero es:
- El CheckBox está habilitado y listo para que el usuario haga clic, IsChecked representa un valor booleano enlazado almacenado en una estructura de datos
- El usuario hace clic en el CheckBox, lo que provoca que se active el evento de clic, pero el valor enlazado en la estructura de datos NO se actualiza y la representación visual del CheckBox NO se actualiza, pero se desactiva para dejar de hacer más clic
- El evento de clic activa un mensaje que se envía a un dispositivo remoto, lo cual demora un tiempo en responder
- El dispositivo remoto responde haciendo que la estructura de datos se actualice con el nuevo valor, el enlace luego actualiza el estado de isChecked y el CheckBox se vuelve a habilitar para seguir haciendo clic.
El problema que tengo es que aunque un enlace de datos de OneWay funciona al no actualizar la estructura de datos cuando se hace clic en el CheckBox, la representación visual cambia (lo cual creo que es extraño, IsChecked no debería actuar ahora como un puntero al valor en el enlace). estructura de datos).
Puedo revertir el cambio en el evento Click () y deshabilitarlo allí también, pero esto es bastante complicado. También puedo tener la propiedad set del valor de la estructura de datos para establecer un valor isEnabled que también está obligado a volver a habilitar el CheckBox, pero eso también parece complicado.
¿Hay una manera limpia de hacer esto? Quizás con una clase CheckBox derivada? ¿Cómo puedo evitar que la representación visual se actualice?
Gracias
Ed
¿Qué pasa con los enlaces de datos a la propiedad IsHitTestVisible ?
Por ejemplo, asumiendo un enfoque MVVM:
- Agregue una propiedad IsReadOnly a su modelo de vista, inicialmente establecido como verdadero para permitir el clic.
- Vinculando esta propiedad a CheckBox.IsHitTestVisible.
- Después del primer clic, actualice su modelo de vista para establecer este valor en falso, evitando que se realicen más clics.
No tengo este requisito exacto, solo necesitaba una casilla de verificación 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 .
Envuelva el CheckBox en un ContentControl. Hacer que el ContentControl IsEnabled = False
Esas dos propiedades son: IsHitTestVisible y Focusable Hacen que estas dos propiedades sean Falso. Esto hace que la casilla de verificación de solo lectura en WPF. Por lo tanto, la declaración final de XAML será la siguiente para la casilla de verificación de solo lectura en WPF:
Esta es la clase que escribí para hacer algo similar, por razones similares (todavía genera todos los eventos de clic y comando como de costumbre, pero no altera la fuente de enlace de manera predeterminada y no cambia automáticamente. Desafortunadamente, todavía tiene la animado desvanecimiento en el clic, que 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 el enlace IsChecked ahora es unidireccional de forma predeterminada; puede declararlo como Dos Vías si lo desea, pero eso anularía parte del punto).
He estado intentando crear mi estilo / plantilla genérica de ReadOnlyCheckBox pero tengo un problema con el enlace a los datos. En el ejemplo, se enlaza directamente con los datos de la definición de ControlTemplate, pero, por supuesto, esto no es realmente lo que quiero, ya que quiero poder declarar la nueva casilla de verificación 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 desencadenante del evento en la viñeta para que sea un enlace de plantilla de IsChecked, ¡tengo exactamente lo que empecé! Supongo que no entiendo por qué establecer el enlace directamente en la viñeta es diferente de establecer IsChecked y luego vincularlo, ¿no es el Enlace de plantilla solo una forma de hacer referencia a lo que se establece en las propiedades del control que se está creando? ¿Cómo hace clic el botón para activar la actualización de la interfaz de usuario incluso cuando los datos no se actualizan? ¿Hay un desencadenante para Clic que puedo anular para detener la actualización?
Conseguí que todas las cosas de DictionaryResource funcionaran bien, así que estoy feliz con eso, aplausos por el puntero.
La otra cosa que tenía curiosidad era si es posible reducir mi plantilla de Control / Estilo usando el parámetro BasedOn en el estilo, entonces solo anularía las cosas que realmente necesito cambiar en lugar de declarar muchas cosas que creo Es parte de la plantilla estándar de todos modos. Podría jugar con esto.
Aclamaciones
ed
No creo que sea necesario crear un control completo para esto. El problema con el que se está encontrando proviene 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á establecido en OneWay, todavía se está actualizando en la interfaz de usuario, incluso si no está configurando el valor de enlace.
Como tal, una manera realmente simple de solucionar esto, es simplemente modificar la Plantilla de Control para la casilla de verificación en cuestión.
Si usamos Blend para capturar la plantilla de control, podemos ver la viñeta dentro de la plantilla 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>
Aquí, IsChecked y RenderPressed son lo que realmente hace que aparezca el ''Cheque'', para corregirlo, podemos eliminar el enlace de la propiedad IsChecked en ComboBox y usarlo para reemplazar el TemplateBinding en la propiedad IsChecked del Bullet.
Aquí hay una pequeña muestra que demuestra el efecto deseado, tenga en cuenta que para mantener el aspecto de CheckBox de Vista, se debe agregar el PresentationFramework.Aero dll 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 valor booleano de respaldo:
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
}
Respuesta tardía, pero acabo de encontrar la pregunta buscando otra cosa. Lo que quiere no es una casilla de verificación con un comportamiento inusual, lo que quiere es un botón con una apariencia inusual. Siempre es más fácil cambiar la apariencia de un control que su comportamiento. Algo a lo largo de estas líneas debería hacer (no probado)
<Button Command="{Binding CommandThatStartsTask}">
<Button.Template>
<ControlTemplate>
<CheckBox IsChecked="{Binding PropertySetByTask, Mode=OneWay}" />
</ControlTemplate>
</Button.Template>
</Button>
Si ayuda a alguien, una solución rápida y elegante que he encontrado es enlazar con los eventos marcados y no verificados y manipular el valor en función de su marca.
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; }
Tenía la necesidad de que la funcionalidad de AutoCheck estuviera desactivada también para un Checkbox / RadioButton, donde quería manejar el evento Click sin tener el control auto-check. He intentado varias soluciones aquí y en otros subprocesos y no estaba contento con los resultados.
Así que me interesé en lo que WPF está haciendo (usando Reflexión), y noté:
- Tanto CheckBox como RadioButton se heredan de la primitiva de control ToggleButton. Ninguno de ellos tiene una función OnClick.
- El ToggleButton se hereda de la primitiva de control ButtonBase.
- El botón ToggleButton anula la función OnClick y hace: this.OnToggle (); base.OnClick ();
- ButtonBase.OnClick hace ''base.RaiseEvent (nuevo RoutedEventArgs (ButtonBase.ClickEvent, this));''
Básicamente, todo lo que tenía que hacer era anular el evento OnClick, no llamar a OnToggle y hacer base.RaiseEvent
Aquí está el código completo (tenga en cuenta que esto también se puede modificar fácilmente para hacer 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));
}
}
}
Ahora tengo un CheckBox que no se verifica automáticamente y todavía dispara el evento Click. Además, aún puedo suscribirme a los eventos marcados / no controlados y manejar las cosas allí cuando cambio la propiedad IsChecked mediante programación.
Una nota final: 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 activen hasta que usted establezca la propiedad IsChecked mediante programación.
Use la validación para evitar que se active el booleano cuando no quiera que lo haga: http://www.codeproject.com/KB/WPF/wpfvalidation.aspx
Esto es mucho menos aterrador que la otra respuesta, o engancharlo.