如何创建可重用的 WPF 网格布局

Posted

技术标签:

【中文标题】如何创建可重用的 WPF 网格布局【英文标题】:How to create reusable WPF grid layout 【发布时间】:2011-02-22 10:52:57 【问题描述】:

我有一个带有选项卡控件和页数的窗口 - 选项卡项。每个选项卡项具有相同的网格布局 - 6 行和 4 列。现在,每个选项卡项都包含带有行和列定义的网格,因此几乎一半的 XAML 是网格定义。

如何在一个地方定义此网格并在我的应用程序中重复使用该定义?模板?用户控制?

除了 6x4,我只有两个重复的网格尺寸:8x4 和 6x6。

编辑: 忘了提一下:每个选项卡的网格控件都不同。我只想在某些资源中定义一次网格,以便我可以在不同的标签页上重用它们。现在 XAML 看起来像这样:

    <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>

此网格定义对表单上的每个选项卡项重复。让我恼火的是 XAML 的一半是网格定义。

有没有办法在一个地方定义这个网格,然后重复使用这个定义?

【问题讨论】:

【参考方案1】:

我认为最好的方法是使用ItemsControlItemsPanelTemplate,因为您需要一个容器来容纳多个项目:

<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>

【讨论】:

看起来很简单 很好的答案!这正是我想要的,非常感谢分享:-)【参考方案2】:

或者你可以从 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());
        
    

然后使用它而不是您的普通网格。

【讨论】:

这听起来像是一个更好的计划,但这是我在设计时没有得到列和行。即网格中定义的任何元素都不会正确定位【参考方案3】:

我有类似的东西。问题是您如何不将数据放入网格中?

当您一遍又一遍地使用相同的布局时,我猜您在每个单元格中放置了类似的东西。

我为每个选项卡创建了一个自定义 ItemsControl 以将数据放入其中,然后为显示网格的 ItemsControl 创建了一个样式。

<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>

在窗口中

<TabControl>
<TabItem>
    <local:tab1 Style="StaticResource GridItemsStyle" />
</TabItem>
<TabItem>
    <local:tab2 Style="StaticResource GridItemsStyle" />
</TabItem>

那么每个CustomControl都继承自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>

这与 Aelij 所做的非常相似,只是我设置了 ContentPresenter 并将其绑定到一个名称并将 itemsControl 放入它自己的东西中(您可以混合使用这两种解决方案)。

它仍然是很多代码,但我会说你一直保存自己所有的行和列定义,如果你必须修改它,你也只需要在一个地方更改网格。

【讨论】:

【参考方案4】:

通常人们会为进入选项卡的数据编写一个 DataTemplate。该 DataTemplate 将包含网格。

【讨论】:

【参考方案5】:

如何在一个地方定义此网格并在我的应用程序中重复使用该定义?模板?用户控制?

如果我是你,我会使用重复的代码创建一个 UserControl 或自定义 Control,然后从当前代码中使用该控件。

另一种方法是使用DataTemplateControlTemplate

TabItems 创建一个ControlTemplate 也很有效。

【讨论】:

【参考方案6】:

你可以做到,但需要一些工作:

1) 像这样创建一个集合和一个附加属性:

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) 然后您可以将其用作资源和网格样式,如下所示: 请注意,必须使用x:Shared="False"。如果不同,则会将相同的定义添加到许多导致 WPF 抛出的网格中。

<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>

【讨论】:

以上是关于如何创建可重用的 WPF 网格布局的主要内容,如果未能解决你的问题,请参考以下文章

WPF ListBox 中使用网格布局

如何使用新的 CSS GRID 布局创建像 12 列(仅网格)网格系统的 Bootstrap

如何在 kivymd 中制作网格布局?

如何使用 CSS 网格创建以下布局? [复制]

如何使用 UICollectionViewCompositionalLayout 创建具有相等行高的网格布局

在 WPF 中折叠网格