Control template: how to create bindings - c#

So, I have a datagrid that has different colour cells depending on the cell's value.
I also have a tooltip that displays some further information. This all works fine.
I, however, would like to alter the tooltip to show further information and also to be the same colour as the cell. So, I thought it would be wise to create a custom style for my tool tips. So, I have the below code.
<Style TargetType="ToolTip">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToolTip">
<Border CornerRadius="15,15,15,15"
BorderThickness="3,3,3,3"
Background="#AA000000"
BorderBrush="#99FFFFFF"
RenderTransformOrigin="0.5,0.5">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"/>
<TextBlock Grid.Row="1"/>
<TextBlock Grid.Row="2"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I have an object shown below that is bound to my datagrid. I want to bind the three properties to the three textboxes in my tooltip.
class MyTask
{
public string Name;
public int Code;
public string Description;
}
In my DataGrid I do the following to bind my data to my datagrid
ItemsSource="{Binding TaskList}"
Then in the DataGridTextColumn I bind to a property like below
DataGridTextColumn Header="Code" Binding="{Binding Code}"
This makes sense to me. I am however at a loss to see how I use binding when creating my custom tooltip. I read that I can use templatebinding. I still don't understand how my tooltip will bind to my object of type MyTask in my xaml above?
Update - hopefully make my question clearer
I want to know how to create the bindings in my control template (for the 3 textboxes) and then in the main part of my code how I bind to these text boxes. I then would like to know how to create a binding for the background colour of my control template, I believe this is something to do with relativesource?
When I'm reading other examples (changing the Template Property) I see lines like below. I don't really understand why you have to do it? Is it a case of if you didn't right the line below you wouldn't be able to create a binding on the Padding property?
<Border Padding="{Binding Padding}" ...>

You don't need TemplateBindng, as that is used for setting up the resulting template object to layout based on dynamically using the properties of the implementing control. See this CodePlex article for a good example of when you'd need such functionality.
You simply need to set the bindings of your TextBlock elements within your ToolTip. You don't really need a Template at all in this case, except that since you are using the same ToolTip across all your column cells, it will help you out as you don't need to copy-paste the same code three times. You are after something similar to this article, Tooltip in DataGrid in WPF.
A solution which would work specifically to your case would be like:
<DataGrid Name="TestGrid1" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Name">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}">
<TextBlock.ToolTip>
<ToolTip />
</TextBlock.ToolTip>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Code">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Code}">
<TextBlock.ToolTip>
<ToolTip />
</TextBlock.ToolTip>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Description">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}">
<TextBlock.ToolTip>
<ToolTip />
</TextBlock.ToolTip>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
<DataGrid.Resources>
<Style TargetType="ToolTip">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToolTip">
<Border CornerRadius="15,15,15,15"
BorderThickness="3,3,3,3"
Background="#AA000000"
BorderBrush="#99FFFFFF"
RenderTransformOrigin="0.5,0.5">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Name}"/>
<TextBlock Grid.Row="1" Text="{Binding Code}"/>
<TextBlock Grid.Row="2" Text="{Binding Description}"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.Resources>
</DataGrid>
You set the ToolTip property within the CellTemplate so that the resulting ToolTip that pops up has the same DataContext as the active row in the DataGrid. This way, you can simply do your property bindings as normal within your ToolTip ContentTemplate, since it has access to all the same properties as does your DataGrid for the row.

