Bind column width of grid from two itemtemplates - wpf

In one XAML window, I have two seperate ItemsControls for different ViewModels. The ItemsControls both have grids where the first columns should be the same width. Right now I have the following, but the grids just manage their size independently. I want the first columns to be the same width. Here is my XAML
<TabControl>
<TabItem Header="x">
<Grid Grid.IsSharedSizeScope="True">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid Margin="10" Grid.Row="0" Grid.IsSharedSizeScope="True">
<ItemsControl Name="inputs1" Grid.IsSharedSizeScope="True">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" SharedSizeGroup="1" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Text="{Binding Description}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
<Grid Margin="10" Grid.Row="1" Grid.IsSharedSizeScope="True">
<ItemsControl Name="inputs2" Grid.IsSharedSizeScope="True">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,0,0,5">
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="1" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Text="{Binding Description}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Grid>
</TabItem>
</TabControl>
They have the same sharedsizegroup so they should be equal right?
Result now: https://gyazo.com/2284485127427673269dfd8e26e42682

Remove all Grid.IsSharedSizeScope="True" properties in the lower hierarchy levels and only keep it in the control that spans all your involved grids. It seems that for each of these properties, a new shared scope is opened, so higher level scopes are ignored (though I couldn't really read that from documentation).

Related

Sharing column widths between multiple grids

I have the following XAML:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="Header 1" />
<TextBlock Grid.Column="1" Text="Header 2" />
</Grid>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding SomeList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding Value1}" />
<TextBox Grid.Column="1" Text="{Binding Value2}" />
<Button Grid.Column="2" Content="Button" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
and I want to achieve something like that:
Header 1 |Header 2 | <-- this is the top grid
------------------------------------------------------
Value 1.1 |Value 1.2 |[Button] |
Value 2.1 |Value 2.2 |[Button] <-- | this is the scroll viewer
Value 3.1 |Value 3.2 |[Button] |
which looks similar to a data grid with the fixed header row. I can't use an actual DataGrid because in the real app "Header 1" and "Header 2" are actually not simple grid text headers but rather interactable elements with quite complex markup. The main question is how to achieve the width synchronization for all three columns? The last column should size itself to the widest button in that column while the first two columns should share the remaining space in 50/50 proportion. It feels like this can be achieved using the SharedSizeGroup property but I cannot figure out how exactly.
I think I figured out how to do it:
<Grid Grid.IsSharedSizeScope="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid Grid.Row="0" Width="{Binding ActualWidth, ElementName=itemsControl}"
HorizontalAlignment="Left">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" SharedSizeGroup="ButtonColumn" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="Header 1" />
<TextBlock Grid.Column="1" Text="Header 2" />
</Grid>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
<ItemsControl Name="itemsControl" ItemsSource="{Binding SomeList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" SharedSizeGroup="ButtonColumn" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding Value1}" />
<TextBox Grid.Column="1" Text="{Binding Value2}" />
<Button Grid.Column="2" Content="Button" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
Grid.IsSharedSizeScope="True" and SharedSizeGroup="ButtonColumn" synchronize the last columns.
Width="*" splits the remaining space between first two columns in 50/50 proportion.
Width="{Binding ActualWidth, ElementName=itemsControl}" synchronizes the width of the top header grid with the width of the ItemsControl below which can change when the scroll bar is shown or hidden.
HorizontalAlignment="Left" pushes the header grid to the left to align it with the ItemsControl below.

How to use only 1 grid layout for both header and detail with itemscontrol?

Now, when i want to create a table with a header and detail i have to define a columns 2 times for each row. the first row is header and the second row i used itemscontrol to binding data.
below is my sample code.
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource WidthColumn}" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="40" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding ListTitle}"/>
<TextBlock Grid.Column="1" Text="{Binding ListDetailTitle}" />
<TextBlock Grid.Column="2" Text="{Binding ListValueTitle}" />
</Grid>
<StackPanel Grid.Row="1">
<ItemsControl ItemsSource ="{Binding Samples}">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type parameters:SampleParameters}">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource WidthColumn}" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="40" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Position}"/>
<TextBox Grid.Column="1" DataContext="{Binding Name}" Text="{Binding Value}"/>
<Button Grid.Column="2" Name="Button"/>
</Grid>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
</StackPanel>
</Grid>
Can it use only 1 time grid layout? or there is another solution to do with?

