Binding the Trigger value dynamic in WPF - c#

I'm using ListView along with the GridView for displaying the data in tabular format:
<ListView>
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="120" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Risk" Width="150" DisplayMemberBinding="{Binding RiskName}" />
</GridView>
</ListView.View>
I have to change the background color based on the RiskName. For example, if RiskName is "High" then the background would be Red, if RiskName is "Medium" then the background would be "Yellow", and so on.
I added the style along with trigger to set background based on the value,
<Trigger Property="Text" Value="High">
<Setter Property="Background" Value="Red"/>
</Trigger>
<Trigger Property="Text" Value="Medium">
<Setter Property="Background" Value="Yellow"/>
</Trigger>
It works fine, but in my case the text of the RiskName is not constant. The value comes dynamically. In WPF is there any way I can set the Value of trigger property dynamically which look something like this?
<Trigger Property="Text" Value="{Binding RiskName}">
<Setter Property="Background" Value="{Binding RiskBrush}"/>
</Trigger>
Any suggestion? If not then what is the other work around?

Something like this?
<converters:MyBrushLookupConverter x:Key="brushLookup" BrushDictionary="{Binding KeyedBrushes}" />
where MyBrushLookupConverter looks like
public class MyBrushLookupConverter : DependencyObject, IValueConverter
{
// This is a dependency property - dependency property gumf omitted for brevity
public Dictionary<string, Brush> BrushDictionary {get; set;}
// Convert method
public Convert(object value, ...)
{
return BrushDictionary[(string)value];
}
}

Use a Converter instead of a Trigger. Bind the Background to RiskName and write a converter that returns a Brush determined by the value of RiskName.
Link to MSDN for the interface you need to use IValueConverter - http://msdn.microsoft.com/en-us/library/system.windows.data.ivalueconverter%28v=vs.110%29.aspx
A link to a good tutorial on Converters: http://wpftutorial.net/ValueConverters.html

Related

WPF Changing DataGrid Row Background Colour if JobTimeStart Column is 1 hour away from JobTimeFinish Column

In my database "JobTimeStart" and "JobTimeFinish" are both stored as 'Time' Datatypes. I need the row background colour to change to red in my DataGrid if "JobTimeStart" is 1 hour away from "JobTimeFinish".
I made a trigger for my "JobDate" column so that if it matches todays date it will change colour to orange. I have been trying to see if I could maybe do it the same way using triggers, but have had no luck finding anything similar to what I want.
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="Height" Value="40"></Setter>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding JobDate.Date}" Value="{x:Static System:DateTime.Today}">
<Setter Property="Background" Value="#ff8d00"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
..
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding ID}"/>
<DataGridTextColumn Header="Job" Binding="{Binding JobType}"/>
<DataGridTextColumn Header="Date" Binding="{Binding Path=JobDate, StringFormat=\{0:dd/MM/yyyy\}}" x:Name="JobDate"/>
<DataGridTextColumn Header="Start Time" Binding="{Binding JobTimeStart}"/>
<DataGridTextColumn Header="Finish Time" Binding="{Binding JobTimeFinish}" x:Name="JobTimeFinish"/>
</DataGrid.Columns>
Anything to help push me in the right direction would be appreciated.
If those properties are never to be changed, you can bind the trigger to the data item itself, rather than the DateProperty and then use a converter to check whether those properties match the predicate. However, if those are changed at run-time, the data trigger would not be notified:
<Style.Triggers>
<DataTrigger Binding="{Binding, Converter={StatocResource SomeConverter}}" Value="true">
<Setter Property="Background" Value="#ff8d00"/>
</DataTrigger>
</Style.Triggers>
If using triggers is not a requirement, I would use the RowStyleSelector feature.
There is no way to subtract times and compare the result to a pre-defined TimeSpan in XAML.
What you should to is to add a property to your view model or model that returns a value that indicates whether Background property should be set in the view:
public bool Warn => JobTimeFinish.Subtract(JobTimeStart).TotalHours >= 1;
You can then trigger on this property:
<DataTrigger Binding="{Binding Warn}" Value="True">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
This way you also keep your logic in your view model or model where it belongs and can be tested separately. XAML is a markup language.

