Wpf - Create a control / element as a resource and use it in XAML - wpf

I have multiple DataGrid objects in different tabs of WPF window that share the same columns.
I can define a column as a resource like this:
<TabControl.Resources>
<DataGridTextColumn x:Key="NameGridColumn" Binding="{Binding Name}" IsReadOnly="True" Width="*">
<DataGridTextColumn.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name" VerticalAlignment="Center"/>
<Button Command="{Binding SortByNameCommand}" Content="▲" Background="Transparent" BorderThickness="0" Margin="5 0 0 0"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
</TabControl.Resources>
But how can I then use these resource columns in a DataGrid?
<TabItem Header="Tab 1">
<DataGrid ItemsSource="{Binding ItemsCollection}" CanUserAddRows="False" CanUserDeleteRows="False" CanUserSortColumns="False" AutoGenerateColumns="False">
<DataGrid.Columns>
<????>
<!-- notice that I have many columns, not just one -->
</DataGrid.Columns>
</DataGrid>
</TabItem>

As I know WPF has restriction - UI element can belong only to one parent control.
So it seems to you can't reuse one column in different grids.
But you can create UserControl and specify in XAML Grid with your columns.
In such case if DataContext will be the same (for example same VM -> you can simple use it in different places of your application).

Not the ideal answer, but fairly good.
This is an answer specific for data grid column headers (the columns you define are not actual controls, but representations for controls that will be generated later). If you want an answer for actual controls in general, you can resort to this answer
You can use a DataTemplate to define the headers:
<TabControl.Resources>
<!--Converter to change arrow buttons appearance-->
<local:EnabledToBrushConverter x:Key="EnabledToBrushConverter"/>
<!--Style for all arrow buttons-->
<Style TargetType="Button">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Margin" Value="5 0 0 0"/>
</Style>
<!-- each data template is a header -->
<DataTemplate x:Key="NameGridHeader">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name" VerticalAlignment="Center"/>
<Button Command="{Binding Path=DataContext.SortByNameCommand, RelativeSource={RelativeSource AncestorType=DataGrid, Mode=FindAncestor}}" Content="▲"
Foreground="{Binding Path=DataContext.IsSortByName, Converter={StaticResource EnabledToBrushConverter}, RelativeSource={RelativeSource AncestorType=DataGrid, Mode=FindAncestor}}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="AnotherHeader">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Another value" VerticalAlignment="Center"/>
<Button Command="{Binding Path=DataContext.SortByAnotherValueCommand, RelativeSource={RelativeSource AncestorType=DataGrid, Mode=FindAncestor}}" Content="▼"
Foreground="{Binding Path=DataContext.IsSortByAnotherValue, Converter={StaticResource EnabledToBrushConverter}, RelativeSource={RelativeSource AncestorType=DataGrid, Mode=FindAncestor}}"/>
</StackPanel>
</DataTemplate>
</TabControl.Resources>
And you can use the HeaderTemplate property of a DataGridTextColumn or a DataGridTemplateColumn.
<DataGrid.Columns>
<DataGridTextColumn Header="Type" Binding="{Binding ItemType}" IsReadOnly="True"/>
<DataGridTextColumn HeaderTemplate="{StaticResource NameGridHeader}" Binding="{Binding Name}" IsReadOnly="True" Width="*"/>
<DataGridTextColumn HeaderTemplate="{StaticResource AnotherHeader}" Binding="{Binding AnotherValueString}" IsReadOnly="True"/>
</DataGrid.Columns>
<!-- Define as many datagrids as you like using the same column header templates -->
The downsides are:
Cannot declare the column bindings in the template (might be an advantage if you want the same header for different data, though)
Cannot declare an entire column (header and cell template), must use separate cell templates (with DataGridTemplateColumn's CellTemplate or CellEditingTemplate) or styles if it's the case
Bindings in the headers only work with RelativeSource set to some element above it

There is a way to use DataTemplate for the Cell and another one for the Header:
<DataTemplate x:Key="Head1Template">
<TextBlock Text="Product Name"/>
</DataTemplate>
<DataTemplate x:Key="Head1CellTemplate">
<TextBlock Text="{Binding Product}"/>
</DataTemplate>
and using it in the DataGrid:
<DataGridTemplateColumn CellTemplate="{StaticResource Head1CellTemplate}" HeaderTemplate="{StaticResource Head1Template}"/>

But how can I then use these resource columns in a DataGrid?
You can't. At least not using pure XAML.
What you can to is to define the entire DataGrid, including all columns, as a resource or a separate control and reuse it like any other control, e.g.:
<local:CustomDataGrid ItemsSource="{Binding ItemsCollection}" />
Another option is to redefine the column(s) in each tab but define the StackPanel as a resource and set the header of each column like this:
<DataGridTextColumn Binding="{Binding Name}" IsReadOnly="True" Width="*"
Header="{StaticResource theStackPanel}">
You would then define the StackPanel something like this:
<StackPanel x:Key="theStackPanel" x:Shared="false" Orientation="Horizontal">
<TextBlock Text="Name" VerticalAlignment="Center"/>
<Button Command="{Binding SortByNameCommand}" Content="▲" Background="Transparent" BorderThickness="0" Margin="5 0 0 0"/>
</StackPanel>
Either way, you cannot add a column resource to <DataGrid.Columns> in XAML. You can do it programmatically though:
dataGrid.Columns.Add(tabControl.Resources["NameGridColumn"] as DataGridColumn);

Related

Sorting Datagrid with DataGridTemplateColumn

I am using a DataGrid and the ItemSource is Bound to a list of strings.
The problem is that the sorting is not working.
The header is enabled and can be clicked but the data is not sorted.
<DataGrid ItemsSource="{Binding CollectionNames}" SelectedItem="{Binding CurrentName}" SelectionUnit="FullRow" CanUserAddRows="False" AutoGenerateColumns="False" SelectionMode="Single" >
<DataGrid.Columns>
<DataGridTemplateColumn Width="400" CanUserSort="True" SortMemberPath="Name">
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<TextBlock Text="Name" Foreground="#FF40A4E0" HorizontalAlignment="Left" VerticalAlignment="Center"></TextBlock>
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentControl>
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" Text="{Binding}"/>
</ContentControl>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGrid.CellStyle>
</DataGrid>
I think the problem is SortMemberPath="Name" but I dont know what to put instead of "Name"
Simply you can set SortMemberPath=".". This usage is similar when you set the Binding's Path to ".", which means the whole item will be bound, in this case the whole item will be used as the input value for the sorter.

WPF List control with header

I'm new to WPF. I trying to display information in a scrollable list/grid that has a header and the user is NOT allowed to click on the items in the list. Also I need a divider between each item in the list.
Right now I'm using a HeaderedContentControl and putting a ListView in the HeaderedContentControl.Content. I can't seem to get the ListView to scroll without setting a specific height on it and it would be nice to have the control be able to resize.
<UserControl ...>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<HeaderedContentControl Margin="10">
<HeaderedContentControl.Header>
<Label ... />
</HeaderedContentControl.Header>
<HeaderedContentControl.Content>
<ListView ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Info1}" />
<TextBlock Text="{Binding Info2}" />
<Separator />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</HeaderedContentControl.Content>
</HeaderedContentControl>
</Grid>
</UserControl>
In the past I've been able to get a ListView scrolling if I put it as a direct child of Grid, but here it's not picking up the height of * as the constraint.
Have you considered using a datagrid? Based on your description I created the following xaml and tested and I believe it satisfies your requirements.
<DataGrid ItemsSource="{Binding Items}" HeadersVisibility="Column"
VerticalScrollBarVisibility="Auto" IsReadOnly="True"
CanUserSortColumns="False" AutoGenerateColumns="False"
IsHitTestVisible="True"
CanUserReorderColumns="False" Height="100"
>
<DataGrid.Columns>
<DataGridTextColumn Header="Header1" Binding="{Binding Info1}"/>
<DataGridTextColumn Header="Header2" Binding="{Binding Info2}"/>
</DataGrid.Columns>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="IsHitTestVisible" Value="False"/>
</Style>
</DataGrid.CellStyle>
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="IsHitTestVisible" Value="False"/>
</Style>
</DataGrid.ColumnHeaderStyle>
</DataGrid>
The IsHitTestVisible must be set false in the cell style and column header style to allow the scroll bar to still work.
Datagrids have a lot more properties which you may be able to exploit to get exactly what you are looking for.

