Stretching the items of an ItemsControl - wpf

I'm bulding a small WPF app over here. It's all built strictly with MVVM, using nothing but DataTemplates linked to view model types.
I've seen alot of questions about how to stretch and clip the contents of ListBoxes so that the items fill its parent. After alot of experimenting I managed to get my head around that but now I find myself in the same scenario with the ItemsControl but the same tricks doesn't seem to work.
Here's one of the DataTemplates in use (a simple TextBox). Note how I tried setting the HorizontalAlignment ...
<DataTemplate DataType="{x:Type vm:OneOfMyViewModelTypes}">
<TextBox
Text="{Binding Path=Value}"
HorizontalAlignment="Stretch"
/>
</DataTemplate>
Here's the ItemsControl inside a Grid ...
<Grid Background="Gray">
<Grid.Margin>
<Thickness
Left="{StaticResource ConfigurationDefaultMargin}"
Right="{StaticResource ConfigurationDefaultMargin}"
Bottom="{StaticResource ConfigurationDefaultMargin}"
Top="{StaticResource ConfigurationDefaultMargin}"
/>
</Grid.Margin>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="_key" Width="Auto"/>
<ColumnDefinition SharedSizeGroup="_value" Width="*"/>
</Grid.ColumnDefinitions>
<ItemsControl
Background="DimGray"
Grid.IsSharedSizeScope="True"
ItemsSource="{Binding Path=Configuration, Mode=OneWay}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="_key"/>
<ColumnDefinition SharedSizeGroup="_value"/>
</Grid.ColumnDefinitions>
<TextBlock
Style="{StaticResource ExtensionConfigurationLabel}"
Grid.Column="0"
Margin="5,5,5,0"
Text="{Binding Path=Caption}"
/>
<ContentPresenter
Grid.Column="1"
HorizontalAlignment="Stretch"
Margin="5,5,5,0"
Content="{Binding}"
/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
I've used colors to see how the controls sizes. The Grid is gray and the ItemsControl is dark gray.
This is the result ...
As you can see from the colors the containing Grid stretches while the ItemsControl does not. I did set its HorizontalAlignment property to Stretch but it seems it has no effect. Is there anything else I need do?
Thanks

You have two columns in your main (outer) grid, yet you use only the first column.
The second column uses all the remaining space.

Related

Sharing column width between grids defined in a template

I am trying to layout a couple of controls:
The light gray vertical lines are grid splitters. Each line in this table is a grid with three columns, the label on the left, the splitter in the middle and the control on the right, including the text boxes and the image.
I want all columns to be aligned. Here is the data template I use for each line:
<DataTemplate x:Key="PropertyLineTemplate">
<Grid Margin="0,0,0,1">
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="Name"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition SharedSizeGroup="Value"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
VerticalAlignment="Center"
Text="{Binding Title}" />
<GridSplitter Grid.Column="1" Width="2"/>
<SomeUserControl
Grid.Column="2"
Content="{Binding}"
Margin="4, 0, 0, 0"/>
</Grid>
</DataTemplate>
and the template for the view itself:
<DataTemplate DataType="{x:Type vm:PropertiesViewModel}">
<DockPanel>
<!-- Properties -->
<ScrollViewer>
<StackPanel Margin="10">
<ItemsControl ItemsSource="{Binding Properties}"
ItemTemplate="{StaticResource PropertyLineTemplate}" />
</StackPanel>
</ScrollViewer>
</DockPanel>
</DataTemplate>
and the usage of both:
<ContentControl Content="{Binding SelectedItem}" Grid.IsSharedSizeScope="True">
</ContentControl>
where SelectedItem is of type PropertiesViewModel.
I also tried to place Grid.IsSharedSizeScope="True" in each individual grid directly and on the panels containing the control, without effects. How can I achieve that all columns share the width?
Grid.IsSharedSizeScope should be set on the first parent ItemsControl of a Grid, in your case it would be the ItemsControl in the ViewModel template. Then the columns width will be adjusted.
However, if you move one GridSplitter, then it will only adjust that row, not the others (See WPF SharedSizeGroup GridSplitter Issue for a potential solution, if you need resizing.)
Other stuff: you don't need the Stackpanel wrapper around the ItemsControl, since the ItemsControl is the only child. If you need to adjust the panel the ItemsControl uses, then set it on the Property ItemsPanel, like this:
<ItemsControl ItemsSource="{Binding Properties}"
Grid.IsSharedSizeScope="True"
ItemTemplate="{StaticResource PropertyLineTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/> <!-- Or <WrapPanel/> or <UniformGrid/> etc. -->
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
However, the Stackpanel is the default panel of the ItemsControl.
Based on Nautious' answer I've solved the problem by defining two ColumnDefinitions as resources and binding the Width property of all columns in question to the Width property of these 'master column definitions':
<ColumnDefinition x:Key="MasterColumnDefinition" />
<ColumnDefinition x:Key="MasterColumnDefinition2" />
<DataTemplate x:Key="PropertyLineTemplate">
<Grid Margin="0,0,0,1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding Source={StaticResource MasterColumnDefinition}, Path=Width, Mode=TwoWay}"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="{Binding Source={StaticResource MasterColumnDefinition2}, Path=Width, Mode=TwoWay}"/>
</Grid.ColumnDefinitions>
...
</Grid>
</DataTemplate>
Might be a little hacky, but it's simple and works. Not, however, that the shared size scope of this solution is the scope in which the resources are defined. I.e. placing these template in the app's resources will lead to all columns in the application being resized synchronously.

