How to set different background according to one property? - c#

I have ListView which ItemSource bindend to ObservableCollection<Period> where Period is
public class Period : INotifyPropertyChanged
{
//some stuff
//
public Status PeriodStatus
{
get;
set;
}
#region PropertyChangedEventHandler members
public void SendPropertyChanged(string name)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(name));
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
public enum Status
{
None,
Added,
Deleted,
Edited
}
And i want set background of each ListViewItem in this order : added-green/deleted-red/edited-yellow/none-default . Found at here many solutions, but didn't provide my solution. If there is exist question , please comment and i will close this
[EDIT]
I wanted to use DataTemplate in this way: create template which create binding with Background property and Status in Period which uses converter. But didn't know how to keep rest of design

Please use DataTriggers for the Background property in the Style of ListViewItem (example in this question: you don't need the converter, use the enum values instead of integer values).
<ListView>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=PeriodStatus}" Value="Added">
<Setter Property="Background" Value="Green" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=PeriodStatus}" Value="Deleted">
<Setter Property="Background" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=PeriodStatus}" Value="Edited">
<Setter Property="Background" Value="Yellow" />
</DataTrigger>
<Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
</ListView>

The simplest one is to use Triggers in your ListView.ItemContainerStyle.
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=PeriodStatus}" Value="Added">
<Setter Property="Background" Value="Green" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=PeriodStatus}" Value="Deleted">
<Setter Property="Background" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=PeriodStatus}" Value="Edited">
<Setter Property="Background" Value="Yellow" />
</DataTrigger>
<Style.Triggers>
<Style>
<ListView.ItemContainerStyle>
This is a sample code: you might need to add a namespace with your enum to use it in XAML.

I find it more easy to have a dedicated PeriodStatusColor readonly property, less 'pure xaml', yes, but less code, and all code in the same place. So this property just returns color for current PeriodStatus. On PeriodStatus change, raise also a PeriodStatusColor PropertyChanged. Use static frozen color, and maybe use a PeriodStatus --> Color static Dictionnary to have clean code.

Related

Bind Control depending bool

I have to accomplish the following task and don't know how to do it at all.
I am using a WizardControl (XCeed Wpf Toolkit) and want to skip one page, depending on a certain state in the ViewModel (let's say a bool-variable).
To achieve this I have to bind a different Control (a WizardPage) to the previous WizardPage's NextPage DependencyProperty according to this bool-variable. I think this can be done somehow with DataTrigger, but I am not so experienced in this topic. Can someone please help me?
The minimal code sample ist:
<xctk:Wizard>
<xctkWizardPage x:Name="Page1" NextPage="Page2"/>
<xctkWizardPage x:Name="Page2"/>
<xctkWizardPage x:Name="Page3"/>
</xctkWizardPage>
</xctk:Wizard>
public bool Property { get; set; }
What I want to do is make the "NextPage" of "Page1" dependant on the "Property", e.g. have "Page2" as NextPage if Property == true, otherwise go to "Page3".
Thank you very much for your help!
Jan
Maybe something like:
<Style TargetType="xctWizardPage">
<Style.Triggers>
<DataTrigger Property="YourProperty" Value="True">
<Setter Property="NextPage" Value="Page2" />
</DataTrigger>
<DataTrigger Property="YourProperty" Value="False">
<Setter Property="NextPage" Value="Page3" />
</DataTrigger>
</Style.Triggers>
</Style>
Try something like this.
This solution worked for me:
<xctk:Wizard>
<xctk:WizardPage x:Name="Page1">
<Style TargetType="xctk:WizardPage">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Property}" Value="True">
<Setter Property="NextPage" Value="{Binding ElementName=Page2}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=Property}" Value="False">
<Setter Property="NextPage" Value="{Binding ElementName=Page3}" />
</DataTrigger>
</Style.Triggers>
</Style>
</xctk:WizardPage>
<xctk:WizardPage x:Name="Page2"/>
<xctk:WizardPage x:Name="Page3"/>
</xctk:Wizard>
Thanks for all replies.

WPF Binding DataTrigger To A Different Property

I have a Teleric RadGrid View like this that's bound to a property called Load:
<telerik:RadGridView x:Name="myRadGridView"
ItemsSource="{Binding Load}">
I have a DataTrigger for that same grid that I want to bind to a property called checkColor which is in the same class as the RadGridView's Load property. I think this isn't working because both properties are in the same class? What would be the correct syntax?
<telerik:RadGridView.Resources>
<Style TargetType="telerik:GridViewRow">
<Style.Triggers>
<DataTrigger Binding="{Binding checkColor}" Value="true">
<DataTrigger.Setters>
<Setter Property="Background" Value="Blue" />
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
</telerik:RadGridView.Resources>
P.s. revisited this and finally found the real solution.
1) Added an item to the Window pointing at the class I wanted to reference.
xmlns:local="clr-namespace:MyClass"
2) Created a static resource to it on a parent component:
<DockPanel.Resources>
<local:Changes x:Key="MyViewModel"/>
</DockPanel.Resources>
3) Set my setter binding to point at it:
<Setter Property="Background" Value="{Binding Source={StaticResource MyViewModel}, Path=checkColor}"/>
Old "Solution":
Ended up solving the problem by putting the data the checkColor function was going to return into a new column in the DataTable that the RadGridView is bound to. Since columns are part of the RadGridView's binding scope I could bind it to the DataTrigger like this:
<telerik:RadGridView.Resources>
<Style TargetType="telerik:GridViewRow">
<Style.Triggers>
<DataTrigger Binding="{Binding checkColorDataColumn}" Value="1">
<DataTrigger.Setters>
<Setter Property="Background" Value="Blue" />
</DataTrigger.Setters>
</Style.Triggers>
</Style>
</telerik:RadGridView.Resources>

