Applying TextDecoration to an WPF ListViewItem - wpf

I can see how to apply a text decoration to a GridViewColumn.
<GridViewColumn Header="Tool" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Entity.ToolId}" TextDecorations="{Binding Path=TextDecoration}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
But if I want the TextDecoration to apply to the entire row (in my case a strikethrough), then I have to duplicate the above code to every GridViewColumn.
What I can not figure out is how to apply the TextDecoration to the entire listview item possibly via the ItemContainerStyle.
Can anyone give me a head up on how this could be done?

You should not define the binding per column, if the cells of each column support TextDecoration, because then you need to repeat it for every column. Better apply it to DataGrid.CellStyle property:
<DataGrid.CellStyle>
<Style TargetType="TextBlock">
<Setter Property="TextDecorations"
Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}},
Path =Item.TextDecoration}" />
</Style>
</DataGrid.CellStyle>
The challenge here is to access the property TextDecoration. Use relative source binding to find the parent DataGridRow which has an Item property which contains the data for that row.
Formatting WPF DataGrid using binding can be rather perplexing. See my article on CodeProject where I cover the most important cases, including applying TextDecoration on single cells (or all cells in your case): Guide to WPF DataGrid Formatting Using Bindings

use GridViewRow.CellTemplate

Related

WPF globally styling a TextBlock inside a DataGrid

I am encountering a very weird issue. I am trying to apply global styling to several controls within a DataGrid. Most of them work exactly how I would expect them to. However, the styling for the TextBlock never gets applied. Styles for ComboBox, TextBox, Label, and several others all are getting applied to their respective controls, but not the TextBlock. I have simplified the code as much as possible and the issue is still present. I have provided the code sample below.
I need the style to be applied to the TextBlock and I don't want to have to apply it manually to the TextBlock.
<DataGrid ItemsSource="{Binding Data}" AutoGenerateColumns="False">
<DataGrid.Resources>
<Style TargetType="TextBlock">
<Setter Property="ANY_TEXTBLOCK_PROPERTY" Value="VALUE" />
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Test">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="Globably Applied" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
More Information:
Global styles for any control other than TextBlock (TextBox, ComboBox, etc.) work properly.
Defining the global style inside the DataTemplate will work properly.
Directly assigning the style to the TextBlock using an x:Key will work.
Global styles for DataGridCell using TextElement.PROPERTY will get applied to a TextBlock.
While some of these will get the style applied to the TextBlock, they have there own issues. Directly assigning the style or defining the style somewhere within a DataGridColumn will mean that I will have to apply the style more than once. Using the TextElement.PROPERTY on the DataGridCell will apply the style to more than just TextBlock controls and will limit the number of properties that you can set.
So with a bit more digging and a little luck, I discovered that WPF does not apply implicit styles inside templates unless the TargetType derives from Control. Since TextBlock doesn't derive from Control, its style is not applied. So you either have to manually apply the style to every non-Control or define the implicit style inside the template.
The following MSDN blog post explains it in pretty good detail.
https://learn.microsoft.com/en-us/archive/blogs/wpfsdk/implicit-styles-templates-controls-and-frameworkelements
Unfortunately, like BrianP said, WPF does not work that way. But, it is possible to set the TextElement properties of the cell style as follows:
<DataGrid ItemsSource="{Binding Data}" AutoGenerateColumns="False" DockPanel.Dock="Top">
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="TextElement.Foreground" Value="Green" />
</Style>
</DataGrid.CellStyle>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Test">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="not globably applied" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>

DataTrigger on DataGridTextColumn

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>

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

How do I apply a DataTemplate in a Dynamic Grid?

I have a Grid. The grid's columns are auto-generated at run-time based on the user's selection.
I need the cells in the grid to be red if the content is a negative number.
I have created a DataTemplateSelector. The DataTemplateSelector get's correctly called and returns my template if the cell is negative.
Since my columns are auto-generated, I cannot simply put the correct field in the binding in my template.
<DataTemplate x:Key="MontantNegatifTemplate">
<TextBlock Foreground="Red" Text="{Binding}" />
</DataTemplate>
If I do a Template like this the text is the name of the object the grid is bound on.
If I do something like:
<DataTemplate x:Key="MontantNegatifTemplate">
<TextBlock Foreground="Red" />
</DataTemplate>
The cell is empty since the Textblock seems to overwrite the standard auto-generated cell.
Is there a way to make this work? Should I use another approach?
I finally found the awnser to my question.
I needed to use a StyleSelector rather than a DataTemplateSelector.
In the same way I needed to define a Style instead of a DataTemplate in my Grid resources.
<style:NegativeStyleSelector x:Key="NegativeStyleSelector">
<style:NegativeStyleSelector.NegativeStyle>
<Style TargetType="GridViewCell">
<Setter Property="Foreground" Value="Red"/>
</Style>
</style:NegativeStyleSelector.NegativeStyle>
</style:NegativeStyleSelector>

WPF: Dynamically change ListBox's ItemTemplate based on ListBox Items Size

I need to change the DataTemplate of my ListBox, based on the ListBox items count. I have come up with the following XAML:
<Window.Resources>
<DataTemplate x:Key="DefaultTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Text}"/>
<TextBlock Text="default template" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="OtherTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Text}"/>
<TextBlock Text="other template" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<ListBox Name="listBox1" ItemsSource="{Binding Path=Items}">
<ListBox.Style>
<Style TargetType="{x:Type ListBox}">
<Setter Property="ItemTemplate" Value="{StaticResource DefaultTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Items.Count}" Value="1">
<Setter Property="ItemTemplate" Value="{StaticResource OtherTemplate}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Style>
</ListBox>
With the above XAML, once I added two or more items to the bound list, the data template changed as expected (from other to default). However, if I remove the first item in the list with more than two items, the entire listbox just becomes empty (I verified that the bound list is non-empty). Removing the second item in a two items list works fine though (i.e. template changed from default to other).
Any ideas why this is happening? Or perhaps I went about the wrong way to solve this problem?
you could use data triggers, or you could use a DataTemplateSelector Here is an article that shows the basics. and here is the MSDN on applying it to the items control (also, a listbox)
I can't speak for the exact problem or the cause, but it is because a DataTrigger is setting a template when the count is 1 and only 1.
You can do 1 of 3 things to solve this problem, but only 1 I would recommend.
a) Implement your own DataTrigger by deriving from System.Windows.TriggerBase
b) Use an implementation of System.Windows.Data.IValueConverter that will convert from ItemsControl.Items.Count into a DataTemplate. Retrieve the templates by placing an element in scope of your resources as Binding.ConverterParameter, casting them to FrameWorkElement and call FrameWorkElement.FindResource().
C) This is my recommendation, write your own DataTemplateSelector to do the grunt work. This mechanism is specifically targeted at the functionality you with you achieve. I recently wrote one that will pick a DataTemplate based on the type of the source object without requiring a DataTemplate with no x:Key set. Using Properties on the template selector, you can pass DataTemplates into the DataTemplateSelector using XAML, removing that FindResource code 'todo' list.

Resources