Extracting data templates in user controls - c#

There are known problems for setting up caliburn binding in DataTemplates.
In this answer EisenbergEffect suggests extracting Data Templates into a user control.
How can this be achieved?
In my user control I have lots of DataTemplates and ran into those problems. Conventions are not applied and I have to use "classical" Binding.
I could only imagine to extract the whole control with the DataTemplate, this would give me lots of smaller controls, but I see no way to only extract the DataTemplate.
Here is an example XAML
<Grid>
<Grid.Resources />
<Grid.RowDefinitions>
<RowDefinition Height="10*" />
<RowDefinition Height="2*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Border HorizontalAlignment="Stretch"
BorderBrush="Transparent"
BorderThickness="0">
<ScrollViewer HorizontalContentAlignment="Stretch"
Background="Yellow"
BorderBrush="Transparent"
BorderThickness="0"
CanContentScroll="True"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<!-- Conventions work here -->
<ListView x:Name="Computers"
HorizontalContentAlignment="Stretch"
Background="Red"
BorderThickness="0">
<ListView.ItemTemplate>
<DataTemplate>
<Border Background="Transparent"
BorderBrush="Transparent"
BorderThickness="0">
<ListView HorizontalContentAlignment="Stretch"
Background="Black"
ItemsSource="{Binding HardwareComponents}"> <!-- Conventions to not work here -->
<ListView.ItemTemplate>
<DataTemplate>
<Border Background="Aquamarine"
BorderBrush="DarkGray"
BorderThickness="1">
<Grid Background="Transparent" cal:Message.Attach="[Event MouseDown] = [Action Expand($dataContext)]" >
<Grid.RowDefinitions>
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<Border BorderBrush="Red" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<!-- Even more DataTemplates come here -->
</Border>
</Grid>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ScrollViewer>
</Border>
</Grid>

Related

Set height of element to height of neighbouring element

