如何使用行定义名称设置网格行?

7
我正在使用RowDefinitionsColumnDefinition组织我的网格,但每当我想在当前任何RowDefinition之前添加新的RowDefinition时,我需要重新组织所有控件的Grid.Row。
我看到RowDefinitionColumnDefinition有一个Name属性,所以我认为可以使用RowDefinition名称来定义Grid.Row,是这样吗?如果可以,如何实现?
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Name="RowDictTitle" Height="27"/>
        <RowDefinition Name="RowSearchWord" Height="27"/>
        <RowDefinition/>
        <RowDefinition Height="50"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="1*"/>
        <ColumnDefinition Width="2*"/>
    </Grid.ColumnDefinitions>

    <!--Row 1-->
    <TextBlock Text="Word:" VerticalAlignment="Center" Margin="10,0,0,0" Grid.Row="1"/>
    <TextBox Name="Search" Grid.ColumnSpan="2" Margin="50,2,10,2"/>

    <!--Row 2-->
    <ListBox Name="Words" Grid.Row="2" Margin="10"/>
</Grid>

我希望你能够完成以下需求:
<TextBlock Text="Word" Grid.Row="RowSearchWord"/>
4个回答

7
免责声明:本答案有点自我推广,但仅限于这个 meta帖子所暗示的范围内。它宣传的是我编写的一个免费开源项目,目前(撰写本文时)我没有从中获得任何利润。唯一的收获就是如果它能帮助某些未来的SO问题的访问者,那么我花在编写描述控件上的时间就不会被浪费了。
我曾经也有同样的想法。因此,不久前,我编写了一个使用命名列和行的自定义网格类。
我将其放在MIT许可证下的Codeplex上: Name-Based Grid项目 使用该控件,您可以将Xaml源代码重写为以下形式:
<nbg:NameBasedGrid>
    <nbg:NameBasedGrid.RowDefinitions>
        <nbg:ColumnOrRow Name="RowDictTitle" Height="27"/>
        <nbg:ColumnOrRow Name="RowSearchWord" Height="27"/>
        <nbg:ColumnOrRow Name="List"/>
        <nbg:ColumnOrRow Height="50"/>
    </nbg:NameBasedGrid.RowDefinitions>
    <nbg:NameBasedGrid.ColumnDefinitions>
        <nbg:ColumnOrRow Width="1*" Name="Left"/>
        <nbg:ColumnOrRow Width="2*" Name="Right"/>
    </nbg:NameBasedGrid.ColumnDefinitions>

    <!--Row 1-->
    <TextBlock Text="Word:" VerticalAlignment="Center" Margin="10,0,0,0" nbg:NameBasedGrid.Column="Left" nbg:NameBasedGrid.Row="RowSearchWord"/>
    <TextBox Name="Search" nbg:NameBasedGrid.Column="Left" nbg:NameBasedGrid.Row="RowDictTitle" nbg:NameBasedGrid.ExtendToColumn="Right" Margin="50,2,10,2"/>

    <!--Row 2-->
    <ListBox Name="Words" nbg:NameBasedGrid.Column="Left" nbg:NameBasedGrid.Row="List" Margin="10"/>
</nbg:NameBasedGrid>

优势: 您将能够通过名称引用列和行(包括跨列和跨行)-不再需要计算列或行,不再在布局更改时更新列或行跨度。

劣势: 您需要明确说明所有列和行的名称,因为NameBasedGrid完全不支持数字引用。


2

我在UWP中通过绑定一个函数来实现这个功能,该函数接受RowDefinitionCollection和RowDefinition对象作为参数。我对WPF不是很熟悉,但我认为您的帮助程序需要检索这两个对象才能正常工作。 - Ben Jasperson

1
我在寻找同样的事情。没有找到完全符合我的要求,所以我使用附加属性想出了自己的解决方案。
我创建了一个专门的网格,并为行名称和列名称使用了附加属性。 (在这个例子中,我只实现了行名称)
using System.Windows;
using System.Windows.Controls;

namespace GridNamedRows.CustomControl
{
    public class MyGrid: Grid
    {
        public static readonly DependencyProperty RowNameProperty =
                DependencyProperty.RegisterAttached(
                      "RowName",
                      typeof(string),
                      typeof(MyGrid),
                      new FrameworkPropertyMetadata(
                              "",
                              FrameworkPropertyMetadataOptions.AffectsParentArrange,
                              new PropertyChangedCallback(RowNameChanged)),
                      new ValidateValueCallback(IsStringNotNull));