datatrigger on enum to change image

I've got a button with a fixed background image and would like to show a small overlay image on top of it. Which overlay image to chose depends on a dependency property (LapCounterPingStatus) of the according viewmodel.
This is what I got so far:
<Button>
<Grid>
<Image Stretch="None"> <!-- Background Image -->
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Source" Value="/Images/Pingn.png"/>
</Style>
</Image.Style>
</Image>
<Image Stretch="None" Panel.ZIndex="1"> <!-- Small Overlay Image -->
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=LapCounterPingStatus}" Value="PingStatus.PING_UNKNOWN">
<Setter Property="Source" Value="/Images/RefreshOverlayn.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=LapCounterPingStatus}" Value="PingStatus.PING_FAILURE">
<Setter Property="Source" Value="/Images/ErrorOverlayn.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=LapCounterPingStatus}" Value="PingStatus.PING_SUCCESS">
<Setter Property="Source" Value="/Images/CheckmarkOverlayn.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</Grid>
</Button>
Relevant parts of my viewmodel
public class ConfigurationViewModel
{
public enum PingStatus { PING_UNKNOWN, PING_SUCCESS, PING_FAILURE };
public PingStatus LapCounterPingStatus
{
get { return _lapCounterPingStatus; }
set
{
_lapCounterPingStatus = value;
RaisePropertyChanged(LapCounterPingStatusPropertyName);
}
}
}
Right now, no overlay image at all is displayed. What could be wrong?
UPDATE
Trace window of my IDE is showing System.ArgumentException and System.FormatException.
Could the problem source be a unknown type of enumeration PingStatus im the XAML?
You need 2 things to get this working:
1 - Add an xmlns reference in the root element of your XAML file, to the namespace where your Enum is defined:
<UserControl ...
xmlns:my="clr-namespace:YourEnumNamespace;assembly=YourAssembly">
2 - in the Value property of the DataTrigger, use the {x:Static} form:
<DataTrigger Binding="{Binding Path=LapCounterPingStatus}" Value="{x:Static my:PingStatus.PING_UNKNOWN}">
Notice that the Enum type must be prefixed with the xmlns prefix you defined above.
Edit:
If your Enum is declared inside a class you need to use the syntax:
{x:Static namespace:ClassName+EnumName.EnumValue}
for example:
{x:Static my:ConfigurationViewModel+PingStatus.PING_UNKNOWN}
Complete worked example for WPF + MVVM.
Tested on MSVC 2017.
In the view:
<TextBlock Text="Some text to be colored by an enum">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding StatusIcon}" Value="{x:Static my:StatusIcon.Warning}">
<Setter Property="Foreground" Value="Yellow"/>
</DataTrigger>
<DataTrigger Binding="{Binding StatusIcon}" Value="{x:Static my:StatusIcon.Error}">
<Setter Property="Foreground" Value="Red}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
If using ReSharper, and if the DataContext is set up properly, there will be intellisense when you hit the . after StatusIcon, i.e. it will show the properties of the enum which are Debug, Info, Warning or Error.
If using ReSharper, it will suggest the following update to the namespace in the header for the XAML file(its
good like that):
xmlns:my="clr-namespace:Class.Path.MyViewModel;assembly=MyAssembly"
And the VieModel:
public enum StatusIcon
{
Debug,
Info,
Warning,
Error
}
public class MyViewModel
{
public StatusIcon StatusIcon { get; }
}
We also use Fody for automated binding.
You can simply set enum value as DataTrigger Value... Tested on MSVC 2017.
<TextBlock Text="Some text to be colored by an enum">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding StatusIcon}" Value="Warning">
<Setter Property="Foreground" Value="Yellow"/>
</DataTrigger>
<DataTrigger Binding="{Binding StatusIcon}" Value="Error">
<Setter Property="Foreground" Value="Red}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>

Why does this DataTrigger not work?