I have a GUI in WPF application which looks like that:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Vertical">
<Border Background="Beige" BorderThickness="1" BorderBrush="Black" Width="100" Height="50" Margin="2"></Border>
<Border Background="Beige" BorderThickness="1" BorderBrush="Black" Width="100" Height="90" Margin="2"></Border>
</StackPanel>
<Border Background="Beige" BorderThickness="1" BorderBrush="Black" Margin="2">
<StackPanel>
<StackPanel Orientation="Horizontal">
<Button Content="Some"/>
<Button Content="Buttons"/>
</StackPanel>
<Label Content="Some Label"/>
<ListView ScrollViewer.VerticalScrollBarVisibility="Auto">
<ListViewItem>Item1</ListViewItem>
<ListViewItem>Item2</ListViewItem>
<!-- and so on...-->
<ListViewItem>Item8</ListViewItem>
</ListView>
</StackPanel>
</Border>
</StackPanel>
</Grid>
The two borders to the left are just placeholders for the GUI elements in the real application. The GUI element looks like this:
I want the right StackPanel to be just as high as the left StackPanel, like in the following screenshot:
For the screenshot, I was manually setting the MaxHeight property of the ListView to right value, but of course that is no satisfying solution and it will become impossible if I don't know at compile time which and how many elements populate the left StackPanel.
Is there any solution to this Problem? I was trying a binding:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Vertical" Name="Left">
<Border Background="Beige" BorderThickness="1" BorderBrush="Black" Width="100" Height="50" Margin="2"></Border>
<Border Background="Beige" BorderThickness="1" BorderBrush="Black" Width="100" Height="90" Margin="2"></Border>
</StackPanel>
<Border Background="Beige" BorderThickness="1" BorderBrush="Black" Margin="2" Height="{Binding ElementName=Left, Path=Height}">
<!-- as above -->
</Border>
</StackPanel>
</Grid>
But I did not succeed with it, also when I bind to the ActualHeight of the left StackPanel. Any suggestions?
Stack panels expand to fit their contents. Instead you need to put the ListView in a container of a fixed size (I've used a Grid with a row of height *), that takes its size from the boxes in the first column.
I used a binding to ActualHeight, that was very similar to yours.
The other important difference was setting VerticalAlignment="Top" on the first stack panel, otherwise it automatically sizes to the same height as the border.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Vertical" Name="Left" VerticalAlignment="Top">
<Border Background="Beige" BorderThickness="1" BorderBrush="Black" Width="100" Height="50" Margin="2"></Border>
<Border Background="Beige" BorderThickness="1" BorderBrush="Black" Width="100" Height="90" Margin="2"></Border>
</StackPanel>
<Border Height="{Binding ElementName=Left, Path=ActualHeight}" >
<Border Background="Beige" BorderThickness="1" BorderBrush="Black" Margin="2">
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<Button Content="Some"/>
<Button Content="Buttons"/>
</StackPanel>
<Label Grid.Row="1" Content="Some Label"/>
<ListView Grid.Row="2" ScrollViewer.VerticalScrollBarVisibility="Auto">
<ListViewItem>Item1</ListViewItem>
<ListViewItem>Item2</ListViewItem>
<ListViewItem>Item2</ListViewItem>
<ListViewItem>Item2</ListViewItem>
<ListViewItem>Item2</ListViewItem>
<ListViewItem>Item2</ListViewItem>
<!-- and so on...-->
<ListViewItem>Item8</ListViewItem>
</ListView>
</Grid>
</Border>
</Border>
</StackPanel>
</Grid>
<StackPanel>
<Grid Width="300" Height="200">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="3*"/>
</Grid.RowDefinitions>
<Border Background="Beige" BorderThickness="1" BorderBrush="Black" Margin="2"></Border>
<Border Grid.Row="1" Background="Beige" BorderThickness="1" BorderBrush="Black" Margin="2"></Border>
</Grid>
<Border Grid.Column="1" Background="Beige" BorderThickness="1" BorderBrush="Black" Margin="2">
<StackPanel>
<StackPanel Orientation="Horizontal">
<Button Content="Some"/>
<Button Content="Buttons"/>
</StackPanel>
<Label Content="Some Label"/>
<ListView ScrollViewer.VerticalScrollBarVisibility="Auto">
<ListViewItem>Item1</ListViewItem>
<ListViewItem>Item2</ListViewItem>
<!-- and so on...-->
<ListViewItem>Item8</ListViewItem>
</ListView>
</StackPanel>
</Border>
</Grid>
</StackPanel>
Try this:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel
Name="FirstPanel"
Grid.Column="0"
VerticalAlignment="Top">
<Border
Background="Beige"
BorderThickness="1"
BorderBrush="Black"
Width="100"
Height="50"
Margin="2"/>
<Border
Background="Beige"
BorderThickness="1"
BorderBrush="Black"
Width="100"
Height="90"
Margin="2"/>
</StackPanel>
<Border
Name="ContainerBorder"
Grid.Column="1"
Background="Beige"
BorderThickness="1"
BorderBrush="Black"
Margin="2"
VerticalAlignment="Top"
MaxHeight="{Binding ElementName=FirstPanel, Path=ActualHeight}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<Button Content="Some"/>
<Button Content="Buttons"/>
</StackPanel>
<Label Grid.Row="1" Content="Some Label"/>
<ListView Grid.Row="2" ScrollViewer.VerticalScrollBarVisibility="Auto">
<ListViewItem>Item1</ListViewItem>
<ListViewItem>Item2</ListViewItem>
<ListViewItem>Item3</ListViewItem>
<ListViewItem>Item4</ListViewItem>
<ListViewItem>Item5</ListViewItem>
<ListViewItem>Item6</ListViewItem>
<ListViewItem>Item7</ListViewItem>
<ListViewItem>Item8</ListViewItem>
</ListView>
</Grid>
</Border>
</Grid>
Output:

Why UserControl isn't filling VerticalContentAlignment 'Stretch'?

Here is the parent Xaml:
<Grid VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="*" /><!-- Should stretch vertically -->
</Grid.RowDefinitions>
<DockPanel Grid.Row="0">
<!-- Menu definition removed for brevity -->
</DockPanel>
<DockPanel Grid.Row="1"
VerticalAlignment="Stretch"
LastChildFill="True">
<ItemsControl DockPanel.Dock="Top"
ItemsSource="{Binding Designer}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalAlignment="Stretch"
VerticalContentAlignment="Stretch"
Background="YellowGreen"/>
</DockPanel>
</Grid>
The ItemsSource binding is to an ObservableCollection. When the view is added to the collection, it gets updated in the main shell (view). Here is the UserControl that is added:
<UserControl x:Class="Prototype.StateMachineDesignerApp.Views.ProjectDesigner"
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:Prototype.StateMachineDesignerApp.Views"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="500"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalAlignment="Stretch"
VerticalContentAlignment="Stretch"
Margin="0">
<Grid VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border Grid.Row="0"
BorderBrush="DimGray"
BorderThickness="1"
Background="LightSlateGray"
Margin="1" />
<Border Grid.Row="1"
Margin="1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="3" />
<ColumnDefinition Width="5*" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0"
BorderBrush="DarkGoldenrod"
BorderThickness="1" />
<GridSplitter Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" />
<Border Grid.Column="2"
BorderBrush="DarkGoldenrod"
BorderThickness="1" />
</Grid>
</Border>
</Grid>
</UserControl>
The UserControl is not filling the entire vertical space:
Row[1] of the outermost Grid is stretching vertically as is evidenced by the ItemsControl.Background filling the area.
For reference, this is what I am expecting:
For what its worth, I've looked at numerous questions on SO regarding this exact issue but none of the solutions seem to help. Then again, none of the examples I saw used an ItemsControl with binding.
This happens because the ItemsControl throw all of our items into a vertically aligned StackPanel by default. It's very easy to change though, since the ItemsControl allows you to change which panel type is used to hold all the items.
<ItemsControl DockPanel.Dock="Top"
ItemsSource="{Binding Designer}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalAlignment="Stretch"
VerticalContentAlignment="Stretch"
Background="Transparent">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>

ScrollViewer does not react to the Grid.Row position

My StackPanel and ScrollViewer seem to just not end at the Grid.Row position. I am making a Metro app so it is mandatory for the grid to be dynamic, as well as all the elements.
The code:
<Grid Background="#FFE4E4E4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.5*"/>
<ColumnDefinition Width="0.5*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="0.5*"/>
<RowDefinition Height="0.5*"/>
</Grid.RowDefinitions>
<!--News/Leaderboard Feed-->
<StackPanel Grid.Column="0" Grid.Row="0">
</StackPanel>
<!--Marketplace Feed-->
<StackPanel Grid.Column="0" Grid.Row="1">
</StackPanel>
<!--Detailed Marketplace Account-->
<StackPanel Grid.Column="1" Grid.Row="1">
</StackPanel>
<!--Marketplace View-->
<StackPanel Grid.Column="1" Grid.Row="0" VerticalAlignment="Top">
<ScrollViewer VerticalAlignment="Top">
<!--Allows scrolling-->
<GridView x:Name="MarketplaceFeed" ItemsSource="{Binding StockList}" ItemTemplate="{StaticResource MarketplaceFeedTemplate}" VerticalAlignment="Top">
<!--Displays the stock markets the user is interested in.-->
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<WrapGrid Orientation="Horizontal" />
</ItemsPanelTemplate>
</GridView.ItemsPanel>
</GridView>
</ScrollViewer>
</StackPanel>
</Grid>
You should put the ScrollViewer on the outside. This will auto-fit to the grid, and anything inside it will get the scrolling treatment.
<!--Marketplace View-->
<ScrollViewer VerticalAlignment="Top" Grid.Column="1" Grid.Row="0">
<StackPanel VerticalAlignment="Top">
<!-- other content -->
</StackPanel>
</ScrollViewer>

WPF DataGrid Scrollbar

I'm trying to make a WPF DataGrid show scrollbars when necessary.
You can see the basic XAML code of my user control below:
<Grid x:Name="Data" Grid.Column="0" VerticalAlignment="Stretch" Height="Auto" HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Expander Header="Route Setup" Grid.Row="0" VerticalAlignment="Top" Background="White">
</Expander>
<Expander Header="Select Locations" Grid.Row="1" VerticalAlignment="Top" Background="White">
</Expander>
<DataGrid Grid.Row="2" ItemsSource="{Binding Locations, Mode=TwoWay}" Height="Auto" AutoGenerateColumns="False" ScrollViewer.CanContentScroll="True"
ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Auto">
</DataGrid>
This isn't working, I don't see any scroolbars when the DataGrid grows beyond the available space. I've already tried to use a scrollview around my DataGrid but that doesn't change anything.
Update
It might be important to know that the usercontrol is loaded into the LeftRegion of a shell that has the following markup:
<Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions >
<ColumnDefinition Width="*" MinWidth="400" MaxWidth="600"/>
<ColumnDefinition Width="9" />
<ColumnDefinition Width="*" MinWidth="300" />
</Grid.ColumnDefinitions>
<GridSplitter x:Name="MainSplitter" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" Grid.Column="1"
Margin="0" Width="9" Style="{DynamicResource gridSplitterVerticalStyle}"/>
<ItemsControl Name="LeftRegion" Grid.Column="0" Background="Azure" Height="Auto" VerticalAlignment="Stretch" VerticalContentAlignment="Stretch" cal:RegionManager.RegionName="LeftRegion">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<ItemsControl Name="RightRegion" Height="Auto" Background="DarkGreen" VerticalAlignment="Stretch" VerticalContentAlignment="Stretch" Grid.Column="2" cal:RegionManager.RegionName="RightRegion">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
Best Regards
Jay
In your grid named "Data", remove the Height="Auto" from the third RowDefinition. At least one row must have the "*" height (which is the default) to take the remaining available space.
Solved it. I needed to remove Height="Auto" from the thrird RowDefinition.

how to find a control inside ItemsPanelTemplate in wpf?

I am trying to access a Grid inside the DataTemplate while the ItemsControl is binded by ItemsSource.
this is the full XMAL code, How do i find a certain element from outside?
for (int i = 0; i < allViewControl.Items.Count; i++)
{
var container = allViewControl.ItemContainerGenerator.ContainerFromItem(allViewControl.Items[i]) as FrameworkElement;
var grid = allViewControl.ItemTemplate.FindName("grid", container) as DataGrid;
}
i found this always returning null ?
<ScrollViewer Grid.Row="0" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<ItemsControl x:Name="allViewControl" Focusable="False" HorizontalContentAlignment="Center"
Grid.IsSharedSizeScope="true" ItemsSource="{Binding AllClassCharacters}"
ItemTemplate="{StaticResource CharacterViewModelTemplate}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Extensions:AnimatedWrapPanel IsItemsHost="true" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
<DataTemplate x:Key="CharacterViewModelTemplate" DataType="{x:Type ViewModel:CharacterViewModel}">
<Grid x:Name="grid" Width="200" Height="Auto" MinHeight="115" Margin="1" MinWidth="130" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" RenderTransformOrigin="0.5,0.5" Background="#66000000" >
<Grid.RowDefinitions>
<RowDefinition Height="70"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ProgressBar x:Name="playerProgressBar" VerticalAlignment="Top" Background="Transparent" Height="5" Width="Auto" Value="0" Visibility="Collapsed" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan ="2" Grid.RowSpan="2" Foreground="White" BorderThickness="0" Style="{DynamicResource ProgressBarStyle1}" />
</Grid>
Short answer is that you shouldn't need to do this - using MVVM should give you simpler solutions to whatever you're trying to achieve.
If you need it for some niche cases like setting the focus, search for 'find control wpf' on so - there are some existing questions (example) to hack and get controls out of the WPF UI Tree

Categories

Resources