Datatrigger cannot see default property value in xaml - c#

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.

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

How to handle visibility and isEnabled for multiple buttons and other elements effectively

I have some elements on my main filter, in my wpf application.
But I donĀ“t want to set visibility and isEnabled one by one in get/set. Is there more elegant way, how to change it from view model?
Thank you! :)
You can use a DataTrigger to change some properties of your Button based on the view model properties:
<Window.Resources>
<local:MyViewModel x:Key="viewModelInstance"></local:MyViewModel>
</Window.Resources>
<StackPanel>
<Button DataContext="{StaticResource viewModelInstance}" Content="My Button">
<Button.Style>
<Style TargetType="Button">
<!-- Default style is Visible and Enabled -->
<Setter Property="IsEnabled" Value="True"></Setter>
<Setter Property="Visibility" Value="Visible"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding IsAllowed}" Value="False">
<!-- Hide and disable when IsAllowed is false -->
<Setter Property="IsEnabled" Value="False"></Setter>
<Setter Property="Visibility" Value="Hidden"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>
Assuming you have a view model class defined like:
public class MyViewModel : INotifyPropertyChanged {
public bool IsAllowed { get; set; } = true;
//Put more logic here of course.
}
MyViewModel should implement INotifyPropertyChanged to automatically notify the UI to update the view when the IsAllowed property changes, for example like this:
public class MyViewModel : INotifyPropertyChanged {
//Backing field for IsAllowed
private bool _isAllowed = true;
/// <summary>
/// Gets or sets the IsAllowed property.
/// </summary>
public bool IsAllowed {
get => _isAllowed; set {
if (_isAllowed != value) {
_isAllowed = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsAllowed)));
}
}
}
//INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
}
You can also look into the IValueCOnverter interface and XAML converters for other ways.

Updating toggle buttons check state when one of them is toggled

I have a XamDataGrid with 2 fields. In the second field, I have toggle buttons. When the ToggleButton is clicked, the value gets set correctly. But at any time user should be able to check only one ToggleButton among all the records. The data in the set logic for the Default property works fine. But I need to set the values of Default for other items in DataSource.
Using a RadioButton instead of ToggleButton would the ideal solution. But it is possible that none of the records are default. So I want a solution to make the other default fields false when one of them becomes true.
XAML for field:
<igWPF:Field Name="Default" Width="84">
<igWPF:Field.Settings>
<igWPF:FieldSettings CellValuePresenterStyle="{StaticResource ButtonDefault}" />
</igWPF:Field.Settings>
</igWPF:Field>
XAML for CellValuePresenterStyle:
<Style x:Key="ButtonDefault" TargetType="{x:Type igWPF:CellValuePresenter}" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type igWPF:CellValuePresenter}">
<ToggleButton Content="Default"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="16"
Width="80"
Height="36"
IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Datasource:
public class LanguageSettingItem : INotifyPropertyChanged
{
private string name;
private bool isDefault;
public event PropertyChangedEventHandler PropertyChanged;
public string Name
{
get { return this.name; }
set { this.name = value; PropChanged("Name"); }
}
public bool Default
{
get { return this.isDefault; }
set { this.isDefault= value; PropChanged("Default"); }
}
public void PropChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
You can have Toggle Button event Checked and Unchecked with same event Handler.
Then just put a if condition - (if toggle_btn.isChecked)'checked code' else 'unchecked code'

Binding Button.IsEnabled to Boolean Property

I have a boolean property that looks at several checkboxes and returns true if any of them are checked. I would like to enable a button if any checkboxes are checked (property returns true).
Currently I have the following:
The data context set
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
The button binding set
<Button Name="button" IsEnabled="{Binding ButtonEnabled}">Apply</Button>
The property
public bool ButtonEnabled
{
get
{
if(checkboxes_enabled)
return true;
else
return false;
}
}
I have verified that the property is updating as it is supposed to, so it's narrowed down to a binding issue. I have also tried data triggers within the button:
<Button Name="button" Content="Apply">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding ButtonEnabled}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
<DataTrigger Binding="{Binding ButtonEnabled}" Value="False">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Two things:
You need INotifyPropertyChanged if you are making updates to a property that is bound.
public class MyClass
{
private bool _buttonEnabled;
public bool ButtonEnabled
{
get
{
return _buttonEnabled;
}
set
{
_buttonEnabled = value;
OnPropertyChanged();
}
}
public SetButtonEnabled()
{
ButtonEnabled = checkboxes_enabled;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged<T>([CallerMemberName]string caller = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(caller));
}
}
}
You should also not have two triggers, and just use a default value.
<Button Name="button" Content="Apply">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="IsEnabled" Value="True"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ButtonEnabled}" Value="False">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
you need to add the following code to implement INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
then call OnPropertyChanged from the property setter
I Would suggest binding the button to a command rather then an event, This way you can just set the command's "canexecute" property to false and disable the whole command that will intern disable the button for you.
I recommend the below tutorial to get a good understanding on WPF commands and how to use them, Once you understand how they work I find they are extremely useful.
http://www.codeproject.com/Articles/274982/Commands-in-MVVM#hdiw1

WPF UI controls not validating correctly on ErrorsChanged event