Yes, the datatrigger is inside a style. Now that that issue is behind us, I'm interested to know why the following code is nonfunctional.
I should see a blue background for the data grid but the style is ignored. What am I doing wrong? Note I've named the Window element "root".
<Window x:Class="DataGridTriggerTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" x:Name="root">
<Grid>
<DataGrid ItemsSource="{Binding SomeData}" >
<DataGrid.Style>
<Style TargetType="DataGrid">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=root, Path=SomeCondition}" Value="true">
<Setter Property="Background" Value="Red"></Setter>
<Setter Property="RowBackground" Value="Red"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=root, Path=SomeCondtion}" Value="false">
<Setter Property="Background" Value="Blue"></Setter>
<Setter Property="RowBackground" Value="Blue"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Style>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding}" Header="Data"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
And here is the code:
public partial class MainWindow : Window
{
public bool SomeCondition { get; set; }
public List<string> SomeData { get; set; }
public MainWindow()
{
InitializeComponent();
DataContext = this;
SomeData = new List<string> { "hello", "world" };
}
}
XAML Booleans are Case-Insensitive, however, I believe you need to use "False" and "True" when using it in the Value property.
You have a few issues. The first is that you need to either implement the INotifyPropertyChanged interface and raise the PropertyChanged event on the SomeCondition setter property, or make SomeCondition a DependencyProperty. Without doing that, your UI will never know that the property value has changed.
The second is that I believe that datatriggers won't occur if the value is the same as the default value. So, the false trigger will never occur because the boolean default is false. I think that it's expected that you will set the default style values to match the default value of the property.. false in this case... like this:
<Style TargetType="DataGrid">
<Setter Property="Background" Value="Blue" />
<Setter Property="RowBackground" Value="Blue" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=root, Path=SomeCondition}" Value="true">
<Setter Property="Background" Value="Red"></Setter>
<Setter Property="RowBackground" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
The default is blue when the property is false, and changes when the property is true.
Finally, you should use an ObservableCollection instead of a List for SomeData.

How to implement NullText in a TextBlock with Binding?

I would like to implement a "NullText" behavior for a TextBlock that is bound to a property in a ViewModel. When that property in the ViewModel is null or empty, I would like to display gray italic text something like "No Data". I'd like this to follow MVVM pattern but I am lost...
Update
So after playing around with the solution James Webster suggested, I got it to work like this...
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
<c:NullOrEmptyValueConverter x:Key="NullOrEmptyValueConverter" Text="(No Data)"/>
</UserControl.Resources>
...
<TextBlock Name="SerialNumberTextBlock" Text="{Binding Path=SerialNumber, Converter={StaticResource NullOrEmptyValueConverter}}">
<TextBlock.Resources>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=SerialNumberTextBlock, Path=Text}" Value="(No Data)">
<Setter Property="FontStyle" Value="Italic"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Resources>
</TextBlock>
I'd recommend implementing an IValueConverter; if the source value is not null or empty, then pass it through to the TextBlock. If the source value is null or empty, then render your chosen text.
public class NullValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string str = (string)value;
if (str.IsNullOrWhitespace())
{
return "No Data";
}
return str;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
... //An empty implementation I expect...
}
}
However I have just realised that you want to set the style as well... hmmm, probably a DataTrigger that sets the style if the value is 'No Data' required I expect;
<TextBlock Text="{Binding Path=SomeProperty, Converter={StaticResource keyToNullValueConverter}">
<TextBlock.Triggers>
<DataTrigger Binding="{Binding Path=Text}" Value="No Data">
<Setter Property="FontStyle" Value="Italic"/>
</DataTrigger>
</TextBlock.Triggers>
</TextBlock>
Something along those lines might work.
I think you don't need to create Converter Class, you can simply write your style code like this.
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=SerialNumberTextBlock, Path=Text}" Value="{x:Null}">
<Setter Property="FontStyle" Value="Italic"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=SerialNumberTextBlock, Path=Text}" Value="{x:Static System:String.Empty}">
<Setter Property="FontStyle" Value="Italic"/>
</DataTrigger>
</Style.Triggers>
</Style>
Note :- You need to include the system namespace as
xmlns:System="clr-namespace:System;assembly=mscorlib"
You could try to bind to a property that looks thus
private string _textBlockText;
public string TextBlockText
{
get
{
if(string.IsNullOrEmpty(_textBlockText))
{
return "No Data";
}
return _textBlockText;
}
set
{
_textBlockText = value;
}
}
and then use the XAML that James has mentioned for styling. Saves the need for a converter.
Very late to the party, but here is my answer.
<TextBox >
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Text" Value="{Binding MyText, TargetNullValue=No data}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding MyText}" Value="{x:Null}">
<Setter Property="FontStyle" Value="Italic"/>
</DataTrigger>
<DataTrigger Binding="{Binding MyText}" Value="{x:Static System:String.Empty}">
<Setter Property="FontStyle" Value="Italic"/>
</DataTrigger>
<Trigger Property="IsFocused" Value="True">
<Setter Property="Text" Value="{Binding MyText}"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
My answer assumes that you want the default text to disappear on focus, which is more in line how inputs with default text usually behave. Also this works only for null values. If you need a check for string empty or additional logic, you could instead use a converter like in other provided answers. The key idea is, that you remove null value text or converter in the binding when control gains focus and thus only display the default text when control has no focus. This also prevents the default value flowing back to your view model.
DataTriggers part for font style was kindly borrowed from pchajer's answer.

Categories

Resources