DataTrigger on DataGridTextColumn - wpf

I am trying to attach a datatrigger on one of my combobox element. The trigger should read the attached property of the DataGridTextColumn (combobox's ancestor) and take decision based on that. Now the problem is that the DataGridTextColumn isn't part of Visual Tree so I cannot get it by RelativeSource Ancestor. Here is sample code.
<ComboBox Name="cmbFilter" DisplayMemberPath="CategoryName">
<ComboBox.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGridTextColumn}},
Path=Header}"
Value="Id">
<Setter Property="Control.Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
Any one maybe suggest some alternative
Edit:
#denis
I don't really understand your solution, so let me explain in detail. I am developing a general purpose filter that would apply to all DataGrid's that need the filtering functionality. The DataGrid definition will specify whether it wants filtering or not by specifying custom attached property "IsFilterable" on DataGrid. Individual DataGridColum will specify what kind of filter they want (combobox or textbox) by specifying "FilterDisplayType" on DataGridColumn. The DataGrid will know nothing other than above. All the functionality will then be handled by the Filter based on the above attached properties on DataGrid and DataGridColumn (all types of columns).

My point in my comment was that, you can trigger on a property that you bind to, not on the header.. Because if you respond to a header change, than you have to trigger the header, which is fine, but that can be the exact same Property on your model that you respond to, only in a diff place.
Also you can't put a combo box in DataGridTextColumn,
So you'd have to either:
<DataGridComboBoxColumn ItemsSource="{Binding CategoryNameItems}"
DisplayMemberPath="{Binding CategoryName}"
Visibility="{Binding MyVisibilityProperty, Converter={StaticResource BoolToVisibility}}" />
Which will hide the whole column or to hide only the combobox put it in the CellTemplate:
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding CategoryNameItems}" DisplayMemberPath="{Binding CategoryName}"
Visibility="{Binding MyVisibilityProperty, Converter={StaticResource BoolToVisibility}}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

Related

How do I bind the visibility of a CheckBox in a DataGrid to the DataContext of that row?

I have a JibGrid DataGrid like so (JibGrid is an open-source relatively simple subclass of a standard DataGrid to implement stuff like filtering etc):
<dataGrid:JibGrid ItemsSource="{Binding AvailableRDs}"
FilteredItemsSource="{Binding AvailableRDs}"
SelectedItem="{Binding SelectedAvailRD}"
AutoGenerateColumns="False" >
<dataGrid:JibGrid.Columns>
<DataGridCheckBoxColumn Header="Add?" Binding="{Binding Add}" Visibility="{Binding GetAddVisibility}"/>
<DataGridTextColumn Header="Tag" Binding="{Binding Tag}" />
<DataGridTextColumn Header="Revision Tag" Binding="{Binding RevisionTag}" />
<DataGridTextColumn Header="Current System" Binding="{Binding SystemStr}" />
</dataGrid:JibGrid.Columns>
</dataGrid:JibGrid>
The intent is that there is a custom class for each row that provides properties for the content of each cell in that row - Tag, RevisionTag, etc. All of that works fine. What I can't get to work is that Visibility binding. I would like for the checkbox in each row to be invisible if the CanAdd property on the class representing that Row in the DataContext returns False. When I add the Visibility binding in XAML as above, it seems that what happens is that WPF attempts to apply this binding to the actual column instead of for each row, so the binding fails to connect. I can go in using WPF explorer and manually bind a row's checkbox visibility DependencyProperty to the CanAdd property of the Row's datacontext, and that works fine, but I can't figure out how to, in XAML or code, cause it to generate that binding for the checkbox in every row automatically. Anybody have any ideas on that?
I have searched around for questions like this, and it seems that, for some reason, everybody wants to change the visibility of the column itself based on something in the datacontext of the whole grid, and nobody else wants to change the visibility of things in individual rows based on that row's datacontext. I tried the solution here while I was trying to figure this out, and that's what that answer is trying to do.
You can use DataGridTemplateColumn and BooleanToVisibilityConverter to achieve the desired result
Add BooleanToVisibilityConverter to your resources
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
</Window.Resources>
Set Converter for the DataGridTemplateColumn binding
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Add}"
Visibility="{Binding CanAdd, Converter={StaticResource BoolToVis}}" ></CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

Why is my DataGridComboBoxColumn clearing its value when I navigate away from it?