WPF DataGrid cell style from different property in XAML

Is it possible to bind a data grid column value to one property of the bound class specified in ItemsSource, but reference a different property in that class to style the cell?
Here is a code example:
<DataGrid ItemsSource="MyCollection">
<DataGridTextColumn Header="MyColumn" Binding={Binding ColumnText} />
<DataGridTextColumn Header="MyColumn2" Binding={Binding ColumnText2} />
</DataGrid>
Suppose that I have a two further properties (Enum or Brush) in the objects in MyCollection that I want to refer to to set the background colour of the respective cells; can this be done?
EDIT - I need to do this for multiple columns, each looking at a different property to ascertain the colour it should be; I have added a second column to the code example above.
I know I can set the style based on the value in ColumnText, but that is not what I need to do.
I have tried setting a style data trigger, but when I try to bind, I can only bind to something from the overall data context, not to another property within the object that is populating the current row.
Many thanks!
If I understand correctly, you are trying to set a cell background by a property from the Row model.
You can achieve this by setting a cell style, and set a DataTrigger to that style to bind to a property you want.
Example
You want to make each cell, that has the number 3 to be painted green:
<DataGrid ItemsSource="{Binding Rows}" AutoGenerateColumns="True">
<DataGrid.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<DataTrigger Binding="{Binding Number}" Value="3">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="Green"/>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
</DataGrid>
And, of course, you can change the Number property with your own property, and the value you are interested in.
If you want to do something more complex, like range of values and such, you should go with the conventional converters way.
Edit
If you want to make a different cell style for each column, you should explicitly set columns:
<DataGrid ItemsSource="{Binding Rows}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Number}">
<DataGridTextColumn.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<DataTrigger Binding="{Binding Number}" Value="3">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="Green"/>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
One important thing to notice though,
You have to set AutoGenerateColumns="False" or else the columns will generate twice.

ListView ItemsSource binding

I have a ListView with 3 columns:
<ListView ItemsSource="{Binding ParamName}" HorizontalAlignment="Left" Height="109" Margin="10,87,0,0" VerticalAlignment="Top" ScrollViewer.HorizontalScrollBarVisibility="Disabled" Width="281">
<ListView.View>
<GridView>
<GridView.ColumnHeaderContainerStyle>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="TextElement.Foreground" Value="Black"/>
</Trigger>
</Style.Triggers>
</Style>
</GridView.ColumnHeaderContainerStyle>
<GridViewColumn Header="Name" Width="60"/>
<GridViewColumn Header="Type" Width="60"/>
<GridViewColumn Header="Content" Width="156"/>
</GridView>
</ListView.View>
</ListView>
Currently, I have set ParamName as my ItemsSource, which will obviously not achieve the desired result this way.
In my ViewModel, I have ObservableCollections for each Column (ParamName for Name, ParamType for Type and ParamContent for Content). Those ObservableCollections are correctly filled and I am able to receive their data through the Binding, but I cannot fill the columns with their respective data.
I have thought of certain possible solutions, but none of them seem to work. What would be the best approach for this problem?
Here's how it looks like (left) and how it should look like (right):
Naming them after their Types might be a little bit confusing.
You have three Collections, but only one is binded. So all columns have same value.
Try to create one Collection containing all you need:
ObservableCollection<MyRow>
where MyRow is a Class or Struct with Properties you need.
If you already have these Collections - try to concatenate it to one major Collection and tell the GridColumns which Properties you wish to bind to each Column, but I'm not sure if it's possible with ObservableCollections - what if they have different length?
And you can still create your own Collection from these three - just parse it...

Why is my WPF listview code throwing an excpetion when programatically selecting a row