I have the following INotifyDataErrorInfo implementation in an abstract base class.
private IEnumerable<ValidationErrorModel> _validationErrors = new List<ValidationErrorModel>();
public IEnumerable<ValidationErrorModel> ValidationErrors
{
get { return _validationErrors; }
private set
{
_validationErrors = value;
OnPropertyChanged();
}
}
protected abstract Task<ValidationResult> GetValidationResultAsync();
public IEnumerable GetErrors(string propertyName)
{
if (string.IsNullOrEmpty(propertyName) ||
ValidationErrors == null)
return null;
IEnumerable<string> errors = ValidationErrors
.Where(p => p.PropertyName.Equals(propertyName))
.Select(p => p.ToString())
.ToList();
return errors;
}
public bool HasErrors
{
get
{
bool hasErrors = ValidationErrors != null && ValidationErrors.Any();
return hasErrors;
}
}
public Task<ValidationResult> ValidateAsync()
{
Task<ValidationResult> validationResultTask = GetValidationResultAsync();
validationResultTask.ContinueWith((antecedent) =>
{
if (antecedent.IsCompleted &&
!antecedent.IsCanceled &&
!antecedent.IsFaulted)
{
ValidationResult validationResult = antecedent.Result;
if (validationResult != null)
{
lock (ValidationErrors)
{
ValidationErrors =
validationResult.Errors
.Select(validationFailure =>
new ValidationErrorModel(validationFailure.PropertyName, validationFailure.ErrorMessage))
.ToList();
foreach (ValidationErrorModel validationErrorModel in ValidationErrors)
{
RaiseErrorsChanged(validationErrorModel.PropertyName);
}
}
}
}
});
return validationResultTask;
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged = delegate { };
protected virtual void RaiseErrorsChanged(string propertyName)
{
var handler = ErrorsChanged;
if (handler != null)
{
Dispatcher.InvokeOnMainThread(() =>
{
handler(this, new DataErrorsChangedEventArgs(propertyName));
});
}
}
In models deriving from the base class I implement the Task<ValidationResult> GetValidationResultAsync() required method, it uses fluent validation Nuget package.
private readonly ModelValidator _modelValidator = new ModelValidator();
protected override Task<ValidationResult> GetValidationResultAsync()
{
return _modelValidator.ValidateAsync(this);
}
The problem is that when I invoke from a ViewModel the ValidateAsync() method of a model the UI input controls are not invalidate/validate correctly, I actually have a tab control and validate the models in tab index changed, some might show the red border once I change tab but then again return to normal state to the next tab change.
In debug it shows that the ValidationErrors property returns errors.
My XAML input controls code is like below.
<Grid>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name:"/>
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, ValidatesOnNotifyDataErrors=True}" Width="200"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Scheduled Date:"/>
<DatePicker DisplayDate="{Binding ScheduledDate, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, ValidatesOnNotifyDataErrors=True}"/>
</StackPanel>
</StackPanel>
</Grid>
[Update 1]
I should mention that I use in the MainWindow a tab control and 3 tab items, each tab item is a UserControl.
I hooked up to the Validation.Error event of all the XAML UserControls and I noticed that even I get tab selected index changed value the Validation.Error fires once for the first tab and never again, I suspect there is a cleanup somewhere for a reason.
Code for the SelectedTabIndex that fires the models validations.
private int _selectedTabIndex = 0;
public int SelectedTabIndex
{
get { return _selectedTabIndex; }
set
{
_selectedTabIndex = value;
ValidateModels();
Tab2ViewModel.ValidateModels();
Tab3ViewModel.ValidateModels();
OnPropertyChanged();
}
}
The ValidateModels method calls ValidateAsync of the model in the ViewModel.
public override Task ValidateModels()
{
return Model.ValidateAsync();
}
MainWindow TabControl XAML.
<TabControl SelectedIndex="{Binding SelectedTabIndex, Mode=TwoWay}">
[Update 2]
After adding a custom error style and a custom error template, I see that the controls tooltip stay with the condition not met error but the error template is clearing. So, the TextBox shows no error template, custom or default, but the validation error exists and the tooltip shows the error.
Why the XAML templates clear on TabIndexChange and how come they don't refresh at least on the active tab item I'm viewing. This might be the problem that I should solve.
Also, as mentioned before, I don't see the ErrorsChanged revalidating the controls except the first time the SelectedTabIndex setter is invoked.
The templates I added.
<Application.Resources>
<Style x:Key="ErrorStyle"
TargetType="FrameworkElement">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding (Validation.Errors).CurrentItem.ErrorContent, RelativeSource={x:Static RelativeSource.Self}}"></Setter>
</Trigger>
</Style.Triggers>
</Style>
<ControlTemplate x:Key="TextBoxErrorTemplate">
<DockPanel>
<Ellipse DockPanel.Dock="Right"
Margin="2,0"
ToolTip="Contains Invalid Data"
Width="10"
Height="10"
>
<Ellipse.Fill>
<LinearGradientBrush>
<GradientStop Color="#11FF1111" Offset="0"/>
<GradientStop Color="#FFFF0000" Offset="1"/>
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
<Style TargetType="TextBox">
<Setter Property="Margin" Value="4,4,15,4"/>
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource TextBoxErrorTemplate}"/>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip">
<Setter.Value>
<Binding Path="(Validation.Errors).CurrentItem.ErrorContent" RelativeSource="{x:Static RelativeSource.Self}"/>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</Application.Resources>
The problem is that tabs, expanders etc don't work well with validators, you need to include AdornerDecorator, or not use tabs which in my case is not an option.
Issue with WPF validation(IDataErrorInfo) and tab focusing.

Categories

Resources