To use underlying DataGridCell Background as ToolTip Background, bind your Border Background as Background="{Binding PlacementTarget.Background, RelativeSource={RelativeSource AncestorType=ToolTip, Mode=FindAncestor}}" .
You are trying to show all the fields in the tooltip of a cell. That doesn't make sense. But still you can do that easily using
PlacementTarget property, which gives you the underlying Visual element. ContextMenu, Popup too expose this property.
PlacementTarget.DataContext will give you the underlying MyTask object.
PlacementTarget.Content will give you the content of the corresponding DataGridCell, in your case it will be TextBlock.
So, if you want to show 3 fields in your cell's tooltip, below code will work for you using point number 2 above.
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="Background" Value="Tomato"/>
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip>
<ToolTip.Style>
<Style TargetType="ToolTip">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToolTip">
<Border CornerRadius="15,15,15,15"
BorderThickness="3,3,3,3"
Background="{Binding PlacementTarget.Background, RelativeSource={RelativeSource AncestorType=ToolTip, Mode=FindAncestor}}"
BorderBrush="#99FFFFFF"
RenderTransformOrigin="0.5,0.5">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding PlacementTarget.DataContext.Name, RelativeSource={RelativeSource AncestorType=ToolTip, Mode=FindAncestor}}"
/>
<TextBlock Grid.Row="0" Text="{Binding PlacementTarget.DataContext.Code, RelativeSource={RelativeSource AncestorType=ToolTip, Mode=FindAncestor}}"
/>
<TextBlock Grid.Row="0" Text="{Binding PlacementTarget.DataContext.Description, RelativeSource={RelativeSource AncestorType=ToolTip, Mode=FindAncestor}}"
/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ToolTip.Style>
</ToolTip>
</Setter.Value>
</Setter>
</Style>
</DataGrid.CellStyle>
And, if you want to show only corresponding cell's field in your cell's tooltip, then remove the remaining 2 textblocks, and use only one as :
<TextBlock Text="{Binding PlacementTarget.Content.Text, RelativeSource={RelativeSource AncestorType=ToolTip, Mode=FindAncestor}}" />
If you want to show all 3 fields, then apply the ToolTip to DataGridRow using DataGrid.RowStyle. No change in code would be needed.

What about using a color property in MyTask?
class MyTask
{
public string Name { get; set; }
public int Code { get; set; }
public string Description { get; set; }
public SolidColorBrush Color { get; set; }
}
And binding to the color property:
<Style TargetType="ToolTip">
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="HasDropShadow" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToolTip">
<Border Name="Border" Background="{Binding Color}" BorderBrush="{StaticResource SolidBorderBrush}" BorderThickness="1" Width="{TemplateBinding Width}" Height="{TemplateBinding Height}">
<StackPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Code}" />
<TextBlock Text="{Binding Description}" />
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Why don't you go for DataTemplate of tool tip like give it's a key and apply it in your cell tool tip style.
<Style TargetType="ToolTip" x:Key="ToolTipStyle">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate TargetType="ToolTip">
<Border CornerRadius="15,15,15,15"
BorderThickness="3,3,3,3"
Background="#AA000000"
BorderBrush="#99FFFFFF"
RenderTransformOrigin="0.5,0.5">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"/>
<TextBlock Grid.Row="1"/>
<TextBlock Grid.Row="2"/>
</Grid>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Now, you can bind your property to textblock too.

Related

ListBox.ItemTemplate with long text does not match ListBox width