Datagrid Column Header Templating

I want to template some headercolumns in a pretty simple way, but I don't get the results I am looking for. I want the Border to fill the whole header, not just the textbox inside. Right now I am still having the default gray behind my own controls.
<DataGrid x:Name="grdItems"
Grid.Row="0"
CanUserAddRows="False"
CanUserDeleteRows="False"
ItemsSource="{Binding Path=Items}"
CanUserSortColumns="False" AutoGenerateColumns="False" CanUserResizeColumns="False">
<DataGrid.Columns>
<DataGridComboBoxColumn ItemsSource="{Binding Source={StaticResource GetSignalTypeValues}}" Width="auto" SelectedValueBinding="{Binding SignalType}" >
<DataGridComboBoxColumn.HeaderTemplate>
<DataTemplate>
<Border Background="Orange" >
<TextBlock Text="Signal Type" />
</Border>
</DataTemplate>
</DataGridComboBoxColumn.HeaderTemplate>
</DataGridComboBoxColumn>
<DataGridTextColumn Width="auto" Binding="{Binding AmplitudeMax}">
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<Border Background="Violet">
<StackPanel>
<TextBlock Text="Amplitude" HorizontalAlignment="Center" />
<TextBlock Text="-maximum-" HorizontalAlignment="Center" />
</StackPanel>
</Border>
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
</DataGridTextColumn>
If you are only after background color of column header you can utilize this:
<DataGridComboBoxColumn Width="auto" SelectedValueBinding="{Binding SignalType}" >
<DataGridComboBoxColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="Background" Value="Orange"/>
</Style>
</DataGridComboBoxColumn.HeaderStyle>