How to add new elements to the base Grid from an inherited DataTemplate?

I have two objects: Bicycle and BicycleFullSuspension, which inherits Bicycle and contains additional properties (strings and decimals). I managed to make a functioning DataTemplateSelector that will choose either my LbxItemTemplateBicycle DataTemplate or my LbxItemTemplateFullSuspension DataTemplate, depending on to which class the object in an ObservableCollection belongs. Both of these templates are bound to the parent template ListBoxItemTemplate.
I am trying to display information about each object in a ListBox, and each of the inheriting templates should add a few more fields to the grid in the parent DataTemplate. My problem is that I cannot figure out how to add WPF elements to the grid in ListBoxItemTemplate from one of the inherited templates. I cannot assign a key to the grid in the template, so I am not sure how to specify that additional TextBlocks should end up in the same grid that is in the parent template. (Right now, additional TextBlocks are stacked on top of the parent grid.)
<DataTemplate x:Key="ListBoxItemTemplate">
<Border Name="LbxItemTemplate" BorderBrush="DarkRed" BorderThickness="2" Padding="5" Margin="5">
<Grid>
<Grid.RowDefinitions>
...
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
...
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Bike Year:" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding BikeYear}" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Bike Color: "/>
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding BikeColor}"/>
...
</Grid>
</Border>
</DataTemplate>
<DataTemplate x:Key="LbxItemTemplateFullSuspension" DataType="{x:Type app:BicycleFullSuspension}">
<Grid>
<ContentPresenter Content="{Binding}" ContentTemplate="{StaticResource ListBoxItemTemplate}" />
<TextBlock Grid.Row="6" Grid.Column="0" Text="Shock Travel"/>
<TextBlock Grid.Row="6" Grid.Column="1" Text="{Binding ShockTravel}"/>
</Grid>
</DataTemplate>
I found these links helpful for getting to this point:
http://dariosantarelli.wordpress.com/2011/07/28/wpf-inheritance-and-datatemplates/
http://zamjad.wordpress.com/2009/12/31/applying-data-template-conditionally/
Is there a way to use data-template inheritance in WPF?
Edit:
I'm not sure why I didn't think to put the Border on the template inheriting the base, but by nesting a StackPanel inside of the Border in the inheriting template (StackPanel contains the ContentPresenter with the base template content along with the Grid that has the added information), everything lines up very nicely:
Working solution, using input from XAMeLi:
<DataTemplate x:Key="ListBoxItemTemplate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
...
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" SharedSizeGroup="LabelColumnGroup" />
<ColumnDefinition Width="150" SharedSizeGroup="LabelColumnGroup" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Bike Year:" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding BikeYear}" />
...
</Grid>
</DataTemplate>
<DataTemplate x:Key="LbxItemTemplateFullSuspension" DataType="{x:Type app:BicycleFullSuspension}" >
<Border BorderBrush="DarkRed" BorderThickness="2" Padding="5" Margin="5" Grid.IsSharedSizeScope="True" Width="500">
<StackPanel>
<ContentPresenter Content="{Binding}" ContentTemplate="{StaticResource ListBoxItemTemplate}" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
...
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="LabelColumnGroup" />
<ColumnDefinition Width="Auto" SharedSizeGroup="LabelColumnGroup" />
</Grid.ColumnDefinitions>
...
<TextBlock Grid.Row="7" Grid.Column="0" Text="Shock Type: "/>
<TextBlock Grid.Row="7" Grid.Column="1" Text="{Binding ShockType}"/>
...
</Grid>
</StackPanel>
</Border>
</DataTemplate>
You cannot add UIElemets to a data template, but you can layout the bigger template to look like it has added text to the base template.
Basically, you are on the right path, only the layout of the big template is corrupt. (Too bad you omitted the structure of your rows and columns)
Try this:
<DataTemplate x:Key="ListBoxItemTemplate">
<Border Name="LbxItemTemplate" BorderBrush="DarkRed" BorderThickness="2" Padding="5" Margin="5">
<Grid>
<Grid.RowDefinitions>
...
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"
SharedSizeGroup="LabelGroupColumn"/>
...
</Grid.ColumnDefinitions>
....
</Border>
</DataTemplate>
<DataTemplate x:Key="LbxItemTemplateFullSuspension">
<Grid Grid.IsSharedSizeScope="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/><!--This will hold the default template-->
<!--From here define the same structure as in the default template, if you have rows for margins and such-->
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"
SharedSizeGroup="LabelGroupColumn"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ContentPresenter Content="{Binding}" ContentTemplate="{StaticResource ListBoxItemTemplate}" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Shock Travel"/>
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding ShockTravel}"/>
</Grid>
</DataTemplate>
It seems that your grids have Label-Value structures and you want all the values to start from the same vertical line (this is very good from UX point of view). Notice the SharedSizeGroup setters on column definitions and the Grid.IsSharedSizeScope="True" on a mutual parent. This will keep the width of the label column to be synchronized among the grids.

