Utilizing Override in UserControl to Change Property Breaks Trigger - c#

I have created an instance of a TextBox that implements ICommandSource, I would like to control the IsEnabled property via the DataContext. This portion of my code works, on top of this I would like to control the Text property via this same method or by extension the IsEnabled property.
Basically when the TextBox transitions from IsEnabled="False" to IsEnabled="True" I would like to reset the Text field to an empty string or preferably null.
I have attempted to do this in a handful of ways without success.
Attempt 1
<ctrl:CommandTextBox x:Name="txtSerialNumber"
Command="{Binding VMFactory.CreateViewModelCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding Text, RelativeSource={RelativeSource Self}}" DecoderPrefix="S">
<ctrl:CommandTextBox.Style>
<Style TargetType="{x:Type ctrl:CommandTextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="True" />
<Setter Property="Text" Value="{x:Null}" />
</DataTrigger>
</Style.Triggers>
<Setter Property="IsEnabled" Value="False" />
<Setter Property="Text" Value="{Binding SerialNumber, Mode=OneWay}" />
</Style>
</ctrl:CommandTextBox.Style>
</ctrl:CommandTextBox>
This does work but only when the CommandParameter does not need to be "Decoded". It seems as though when my text property is changed via the override it breaks the trigger until the application is restarted.
CommandTextBox.cs
public class CommandTextBox : DecoderTextBox, ICommandSource
{
// Additional Fields, Properties, and Methods removed for the sake of brevity.
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.Key == Key.Enter && Command != null)
{
RoutedCommand command = Command as RoutedCommand;
if (command != null)
command.Execute(CommandParameter, CommandTarget);
else
Command.Execute(CommandParameter);
if (CommandResetsText)
this.Text = String.Empty;
e.Handled = true;
}
}
}
DecoderTextBox.cs
public class DecoderTextBox : TextBox
{
public static DependencyProperty DecoderPrefixProperty = DependencyProperty.Register("DecoderPrefix", typeof(string), typeof(DecoderTextBox), new PropertyMetadata(String.Empty));
public string DecoderPrefix
{
get { return (string)GetValue(DecoderPrefixProperty); }
set { SetValue(DecoderPrefixProperty, value); }
}
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
string text = this.Text;
// If the if statement returns true the trigger will break.
if (text.Substring(0, Math.Min(DecoderPrefix.Length, text.Length)) == DecoderPrefix)
this.Text = text.Remove(0, DecoderPrefix.Length);
}
base.OnKeyDown(e);
}
}
Is there something specific to my implementation of OnKeyDown that is breaking this trigger?

There is an issue related to setting the value of a DependencyProperty locally. It appears as though you have to use SetCurrentValue to maintain the binding.
DecoderTextBox.cs
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
if (Text.StartsWith(DecoderPrefix))
SetCurrentValue(TextProperty, Text.Remove(0, DecoderPrefix.Length));
}
base.OnPreviewKeyDown(e);
}

Related

Binding foreground color of control to mouse hover

