Share column width between Grids in items of an ItemsControl - wpf

I'm writing a control to display and edit objects in a form. The control (FormDataView) is an ItemsControl where each item is a FormField control made of a Grid, with the field name in the left column and the editor (e.g. TextBox) in the right column. In order to align the editors, I want the first column in each Grid to share the same width.
So I tried to use IsSharedSizeScope and SharedSizeGroup, but it doesn't work, the first column has a different width in each FormField.
Here are the styles for these controls:
<Style TargetType="{x:Type ctl:FormDataView}" BasedOn="{StaticResource ResourceKey={x:Type ItemsControl}}">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"
Grid.IsSharedSizeScope="True"
IsItemsHost="True" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type ctl:FormField}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ctl:FormField}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="headerColumn" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0"
Content="{TemplateBinding Header}"
Margin="3"
TextElement.FontWeight="Bold" />
<ContentPresenter Grid.Column="1"
Name="PART_Display"
ContentTemplate="{TemplateBinding DisplayTemplate}"
Margin="2"/>
<ContentPresenter Grid.Column="1"
Name="PART_Editor"
ContentTemplate="{TemplateBinding EditorTemplate}"
Margin="2"
Visibility="Collapsed" />
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding IsInEditMode, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ctl:FormDataView}}}"
Value="True">
<Setter TargetName="PART_Display" Property="Visibility" Value="Collapsed" />
<Setter TargetName="PART_Editor" Property="Visibility" Value="Visible" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Notice how Grid.IsSharedSizeScope is set in the ItemsPanel of FormDataView, while SharedSizeGroup is set in the template of FormField. This correctly expresses what I want to do: each FormField should use the same width for the first column. However, according to the documentation for the SharedSizeGroup property, this scenario is not supported:
Grid size-sharing does not work if you set IsSharedSizeScope to true
within a resource template and you define SharedSizeGroup as outside
that template.
OK, so I can understand why it doesn't work... but I don't know how to work around this limitation.
Any idea?
N.B.: I don't want to assign a fixed width to the first column of course...

Sadly I have no access to my Visual Studio Enviroment so I couldnt check the following tips...
Assign Grid.IsSharedSizeScope="True" to FormDataView itself and not to the ItemsPanel. Do you really need StackPanel as the items panel? cant you live without that?
See if the above change works first...
if not then revamp your item level code and assign SharedSizeGroup="headerColumn" in your item data template of your FormDataView and not in the ControlTemplate of individual FormField.
Let me know if this helps....

Related

How to get the content width of the TextBox except for width of the VerticalScrollBar?