Telerik Silverlight RadPanelBar Hierarchical Data Template

I need to display the following layout with a telerik PanelBar.
With the code below I was able to achieve everything except the 92% stuff in each panel.
XAML:
<UserControl.Resources>
<DataTemplate x:Key="PanelBarItemTemplate">
<Grid x:Name="grdCategory" ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition Height="30"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60*"></ColumnDefinition>
<ColumnDefinition Width="40*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid x:Name="grdSubCategory" Grid.Column="0" Style="{StaticResource CategoryLeftStyle}" >
<Grid.RowDefinitions>
<RowDefinition Height="20"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50*"></ColumnDefinition>
<ColumnDefinition Width="25*"></ColumnDefinition>
<ColumnDefinition Width="25*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding CategoryTitle}" Grid.Row="0" Grid.Column="0"/>
<HyperlinkButton Grid.Row="0" Grid.Column="1" Style="{StaticResource DetailLinkStyle}" Content="Details" Click="Home_Click"></HyperlinkButton>
<TextBlock Text="{Binding Score}" Grid.Row="0" Grid.Column="2"/>
</Grid>
<TextBlock Text="92%" Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" FontSize="32" FontWeight="Bold"/>
</Grid>
</DataTemplate>
<telerik:HierarchicalDataTemplate x:Key="PanelBarHeaderTemplate"
ItemsSource="{Binding SubReports}"
ItemTemplate="{StaticResource PanelBarItemTemplate}">
<TextBlock Text="{Binding CategoryTitle}" />
</telerik:HierarchicalDataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<telerik:RadPanelBar x:Name="radPanelBar"
ItemTemplate="{StaticResource PanelBarHeaderTemplate}"
IsSingleExpandPath="False" >
</telerik:RadPanelBar>
</Grid>
in the xaml.cs file I provided the ItemsSource.
Can somebody help me out?
All that code works perfectly well for the individual items, but to place the 92% relative to those items (somewhat outside of the sub-items) you would need to also modify the ItemContainerStyle of RadPanelBar. Easiest way is to extract it in Blend, then look for the section under PanelBarItemTopLevelTemplate named ItemsContainer. This is a somewhat crude version, but I made a public property on my item called CalcInt that calculates the sum of a property on the SubReport items so it can be bound from the base item level. My modified code looks like so:
<Grid x:Name="ItemsContainer" Grid.Row="1" Visibility="Collapsed">
<telerik:LayoutTransformControl x:Name="transformationRoot">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ItemsPresenter/>
<TextBlock Text="{Binding CalcInt}" FontSize="48" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</telerik:LayoutTransformControl>
</Grid>
I basically changed it from just containing an ItemsPresenter to a grid with some layout to display my extra-large TextBlock.
If you need a sample of the code or have any other questions feel free to hit me up on Twitter - #EvanHutnick.
Cheers!
-Evan
Hi I think you need to define a converter : IValueConverter and bind to the Label so you can calculate the percentual.
Mario