I'm trying to put a StackPanel inside a ListBox and this inside a Grid that is a part of a UserControl.
So far I have come up with the following XAML but I cannot fix the width. When you take a look at the screenshot you'll see that the StackPanel is few pixels too wide. I tried HorizontalAlignment=Stretch on every element but on the Width={Binding...} manages to limt the width, although not perfectly. What do I miss?
<UserControl x:Class="Reusable.Apps.ExceptionControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Reusable.Apps"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" Width="{Binding (Window.Width), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">
<UserControl.Resources>
<local:ExceptionVisualizerData x:Key="DesignViewModel" />
<Style TargetType="TextBlock" x:Key="NameStyle">
<Setter Property="FontSize" Value="20"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FontFamily" Value="Consolas"/>
<Setter Property="Foreground" Value="DarkMagenta"/>
</Style>
<Style TargetType="TextBlock" x:Key="MessageStyle">
<Setter Property="FontSize" Value="16"></Setter>
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="TextWrapping" Value="Wrap"/>
</Style>
</UserControl.Resources>
<Grid Width="{Binding (UserControl.Width), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
<ListBox ItemsSource="{Binding Exceptions}" d:DataContext="{Binding Source={StaticResource DesignViewModel}}" Width="{Binding (Grid.Width), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Grid}}}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Width="{Binding (ListBox.Width), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}}">
<TextBlock Text="{Binding Name}" Style="{StaticResource NameStyle}" />
<TextBlock Text="{Binding Message}" Style="{StaticResource MessageStyle}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>
It sounds like you should get rid of all the width bindings and just put a margin around your StackPanel, e.g:
<StackPanel Margin="10" >
--- UPDATED --
Frustrating is the right word. Okay, here's the solution.
(1) Replace StackPanel with Grid.
(2) In the ListBox, set ScrollViewer.HorizontalScrollBarVisibility to "Disabled"
<ListBox ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<Grid Margin="10" >
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Title" Style="{StaticResource NameStyle}" />
<TextBlock Grid.Row="1" TextWrapping="Wrap" Text="lots of text lots of text lots of text lots of text lots of text lots of text lots of text lots of text lots of text lots of text lots of text lots of text lots of text lots of text lots of text lots of text lots of text lots of text lots of text lots of text lots of text lots of text lots of text " Style="{StaticResource MessageStyle}" />
</Grid>
</ListBox>
This should do the trick.
I finally got it! I found a similar question and adopted its answer to my needs:
<Grid >
<ListBox ItemsSource="{Binding Exceptions}" d:DataContext="{Binding Source={StaticResource DesignViewModel}}" Margin="30" >
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Width" Value="{Binding (Grid.ActualWidth), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Grid}}}" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" Style="{StaticResource NameStyle}" />
<TextBlock Text="{Binding Message}" Style="{StaticResource MessageStyle}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
The trick was to bind the Width of the ListBoxItem to the parent. Holy cow! This was tricky. Now it also nicely scales when resizing the window.
Instead of referring to ListBox, refer width of ListBoxItem
<StackPanel Width="{Binding (ListBoxItem.Width), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}">
The reason for the overlap is due to the default template for a ListBoxItem, which has Padding set to "4,1" and a BorderThickness of "1" on both itself and the inner Border object. You can use a tool such as WPF Snoop to see this.
The solution is to bind to the ActualWidth of the next level of container, in this case the ContentPresenter.
<ListBox ItemsSource="{Binding DataItems}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Background="Yellow"
Width="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ContentPresenter}},
Path=ActualWidth}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Id}" />
<TextBlock Grid.Row="1" Text="{Binding Description}" TextWrapping="Wrap" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Note the properties set on the ListBox, which prevent the content growing beyond the width of the ListBox, and forcing the content to resize as the ListBox width is increased.

Binding within a template

I have the following ControlTemplate, as shown below, and I would like to avoid hard coding the hight and width of the image, instead I would like to bind the Height and width of the Image control
<Image Source="/Rotate.Pictures;component/Images/error.png"
Stretch="Uniform"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Height="14"
Width="14"/>
to the TextBlock's Text (TextBlock named "ErrorText") property path = "Height". Both Height and Width of the image should be bound to the Height value of the TextBlock's Text Height property
Does anyone know how I can accomplish this?
Template:
<Grid IsSharedSizeScope="True">
<Grid.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<AdornedElementPlaceholder>
<Border BorderBrush="Red" BorderThickness="2"/>
</AdornedElementPlaceholder>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="/Rotate.Pictures;component/Images/error.png" Stretch="Uniform" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Height="14"
Width="14"/>
<TextBlock x:Name="ErrorText" Text="{Binding ErrorContent}" Foreground="Red"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
Key to the answer is to reference past controls (control that the thread of execution already encountered in the "{Binding ElementName=ErrorText, Path=ActualHeight}"). So the solution uses a grid, then specify Grid.Column 1 before specifying Grid.Column 0 that references the ElementName in Grid.Column 1.
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<AdornedElementPlaceholder>
<Border BorderBrush="Red" BorderThickness="2"/>
</AdornedElementPlaceholder>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock x:Name="ErrorText" Grid.Column="1" Text="{Binding ErrorContent}" Foreground="Red"/>
<Image Grid.Column="0" Source="/Rotate.Pictures;component/Images/error.png"
Height="{Binding ElementName=ErrorText, Path=ActualHeight}"
Width="{Binding ElementName=ErrorText, Path=ActualHeight}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

