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");
}
}
Related
I use a DataGrid in WPF to display values.
Now I want to have green and red rows. I tried it out with DataTrigger but nothing happends.
My XAML:
<DataGrid x:Name="dgAbos" ItemsSource="{Binding Source=AboList}" HorizontalAlignment="Stretch" Margin="10,30,10,10" VerticalAlignment="Stretch" Height="Auto" Width="Auto">
<DataGrid.Columns>
<DataGridTextColumn Header="ItemID" Binding="{Binding ItemID}" />
</DataGrid.Columns>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding Active}" Value="false">
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Active}" Value="true">
<Setter Property="Background" Value="Green"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
</DataGrid>
The Binding type is:
ObservableCollection<OPCItem> AboList = new ObservableCollection<OPCItem>();
And the Item to display is OPCItem:
class OPCItem
{
public String ItemID { get; set; }
public String Name { get; set; }
public String Value { get; set; }
public DateTime DateTime { get; set; }
public String Group { get; set; }
private Boolean _Active;
public String Active
{
get
{
return (_Active == true ? "Aktiv" : "Inaktiv");
}
set
{
_Active = Convert.ToBoolean(value);
}
}
}
How I fill the list:
AboList.Add(new OPCItem { ItemID = Item.ItemID, Group = GroupName, Active = "true" });
But the row doesnt change the color, why?
The value of your Active property is never "true" nor "false", so the triggers are never actually triggered. You should modify the expected values of your triggers to reflect the values that Active might take (which are "Aktiv" and "Inaktiv"):
<DataTrigger Binding="{Binding Active}" Value="Inaktiv">
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Active}" Value="Aktiv">
<Setter Property="Background" Value="Green"></Setter>
</DataTrigger>
This is a really bad idea:
private Boolean _Active;
public String Active
{
get
{
return (_Active == true ? "Aktiv" : "Inaktiv");
}
set
{
_Active = Convert.ToBoolean(value);
}
}
So you set Active to "true" or "false", and you get back "Aktiv" or "Inaktiv"? The natural result of that is you confuse yourself to the point where you write a trigger assuming that Active.get will return "true" or "false" instead of "Activ" or "Inaktiv". You can fix the immediate problem by just fixing the DataTrigger.Value attributes as Grx70 suggests, but it's better in the long run to fix the underlying issue and to avoid bad habits like this. Particularly if you intend to work in the field, you really don't want to be writing this kind of stuff. Active looks like a simple property, but the behavior is nothing that anybody would ever expect, so anybody interacting with it will be taken by surprise, and then they'll have to read the code to figure out what it does. Think about it this way: When you see a property on a WPF control called IsEnabled, just by looking at the property name you know exactly what it means, how to use it, and what type it is. It makes your life easier.
So instead, I recommend you make your boolean property an actual boolean:
private bool _isActive;
public bool IsActive {
get { return _isActive; }
set { _isActive = value; }
}
XAML:
<Style.Triggers>
<DataTrigger Binding="{Binding IsActive}" Value="false">
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding IsActive}" Value="true">
<Setter Property="Background" Value="Green"></Setter>
</DataTrigger>
</Style.Triggers>
If you wan to display the strings "Aktiv" and "Inaktiv" somewhere in the UI, you can do that with another trigger, or with a readonly property called something like ActiveDisplayString.
You should probably be implementing INotifyPropertyChanged as well in that OPCItem class; that way, you can change the property values after they're in the grid, and the UI will reflect the changes.
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
Hie.
So I have a password box.
What I basically am trying to do is to get it's border colour to change to Red if it's empty and change to white if it's not.
A simple mechanism just to inform the user which field they left blank on the sign up form.
After a bit of research I put together this code.
<Window.Resources>
<conv:rules x:Key="isNullConverter"/>
<Style TargetType="{x:Type PasswordBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding Password, ValidatesOnDataErrors=False, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource isNullConverter}}" Value="False">
<Setter Property="BorderBrush" Value="White"/>
</DataTrigger>
<DataTrigger Binding="{Binding Password, ValidatesOnDataErrors=False, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource isNullConverter}}" Value="True">
<Setter Property="BorderBrush" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
'conv' is set to reference 'rules' namespace.
'rules' is the class where I put the below code.
using System.Windows.Data;
using System.Globalization;
namespace Masca
{
public class rules : IValueConverter, INotifyPropertyChanged, IDataErrorInfo
{
private string something;
public string Password
{
get
{
return something;
}
set
{
if (something!= value)
{
something = value;
RaisePropertyChanged("Password");
}
}
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
{
value = false;
}
if (value != null)
{
value = true;
}
return value;
}
private void RaisePropertyChanged(string prop)
{
if (PropertyChanged == null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public string Error
{
get { return null; }
}
Every time I run the program though, There is always a red border around the password box, even after I've typed something. Shouldn't the DataTrigger update the boundary to white? Or am I missing something?
"What I basically am trying to do is to get it's border colour to change to Red if it's empty and change to white if it's not."
If this is your problem, then simply have the trigger activate off of the Text property of the TextBox. If it is "", then the BorderBrush is red; otherwise White. FYI, you only need one trigger for this, not two. Set the "normal" default color as a property on the control, then a trigger for if Text is "":
<Setter Property="BorderBrush" Value="White">
and
<Trigger Property="Text" Value="">
<Setter Property=BorderBrush" Value="Red"/>
</Trigger>
P.S. You may want to ensure that the TextBox has received focus at least once before you color the border Red
[My main idea of this is to set visible/hidden for a usercontrol. I used WPF with Mvvmcross.]
I have a user control call SpinningWheelUserControl. I want to visible/hide it with the datatrigger. Below is my xaml code in App.xaml
In App.xaml I have added the namespace of the usercontrol as below.
xmlns:local="clr-namespace:UserControl"
The following is a style setting for my usercontrol.
<Style x:Key="SpinningWheel" TargetType="{x:Type local:SpinningWheelUserControl}" >
<Style.Triggers>
<DataTrigger Binding="{Binding IsVisible}" Value="true">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding IsVisible}" Value="false">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
There is a class for SpinningWheel
public class SpinningWheelViewModel
: MvxNotifyPropertyChanged
{
public bool IsVisible { get; set; }
}
In a constructor of parent class, i use like this code
SpinningWheel = new SpinningWheelViewModel();
SpinningWheel.IsVisible = false;
The usercontrol is hidden for a first running. But when I change the IsVisble to true, it has no change.
SpinningWheel.IsVisible = true
You need to set Visibility instead of IsVisible like this:
SpinningWheel.Visibility = Visibility.Visible;
Oh now i see, you are setting your custom IsVisibility instead of UIElement property.
Issue with your code is you haven't raised PropertyChanged to let UI know that some property change in underlying source object.
private bool isVisible;
public bool IsVisible
{
get { return isVisible;}
set
{
if(isVisible != value)
{
isVisible = value;
RaisePropertyChanged("IsVisible");
}
}
}
Assuming you have implemented INotifyPropertyChanged on your class.
This n+1 video called N=34 : a data-bound busy dialog shows exactly how to do what you are trying to do.
I have a comboBox that has a datatrigger that set its SelectedIndex based on a .NET Property's value that in the VM. My problem is that I can't get the setter to set the Selected Index.
The ItemSource is based on a enum array.
The DataContext of the Window is the VM which has the Modulation, and Bandwidth properties.
I'm new to WPF so I'm sure I'm not understanding binding correctly, but I'm pulling my hair out! Thanks for your help in advance.
Here's the Style.
<Style x:Key="BWCombBoxStyle" TargetType="{x:Type ComboBox}" BasedOn="{StaticResource {x:Type ComboBox}}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors).CurrentItem.ErrorContent}"/>
</Trigger>
<DataTrigger
Binding="{Binding Modulation}" Value="P25">
<Setter Property="SelectedIndex" Value="2"/>
</DataTrigger>
</Style.Triggers>
</Style>
Here's the ComboBox:
<ComboBox Name="bandwidth"
Height="Auto" Width="70"
Style="{StaticResource BWCombBoxStyle}"
ItemsSource="{Binding BandwidthOptions, Mode=OneWay, ValidatesOnDataErrors=true, NotifyOnValidationError=true, UpdateSourceTrigger=PropertyChanged}"
SelectedValue="{Binding IFBandwidth, Mode=TwoWay, ValidatesOnDataErrors=True,
NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged}"/>
Here are the .Net Properties in my VM:
public TMod Modulation
{
get { return modulation_; }
set { modulation_ = value; NotifyPropertyChanged("Modulation"); }
}
public Channel.TBnd IFBandwidth
{
get { return chan_.IFBandwidth; }
set
{
chan_.IFBandwidth = value;
NotifyPropertyChanged("IFBandwidth");
}
}
public Channel.TBnd[] BandwidthOptions
{
get
{
return (Channel.TBnd[])System.Enum.GetValues(typeof(Channel.TBnd));
}
}
Here are the enums:
public enum TMod
{
FM = 0,
AM = 1,
P25 = 2,
TRK = 3
}
public enum TBnd
{
Std = 0,
Nar = 1,
Wide = 2,
XWide = 3
}
Change your ComboBox binding to use SelectedValue instead of SelectedPath. That will properly set the IFBandwidth view model property when the value is changed.
What exactly is the trigger going to be used for? It may be a better option to change your Modulation property to be something like this...
public TMod Modulation
{
get { return modulation_; }
set
{
modulation_ = value;
NotifyPropertyChanged("Modulation");
if( modulation == TMod.P25 )
{
IFBandwith = TBand.Wide;
}
}
}