How to create reusable WPF grid layout
I have a window with tab control and number of pages - tab items. Each tab item has same grid layout - 6 rows and 4 columns. Now, each tab item contains grid with row and column definitions, so almost half of XAML is definition of grids.
How can I define this grid in one place and reuse that definition in my application? Template? User control?
Besides 6x4, I have only two more grid dimensions that repeat: 8x4 and 6x6.
Edit:
Forgot to mention: controls in grid are different for each 开发者_StackOverflow中文版tab. I just want to have grid defined once in some resource so that I can reuse them on different tab pages. Now XAML looks like this: <TabControl>
<TabItem Header="Property">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<!-- some controls here -->
</Grid>
</TabItem>
<TabItem Header="Style">
<Grid >
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<!-- some controls here -->
</Grid>
</TabItem>
... and this repeats for several more tab items
</TabControl>
This grid definition repeats for each tab item on the form. It annoys me that half of XAML is grid definition.
Is there a way to define this grid at one place and then reuse that definition?
The best way in my opinion would be to use ItemsControl
with an ItemsPanelTemplate
, since you need a container for multiple items:
<FrameworkElement.Resources>
<Style x:Key="GridItemsStyle"
TargetType="ItemsControl">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
</FrameworkElement.Resources>
<TabControl>
<TabItem>
<ItemsControl Style="{StaticResource GridItemsStyle}">
<TextBlock Grid.Row="1" Text="R1" />
<TextBlock Grid.Column="1"
Text="C1" />
</ItemsControl>
</TabItem>
<TabItem>
<ItemsControl Style="{StaticResource GridItemsStyle}">
<TextBlock Grid.Row="2"
Text="R2" />
<TextBlock Grid.Column="2"
Text="C2" />
</ItemsControl>
</TabItem>
</TabControl>
Or you could just inherit from Grid...
using System.Windows.Controls;
public class AlreadySetupGrid:Grid
{
public AlreadySetupGrid()
{
for (int i = 0; i < 4; i++)
{
ColumnDefinitions.Add(new ColumnDefinition());
}
for (int i = 0; i < 6; i++)
{
RowDefinitions.Add(new RowDefinition());
}
}
}
And then use that instead of your normal Grid.
I had something similiar. question is how do you wan't to put your data into the grids?
As you use the same layout over and over again I guess you are putting similiar things to each cell.
I created a custom ItemsControl for each Tab to put the Data into and then created a style for the ItemsControl that showed the grid.
<Style x:Key="GridItemsStyle"
TargetType="ItemsControl">
<Setter Property="ItemsPanel">
<Setter.Value>
<ControlTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ContentPresenter Content="{Binding ElementName=Cell00}" Grid.Row="0" Grid.Column="0" />
<ContentPresenter Content="{Binding ElementName=Cell01}" Grid.Row="0" Grid.Column="1" />
<ContentPresenter Content="{Binding ElementName=Cell10}" Grid.Row="1" Grid.Column="0" />
<!-- ...And so on -->
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
and in the window
<TabControl>
<TabItem>
<local:tab1 Style="{StaticResource GridItemsStyle}" />
</TabItem>
<TabItem>
<local:tab2 Style="{StaticResource GridItemsStyle}" />
</TabItem>
then each CustomControl inheriting from ItemsControl
<ItemsControl x:Class="your_app.tab1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:your_app">
<ContentControl x:Name="Cell00">
<!-- Some stuff here -->
</ContentControl>
<ContentControl x:Name="Cell01">
<!-- Some stuff here -->
</ContentControl>
<ContentControl x:Name="Cell10">
<!-- Some stuff here -->
</ContentControl>
This is very similiar to what Aelij is doing except I set the ContentPresenter and bind it to a name and put the itemsControl in it's own thing (you can mix both solutions).
It still is alot of code but I would say you save yourself all the Row and Column definitions all the time and you also only have to change the grid at one place if you have to modify it somewhat.
Usually one would write a DataTemplate for the data that goes into the Tabs. That DataTemplate would contain the Grid.
How can I define this grid in one place and reuse that definition in my application? Template? User control?
If I were you, I would create a UserControl
or a custom Control
with the repeated code, and then just consume that control from the current code.
Another approach would be to use a DataTemplate
or a ControlTemplate
.
Creating a ControlTemplate
for the TabItem
s would also work great.
You can do it but it requires some work:
1) Create a collection and an attached property like this:
public class ColumnDefinitions : Collection<ColumnDefinition>
{
public static readonly DependencyProperty SourceProperty = DependencyProperty.RegisterAttached(
"Source",
typeof(ColumnDefinitions),
typeof(ColumnDefinitions),
new PropertyMetadata(
default(ColumnDefinitions),
OnColumnDefinitionsChanged));
public static void SetSource(Grid element, ColumnDefinitions value)
{
element.SetValue(SourceProperty, value);
}
[AttachedPropertyBrowsableForChildren(IncludeDescendants = false)]
[AttachedPropertyBrowsableForType(typeof(Grid))]
public static ColumnDefinitions GetSource(Grid element)
{
return (ColumnDefinitions)element.GetValue(SourceProperty);
}
private static void OnColumnDefinitionsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var grid = (Grid)d;
grid.ColumnDefinitions.Clear();
var columnDefinitions = (ColumnDefinitions)e.NewValue;
if (columnDefinitions == null)
{
return;
}
foreach (var columnDefinition in columnDefinitions)
{
grid.ColumnDefinitions.Add(columnDefinition);
}
}
}
2) Then you can use it as a resource and in a style for grid like this:
Note that x:Shared="False"
must be used. If not the same definition will be added to many grids causing WPF to throw.
<UserControl.Resources>
<demo:ColumnDefinitions x:Key="SomeColumnDefinitions" x:Shared="False">
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</demo:ColumnDefinitions>
<Style x:Key="SomeGridStyle" TargetType="{x:Type Grid}">
<Setter Property="demo:ColumnDefinitions.Source" Value="{StaticResource SomeColumnDefinitions}"></Setter>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="5"/>
<RowDefinition />
</Grid.RowDefinitions>
<Grid Style="{StaticResource SomeGridStyle}">
<Rectangle Grid.Row="0"
Grid.Column="0"
Width="120"
Fill="Blue" />
<Rectangle Grid.Row="0"
Grid.Column="1"
Fill="Yellow" />
</Grid>
<Grid Grid.Row="2" demo:ColumnDefinitions.Source="{StaticResource SomeColumnDefinitions}">
<Rectangle Grid.Row="0"
Grid.Column="0"
Width="120"
Fill="Blue" />
<Rectangle Grid.Row="0"
Grid.Column="1"
Fill="Yellow" />
</Grid>
</Grid>
精彩评论