I created HighlightTextBox that derived TextBox. And the code is as shown below.
<Style TargetType="{x:Type host:HighlightTextBox}">
<Setter Property="AcceptsReturn" Value="True" />
<Setter Property="HorizontalScrollBarVisibility" Value="Auto" />
<Setter Property="VerticalScrollBarVisibility" Value="Auto" />
<Setter Property="TextWrapping" Value="NoWrap"/>
<Setter Property="Foreground" Value="#00000000"/>
<Setter Property="FontSize" Value="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=FontSize, Mode=TwoWay}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate x:Name="textArea" TargetType="{x:Type host:HighlightTextBox}">
<Border BorderThickness="{Binding BorderTickness}"
BorderBrush="{Binding BorderBrush}"
Background="{Binding BackGround}">
<Grid Margin="{TemplateBinding Padding}" x:Name="PART_Grid">
<host:TextCanvas x:Name="PART_RenderCanvas" ClipToBounds="True"
TextOptions.TextRenderingMode="ClearType" TextOptions.TextFormattingMode="Display"
LineHeight="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=LineHeight}"/>
<ScrollViewer x:Name="PART_ContentHost" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The important point is ControlTemplate.
As you can see, the content of the HighlightTextBox consists of TextCanvas and ScrollViewer.
The HighlightTextBox highlights the currently selected line by painting on the TextCanvas but it invasion the VerticalScrollViewer section as below.
I want to display it by not invasion the VerticalScrollViewer.
I think that maybe the cause is TextCanvas occupy entire section of TextBox.
So I tried to move TextCanvas into the ScrollViewer but this way make facing the run-time error that "PART_ContentHost can't have child element.
I think that another way is to get the content width of the TextBox except for the width of the VerticalScrollBar and binding it to the width of the TextCanvas. But I don't know how to get it.
What I should do to solve this problem?
If you have a better way or another way to solve then please let me know.
Thank you for reading.
Search the visual tree for your ScrollViewer's content presenter, that will give you the width of the content area itself:
var scrollViewer = yourTextBox.Template.FindName("PART_ContentHost", yourTextBox) as ScrollViewer;
var contentPresenter = UIHelper.FindChild<ScrollContentPresenter>(scrollViewer, String.Empty);
var width = contentPresenter.ActualWidth;
UPDATE: You can bind to the ScrollContentPresenter's content control directly like this:
<Grid Margin="{TemplateBinding Padding}" x:Name="PART_Grid">
<TextBlock Text="{Binding ElementName=PART_ContentHost, Path=Content.ActualWidth}" Background="CornflowerBlue" VerticalAlignment="Top"/>
<ScrollViewer x:Name="PART_ContentHost" />
</Grid>
Keep in mind though that this gives you the area of the ScrollViewers content width which, in the example I've given above, will a bit smaller than the TextBlock's width due to the fact that the ScrollViewers content is a TextBoxView with a 2,0,2,0 margin:
To compensate for this you'll probably want to bind your Canvas margin to the TextBoxView margin (which in my case is a TextBlock rather than a Canvas):
<Grid Margin="{TemplateBinding Padding}" x:Name="PART_Grid">
<TextBlock Text="{Binding ElementName=PART_ContentHost, Path=Content.ActualWidth}"
Margin="{Binding ElementName=PART_ContentHost, Path=Content.Margin}"
Background="CornflowerBlue" VerticalAlignment="Top" />
<ScrollViewer x:Name="PART_ContentHost" Padding="0" Margin="0"/>
</Grid>
This will keep your Canvas's alignment in the parent Grid the same as for the TextBoxView so that everything lines up properly:
(You could also just remove the TextBoxView's margin, but that's probably not what you want).

Why is my Fluent Window title staying centered?

I'm using the latest version of Fluent.Ribbon. I've been doing some styling, most of which requires completely replacing the Styles and ControlTemplates, but I've hit a snag. The title of my app is centered in the header bar and I can't get it to move to the left.
My visual tree looks like this:
MainWindow
Grid
Adorner
Grid
DockPanel
PART_Icon
PART_RibbonTitleBar
Grid
PART_HeaderHolder [ContentPresenter]
TextBlock
PART_ItemsContainer
PART_QuickAccessToolbarHolder
I copied the current version of the Fluent:RibbonTitleBar ControlTemplate and Style into my override xaml for modification, but nothing I do makes any difference (yes it is loading my overriding styles.)
When I use the inspector tool in the app, the only elements I can highlight are the innermost TextBlock, which fits the text exactly with no stretch, and the DockPanel several levels above, which stretches the full window width. In the original window ControlTemplate, which you can see here, The RibbonTitleBar is the last element of the DockPanel which has LastChildFill set. The RibbonTitleBar does have a RenderSize of the full width, but then the Grid below it has a RenderSize of 0,0. Then PART_HeaderHolder inside that has a RenderSize that exactly covers the title text.
It doesn't seem to matter if I set HorizontalAlignment on various elements to Left or Stretch. I also tried changing the innermost Grid to other container types such as DockPanel and StackPanel. Nothing changes anything about the layout.
Here's my style overrides for the RibbonTitleBar. The only change I've made is that I moved the QuickAccessToolbar to the end and permanently collapsed it (if I try deleting it, the app crashes looking for it) and I tried defining some columns on the inner Grid to no avail.
<Style TargetType="{x:Type Fluent:RibbonTitleBar}">
<Setter Property="Template"
Value="{DynamicResource RibbonTitleBarControlOverride}" />
<Setter Property="Focusable"
Value="False" />
<Setter Property="VerticalAlignment"
Value="Top" />
<Setter Property="HorizontalAlignment"
Value="Stretch" />
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Margin="-2,0"
VerticalAlignment="Center"
HorizontalAlignment="Stretch"
Text="{Binding}"
TextWrapping="NoWrap"
TextTrimming="CharacterEllipsis" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<ControlTemplate x:Key="RibbonTitleBarControlOverride"
TargetType="{x:Type Fluent:RibbonTitleBar}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0" x:Name="PART_HeaderHolder"
HorizontalAlignment="Left"
ContentSource="Header"
IsHitTestVisible="False" />
<Fluent:RibbonContextualGroupsContainer Grid.Column="1" x:Name="PART_ItemsContainer"
IsItemsHost="True" />
<ContentPresenter x:Name="PART_QuickAccessToolbarHolder"
ContentSource="QuickAccessToolBar" Visibility="Collapsed" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsCollapsed"
Value="True">
<Setter Property="Visibility"
Value="Collapsed"
TargetName="PART_ItemsContainer" />
</Trigger>
<Trigger Property="HideContextTabs"
Value="True">
<Setter Property="Visibility"
Value="Collapsed"
TargetName="PART_ItemsContainer" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>

