Une case à cocher en lecture seule dans C# WPF
j'ai un problème délicat, je veux un comportement légèrement inhabituel à partir d'une case à cocher et ne semble pas pouvoir le comprendre. Toutes les suggestions sont la plupart de bienvenue. Le comportement que je veux c'est:
- la case à cocher est activée et prête à cliquer, IsChecked représente une valeur booléenne liée stockée dans une structure de données
- l'utilisateur clique sur la case à cocher provoquant le déclenchement de l'événement de clic mais la valeur liée dans la structure de données n'est pas mise à jour et le visuel la représentation de la case à cocher N'est pas mise à jour mais elle est désactivée pour arrêter de cliquer plus loin
- L'événement click déclenche un message envoyé à un périphérique distant qui prend le temps de répondre
- le périphérique distant répond provoquant la mise à jour de la structure de données avec la nouvelle valeur, la liaison met alors à jour le statut isChecked et la case à cocher est réactivée pour un clic plus poussé
le problème que j'ai est que bien qu'une liaison de données sur une seule voie fonctionne à ne pas mettre à jour la structure des données lorsque la case à cocher est cochée, la représentation visuelle change (ce qui est étrange, je pense, ne devrait pas agir maintenant comme un pointeur vers la valeur dans la structure des données).
je peux inverser le changement dans L'événement Click() et faire la désactivation là aussi mais c'est assez confus. Je peux également avoir la propriété set de la valeur de la structure de données pour définir une valeur isEnabled qui est également liée à rendre la case à cocher, mais cela semble confus trop.
Est-il un moyen propre de faire cela? Peut-être avec une classe dérivée de cases à cocher? Comment puis-je empêcher la mise à jour de la représentation visuelle?
Merci
Ed
10 réponses
Qu'en est-il de la liaison des données à IsHitTestVisible propriété?
par exemple, en supposant une approche MVVM:
- Ajouter un IsReadOnly propriété de votre modèle de vue, initialement défini comme true pour permettre le clic.
- liant cette propriété à une case à cocher.IsHitTestVisible.
- après le premier clic, mettez à jour votre modèle de vue pour définir cette valeur à false, empêchant tout autre clic.
je n'ai pas cette exigence exacte, j'avais juste besoin d'une case à cocher lecture seule, et il semble pour résoudre le problème joliment. Notez également le commentaire de Goran ci-dessous sur le Focusable propriété.
Je ne pense pas qu'il soit nécessaire de créer un contrôle complet pour cela. Le problème que vous rencontrez vient du fait que l'endroit où vous voyez "le chèque" n'est pas vraiment la case à cocher, c'est une balle. Si l'on regarde l' ControlTemplate pour une case à cocher, nous pouvons voir comment cela se produit (bien que je préfère le modèle de mélange). Dans le cadre de cela, même si votre reliure sur la propriété IsChecked est réglée sur OneWay, elle est toujours mise à jour dans L'interface utilisateur, même si elle n'est pas réglage de la valeur contraignante.
en tant que tel, une façon très simple de corriger cela, est de simplement modifier le ControlTemplate pour la case à cocher en question.
si nous utilisons Blend pour saisir le modèle de contrôle, nous pouvons voir la balle à l'intérieur du temps de contrôle qui représente la zone réelle de la case à cocher.
<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>
ici, les IsChecked et RenderPressed sont ceux qui font réellement apparaître le "Check", donc pour le fixer, nous pouvons enlever la liaison de la propriété IsChecked sur le ComboBox et l'utiliser pour remplacer la reliure de Templiers sur la propriété IsChecked de la balle.
voici un petit échantillon démontrant l'effet désiré, notez que pour maintenir la case à cocher Vista, regardez le cadre de présentation.Aero dll doit être ajouté au projet.
<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>
Et le code-behind, avec notre soutien valeur Booléenne:
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
}
C'est la classe que j'ai écrite pour faire quelque chose de similaire, pour des raisons similaires (soulève toujours tous les évènements de clic et de commande comme normal, mais ne modifie pas la source de liaison par défaut et ne s'active pas automatiquement. Malheureusement, il a toujours le fade-in-out animé sur le clic, ce qui est un peu étrange si le code de manipulation de clic ne finit pas changer 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.
}
}
Utilisation:
<yourns:OneWayCheckBox IsChecked="{Binding SomeValue}"
Command="{x:Static yourns:YourApp.YourCommand}"
Content="Click me!" />
réponse tardive, mais je suis tombé sur la question à la recherche de quelque chose d'autre. Ce que vous voulez n'est pas une case à cocher avec un comportement inhabituel, ce que vous voulez, c'est un bouton avec l'apparence inhabituelle. Il est toujours plus facile de changer l'apparence d'un contrôle de son comportement. Quelque chose dans ce sens devrait être fait (non testé)
<Button Command="{Binding CommandThatStartsTask}">
<Button.Template>
<ControlTemplate>
<CheckBox IsChecked="{Binding PropertySetByTask, Mode=OneWay}" />
</ControlTemplate>
</Button.Template>
</Button>
j'avais besoin que la fonctionnalité de vérification automatique soit désactivée aussi bien pour une case à cocher/RadioButton, où je voulais gérer L'événement de clic sans avoir le contrôle auto-vérification. J'ai essayé diverses solutions ici et sur d'autres fils et j'étais malheureux avec les résultats.
alors j'ai creusé dans ce que fait la WPF (en utilisant la réflexion), et j'ai remarqué:
- CheckBox et RadioButton héritent de la primitive ToggleButton control. Aucun d'entre eux ont un OnClick fonction.
- le ToggleButton hérite de la primitive de contrôle de ButtonBase.
- le bouton ToggleButton remplace la fonction OnClick et fait: ceci.OnToggle (); base.OnClick ();
- ButtonBase.OnClick fait la base.RaiseEvent(nouveau RoutedEventArgs(ButtonBase.ClickEvent, this));'
donc en gros, tout ce que j'avais à faire était de passer outre L'événement OnClick, ne pas appeler OnToggle, et faire la base.RaiseEvent
voici le code complet (notez que ceci peut facilement être retravaillé pour faire des RadioButtons aussi bien):
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));
}
}
}
j'ai maintenant une case à cocher qui ne vérifie pas automatiquement, et déclenche toujours L'événement de clic. De plus, je peux toujours m'abonner aux événements contrôlés/non contrôlés et y Gérer les choses lorsque je change programmatiquement la propriété IsChecked.
une dernière remarque: contrairement à d'autres solutions qui font quelque chose comme IsChecked != IsChecked dans un événement de clic, cela ne provoquera pas les événements cochés/non cochés/indéterminés à tirer jusqu'à ce que vous définir par programmation la propriété IsChecked.
Utiliser la Validation de bloquer le booléen de se basculé lorsque vous ne voulez pas qu'elle - http://www.codeproject.com/KB/WPF/wpfvalidation.aspx
C'est beaucoup moins effrayant que l'autre réponse, ou d'accrocher Cliqué
j'ai essayé de créer mon modèle générique de ReadOnlyCheckBox mais j'ai un problème avec la liaison aux données. Dans l'exemple, vous vous liez directement aux données de la définition de ControlTemplate, mais bien sûr ce n'est pas vraiment ce que je veux, car je veux pouvoir déclarer la nouvelle case à cocher quelque chose comme ceci:
<CheckBox x:Name="uiComboBox" Content="Does not set the backing property, but responds to it."
Style="{StaticResource ReadOnlyCheckBoxStyle}" IsChecked="{Binding MyBoolean}" Click="uiComboBox_Click"/>
sauf bien sûr quand je fais ça et que je mets le déclencheur de l'événement sur la balle pour être un TemplateBinding D'IsChecked j'ai exactement ce que j'ai commencé par! Je suppose que je ne comprends pas pourquoi mettre la reliure directement dans la balle est différent de mettre IsChecked et puis lier à cela, est-ce que le TemplateBinding n'est pas juste une façon de se référer à ce qui est mis dans les propriétés du contrôle étant créé? Comment le clic déclenche-t-il la mise à jour de L'interface même si les données ne sont pas mises à jour? Y a-t-il un déclencheur pour le clic que je peux annuler pour arrêter la mise à jour?
j'ai toutes les ressources Dictionaires qui fonctionnent bien donc je suis heureux avec que vive le pointeur.
l'autre chose qui m'a intrigué était de savoir s'il était possible de réduire mon modèle de contrôle/Style en utilisant le paramètre BasedOn dans le style, alors je ne ferais que passer outre les choses que j'ai réellement besoin de changer plutôt que de déclarer beaucoup de choses qui, je pense, font partie du modèle standard de toute façon. Je pourrais avoir un jouer avec cela.
Cheers
ed
si cela peut aider quelqu'un, une solution rapide et élégante que j'ai trouvé est d'accrocher avec les événements contrôlés et non contrôlés et de manipuler la valeur basée sur votre drapeau.
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; }
envelopper la case à cocher dans un ContentControl. Make the ContentControl IsEnabled=False
ces deux propriétés sont-IsHitTestVisible et focalisable Faites ces deux propriétés à False. Cela fait la case à cocher en lecture seule dans WPF. Ainsi, la déclaration finale de XAML sera comme suit pour la case à cocher readonly dans WPF -