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
Related
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));
}
}
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.
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.
I'm getting SO frustrated here.. I can't get these datatriggers to consistently work..
It works when I first run the program as I initialize a global UdpMessageAuthentication class (as it sets it to "test0"... but then I have a button that calls the SendAuthPacket method.. and from debugging I see it go into the OnPropertyChanged when I hit the button but the label won't change caption or color or any other property...once I use AuthenticateStatus to "test1".
Obviously I tried more realistic variables besides test0 and test1 but no matter what I'm doing I can't get the triggers to update
Please help =T
<Label Name="Label_Authentication" Margin="5,0,0,0" VerticalAlignment="Center" Grid.Column="0" FontSize="14">
<Label.Style>
<Style TargetType="Label">
<Setter Property="Content" Value="Initial Content"></Setter>
<Setter Property="Foreground" Value ="Red"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=AuthenticateStatus}" Value="test0">
<Setter Property="Content" Value="Authentication Required" />
<Setter Property="Foreground" Value="Red"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Path=AuthenticateStatus}" Value="test1">
<Setter Property="Content" Value="Attempting Authentication..." />
<Setter Property="Background" Value="Blue"></Setter>
</DataTrigger>
etc....
public class UdpMessageAuthentication : INotifyPropertyChanged
{
private string _authenticateStatus;
public string AuthenticateStatus
{
get { return _authenticateStatus; }
set
{
if (_authenticateStatus != value)
{
_authenticateStatus = value;
OnPropertyChanged("Authenticate Status");
}
}
}
public UdpMessageAuthentication()
{
_udpClient = new UdpClient();
AuthenticateStatus = "test0";
}
public void SendAuthPacket(IPAddress ip, string userID)
{
etc etc....
AuthenticateStatus = "test1";
etc etc....
}
Make sure you explicitly set the Mode Property when you use (any) binding.
<DataTrigger Binding="{Binding Path=AuthenticateStatus, Mode=OneWay}" Value="test0">
Also, you have to make sure you're raising the property changed event with the PropertyName string set to exactly the name of the proprty being raised, since the system is using Reflection under the hood to find the changed property based on said string. Thus, try using this in your ViewModel:
get { return _authenticateStatus; }
set
{
if (_authenticateStatus != value)
{
_authenticateStatus = value;
OnPropertyChanged("AuthenticateStatus");
}
}
I've created a WPF UserControl which contains a Button and a ComboBox. I'd like to change the style of both, depending on the position of the mouse, so the UIElement with the mouse over is coloured Black and the other is coloured Red. If neither are styled then the default styling will apply.
Don't worry, this nightmarish colour scheme is just to illustrate the concept!
Thanks in advance for your help.
XAML
<UserControl x:Class="WpfUserControlSample.ToolbarButtonCombo"
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:WpfUserControlSample"
x:Name="Control"
mc:Ignorable="d"
d:DesignHeight="30">
<UserControl.Resources>
<Style TargetType="{x:Type local:ToolbarButtonCombo}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsButtonMouseOver}" Value="True">
<Setter Property="ButtonStyle" Value="Black"/>
<Setter Property="ComboStyle" Value="Red"/>
</DataTrigger>
<!--
<DataTrigger Binding="{Binding IsComboMouseOver}" Value="True">
<Setter Property="ButtonStyle" Value="Red"/>
<Setter Property="ComboStyle" Value="Black"/>
</DataTrigger>
-->
</Style.Triggers>
</Style>
</UserControl.Resources>
<StackPanel Orientation="Horizontal" Height="30">
<Button Name="btn" Background="{Binding ButtonStyle,ElementName=Control,Mode=OneWay}">
Test
</Button>
<ComboBox Name="cmb" Background="{Binding ComboStyle,ElementName=Control,Mode=OneWay}"></ComboBox>
</StackPanel>
</UserControl>
Codebehind:
namespace WpfUserControlSample
{
public partial class ToolbarButtonCombo : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public ToolbarButtonCombo()
{
InitializeComponent();
btn.MouseEnter += new MouseEventHandler(btn_MouseChanged);
btn.MouseLeave += new MouseEventHandler(btn_MouseChanged);
}
void btn_MouseChanged(object sender, MouseEventArgs e)
{
OnPropertyChanged("IsButtonMouseOver");
}
public bool IsButtonMouseOver
{
get { return btn.IsMouseOver; }
}
public static readonly DependencyProperty IsButtonMouseOverProperty =
DependencyProperty.Register("IsButtonMouseOver", typeof(string), typeof(ToolbarButtonCombo), new PropertyMetadata("false"));
public string ButtonStyle { get; set; }
public static readonly DependencyProperty ButtonStyleProperty =
DependencyProperty.Register("ButtonStyle", typeof(string), typeof(ToolbarButtonCombo));
public string ComboStyle { get; set; }
public static readonly DependencyProperty ComboStyleProperty =
DependencyProperty.Register("ComboStyle", typeof(string), typeof(ToolbarButtonCombo));
}
}
There are a two problems.
First your DataTrigger bindings do not look correct. They are looking for the IsButtonMouseOver on the DataContext, not the associated control. You'd need to use:
<DataTrigger Binding="{Binding IsButtonMouseOver, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="ButtonStyle" Value="Black"/>
<Setter Property="ComboStyle" Value="Red"/>
</DataTrigger>
Or:
<Trigger Property="IsButtonMouseOver" Value="True">
<Setter Property="ButtonStyle" Value="Black"/>
<Setter Property="ComboStyle" Value="Red"/>
</Trigger>
The other is your IsButtonMouseOver is not implemented correctly. You should do something like:
public static readonly DependencyProperty IsButtonMouseOverProperty = DependencyProperty.Register("IsButtonMouseOver",
typeof(bool), typeof(ToolbarButtonCombo), new PropertyMetadata(false));
public bool IsButtonMouseOver
{
get { return (bool)this.GetValue(IsButtonMouseOverProperty); }
set { this.SetValue(IsButtonMouseOverProperty, value); }
}
void btn_MouseChanged(object sender, MouseEventArgs e)
{
this.IsButtonMouseOver = this.btn.IsMouseOver;
}
Or even more correctly, make the IsButtonMouseOver a read-only dependency property like so:
private static readonly DependencyPropertyKey IsButtonMouseOverPropertyKey = DependencyProperty.RegisterReadOnly("IsButtonMouseOver",
typeof(bool), typeof(ToolbarButtonCombo), new FrameworkPropertyMetadata(false));
public static readonly DependencyProperty IsButtonMouseOverProperty = ToolbarButtonCombo.IsButtonMouseOverPropertyKey.DependencyProperty;
public bool IsButtonMouseOver {
get { return (bool)this.GetValue(IsButtonMouseOverProperty); }
private set { this.SetValue(IsButtonMouseOverPropertyKey, value); }
}
Your other properties (ButtonStyle and ComboStyle) would need to be properly implemented also, and their get/set methods are not backed by the dependency property.