Is it possible to implicitly style a WPF ListView in GridView mode?

I can easily "implicitly" style a ListView in basic (non-GridView) mode but my attempts to implicitly style a ListView in GridView mode have failed miserably. The below works because I explicity set the Style and ItemContainerStyle of the second ListView. If you remove those two settings, the second ListView does not get implicitly styled liked the first one does. It seems that a basic ListView needs a ContentPresenter and a GridView ListView needs a GridViewRowPresenter.
Am I runnning into a WPF brick wall here? Is this even possible? If not, it makes creating an application skin less robust because now your users have to know to explicitly set the Style and ItemContainerStyle on ListViews that display in GridView mode.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style TargetType="ListView">
<Setter Property="Background" Value="Lime"/>
</Style>
<Style TargetType="ListViewItem">
<Setter Property="Background" Value="Yellow"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Grid>
<ContentPresenter x:Name="ContentHost" Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="{x:Static GridView.GridViewStyleKey}"
TargetType="{x:Type ListView}">
<Setter Property="Background" Value="Lime"/>
</Style>
<Style x:Key="{x:Static GridView.GridViewItemContainerStyleKey}"
TargetType="{x:Type ListViewItem}">
<Setter Property="Background" Value="Yellow"/>
</Style>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ListView x:Name="_listView1">
<system:String>Item 1</system:String>
<system:String>Item 2</system:String>
<system:String>Item 3</system:String>
</ListView>
<ListView x:Name="_listView2" Grid.Column="1"
Style="{StaticResource {x:Static GridView.GridViewStyleKey}}"
ItemContainerStyle="{StaticResource {x:Static GridView.GridViewItemContainerStyleKey}}">
<ListView.View>
<GridView>
<GridViewColumn Header="Date"/>
<GridViewColumn Header="Day of Week" DisplayMemberBinding="{Binding DayOfWeek}" />
<GridViewColumn Header="Year" DisplayMemberBinding="{Binding Year}" />
</GridView>
</ListView.View>
<system:DateTime>1/1/2010</system:DateTime>
<system:DateTime>1/1/2011</system:DateTime>
<system:DateTime>1/1/2012</system:DateTime>
</ListView>
</Grid>
</Window>
I ran into the same odd/tricky problem as you're describing above, and ran into a blog post that suggested a neat little hack/fix which seems to provide the behaviour you're after. The main points are repeated in case the link dies.
You've already described the odd requirements for ListView styling if you want to cover views with and that make use of GridViews; a basic ListView needs a ContentPresenter, a GridView ListView needs a GridViewRowPresenter.
The poster managed to get around this by including both presenters within his style, and using a Setter to show the ContentPresenter only when required.
So your ControlTemplate could implement something along these lines (with your extra styling properties added as required):
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<!-- Pair of presenters -->
<Grid>
<GridViewRowPresenter x:Name="gridrowPresenter"
Content="{TemplateBinding Property=ContentControl.Content}"/>
<ContentPresenter x:Name="contentPresenter"
Content="{TemplateBinding Property=ContentControl.Content}" Visibility="Collapsed"/>
</Grid>
<!-- Visibility Controlling Setter -->
<ControlTemplate.Triggers>
<Trigger Property="GridView.ColumnCollection" Value="{x:Null}">
<Setter TargetName="contentPresenter" Property="Visibility" Value="Visible"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
Both GridViewRowPresenter and ContentPresenter are present in the style, but the ContentPresenter is hidden (Visibility="Collapsed").
The neat trick is the use of a Trigger on GridView.ColumnCollection; if this value is null (which occurs when the GridViewRowPresenter has no content), the ContentPresenter will be made visible, correctly displaying your normal ListView content). The GridViewRowPresenter will have no content, so it won't display any conflicting visuals.
If the GridView has content, it will be displayed (providing the correct row formatting), and the ContentPresenter will remain hidden.
Original blog entry: http://www.steelyeyedview.com/2010/03/contentpresenter-gridviewrowpresenter.html

