"Cascade" Databinding - c#

i am beginner in wpf and i want to help with this piece of XAML code.
<DataGrid ItemsSource="{Binding Elements[person]}" >
<DataGrid.Columns>
<DataGridTextColumn x:Name="headerPhone" Binding="{Binding Element[phone].Value}">
<DataGridTextColumn.CellStyle>
<Style TargetType="{x:Type DataGridCell}" x:Name="headerPhoneCStyle">
<Style.Triggers>
<DataTrigger Binding="{Binding Element[phone].Attribute[changed].Value}" Value="yes">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
Is there some way how to reduce this:
Binding="{Binding Element[phone].Attribute[changed].Value}"
To just something like this:
Binding="{Binding Attribute[changed].Value}"

To do that you would need to change the DataContext of the items. Currently, your DataGrid.ItemsSource property is data bound to the Elements[person] object... this must be a collection of some kind and so the DataContext of each item is set to an element from this collection.
It's hard to tell the exact structure of your data class because you didn't show us [you should always show all the relevant code], but if your Binding works, then it seems as though each item has an indexed property named Element in it. Now if you want to data bind directly with the object that is returned when using the index phone, you can just add that to the ItemsSource Binding instead:
<DataGrid ItemsSource="{Binding Elements[person].Element[phone]}">
<DataGrid.Columns>
<DataGridTextColumn x:Name="headerPhone" Binding="{Binding Value}">
<DataGridTextColumn.CellStyle>
<Style TargetType="{x:Type DataGridCell}" x:Name="headerPhoneCStyle">
<Style.Triggers>
<DataTrigger Binding="{Binding Attribute[changed].Value}"
Value="yes">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
However, if Elements[person].Element[phone] is not a collection, there is not much point in using a DataGrid to display it... if it is a collection, then this should work just fine. Let me know how you get on. Also, you should see the Property Path Syntax and Binding.Path Property pages on MSDN for more help with Binding.Path syntax.

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.

How to enable or disable the checkbox based on property value?

In my DataGrid I've a DataGridCheckBoxColumn implementation:
<DataGrid ItemsSource="{Binding Collection}">
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding Selected}"
ElementStyle="{StaticResource MaterialDesignDataGridCheckBoxColumnStyle}"
EditingElementStyle="{StaticResource MaterialDesignDataGridCheckBoxColumnEditingStyle}">
<DataGridCheckBoxColumn.CellStyle>
<Style TargetType="DataGridCell">
<EventSetter Event="CheckBox.Unchecked" Handler="Match_Unchecked"/>
<Style.Triggers>
<!-- Enabling -->
<DataTrigger Binding="{Binding DataContext.IsAdding,
RelativeSource={RelativeSource AncestorType=Window}}" Value="false">
<Setter Property="IsEnabled" Value="True" />
</DataTrigger>
<!-- Disabling -->
<DataTrigger Binding="{Binding DataContext.IsAdding,
RelativeSource={RelativeSource AncestorType=Window}}" Value="True">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style>
</DataGridCheckBoxColumn.CellStyle>
as you can see I've inserted a DataTrigger on the Style of DataGridCheckBoxColumn, so essentially when the property IsAdding is false, the Cell with the content inside should be enabled, otherwise, disabled.
This doesn't seems to working, the Cell is even enabled, why?
I suspect the problem is with the LogicalTree of your application. The code you have there should actually work just fine, but it appears that you may be testing this inside of the demo application. Normally this would be fine, however there was an addition of a control to display XAML that is breaking the logical tree (I have a fix in progress but it is not complete yet).
You can work around this by making two changes to the demo application:
In the MainWindow.xaml replace the XamlDisplayerHost with a regular ContentControl
In Grids.xaml replace the XamlDisplayerPanel with a regular StackPanel
After that your style should work as you expect.

WPF Disabling DataGridCheckBoxColumn based on observable collection property