XAML GridRow doesn't scale automatically when ErrorTemplate is displayed

I have a XAML window with a TextBox, and this TextBox has an ErrorTemplate.
The ErrorTemplate is shown below, and as you can see, I have an AdornedElementPlaceholder, followed by a textbox whose Text field is bound to the ErrorContent:
<ControlTemplate x:Key="ValidationErrorTemplateTextBlock" TargetType="{x:Type Control}">
<Border BorderBrush="Red" BorderThickness="1">
<StackPanel Orientation="Vertical">
<AdornedElementPlaceholder Name="AdornedElementPlaceholder" />
<TextBlock Text="{Binding ElementName=AdornedElementPlaceholder, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"
FontSize="10"
Background="Red"
Foreground="White"
Padding="2" />
</StackPanel>
</Border>
</ControlTemplate>
<TextBox IsEnabled="{Binding SendMessage}"
Text="{Binding AutoMessageSubject, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource StyleBase}"
Validation.ErrorTemplate="{StaticResource ValidationErrorTemplateTextBlock}"
HorizontalAlignment="Stretch"
Grid.Row="3"
Grid.Column="1"
Grid.ColumnSpan="2" />
This works fine, except for one thing: the TextBox is inside a GridRow, with a Height="Auto". The row scales itself based on the textbox, but when the ErrorTemplate appears, with an extra TextBox on the bottom - the GridRow doesn't scale up to contain the new TextBox, and the new TextBox overlaps the elements below it.
How can I solve this?
Validation.ErrorTemplate: Gets or sets the ControlTemplate used to generate validation error feedback on the adorner layer.
This means that if you use Validation.ErrorTemplate, the validation errors are displayed on the layer above usual content, so the "second" TextBlock is displayed over the grid, not within the grid cell.
I would implement INotifyDataErrorInfo instead of semi-obsolete IDataErrorInfo, use a custom textbox style, and bind the visibility of the second TextBlock to HasErrors property. The example below uses a ToolTip instead of the second TextBlock:
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip DataContext="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget}">
<ItemsControl DisplayMemberPath="ErrorContent" ItemsSource="{Binding Path=(Validation.Errors)}" />
</ToolTip>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
You might want to try adding Row/Column Definitions:
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>

How to use an ItemTemplate on a templated control?