I have a user control for which I have to change color, based on mouse hover, click or none. Following MVVM. This is the code I have:
User control in XAML
<userControls:NC DataContext="{Binding NCVM}" >
</userControls:NC>
User Control View Model
public class NCVM : ObservableObject
{
public NCVM()
{
}
private NCState _currentState = NCState.InActive;
public NCState CurrentState
{
get => _currentState;
set
{
_currentState = value;
switch (_currentState)
{
case NCState.InActive:
ForegroundColor = System.Windows.Media.Brushes.LightGray;
IsActive = false;
break;
case NCState.Active:
ForegroundColor = System.Windows.Media.Brushes.White;
IsActive = true;
break;
case NCState.Hovered:
ForegroundColor = System.Windows.Media.Brushes.White;
IsActive = false;
break;
default:
ForegroundColor = System.Windows.Media.Brushes.LightGray;
IsActive = false;
break;
}
}
}
public bool _isActive;
public bool IsActive
{
get => _isActive;
set => SetProperty(ref _isActive, value);
}
private System.Windows.Media.Brush _foregroundColor = System.Windows.Media.Brushes.LightGray;
public System.Windows.Media.Brush ForegroundColor
{
get => _foregroundColor;
set => SetProperty(ref _foregroundColor, value);
}
}
Main Window View Model
public class MWVM : BVM
{
#region Private Variables
private NCVM _NCVM = new();
#endregion
public MWVM()
{
NCVM.CurrentState = NCState.Active;
}
#region Public Properties
public NCVM NCVM
{
get => _NCVM;
set => SetProperty(ref _NCVM, value);
}
#endregion
}
Right now, it's getting preset as active for checking. Now, I have to make it manual so it changes on hover, but not getting how to do with binding.
The MVVM pattern is about separating the user interface (view) from the data and application logic itself. Your example violates MVVM in that it stores the brushes and the visual states in a view model. The view model should only expose data and commands to be bound, but not user interface elements and it must not contain logic to that relates to the user interface just like managing visual states or appearance. It is too often misunderstood as creating a view model and just putting everything there.
In your case, I think that you can solve your issue by moving everything into a style. The following XAML should show your userControls:NC. There are triggers for different states like Disabled, Hover / Mouse Over. Please note that you need to set a Background, otherwise the control does not participate in hit testing and e.g. the IsMouseOver property will not be True even if you hover over it. For no background use Transparent (which is not equal to not setting a value).
<UserControl ...>
<UserControl.Style>
<Style TargetType="{x:Type userControls:NC}">
<!-- Background must be set at least to "Transparent" -->
<Setter Property="Background" Value="Black"/>
<!-- Default -->
<Setter Property="Foreground" Value="LightGray"/>
<Style.Triggers>
<!-- Hovered -->
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="White"/>
</Trigger>
<!-- Disabled -->
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="LightGray"/>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Style>
<!-- Dummy element for demonstration purposes of foreground -->
<TextBlock Text="This text shows the foreground"/>
</UserControl>
You may take a look at EventTrigger, or Triggers in general to style your control.
*Edit:
A little example, MVVM not considered, just for you to get a glimpse at triggers.
UserControl:
<UserControl x:Class="WpfApp1.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type={x:Type local:UserControl1}}"
Height="200" Width="400">
<UserControl.Style>
<Style TargetType="UserControl">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsMyPropSet}" Value="True">
<Setter Property="Background" Value="Turquoise"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Style>
<GroupBox Header="I am your usercontrol">
<Button Width="100" Height="35" Content="Toggle Property" Click="Button_Click"/>
</GroupBox>
</UserControl>
and code-behind:
public partial class UserControl1 : UserControl, INotifyPropertyChanged
{
public UserControl1()
{
InitializeComponent();
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
public bool IsMyPropSet { get; set; }
private void Button_Click(object sender, RoutedEventArgs e)
{
IsMyPropSet = !IsMyPropSet;
RaisePropertyChanged(nameof(IsMyPropSet));
}
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

Datatrigger cannot see default property value in xaml

I'm trying to display a control in the xaml if the property ConnectionStatus is True, this property have the following structure:
private bool _connectionStatus = true;
public bool ConnectionStatus
{
get { return _connectionStatus; }
set
{
_connectionStatus = value;
OnPropertyChanged();
}
}
and as you can see this property have true as default value.
Then in my xaml window I've used a DataTrigger to show or hide the control based on the ConnectionStatus value. What I did so far:
<StackPanel Grid.Column="1">
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding ConnectionStatus}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<Ellipse Fill="#FF51FF00" Height="17" Width="17" Margin="0,5,0,0" StrokeThickness="1" Stroke="White" />
</StackPanel>
now the problem's that the Ellipse simply not showing, 'cause the whole StackPanel is collapsed, but it shouldn't 'cause the property value is True, when I set False in the xaml code, I get the Ellipse displayed correctly.
Note that: this situation only happen on the preview window, if I start the application all working good. Someone could please explain me why in the preview the trigger doesn't read the property value correctly?
Further information
the window that have the StackPanel have the DataContext declared in this way:
xmlns:local="clr-namespace:MyApp.MVVM.ViewModels"
d:DataContext="{d:DesignInstance local:ConnectionVM}">
then the ConnectionVM have this implementation:
public class ConnectionVM: ViewModel
{
//the property defined on top
}
and the ViewModel have this structure:
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
Thanks.

In a trigger, can you test for the value of another property?

Is it possible to base the value property of a trigger to a property of an element?
For instance, in a ControlTemplate that has a ScrollBar, I'm trying to set its Visibility property to Collapsed if its Minimum and Maximum properties are equal.
However, the following doesn't work because you can't set a Binding as the Value of a Trigger because a Trigger is not a DependencyObject.
<Trigger Property="Minimum"
SourceName="PART_ScrollBar"
Value="{Binding Maximum, SourceName=PART_ScrollBar}">
<Setter Property="Visibility"
TargetName="PART_ScrollBar"
Value="Collapsed" />
</Trigger>
So can this be done purely with triggers, or do I have to do this in code-behind?
In this scenario, Id recommend creating a custom behavior
Like this
public class MinMaxVisibilityBehavior : Behavior<ScrollBar>
{
public override void OnAttached()
{
DependencyPropertyDescriptor
.FromProperty(ScrollBar.MaximumProperty, typeof(ScrollBar))
.AddValueChanged(AssociatedObject, CheckMinMax);
DependencyPropertyDescriptor
.FromProperty(ScrollBar.MinimumProperty, typeof(ScrollBar))
.AddValueChanged(AssociatedObject, CheckMinMax);
}
private void CheckMinMax(object sender, EventArgs e)
{
AssociatedObject.Visibility = AssociatedObject.Minimum ==
AssociatedObject.Maximum ? Visibility.Hidden : Visibility.Visible;
}
}
and then in your XAML
<ScrollBar>
........
<i:Interaction.Behaviors>
<local:MinMaxVisibilityBehavior />
</i:Interaction.Behaviors>
</ScrollBar>

Mouse Double Click DataGrid row

I have a DataGrid style template that I wish to add double click behaviour to. The binding should be correct but I cannot seem to get the xaml compiling / working.
All objects added to an IDictionary must have a Key attribute
or some other type of key associated with them.
What is wrong with the code below?
<Style TargetType="{x:Type DataGridRow}">
<EventSetter Event="MouseDoubleClick" Handler="{Binding Connect}"/>
Update per Viktor's comment (gives exact same error):
<Style x:Key="dataGridRowStyle" TargetType="{x:Type DataGridRow}">
<EventSetter Event="PreviewMouseDoubleClick" Handler="{Binding Connect}"/>
One can use DataGrid InputBindings to achieve goal:
<DataGrid.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding SomeCommand}" />
</DataGrid.InputBindings>
You can apply the following behavior on data grid row and follow the usage for implementation.
Double Click Behavior
public class DoubleClickBehavior
{
#region DoubleClick
public static DependencyProperty OnDoubleClickProperty = DependencyProperty.RegisterAttached(
"OnDoubleClick",
typeof(ICommand),
typeof(DoubleClickBehavior),
new UIPropertyMetadata(DoubleClickBehavior.OnDoubleClick));
public static void SetOnDoubleClick(DependencyObject target, ICommand value)
{
target.SetValue(OnDoubleClickProperty, value);
}
private static void OnDoubleClick(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
var element = target as Control;
if (element == null)
{
throw new InvalidOperationException("This behavior can be attached to a Control item only.");
}
if ((e.NewValue != null) && (e.OldValue == null))
{
element.MouseDoubleClick += MouseDoubleClick;
}
else if ((e.NewValue == null) && (e.OldValue != null))
{
element.MouseDoubleClick -= MouseDoubleClick;
}
}
private static void MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
UIElement element = (UIElement)sender;
ICommand command = (ICommand)element.GetValue(OnDoubleClickProperty);
command.Execute(null);
}
#endregion DoubleClick
}
Usage
<Style BasedOn="{StaticResource {x:Type DataGridRow}}"
TargetType="{x:Type DataGridRow}">
<Setter Property="Helpers:DoubleClickBehavior.OnDoubleClick" Value="{Binding Path=DataContext.MyCommandInVM, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ViewLayer:MyUserControl}}}" />
</Style>
Not sure if you're going the MVVM route, but I've achieved this functionality using an Attached Command Behavior to wire up the double click event to a command in my viewmodel (where "command" is a reference to my attachedCommandBehavior assembly/class):
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="command:CommandBehavior.Event" Value="MouseDoubleClick"/>
<Setter Property="command:CommandBehavior.Command" Value="{Binding SelectItemCmd}"/>
<Setter Property="command:CommandBehavior.CommandParameter" Value="{Binding }"/>
</Style>
</DataGrid.RowStyle>