        private static bool IsStringNotNull(object value)
        {
            return (value as string) != null;
        }

        private static void RowNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue == null) 
            { 
                return; 
            }
            if (!(d is UIElement)) return;
            Grid parent = ((FrameworkElement)d).Parent as Grid;
            if (parent == null) return;
            //Find rowname
            for (int i = 0; i < parent.RowDefinitions.Count; i++)
            {
                if (parent.RowDefinitions[i].Name == e.NewValue.ToString())
                {
                    Grid.SetRow((UIElement)d, i);
                    break;
                }
            }
        }

        public static string GetRowName(DependencyObject target)
        {
            return (string)target.GetValue(RowNameProperty);
        }

        public static void SetRowName(DependencyObject target, string value)
        {
            target.SetValue(RowNameProperty, value);
        }
    }
}

它可以在XAML中像这样使用。
<Window xmlns:CustomControl="clr-namespace:GridNamedRows.CustomControl"  x:Class="GridNamedRows.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <CustomControl:MyGrid>
        <Grid.RowDefinitions>
            <RowDefinition Name="firstRow"/>
            <RowDefinition Name="secondRow"/>
            <RowDefinition Name="thirdRow"/>
        </Grid.RowDefinitions>
        <TextBox Text="one" CustomControl:MyGrid.RowName="secondRow"/>
        <TextBox Text="two" Grid.Row="2"/>
        <TextBox Text="three" CustomControl:MyGrid.RowName="firstRow"/>
    </CustomControl:MyGrid>
</Window>

在设计器中无法正确显示,但在运行时可以正常工作。

0

在其他答案的基础上,我想出了这个附加属性的解决方案,它不需要使用自定义Grid

代码在行和列上大部分都是冗余的,并且可以像这样使用:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition x:Name="ThisRow"/>
        <RowDefinition x:Name="ThatRow"/>
        <RowDefinition x:Name="AnotherRow"/>
    </Grid.RowDefinitions>

    <TextBlock helpers:GridHelper.RowName="ThisRow" Text="..."/>
    <TextBlock helpers:GridHelper.RowName="AnotherRow" Text="..."/>
    <TextBlock helpers:GridHelper.RowName="ThatRow" Text="..."/>

</Grid>

GridHelper.cs:

public class GridHelper
{
    public static string GetRowName(DependencyObject obj)
    {
        return (string)obj.GetValue(RowNameProperty);
    }

    public static void SetRowName(DependencyObject obj, string value)
    {
        obj.SetValue(RowNameProperty, value);
    }

    public static readonly DependencyProperty RowNameProperty =

        DependencyProperty.RegisterAttached("RowName", typeof(string), typeof(GridHelper), new FrameworkPropertyMetadata(string.Empty, GridHelper.OnRowNamePropertyChanged));

    public static void OnRowNamePropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        var name = e.NewValue?.ToString();
        if (string.IsNullOrEmpty(name)) return;

        if (!(sender is FrameworkElement fe)) return;
        if (!(fe.Parent is Grid grid)) return;

        for (int i = 0; i < grid.RowDefinitions.Count; i++)
        {
            var rd = grid.RowDefinitions[i];

            if (rd.Name.Equals(name))
            {
                Grid.SetRow(fe, i);
                return;
            }
        }

        throw new ArgumentException("Invalid RowName: " + name);
    }
    
    public static string GetColumnName(DependencyObject obj)
    {
        return (string)obj.GetValue(ColumnNameProperty);
    }

    public static void SetColumnName(DependencyObject obj, string value)
    {
        obj.SetValue(ColumnNameProperty, value);
    }

    public static readonly DependencyProperty ColumnNameProperty =

        DependencyProperty.RegisterAttached("ColumnName", typeof(string), typeof(GridHelper), new FrameworkPropertyMetadata(string.Empty, GridHelper.OnColumnNamePropertyChanged));

    public static void OnColumnNamePropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        var name = e.NewValue?.ToString();
        if (string.IsNullOrEmpty(name)) return;

        if (!(sender is FrameworkElement fe)) return;
        if (!(fe.Parent is Grid grid)) return;

        for (int i = 0; i < grid.ColumnDefinitions.Count; i++)
        {
            var cd = grid.ColumnDefinitions[i];

            if (cd.Name.Equals(name))
            {
                Grid.SetColumn(fe, i);
                return;
            }
        }

        throw new ArgumentException("Invalid ColumnName: " + name);
    }
}

注意:这可能在设计器中也不起作用——我从未尝试过使用它...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接