Itemscontrol sharing grid between controltemplate and itemtemplate

I have an ItemsControl which has lots of columns and a static header row.
I'm aware of HeaderedItemsControl but I'm not sure it can achieve what I want.
What I currently have is the ControlTemplate serving as the container for the header and the ItemsPresenter, then the ItemTemplate is used to organise the records.
At the moment I've got a duplicate grid in both the content and item templates, and I want to find an elegant solution where I can share the same grid between both templates so I don't have to change two things when editing it around. Mock up of what it look like below, the actual control has many more columns. I've read about IsSharedSizeScope and SharedSizeGroup but I gather this wouldn't work because it only functions when there are two grids sharing a parent element (they would both need to be inside the ControlTemplate)
<ItemsControl ItemsSource="{Binding SomeSource}">
<ItemsControl.Template>
<ControlTemplate TargetType="ItemsControl">
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="Col1" />
<TextBlock Grid.Column="1" Text="Col2" />
<TextBlock Grid.Column="2" Text="Col3" />
</Grid>
<ItemsPresenter/>
</StackPanel>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="ItemCol1" />
<TextBlock Grid.Column="1" Text="ItemCol2" />
<TextBlock Grid.Column="2" Text="ItemCol3" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I've probably done something that is bad practice as well so feel free to give me pointers.
I have a feeling you set the value of ItemsSource completely wrong.
It should look like this:
<ItemsControl ItemsSource="{Binding SomeSource}">
When I take your example and run it all works fine to me.
If you wish to share column sizes then yes you could use the SharedSizeGroup. You will have to set the scope to be the ItemsControl.
You got the wrong definition of SharedSizeGroup. It works no matter where the Grids are placed as long they share same one of their parents. Which is the case for you since ItemsControl is the top most control so it is the parent to every control inside.

Which ItemControl should I choose and Why?

I have an arbitrary count of ViewModels to display and I want that ViewModels to be displayed in two columns. The first column mostly should display a name/description and the content of the second column varies. I would prefer a Grid-like layout with a Width = "auto" for the first column and Width = "*"for the second column.
I tried a ListView with a GridView but that has some flaws.
I can select the ListViewItems(not necessary)
GridViewColumns have headers(wasted space/ not necessary)
GridViewColumns don’t support Width = "*" like a Grid and therefore doesn’t use all the available space
I could use a Grid, but than I would have to add Grid.RowDefinitions for every ViewModel and wouldn’t be possible to set the Grid.Row Property via Binding.
I also thought about using a StackPanel, but that doesn’t support Templating and I don’t know how to sync the Width for the first column.
So, does anybody know which ItemControl is best suited for this purpose?
Without testing it I would expect an ItemsControl with a data template to be a suitable solution.
You can use Grid.IsSharedSizeScope="true" on a parent element, in this case the ItemsControl, and then synchronize the width of the first column using SharedSizeGroup="somename".
<Grid>
<Grid.Resources>
<DataTemplate x:Key="itemTemplate" DataType="{x:Type MyItemType}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="column1" Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Title}"/>
<!-- More stuff here -->
</Grid>
</DataTemplate>
</Grid.Resources>
<ItemsControl Grid.IsSharedSizeScope="True" ItemTemplate="{StaticResource itemTemplate}" />
</Grid>
I would use a ListBox with a Grid as template like this:
<ListBox ItemsSource="{Binding SomeData}" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Data1}" Grid.Column="0"></TextBlock>
<TextBlock Text="{Binding Data1}" Grid.Column="1"></TextBlock>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Itemscontrol stretch silverlight

