I'm trying to bind two properties so I can change the background color in DataGrid based on their value. Based on these answers
How to bind an enum to a combobox control in WPF?
best way to bind enum propery in datagrid
I have implemented the advice in my code, but I'm missing something and it doesn't work.
Thanks for any advices.
namespace Example
{
public class ExampleClass
{
private ExampleObject exampleObject;
public ExampleObject ExampleObject { get; set; }
}
}
namespace Object
{
public class ExampleObject
{
private Value value;
public ExampleObject ExampleObject { get; set; }
}
public enum Value
{
High,
Low
}
}
Wpf DataGrid DataTrigger where I am changing the colour
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding ExampleObject.Value}" Value="{StaticResource CellConverter}">
<Setter Property="Background" Value="Green">
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ExampleObject.Value}" Value="{StaticResource CellConverter}">
<Setter Property="Background" Value="Red">
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
CellConvertor class
public class CellConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Value input = ((Value)value);
switch (input)
{
case Value.High:
return "High";
case Value.Low:
return "Low";
default:
return DependencyProperty.UnsetValue;
}
}
}
You must fix the trigger condition in your example.
Additionally, in order to bind to the ExampleObject.Value enum value, the ExampleObject.Value must be a public property:
public class ExampleObject
{
public Value Value {get; set;}
}
In XAML you reference enum values like static variables and constants by using the x:Static markup extension. In fact, C# enum is implemented as a set of constants. When using the x:Static extension, your current value converter CellConverter becomes obsolete:
<!--
In this example the namespace that defines the enum type 'Value'
is assumed to be registered under the XAML alias 'local'
-->
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding ExampleObject.Value}"
Value="{x:Static local:Value.Low}">
<Setter Property="Background" Value="Green" />
</DataTrigger>
<DataTrigger Binding="{Binding ExampleObject.Value}"
Value="{x:Static local:Value.High}">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
Remarks: in general, if you want to use a value converter, then you must configure the Binding accordingly and assign the IValueConverter instance to the Binding.Converter property. You can't assign a IValueConverter directly to a property to make it convert. The IValueConverter must receive an input that he can convert to produce the output. The input is the value provided by the actual Binding (and that's why Binding has a Binding.Converter property - they always go in tandem).
See Microsoft Docs: DataBinding Overview (Data conversion)
Note, since the property DataTrigger.Value is not a DependencyProperty, you can't define its value via a Binding.
Converter example:
<Window>
<Window.Resources>
<MyValueConverter x:Name="MyValueConverter" />
</Window.Resources>
<SomeObject SomeDependencyProperty="{Binding SourceProperty, Converter={StaticResource MyValueConverter}}" />
</Window>
It's also recommende best practice to define a default enum value to avoid errors. The default value for an enum instance is always 0 which in your case would default to High. Better allow to identify an unset state by adding an explicit 0 value named None or Default:
public enum Value
{
None = 0,
High,
Low
}
See: Microsoft Docs: Enum Design
Related
Consider I have to style basing on two properties:
<Label Style={Binding IsEnabled, Convert={x:Static IsEnabledToStyleConverter}} />
or
<Label Style={Binding IsRequired, Convert={x:Static IsEnabledToStyleConverter}} />
TO determine wheter use binding with IsEnabled or IsRequired is other property - UseRequried. How Can is choose between those two bindings basin on UseRequired?
I have tried to approaches:
Another value converter
I have create own value converter:
public class ControlToLableStleConverter: IValueConverter
{
public object Convert(object value..)
{
var myCtrl = (MyControl) value;
if (myCtrl.UseRequired)
//return style based on IsRequired property
else
//return style based on IsEnabled property
}
}
But the problem is that style is no changing at IsEnabled or IsRequired changed. Quite obvious, so this solution is out.
DataTrigger
I have also created DataTriggers:
<Label>
<Label.Triggers>
<DataTrigger Binding="{Binding UseRequired}" Value="True">
<Setters>
<Setter Property="Style" Value="{Binding IsRequired ....">
</Setters>
</DataTrigger>
<DataTrigger Binding="{Binding UseRequired}" Value="False">
<Setters>
<Setter Property="Style" Value="{Binding IsEnabled ....">
</Setters>
</DataTrigger>
</Label.Triggers>
</Label>
But Label.Triggers can contain only EventTrigger elements.. What can i do else?
You can use MultiBinding in this case, though it might be quite verbose:
<Label>
<Label.Style>
<MultiBinding Converter="{StaticResource yourConverter}">
<Binding Path="IsEnabled" />
<Binding Path="IsRequired" />
</MultiBinding>
</Label.Style>
</Label>
And converter is then:
public class StyleConverter : IMultiValueConverter {
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
// take some caution here, because values can be null or DependencyProperty.UnsetValue in certain cases
var enabled = (bool) values[0];
var required = (bool) values[1];
// choose style
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
Set the triggers within the style.
<Style TargetType="{x:Type MyControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding UseRequired}" Value="True">
<Setters>
<!-- Set properties here, not the Style -->
</Setters>
</DataTrigger>
<DataTrigger Binding="{Binding UseRequired}" Value="False">
<Setters>
<!-- Set properties here, not the Style -->
</Setters>
</DataTrigger>
</Style.Triggers>
</Style>
If you can't change properties then your best option is to use a StyleSelector (MSDN).
Put the such logic in the code behind and trigger changes off of UseRequiered (or elsewhere) and trigger all property changes such as
public bool UseRequiered
{
get { return _UseRequried; }
set { _UseRequried = value; DetermineStyle(); }// PropertyChanged in DetermineStyle()
}
public bool IsRequiered
{
get { return _IsRequiered; }
set { _IsRequiered = value; DetermineStyle();} // PropertyChanged in DetermineStyle()
}
public bool IsEnabled
{
get { return _IsEnabled; }
set { _IsEnabled = value; DetermineStyle(); } // Prop change in DetermineStyle
}
private void DetermineStyle()
{
_UseRequiered = { Whatever logic is deemed };
_IsRequired = { Whatever logic is deemed };
_IsEnabled = { Whatever logic is deemed };
OnPropertyChanged("IsRequired");
OnPropertyChanged("IsEnabled");
OnPropertyChanged("UseRequiered");
}
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.
If IsEnabled property is true, I need to set the Style attribute otherwise it shouldn't be set. In the examples I've seen so far, style properties are set but not the style attribute itself. Below code is not working using Triggers.
<TabItem.Style>
<Style TargetType="TabItem">
<Style.Triggers>
<DataTrigger Binding="{Binding IsEnabled}" Value="True">
<Setter Property="Style" Value="DotcomTabItemStyle" />
</DataTrigger>
</Style.Triggers>
</Style>
</TabItem.Style>
Since you're setting the Trigger through Style, changing the Style would also remove the Trigger... Not really sure if that'll work out :P
Anyway, you're making a mistake on your Setter (setting the resource name directly, not through a static or dynamic resource reference). And you don't need a DataTrigger. It should be:
<Trigger Property="IsEnabled" Value="True">
<Setter Property="Style" Value="{StaticResource DotcomTabItemStyle}" />
</Trigger>
But as I said, this won't probably work as intended, since you're trying to modify the Style property from within the current Style...
One way or another, you'll end up adding different Setters for each property, probably either modifying the DotcomTabItemStyle Style you already have, or creating a new one (based on that one, maybe).
EDIT - Or you could use a Converter and bind the Style property to the IsEnabled property.
I've created a reusable Converter for all this kind of situations:
public class ConditionalSetterConverter : IValueConverter
{
public bool Inverse { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool flag = (bool)value;
if (flag ^ Inverse)
return parameter;
else
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
You use it like this:
<Window>
<Window.Resources>
<converters:ConditionalSetterConverter x:Key="InverseConditionalSetterConverter"
Inverse="True" />
<Style x:Key="DotcomTabItemStyle" TargetType="TabItem">...</Style>
</Window.Resources>
<TabControl>
<TabItem Style="{Binding IsEnabled,
RelativeSource={RelativeSource Mode=Self},
Converter={StaticResource InverseConditionalSetterConverter},
ConverterParameter={StaticResource DotcomTabItemStyle}}" />
</TabControl>
</Window>
EDIT 2 - OR... You could use a Style selector. ItemsControls like TabControl have a property called ItemContainerStyleSelector, of type StyleSelector.
You'd have to create your own class, inheriting StyleSelector, and override the SelectStyle function to include your custom logic there.
Something like this:
public class DotcomTabItemStyleEnabledSelector : StyleSelector
{
private Style style = null;
public override System.Windows.Style SelectStyle(object item, System.Windows.DependencyObject container)
{
var tabItem = container as TabItem;
if (tabItem != null && tabItem.IsEnabled)
{
if (style == null)
style = textBox.TryFindResource("DotcomTabItemStyle") as Style;
return style;
}
return null;
}
}
I've never used Style selectors, so I'm not really sure if this would work out of the box, but at least you get the idea.
[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.
Let's say i have the code below...i have a Field class that holds a state and the actual value to display. I have model that defines an instance of this MyField class named Field1. In by DataGrid i am binding to this Field1 and using a style to display the value as well as color the background if IsStale is true. The problem is neither the value, nor the background is being colored. It appears the problem is that when used as is, the datacontext for the Style is MyData object and not in fact the MyField object, even though i specify the binding as "Field1". The error being printed out is "BindingExpression path error: 'IsStale' property not found on 'object' ''MyDataModel'".
How can properly bind to a complex property in a Datagrid's cell such that i can use multiple attributes of the bound model?
class MyField : BaseModel
{
private bool _isStale;
public bool IsStale
{
get { return _isStale; }
set
{
if (_isStale == value) return;
_isStale = value;
NotifyPropertyChanged("IsStale");
}
}
private double _value;
public double Value
{
get { return _value; }
set
{
if (_value.Equals(value)) return;
_value = value;
NotifyPropertyChanged("Value");
}
}
}
class MyDataModel
{
MyField Field1 {get; set;}
public MyData()
{
Field1 = new Field1();
//when ever underlying property of MyField changes, we need to fire property changed because xaml binds to Field1
Field1.PropertyChanged += (o,e) =>
{
MyField field = o as MyField;
if (field!=null)
NotifyPropertyChanged("Field1");
};
}
}
<DataGridTextColumn Header="Weight" Binding="{Binding Field1}" ElementStyle="{StaticResource DGCellStyle}"/>
Style:
<Style x:Key="DGCellStyle" TargetType="TextBlock">
<Setter Property="Width" Value="Auto"/>
<Setter Property="Text" Value="{Binding Value, Converter={StaticResource NumberFormatConverter}, ConverterParameter=0.00}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsStale}" Value="True">
<Setter Property="Background" Value="Pink"/>
</DataTrigger>
</Style.Triggers>
</Style>