Textbox Event Handling in ViewModel

I have a situation, where I am validating a textbox for enabling the button. If the textbox is empty the button should be disabled and vice verse. I can handle the code and achieve the solution, if I write the logic in the code behind of the XAML but I feel thats not the correct way and the event should be handled from the viewModel instead of the code behind.
Here is what I have done:
XAML
<TextBox Grid.Row="1" Margin="6,192,264,0" Height="60" VerticalAlignment="Top"
x:Name="txtDNCNotes" Text="{Binding Path=DNCNotes, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
TextWrapping="Wrap" VerticalScrollBarVisibility="Auto"
Visibility="{Binding Path=DNCNoteTxtVisibility}" Grid.Column="1"
behaviour:TextBoxFilters.IsBoundOnChange="True"
TextChanged="TextBox_TextChanged" />
ViewModel
public string DNCNotes
{
get { return _dncNotes; }
set {
if (_dncNotes == value) return;
_dncNotes = value;
OnPropertyChanged("DNCNotes");
}
}
Code behind
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
var ctx = LayoutRoot.DataContext as NextLeadWizardViewModel;
BindingExpression binding = txtDNCNotes.GetBindingExpression(TextBox.TextProperty).UpdateSource();
ctx.ShowDoNotContact();
}
I am trying to write following code in the viewModel to achieve the solution but not sure what to write.
public void ShowDoNotContact()
{
Binding myBinding = new Binding("DNCNotes");
//myBinding.Source = DataContext as NextLeadWizardViewModel;
myBinding.Source = txtDNCNotes;
myBinding.Path = new PropertyPath("DNCNotes");
myBinding.Mode = BindingMode.TwoWay;
myBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
BindingOperations.SetBinding(txtDNCNotes, TextBox.TextProperty, myBinding);
if (_dncNotes == null)
OkCommand.IsEnabled = false;
else
OkCommand.IsEnabled = CanEnableOk();
}
If you want to validate a TextBox which would disable the button, i would use a command, something similar to this;
private ICommand showDCNoteCommand;
public ICommand ShowDCNoteCommand
{
get
{
if (this.showDCNoteCommand == null)
{
this.showDCNoteCommand = new RelayCommand(this.DCNoteFormExecute, this.DCNoteFormCanExecute);
}
return this.showDCNoteCommand;
}
}
private bool DCNoteFormCanExecute()
{
return !string.IsNullOrEmpty(DCNotes);
}
private void DCNoteFormExecute()
{
DCNoteMethod(); //This a method that changed the text
}
This would ensure that the user is unable to continue, or save to progress as the TextBox should not accept a null or empty value, shown within the DCNoteFormCanExecute() (the DCNotes is property that you have defined within your Viewmodel).
and in the xaml, bind it to the button like so;
<Button Content="Save" Grid.Column="1" Grid.Row="20" x:Name="btnSave" VerticalAlignment="Bottom" Width="75" Command="{Binding ShowDCNoteCommand}"
For validation, you could do something simple like so, using attribute validation, using this reference using System.ComponentModel.DataAnnotations;
[Required(ErrorMessage = "DCNotes is required")]
[RegularExpression(#"^[a-zA-Z''-'\s]{1,5}$", ErrorMessage = "DCNotes must contain no more then 5 characters")] //You can change the length of the property to meet the DCNotes needs
public string DCNotes
{
get { return _DCNotes; }
set
{
if (_DCNotes == value)
return;
_DCNotes = value;
OnPropertyChanged("DCNotes");
}
}
and within the xaml, you could create a Resource to highlight the box to notify the user of the textbox not been filled out;
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Margin"
Value="4" />
</Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Margin"
Value="4" />
<Style.Triggers>
<Trigger Property="Validation.HasError"
Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
I hope this helps, otherwise, here's the link that might help;
http://www.codeproject.com/Articles/97564/Attributes-based-Validation-in-a-WPF-MVVM-Applicat
OR
http://www.codearsenal.net/2012/06/wpf-textbox-validation-idataerrorinfo.html#.UOv01G_Za0t
The ViewModel is an acceptable place to add supporting properties for your View that do not effect your model. For example, something along the lines of:
public bool DncCanExecute
{
get
{
return "" != _dncNotes;
}
}
public string DNCNotes
{
get { return _dncNotes; }
set {
if (_dncNotes == value) return;
if (("" == _dncNotes && "" != value) || ("" != _dncNotes && "" == value))
{
_dncNotes = value;
OnPropertyChanged("DncCanExecute");
}
else
{
_dncNotes = value;
}
OnPropertyChanged("DNCNotes");
}
}
From there, you can just bind the Button.IsEnabled property to the DncCanExecute property to get the desired functionality.

Categories

Resources