I am trying to create custom templated control based on a ListBox to display only the single selected item in the control's collection.
To this aim, I have defined a Template with an ContentPresenter, databound to the SelectedItem property of the control.
As illustrated in scenario 1, it works well when providing the control with a collection of UIElement.
But when needing to use a DataTemplate to display an entity object, the ItemTemplate is ignored because the control has a Template which will prevent the `ItemTemplate from being used. From what I've read this is by design and it sorts of makes sense.
But how can I use DataTemplates for my control's ItemTemplate and retain my control default template?
I have tried overriding PrepareContainerForItemOverride, and many different template configurations to no avail.
Here is some of what I tried:
<UserControl.Resources>
<local:StringCollection x:Key="MyColors">
<sys:String>Yellow</sys:String>
<sys:String>Purple</sys:String>
</local:StringCollection>
</UserControl.Resources>
<StackPanel Background="White">
<TextBlock Margin="0,25,0,0">Scenario 1: Providing UIElements to the
ControlTemplate's ContentPresenter: Works</TextBlock>
<control:RotatingFlipView x:Name="Works" SelectedIndex="1">
<control:RotatingFlipView.Template>
<ControlTemplate>
<ContentPresenter
Content="{Binding ElementName=Works, Path=SelectedItem}"/>
</ControlTemplate>
</control:RotatingFlipView.Template>
<Rectangle Height="100" Width="100" Fill="Red"/>
<Rectangle Height="100" Width="100" Fill="Blue"/>
<Rectangle Height="100" Width="100" Fill="Green"/>
</control:RotatingFlipView>
<TextBlock Margin="0,25,0,0">Scenario 2: The ItemTemplate provided is ignored in
favor of the RotatingFlipView's Template which displays the source as raw
Strings</TextBlock>
<control:RotatingFlipView x:Name="Broken"
ItemsSource="{StaticResource MyColors}">
<control:RotatingFlipView.Template>
<ControlTemplate>
<ContentPresenter
Content="{Binding ElementName=Broken, Path=SelectedItem}"/>
</ControlTemplate>
</control:RotatingFlipView.Template>
<control:RotatingFlipView.ItemTemplate>
<DataTemplate>
<Rectangle Height="100" Width="100" Fill="{Binding}"/>
</DataTemplate>
</control:RotatingFlipView.ItemTemplate>
</control:RotatingFlipView>
<TextBlock Margin="0,25,0,0">Scenario 3: Removing the RotatingFlipView's
Template, causes the display to fall back on the ListBox's Template,
though now my ItemTemplate is used:</TextBlock>
<control:RotatingFlipView x:Name="Broken2"
ItemsSource="{StaticResource MyColors}">
<control:RotatingFlipView.ItemTemplate>
<DataTemplate>
<Rectangle Height="100" Width="100" Fill="{Binding}"/>
</DataTemplate>
</control:RotatingFlipView.ItemTemplate>
</control:RotatingFlipView>
</StackPanel>
RotatingFlipView.cs
public class RotatingFlipView : ListBox
{
public RotatingFlipView()
{
DefaultStyleKey = typeof(RotatingFlipView);
}
}
generic.xaml
<Style TargetType="local:RotatingFlipView">
<Setter Property="SelectionMode" Value="Single"/>
<Setter Property="SelectedIndex" Value="0"/>
</Style>
Output:
I have now figured it out. Here is how I've done it:
Mainpage.xaml
<UserControl.Resources>
<local:StringCollection x:Key="MyColors">
<sys:String>Yellow</sys:String>
<sys:String>Purple</sys:String>
</local:StringCollection>
</UserControl.Resources>
<StackPanel Background="White">
<control:RotatingFlipView>
<Rectangle Height="100" Width="100" Fill="Red"/>
<Rectangle Height="100" Width="100" Fill="Green"/>
<Rectangle Height="100" Width="100" Fill="Blue"/>
</control:RotatingFlipView>
<control:RotatingFlipView ItemsSource="{StaticResource MyColors}">
<control:RotatingFlipView.ItemTemplate>
<DataTemplate>
<Rectangle Height="100" Width="100" Fill="{Binding}"/>
</DataTemplate>
</control:RotatingFlipView.ItemTemplate>
</control:RotatingFlipView>
<StackPanel/>
RotatingFlipView.cs
public class RotatingFlipView : ListBox
{
public RotatingFlipView()
{
DefaultStyleKey = typeof(RotatingFlipView);
}
}
generic.xaml
<Style TargetType="local:RotatingFlipView">
<Setter Property="SelectionMode" Value="Single"/>
<Setter Property="SelectedIndex" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:RotatingFlipView">
<ContentPresenter
Content="{TemplateBinding SelectedItem}"
ContentTemplate="{TemplateBinding ItemTemplate}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

C# / WPF - DataGrid - Binding the Width of a TextBox in RowDetails to that of the containing DataGrid

My problem is similar to this one;
DataGrid RowDetails Width problem
Except I would like row details never to exceed the width of the columns it spans.
|--0--|--1--|--2--|--3--|--4--|
|---------Row-Details---------|
I have tried AreRowDetailsFrozen and this had no effect. I have also tried binding to the parent Grid's actual width (OneWay) but this causes the width to exceed that of my two screens.
Here is my current attempt (simplified);
<Grid>
<DataGrid x:Name="Grid"
Grid.Row="1"
ItemsSource="{Binding Collection}"
IsReadOnly="True"
AutoGenerateColumns="False"
ColumnWidth="Auto"
CanUserResizeColumns="False"
CanUserResizeRows="False"
RowDetailsVisibilityMode="VisibleWhenSelected"
AreRowDetailsFrozen="True"
SelectionUnit="FullRow"
VerticalAlignment="Top"
HorizontalAlignment="Center">
<DataGrid.RowDetailsTemplate>
<!-- Begin row details section. -->
<DataTemplate>
<TextBox DataContext="{Binding ErrorMessage}"
IsReadOnly="True"
Margin="5"
BorderBrush="Transparent"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.CanContentScroll="True"
TextWrapping="Wrap"
Text="{Binding .}">
</TextBox>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
</Grid>
This results in the following;
|--0--|--1--|--2--|--3--|--4--|
|---------Row-Details are as wide as the longest row in their content ---------|
Binding the width of the TextBox to any parent container (Grid, DataGrid, ItemsPresenter):
Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Grid}}, Path=ActualWidth, Mode=OneWay}"
Results in:
|------Viewable Area-------|
|---- Columns ----|
|---------Row-Details --------------------------------------------------------------|
It's very frustrating, I just want the Row Details not to change the width of the DataGrid, is that so much to ask? :)
The only way to accomplish this is to change DataGridRow ControlTemplate. There we can bind row details host width (DataGridDetailsPresenter) to width of cells. For example:
<Style x:Key="{x:Type dg:DataGridRow}" TargetType="{x:Type dg:DataGridRow}">
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}" />
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="Validation.ErrorTemplate" Value="{x:Null}" />
<Setter Property="ValidationErrorTemplate">
<Setter.Value>
<ControlTemplate>
<TextBlock Margin="2,0,0,0" VerticalAlignment="Center" Foreground="Red" Text="!" />
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type dg:DataGridRow}">
<Border x:Name="DGR_Border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="True">
<dgp:SelectiveScrollingGrid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<dgp:DataGridCellsPresenter x:Name="cellPresenter" Grid.Column="1"
ItemsPanel="{TemplateBinding ItemsPanel}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
<dgp:DataGridDetailsPresenter dgp:SelectiveScrollingGrid.SelectiveScrollingOrientation="{Binding RelativeSource={RelativeSource AncestorType={x:Type dg:DataGrid}}, Path=AreRowDetailsFrozen, Converter={x:Static dg:DataGrid.RowDetailsScrollingConverter}, ConverterParameter={x:Static dg:SelectiveScrollingOrientation.Vertical}}"
Grid.Column="1" Grid.Row="1"
Visibility="{TemplateBinding DetailsVisibility}" Width="{Binding ElementName=cellsPresenter, Path=ActualWidth}"/>
<dgp:DataGridRowHeader dgp:SelectiveScrollingGrid.SelectiveScrollingOrientation="Vertical" Grid.RowSpan="2"
Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type dg:DataGrid}}, Path=HeadersVisibility, Converter={x:Static dg:DataGrid.HeadersVisibilityConverter}, ConverterParameter={x:Static dg:DataGridHeadersVisibility.Row}}"/>
</dgp:SelectiveScrollingGrid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Hope this helps.
I answered a similar question here DataGrid RowDetails Width problem
The answers here felt like a workaround so I did some research and did
find the solution on the Telerik forums, since we use their
RadGridView. Turned out the solution worked for DataGrid as well.
The key is to set the ScrollViewer.HorizontalScrollBarVisibility
property to Disabled, see example below.
<DataGrid ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<Border>
<TextBlock Foreground="White" Text="{Binding RowDetails}"
TextWrapping="Wrap"/>
</Border>
</DataTemplate>
</DataGrid.RowDetailsTemplate> </DataGrid>
I've found another way to fix the Problem:
private void GridOnLoadingRowDetails(object sender, DataGridRowDetailsEventArgs e)
{
var dataGridColumnHeadersPresenter = FindVisualChild<DataGridColumnHeadersPresenter>((DataGrid)sender);
e.DetailsElement.SetBinding(WidthProperty, new Binding("ActualWidth") { Source = dataGridColumnHeadersPresenter });
}
This prevents you from using constant values (e.g. '6') - which won't fit, if someone set DataGrid.RowHeaderWidth - and Converters.
I've added this to the DataGrid.LoadingRowDetails event handler, since I am already tweaking the RowDetails in some other ways.

Categories