I have a DataGrid with two columns:
DataGridComboBoxColumn
DataGridTextColumn.
I have set up data validation so that if one has a value, the other will be in error until it also has a value. The validation is silly, but it provides some simple criteria with which to do validation so I can illustrate this issue.
When I type something into the text cell, press tab, then click back on the first cell, the first cell shows that it is in an error state (which is correct). The problem is that when I select something from the combo box dropdown and navigate away from that cell (either by pressing tab or by clicking in another cell), the value I selected for the combo box disappears. I have the binding set to update my source whenever the property changes, so it gets set to the value I select as soon as I select it. But, when I navigate away from the cell, the property gets set to null. I do not see this behavior if the cell is not in an error state.
Can anyone help please? Here is the XAML for my DataGrid:
<DataGrid Grid.Row="2"
Name="GrdData"
ItemsSource="{Binding Path=Dvm.Data}"
SelectedItem="{Binding Path=Dvm.SelectedData, Mode=TwoWay}"
CanUserAddRows="True"
CanUserDeleteRows="False"
AutoGenerateColumns="False"
Margin="5"
SelectionMode="Single"
IsEnabled="{Binding Path=IsGridEnabled}">
<DataGrid.Columns>
<DataGridComboBoxColumn Header="Column 1"
SelectedItemBinding="{Binding Path=Col1, Mode=TwoWay, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"
Width="*"
DisplayMemberPath="Description">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding Path=DropDownValues, Mode=OneWay}" />
<Setter Property="IsSynchronizedWithCurrentItem" Value="False"/>
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding Path=DropDownValues, Mode=OneWay}"/>
<Setter Property="IsDropDownOpen" Value="True" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
<DataGridTextColumn Header="Column 2"
Binding="{Binding Path=Col2, Mode=TwoWay, ValidatesOnDataErrors=True}"
Width="*"/>
</DataGrid.Columns>
</DataGrid>
I can't imagine what I'm doing wrong. I saw this other link that seems to describe the same problem I am having, but the solution that worked for them doesn't seem to work for me; I added the SelectedValueBinding and SelectedValuePath, but the behavior did not change.
Remove Mode=TwoWay from the bindings.
The problem is caused by a bug in the clipboard and automation support. That works by setting a special property on the cell to ClipboardContentBinding and then reading the value. If that binding is two-way, it winds up sometimes pushing an old value from the special property back to the view model, and validation errors seem to trigger this behavior. DataGridBoundColumns and DataGridComboBoxColumns will supply Binding or SelectedItemBinding if ClipboardContentBinding is null, so you’ll get this bug if you set either of those to a TwoWay binding.
If you don’t set Mode, it will be Default and use the default from the property, which is TwoWay for TextBox.Text and ComboBox.SelectedItem but OneWay for the special clipboard property.

Is it possible to parameterize the binding on a DataGrid CellTemplate in a resource?

I'm putting together some generic CellTemplate styles for a WPF grid (WPFToolKit DataGrid), and I’m not sure of the syntax to use to make the binding generic. So for example, I have this template, which turns the value red if the value is negative:
<DataTemplate x:Key="RedNegativeCellTemplate">
<TextBlock Name="QuantityTextBox" Text="{Binding Quantity, StringFormat='c', Mode=OneWay}"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Quantity, Converter={StaticResource SignConverter}}" Value="-1">
<Setter TargetName="QuantityTextBox" Property="Foreground" Value="Red"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
You’ll notice that this has a binding on the Text box to the column value ‘Quantity’ - i.e., the field/column that the binding comes from is explicit.
So I can use this in my WPF DataGrid as follows:
<sdk:DataGrid ItemsSource="{Binding MyDataSource}" AutoGenerateColumns="False">
<sdk:DataGrid.Columns>
<sdk:DataGridTemplateColumn Header="Net Quantity" CellTemplate="{StaticResource RedNegativeCellTemplate}"/>
</sdk:DataGrid.Columns>
</sdk:DataGrid>
But… what I’d really like to do is to have the field binding generic for the template, so that I can re-use the template as follows:
<sdk:DataGrid ItemsSource="{Binding OrdersQuery}"AutoGenerateColumns="False">
<sdk:DataGrid.Columns>
<sdk:DataGridTemplateColumn Header="Quantity" CellTemplate="{StaticResource RedNegativeCellTemplate}"/>
<sdk:DataGridTemplateColumn Header="Price" CellTemplate="{StaticResource RedNegativeCellTemplate}"/>
<sdk:DataGridTemplateColumn Header="Total" CellTemplate="{StaticResource RedNegativeCellTemplate}"/>
</sdk:DataGrid.Columns>
</sdk:DataGrid>
Now, the problem here is that there doesn't seem to be a way to parameterize the binding in the CellTemplate. But I'm loath to have to have 3+ lines of controltemplate for every column that uses this template, as it clutters the XAML and makes it far less readable (not to mention the fact that if I decide to change the cell template to put a border around the textbox, I'd have to modify it in multiple places.
So I'm thinking that the binding within the CellTemplate should look something like this (note that we're using '.' for the binding path):
Text="{Binding Path=., StringFormat='c', Mode=OneWay}"
And then somehow set the datacontext from the DataGridTemplateColumn declaration – but I can’t see how to do it.
Any idea how to do this?
#Webreaper: If I understand your idea correctly, then you and I are running into the same stuff. I haven't tried my version of the solution, basically, I will bind the path property of a binding to the DataGridTemplateColumn's SortMemberPath.
<DataGrid>
....
<DataGridTemplateColumn CellTemplate={StaticResource CellTemplateKey} />
....
</DataGrid>
Where CellTemplateKey is defined somewhere (in App.xaml, for example):
<DataTemplate x:Key="CellTemplateKey">
<TextBlock Binding="{Binding Source={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGridRow}, Path={Binding Source={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGridTemplateColumn, Path=SortMemberPath}}}}" />
</DataTemplate>
I'll check and update this later. I hope the idea is helpful somehow. I may look into Dyna
For this you can use DataTemplateSelector which is actually pretty straightforward to implement.
If you need some further explanations and examples - let me know.