Inside my itemsControl the item are not occupying the whole width of the user control. Am using DataTemplateSelector (manually written class) for selecting the type of template.
I checked the post Silverlight: Set Items Widths in ItemsControl to Stretch but its not working for me. Items are not automatically stretching and utilizing the full space. Please help! Thanks in advance
<ItemsControl ItemsSource ="{Binding}" Margin="0,5,0,0" HorizontalContentAlignment="Stretch" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:AddressFieldsTemplateSelector Content="{Binding}" x:Name="addressTemplateSelectorObject">
<!-- TextBox template-->
<local:AddressFieldsTemplateSelector.TextBoxDataTemplate>
<DataTemplate>
<Grid Margin="0,0,0,5" HorizontalAlignment="Stretch" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.5*" />
<ColumnDefinition Width=".1*" />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding AddressFieldName}" Grid.Column="0" Style="{StaticResource DefaultTheme_TextBlockStyle}"/>
<TextBox Text="{Binding AddressFieldValue, Mode=TwoWay}" Grid.Column="2" Style="{StaticResource TextBoxStyle}" TextWrapping="NoWrap" MaxLength="50" HorizontalAlignment="Stretch" />
</Grid>
</DataTemplate>
</local:AddressFieldsTemplateSelector.TextBoxDataTemplate>
</local:AddressFieldsTemplateSelector>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Items inside your datatemplate will always take up all available width due to the star width. The problem is most likely with the itemscontrol itself. Are you sure that the itemscontrol is not contained in a StackPanel, or auto-width Grid column. Is the HorizontalAlignment of the ItemsControl set to Left ?

Column Header for a WPF TreeView

I am using the WPF TreeView to display some hierarchical information. Each item in the TreeView consists of several attributes, so I am using a Grid within my HierarchicalDataTemplate to display these attributes:
<HierarchicalDataTemplate x:Key="ArtistTemplate"
ItemsSource="{Binding XPath=Title}"
ItemTemplate="{StaticResource TitleTemplate}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="NameColumn" />
<ColumnDefinition SharedSizeGroup="GenreColumn" />
<ColumnDefinition SharedSizeGroup="BornColumn" />
<ColumnDefinition SharedSizeGroup="DiedColumn" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding XPath=#Name}" />
<TextBlock Grid.Column="1" Text="{Binding XPath=#Genre}" />
<TextBlock Grid.Column="2" Text="{Binding XPath=#Born}" />
<TextBlock Grid.Column="3" Text="{Binding XPath=#Died}" />
</Grid>
</HierarchicalDataTemplate>
This displays as a nice TreeView with 4 columns - so far so good! The only additional thing I need is a header above the TreeView that displays column names. The header column widths should be synchronized with TreeViewItems and also the header styles should be customizable. What's the easiest way to do this?
P.S. I found two solutions that came close:
1) A TreeListView here, but this requires me to implement a custom interface (ITreeModel) to my model. Also the approach in this solution is to start with a ListView and to implement a RowExpander manually. In my case, the TreeView is sufficiently close to what I need, so I am hoping that putting a header on it should be very simple.
2) A TreeListView here. This one indeed starts with a TreeView, but I can't figure out how to customize the header. I suspect that I have to customize the GridViewHeaderRowPresenter in the generic.xaml, but this element does not seem to have its own ControlTemplate.
You can try to reuse your SharedSizeGroup definitions.
For example put another grid above your TreeView for your header and place them inside a stackpanel like:
<StackPanel Grid.IsSharedSizeScope="True" Orientation="Vertical">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="NameColumn" />
<ColumnDefinition SharedSizeGroup="GenreColumn" />
<ColumnDefinition SharedSizeGroup="BornColumn" />
<ColumnDefinition SharedSizeGroup="DiedColumn" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="Name"/>
<TextBlock Grid.Column="1" Text="Genre"/>
<TextBlock Grid.Column="2" Text="Born"/>
<TextBlock Grid.Column="3" Text="Dies"/>
</Grid>
<TreeView>
...
</TreeView>
</StackPanel>
The Grid.IsSharedSizeScope=True will reuse your SharedSizeGroups for your header and tree.
You can also style your Header separately.

Resources