Use checkbox as togglebutton in expander

Im quite new to wpf and have to following problem.
I need to create a List (i am using a listbox) of items that can be expanded (expander).
The problem is, that they can be expanded, only if they have been 'selected'.
Each listboxitem should have a checkbox and some text.
So very basic example to illustrate what i mean:
<listbox>
<item>(checkbox) John Doe</item>
<item>(checkbox) Mike Murray</item>
</listbox>
If any (so multiple is allowed) of the checkboxes in the listbox are checked, then
the item expands showing more data.
Again an example:
<listbox>
<item>
(checkbox-checked) John Doe
Some extra data shown in expanded area
</item>
<item>
(checkbox-unchecked) Mike Murray</item>
</listbox>
I cant get a expander to use a checkbox as 'togglebutton'.
Could anyone help me out? Some example code would be very welcome...
This should do the trick:
<ListBox>
<ListBox.Resources>
<Style TargetType="Expander">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Expander">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<CheckBox
IsChecked="{Binding Path=IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
Content="{TemplateBinding Header}"
/>
<ContentControl
x:Name="body"
Grid.Row="1" Content="{TemplateBinding Content}"
/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="False">
<Setter TargetName="body" Property="Visibility" Value="Collapsed" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Resources>
<Expander Header="One">
Content one
</Expander>
<Expander Header="Two">
Content two
</Expander>
</ListBox>
I've defined a Style here that changes the Template of any Expander controls to which the Style is applied. (And since I've put the Style in the ListBox.Resources it'll automatically apply to an Expander controls in the list.)
The trick to getting the CheckBox to work is that when you put it (or indeed any ToggleButton based control) into an Expander template, you need to use a data binding configured with its RelativeSource set to the TemplatedParent. This enables two-way binding - it means that not only does the CheckBox reflect the current state of the expander, it is also able to change the current state.
All you need to add a check box in the header is this code:
<telerik:RadExpander.Header>
<StackPanel Orientation="Horizontal">
<CheckBox VerticalAlignment="Center"/>
<TextBlock Margin="5,0,0,0">Legend</TextBlock>
</StackPanel>
</telerik:RadExpander.Header>
I am using Rad Control, The same can be done using the standard expander

WPF: How to make a Expander overflow and fill window

I'm trying to create a expander that has a togglebutton/header as a slim bar to the left but when expanded fills over the rest of the window, even over material that's already there.
I'm not really sure what the best way to do it is. I thought perhaps of a grid with 2 columns. First would have the expander, second the other material. Next I would have a trigger that would set the second column width to zero when the Expander IsExpanded.
I'm not really sure how to get that to work or even how to do it properly.
Here is some code example:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Name="SecondColumn" Width="*" />
</Grid.ColumnDefinitions>
<Expander ExpandDirection="Right" IsExpanded="True">
<Expander.Resources>
<Style TargetType="Expander">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Expander" >
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="True" >
<Setter TargetName="SecondColumn" Property="ColumnDefinition.Width" Value="0" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Expander.Resources>
<ListBox >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Expander>
<TabControl Grid.Column="1" />
</Grid>
I wan't the listbox to be seen when expanded, otherwise the TabControl
Any ideas?
It sounds like you're wanting to do something similar to Karl Shifflett's example here. He's just modifying the z-index of the content control in this case and setting the row height manually to give the illusion of a popup, so you'd want to make sure you're not trying to ZIndex other visual elements similarly.
You will want to make sure you're setting ColumnSpan and RowSpan on your Expander so that when it does expand it covers the content of those rows.

Resources