WPF DataGridTextColumn Tooltip

Is there a way to add tool tip to DataGridColumn header and still retain the sorting functionality. The below code doesnt work(It doesnt display the tooltip)
<toolkit:DataGridTextColumn Header="Test" Width="70" Binding="{Binding TestText}" ToolTipService.ToolTip="{Binding TestText}">
And when I use the code below
<toolkit:DataGridTemplateColumn Header="Test" Width="70">
<toolkit:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding TestText}" ToolTip="{Binding TestText}" />
</DataTemplate>
</toolkit:DataGridTemplateColumn.CellTemplate>
</toolkit:DataGridTemplateColumn>
The column loses sorting functionality..Help!
To get the ToolTip to display in the DataGridColumnHeader you'll need to bind the ToolTip property for it to the ToolTip of its DataGridColumn like this
<toolkit:DataGridTextColumn Header="Test"
Width="70"
Binding="{Binding TestText}"
ToolTipService.ToolTip="My Tooltip Text">
<toolkit:DataGridTextColumn.HeaderStyle>
<Style TargetType="toolkit:DataGridColumnHeader">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=Column.(ToolTipService.ToolTip)}"/>
</Style>
</toolkit:DataGridTextColumn.HeaderStyle>
</toolkit:DataGridTextColumn>
When the grid creates automatic columns, it knows which field is being displayed in that column. When you create the column yourself, the data grid doesn't know what data you'll be displaying in that column and so it cannot guess which field to sort the column by.
To make a column you define yourself sortable, add the SortMemberPath property to your DataGridTemplateColumn like this:
<DataGridTemplateColumn Header="Test" Width="70" SortMemberPath="TestText">
...
</DataGridTemplateColumn>
Previous answers are mostly correct, however I find them overly complicated or addressing only one of the two concerns of the post.
Firstly, you can always set the SortPath property to maintain sorting for a DataGridTemplateColumn, or possibly when you want to sort on some property other than what is displayed.
Second, you do not need a DataGridTemplateColumn in order to have a ToolTip on the Column Header like the OP mentions. You might use a template column if you want to add a tooltip to the actual cell (but this probably isn't needed either). In any case, adding a ToolTip to the column header is most easily accomplished by the HeaderStyle
<DataGridTextColumn Header="Test" Binding="{Binding TestText}">
<DataGridTextColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="ToolTip" Value="Test ToolTip" />
</Style>
</DataGridTextColumn.HeaderStyle>
</DataGridTextColumn>
You are adding a tooltip to the column template, not to the header.
Have you tried setting the HeaderStyle property on DataGridColumn to a style that contains a template including a tooltip for the HeaderCell?
Have a look at this example too

WPF Data Trigger not working - ComboBox selected index is not getting set to 0

I want to set the SelectedIndex of ComboBox to 0 when the SelectedItem it is bound to is null by using DataTrigger. But it is not working. Where am I going wrong?
The xaml is as follows:
<ComboBox SelectedItem="{Binding MyObject.color_master, Mode=TwoWay}"
ItemsSource="{Binding MyEntities.color_master}"
DislayMemberPath="COLOR_DESCRIPTION" >
<ComboBox.Style>
<Style TargetType="ComboBox">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=MyObject.color_master}" Value="{x:Null}">
<Setter Property="SelectedIndex" Value="0" />
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
Here MyObject.color_master is null, but still the DataTrigger is not working !
My requirement is very simple, when nothing is selected in combobox, I want the first item to be selected.
It's only a guess but when you use both SelectedItem and SelectedIndex you create dependency on WPF implementation: "who wins?" is an implementation thing. Even if it's documented somewhere it would imply that every developer knows the order (which is not good too, because you never sure who will maintain your code).
I think the simplest thing you can do here is use a single binding to ViewModel's SelectedColorIndex property and let the ViewModel calculate the value based on color_master. So the end result will look like this:
<ComboBox SelectedIndex="{Binding MyObjectViewModel.SelectedColorIndex, Mode=TwoWay}"
ItemsSource="{Binding MyEntities.color_master}"
DislayMemberPath="COLOR_DESCRIPTION" >
</ComboBox>
Update: Since you said your view model can't be touched, here is another option. Write your own IValueConverter which will take an MyObject.color_master and convert it to the index:
<ComboBox SelectedIndex="{Binding MyObject.color_master, Mode=TwoWay, Converter={StaticResouce ColorMasterToIndexConverter}}"
ItemsSource="{Binding MyEntities.color_master}"
DislayMemberPath="COLOR_DESCRIPTION" >
</ComboBox>
Where ColorMasterToIndexConverter defined in a reachable resource dictonary (for say, in the same UserControl.Resources collection).

Resources