List items into a Grid (not GridView)

In my WPF application, I have a collection to which I would like to bind a grid of controls. An example of the desired layout (TL;DR I want this from an ItemsSource):
Desired layout http://imagebin.ca/img/27UTke
<Grid ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0">FileUploader</Label>
<ComboBox Grid.Row="0" Grid.Column="1" />
<Button Grid.Row="0" Grid.Column="2">Edit</Button>
<Label Grid.Row="1" Grid.Column="0">TextUploader</Label>
<ComboBox Grid.Row="1" Grid.Column="1" />
<Button Grid.Row="1" Grid.Column="2">Edit</Button>
<Label Grid.Row="2" Grid.Column="0">UrlShortener</Label>
<ComboBox Grid.Row="2" Grid.Column="1" />
<Button Grid.Row="2" Grid.Column="2">Edit</Button>
<Label Grid.Row="3" Grid.Column="0">ImageUploader</Label>
<ComboBox Grid.Row="3" Grid.Column="1" />
<Button Grid.Row="3" Grid.Column="2">Edit</Button>
</Grid>
This example is has hardcoded values; I want to bind the collection dynamically, like using a ListView. However, using a ListView with a GridView isn't what I want; I don't need selection, headers, sorting, etc. To the user, the layout is a neatly organized set of options, not a data set in a grid.
I haven't been able to do this binding properly. I've tried using an ItemsControl with a Grid on the outside, but the Grid is ignored:
Failed attempt 1 http://imagebin.ca/img/kTUordk
<Grid ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ItemsControl ItemsSource="{Binding MyData}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl>
<Label Grid.Column="0">Text here</Label>
<ComboBox Grid.Column="1" />
<Button Grid.Column="2">Edit</Button>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
I've also used an ItemsControl with a Grid in the panel, but this creates one Grid per item, which is ugly:
Failed attempt 2 http://imagebin.ca/img/xHo__-JD
<ItemsControl ItemsSource="{Binding Data}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<Label Grid.Column="0" Content="{Binding Features}" />
<ComboBox Grid.Column="1" />
<Button Grid.Column="2">Edit</Button>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
After some research, I discovered the SharedSizeGroup property of column and row grid definitions. I have tried this with the previous attempt; however, the middle column (with width *) does not seem to resize itself to fill the full width of the window:
Failed attempt 3 http://imagebin.ca/img/nEoMJmy
<ItemsControl ItemsSource="{Binding Data}" Grid.IsSharedSizeScope="True">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="A" />
<ColumnDefinition Width="*" SharedSizeGroup="B" />
<ColumnDefinition Width="Auto" SharedSizeGroup="C" />
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<Label Grid.Column="0" Content="{Binding Features}" />
<ComboBox Grid.Column="1" />
<Button Grid.Column="2">Edit</Button>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
What is the best way to accomplish the first layout?
John Bowen wrote a blog entry about a pitfall of using * with SharedSizeGroups. Using his suggested workaround, I was able to get the desired layout:
<Grid Name="grid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="A" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" SharedSizeGroup="C" />
</Grid.ColumnDefinitions>
</Grid>
<ItemsControl ItemsSource="{Binding Data}" Grid.IsSharedSizeScope="True">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="A" />
<ColumnDefinition Width="{Binding ColumnDefinitions[1].Width, ElementName=grid}" />
<ColumnDefinition Width="Auto" SharedSizeGroup="C" />
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<Label Grid.Column="0" Content="{Binding Features}" />
<ComboBox Grid.Column="1" />
<Button Grid.Column="2">Edit</Button>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Sadly, this seems like a hack; I'm sure there's a better solution out there for accomplishing what I want.

Resources