I am a new WPF starter and am having trouble achieving a minor thing.
I have a viewModel that has an ObservableCollection
Order and that has two props
IsSelected(to indicate that order has been selected in grid)
EnableOrder(to indicate if an order can be selected).
However the checkbox is not disabled when Orders/EnableOrder is false.
<DataGrid ItemsSource="{Binding Orders}">
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding IsSelected}">
<DataGridCheckBoxColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="IsEnabled" Value="{Binding Orders/EnableOrder}" />
</Style>
</DataGridCheckBoxColumn.CellStyle>
</DataGridCheckBoxColumn>
</DataGrid.Columns>
</DataGrid>
It works when I hard-code False like below
<DataGrid ItemsSource="{Binding Orders}">
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding IsSelected}">
<DataGridCheckBoxColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="IsEnabled" Value="False" />
</Style>
</DataGridCheckBoxColumn.CellStyle>
</DataGridCheckBoxColumn>
</DataGrid.Columns>
</DataGrid>
Also I was not able to specify binding as below as EnableOrder is not visible in XAML intellisense.
<Setter Property="IsEnabled" Value="{Binding EnableOrder}" />
Please note that there were other simple DataGridTextColumn bindings that I had inside the grid and the bindings worked without any problem
I think the binding was wrong. Check with below code. Also check the output window for any binding errors.
<DataGrid ItemsSource="{Binding Orders}">
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding IsSelected}">
<DataGridCheckBoxColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="IsEnabled" Value="{Binding EnableOrder}" />
</Style>
</DataGridCheckBoxColumn.CellStyle>
</DataGridCheckBoxColumn>
</DataGrid.Columns>
</DataGrid>
Copied Comment as Answer Since the OP Found it to Work:
I would imagine that intellisense wouldn't pull up EnableOrder since you are in a Style and that Style may apply to multiple items which may have different DataContexts. So if you bind to EnableOrder even though there isn't any intellisense are you still able to build and run, and if so does it work?

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.

Updating row colour when a value changes in WPF/xaml

I've looked at a number of answers already on stackoverflow, but none of the replies seems to be able to give me what I need. This question seemed to describe my situation quite closely, but the solution did not work for me.
I have a check box column in a DataGrid, and when a user clicks the checkbox, since the data is bound to a BindableList, the value (IsOnList) associated with it gets updated. The type object of the bindable list also stores a bool to indicate if the value has been changed (Changed), which gets flipped when IsOnList changes.
If the value has been changed, I want to indicate this to the user, by setting the back ground to red. Relevant parts of the xaml are below. I have tried setter and trigger style on the data grid row, but with little luck (for testing, I also put in mouse over to make sure these were getting picked up, and it worked fine, as well as trying things like TwoWay). The DataTriggers will be called when the grid is loaded, so, since changed is always false when the grid is loaded, the background will be blue. When I click the check box, that updates Changed to true, the background does not change colour. I have even added a column for the Changed property to see if that value changes, but it does not.
<UserControl.Resources>
<vm:ProductListEditViewModel x:Key="ViewModel"/>
<vm:ChangedHighlightConverter x:Key="ChangedHighlightConverter"/>
</UserControl.Resources>
<tk:DataGrid ItemsSource="{Binding EditableLists}" x:Name="dataGrid" Grid.Row="1"
...
SelectedItem="{Binding Path=SelectedAttribute, Mode=TwoWay}">
<tk:DataGrid.RowStyle>
<Style TargetType="tk:DataGridRow">
<Setter Property="Background" Value="{Binding Changed, Converter={StaticResource ChangedHighlightConverter}}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Changed, Mode=OneWay}" Value="True">
<Setter Property="Background" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding Changed, Mode=TwoWay}" Value="True">
<Setter Property="Background" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding Changed}" Value="False">
<Setter Property="Background" Value="Blue" />
</DataTrigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</tk:DataGrid.RowStyle>
<tk:DataGrid.Columns>
<tk:DataGridTextColumn Header="Group Name" Binding="{Binding GroupDescription}" Width="120" IsReadOnly="True"/>
<tk:DataGridTextColumn Header="List Name" Binding="{Binding ListDescription}" Width="120" IsReadOnly="True"/>
<tk:DataGridTextColumn Header="List ID" Binding="{Binding ListId}" Width="50" IsReadOnly="True"/>
<tk:DataGridTextColumn Header="Quantity" Binding="{Binding Quantity}" Width="50" IsReadOnly="True"/>
<tk:DataGridTextColumn Header="Comment" Binding="{Binding Description}" Width="120" IsReadOnly="True"/>
<tk:DataGridTextColumn Header="Changed" Binding="{Binding Changed, Mode=TwoWay}" Width="40" IsReadOnly="True"/>
<tk:DataGridTemplateColumn Header="On List" Width="50">
<tk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox HorizontalAlignment="Center" IsChecked="{Binding Path=IsOnList, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</tk:DataGridTemplateColumn.CellTemplate>
</tk:DataGridTemplateColumn>
</tk:DataGrid.Columns>
</tk:DataGrid>
The relevant parts of my class are:
public bool IsOnList
{
get { return isOnList; }
set {
Changed = !changed;
isOnList = value;
}
}
public bool Changed
{
get { return changed; }
set { this.changed = value; }
}
I'm pretty new to C# and xaml, so have been struggling trying to make this work. Am I missing something obvious?
Without seeing the entire code of your class, you may need to implement INotifyPropertyChanged in your class so that WPF bindings know when properties are changing. You aren't firing any PropertyChanged events so the background style will never update (nor will the Changed property in the DataGridRow).

Categories