How get a WPF Datagrid with cells that wrap text instead of truncating it?

What must be done to get a WPF DataGrid with cells that wrap text instead of truncating it?
Right now when a text is bigger and don't fit in a column the text is truncated and users can't see it value cos the DataGrid's IsReadOnly property is true. What I want is that the text in cells be wrapped and the cell height (NO CELL WIDTH) increased the amount needed to show all the text.
Thanks for your help #H.B., this did the trick for me (alignment is optional):
<DataGrid.Columns>
<DataGridTextColumn Header="Wrapped & centered" Binding="{Binding field}">
<DataGridTextColumn.ElementStyle>
<Style>
<Setter Property="TextBlock.TextWrapping" Value="Wrap" />
<Setter Property="TextBlock.TextAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
</DataGrid.Columns>
I made something similar to D.Rosados solution. Mine is however reusable if you have more columns that needs wrapping.
<UserControl.Resources>
<Style TargetType="{x:Type TextBlock}" x:Key="WrapText">
<Setter Property="TextWrapping" Value="Wrap"/>
</Style>
</UserControl.Resources>
<DataGrid.Columns>
<DataGridTextColumn IsReadOnly="False" Header="Address"
Binding="{Binding Address}" ElementStyle="{StaticResource WrapText}" Width="150"/>
</DataGrid.Columns>
You could try to template the cells with a TextBlock which has text-wrapping enabled.
Here is another solution in addtional to others
<DataGridTemplateColumn Header="MyFieldName" Width="150" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding MyField}" TextWrapping="Wrap">
<TextBlock.ToolTip>
<TextBlock Text="{Binding MyField}" />
</TextBlock.ToolTip>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Another simple way of setting text wrap for Editing and Text DataGrid columns is to specity the Binding property and TextWrapping property as following:
<DataGridTemplateColumn x:Name="ColumnName" Header="Column Header Goes Here">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=DataBoundProperty, Mode=TwoWay}" TextWrapping="Wrap"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=DataBoundProperty, Mode=OneWay}" TextWrapping="Wrap"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

Tooltip Visibility

I have the following code:
<DataTemplate>
<!--<sdk:DataGridTextColumn Binding="{Binding Description}" Header="Description" Width="205" />-->
<TextBlock Text="{Binding Description}" Width="232">
<ToolTipService.ToolTip >
<ToolTip Visibility="{Binding }">
<sdk:DataGrid AutoGenerateColumns="False" GridLinesVisibility="None" HeadersVisibility="None" Height="Auto" ItemsSource="{Binding Contains}" >
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn Binding="{Binding Code}" Header="Code" CanUserSort="False" />
<sdk:DataGridTextColumn Binding="{Binding Description}" Header="Description" CanUserSort="False"/>
</sdk:DataGrid.Columns>
</sdk:DataGrid>
</ToolTip>
</ToolTipService.ToolTip>
</TextBlock>
</DataTemplate>
I would like my tooltip to show, only if there are at least one row in the ItemsSource="{Binding Contains}"
What do I have to write in the Visibility property?
Something like
<ToolTip Visibility="{Binding Contains.Length > 0}">
But I can't figure out what the syntax should be! Any ideas?
You are going to have to use a custom converter on that binding. Visibility property is not boolean, it is an enum.
This is a job for an implementation of IValueConverter. Having posted many such examples in the past I realised that a couple of more general implementations would work of most of the time, so I blogged them.
The IValueConverter you need is my StringToObjectConverter blogged here. With the code for this converter in your project you can create an instance of it in a resource like this:-
<Grid x:Name="LayoutRoot">
<Grid.Resources>
<local:StringToObjectConverter x:Key="CountToVisibility">
<ResourceDictionary>
<Visibility x:Key="__Default__">Visible</Visibility>
<Visibility x:Key="0">Collapsed</Visibility>
</ResourceDictionary>
</local:StringToObjectConverter>
</Grid.Resources>
and then used when binding to visibility, in you case:-
<ToolTip Visibility="{Binding Contains.Length, Converter={StaticResource CountToVisibility}}">
BTW, are you sure you want Length not Count?
As an alternative way, you can use triggers:
Set visibility of your tooltip to Visible by default and add this markup:
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Contains.Length}" Value="0">
<Setter TargetName="myTooltip" Property="Visibility" Value="Hidden" />
</DataTrigger>
</DataTemplate.Triggers>

Resources