I am trying to programmatically highlight the first row in a WPF listview control using VS2008 with 3.5 of the .NET framework. Here is the C# code for this:
ListViewItem Val = (ListViewItem)ListView1.Items[0];
Val.IsSelected = true;
The code throws an exception at the first line, which is after ListView1 is populated with data. The message in the exception says:
"Unable to cast object of type 'Message.LV1Data' to type 'System.Windows.Controls.ListViewItem'."
LV1Data is the class I am using to bind columns in this control. So, it looks like it is trying to return an LV1Data object instead of a ListViewItem object. Does anyone have any suggestions as to what I am doing wrong or what I need to do in order to programmatically highlight a listview row?
Here is the XAML code for the ListView control:
<ListView x:Name="ListView1" ItemContainerStyle="{StaticResource alternatingListViewItemStyle}" AlternationCount="2" SelectionChanged="ListView1_SelectionChanged"
SelectionMode="Multiple" HorizontalAlignment="Left" ItemsSource = "{Binding ElementName=LobbyWindow, Path=ListCollection1}">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Game}">
<GridViewColumnHeader Content="Game" FontWeight="Bold" />
</GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding Stakes}">
<GridViewColumnHeader Content="Stakes" Width="68" FontWeight="Bold" />
</GridViewColumn>
<GridViewColumn Width="30" DisplayMemberBinding="{Binding Seats}">
<GridViewColumnHeader Content="Seats" FontWeight="Bold" />
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
EDIT
<!-- Define the resource for the alternating background background used in the ListView objects. -->
<StackPanel.Resources>
<Style x:Key="alternatingListViewItemStyle" TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
<Style.Resources>
<!-- Foreground for Selected ListViewItem -->
<!-- <SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="Black"/> -->
<!-- Background for Selected ListViewItem -->
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Green"/>
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="Brown"/>
</Style.Resources>
<Style.Triggers>
<!-- setting up triggers for alternate background colors -->
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
<Setter Property="Background" Value="#FFD9F2BF"></Setter>
</Trigger>
<Trigger Property="ItemsControl.AlternationIndex" Value="2">
<Setter Property="Background" Value="White"></Setter>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="LightBlue"></Setter>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="BorderBrush" Value="LightBlue" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="True"></Condition>
<Condition Property="ItemsControl.AlternationIndex" Value="0"></Condition>
</MultiTrigger.Conditions>
<Setter Property="Background" Value="LightBlue"></Setter>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="True"></Condition>
<Condition Property="ItemsControl.AlternationIndex" Value="1"></Condition>
</MultiTrigger.Conditions>
<Setter Property="Background" Value="LightGreen"></Setter>
</MultiTrigger>
</Style.Triggers>
<!-- setting row height here -->
</Style>
</StackPanel.Resources>
You bound to an items source, which means asking for the items[x] will return a type of the data you bound to (whatever type is stored in ListCollection1).
If you want to alter it's IsSelected, you'll have to create that property on the type in ListCollection1, and bind to it in a style or template.
The IsSelected property you create will have to be implemented as a DependencyProperty, or the Type it's in will have to implement INotifyPropertyChanged, and trigger that event when the property changes.
<ListView ItemsSource="...">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
Then you'll cast to that dataobject type, and set it's IsSelected value.
Find in your code the ListCollection1. It's definition will look like List<Element>. Element is the type you need to cast to.
Element needs to either look like
public class Element : INotifyPropertyChanged
{
private _IsSelected;
public Boolean IsSelected
{
get { return _IsSelected; }
set
{
_IsSelected = value;
if (PropertyChanged != null)
PropertyChanged("IsSelected");
}
}
//snip Implement interface INotifyPropertyChanged.
//snip your other code
}
-OR-
public class Element : DependencyObject
{
public static DependencyProperty IsSelectedProperty =
DependencyProperty.Register("IsSelected" ...
//snip your other code
}
Then your code should look like this.
Element Val = (Element)ListView1.Items[0];
Val.IsSelected = true;
Items is bound to your business object, so that is why it is not actually returning a listview item. You can try three things:
Use SetSelectedItems and only pass in an IEnumerable of one object
OR, you can get the object and then ask for the ListViewItem it refers to
(ListViewItem)ListView1.ItemContainerGenerator.ContainerFromItem(ListView1.Items[0])
OR, you can bind to the IsSelected property and manage that in your viewmodel

Creating a trigger in a CellTemplate for a ListView? (confusion with Templates in general)

I'm starting to get a little confused as I delve further into WPF and I feel like this example will help in better understanding things. My requirement is this: I have a ListView that is using a binding to a collection of plain .NET objects, I want to do two things:
1) highlight the cell of a row in the ListView if the value is a certain value - I figure I can use the GridViewColumn.CellTemplate for this and create a DataTemplate with a DataTrigger, however I am becoming confused here - is the DataType for the DataTemplate supposed to be the ListViewItem or is it supposed to be the type of the underlying object itself?
This is a general point of confusion for me in WPF ..not knowing when to type it to the underlying collection object (which I've seen in examples) vs the list-item type itself. Here is my first attempt:
<GridViewColumn Header="Position">
<GridViewColumn.CellTemplate>
<DataTemplate DataType="{x:Type ListViewItem}">
<TextBlock Text="{Binding Path=PositionCode}"></TextBlock>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding PositionCode}" Value="QB">
<Setter Property="Foreground" Value="Blue" />
</DataTrigger>
<DataTrigger Binding="{Binding PositionCode}" Value="RB">
<Setter Property="Foreground" Value="Green" />
</DataTrigger>
<DataTrigger Binding="{Binding PositionCode}" Value="WR">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
However, this not surprisingly leads to the error message
Cannot find the Template Property 'Background' on the type 'System.Windows.Controls.ContentPresenter'
2) similar to 1) I want to have a similar rule on another criteria I want to highlight the entire row, instead of just the cell based on a similar DataTrigger property but same time I want the cell highlighting to take precedence over the row highlighting.
How would I do this and what template do I need to override to do this? I'm guessing it's the ListView.ItemTemplate but what would the data type be?
Try this:
<GridViewColumn Header="Position">
<GridViewColumn.CellTemplate>
<DataTemplate DataType="{x:Type ListViewItem}">
<TextBlock Name="TextBlockName" Text="{Binding Path=PositionCode}"></TextBlock>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding PositionCode}" Value="QB">
<Setter TargetName="TextBlockName" Property="Foreground" Value="Blue" />
</DataTrigger>
<DataTrigger Binding="{Binding PositionCode}" Value="RB">
<Setter TargetName="TextBlockName" Property="Foreground" Value="Green" />
</DataTrigger>
<DataTrigger Binding="{Binding PositionCode}" Value="WR">
<Setter TargetName="TextBlockName" Property="Foreground" Value="Red" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
I think dvvrd's answer addresses your first question. For the other part (building the row style), you can use the ItemContainerStyleSelector.
<ListView ItemContainerStyleSelector="{StaticResource Selector}" ...
I wrote a simple implementation like this:
public class RowStyleSelector : StyleSelector
{
public override System.Windows.Style SelectStyle(object item, System.Windows.DependencyObject container)
{
var i = (item as Item);
if (i.I == 0) return (Style)App.Current.Resources["Selected"];
else return (Style)App.Current.Resources["Normal"];
}
}
Then the different styles, along with the selector reference, go in App.xaml:
<Application.Resources>
<res:RowStyleSelector x:Key="Selector" />
<Style x:Key="Selected" TargetType="ListViewItem">
<Setter Property="Background" Value="DarkGray" />
</Style>
<Style x:Key="Normal" TargetType="ListViewItem">
<Setter Property="Background" Value="LightBlue" />
</Style>
</Application.Resources>
This approach effectively sets the background color depending on criteria in your model (the Item class in my example), with the column highlighting still in effect.

Categories