Lai32290
Lai32290

Reputation: 8548

How to set Grid Row with RowDefinition name?

I'm organizing my grid with RowDefinitions and ColumnDefinition, but forever when I want add a new RowDefinition in before actual any RowDefinition, I need reorganize Grid.Row of all controls

I saw RowDefinition and ColumnDefinition has a Name property, so I think is possible define Grid.Row with RowDefinition name or not? If is possible, How do

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

I want make below

<TextBlock Text="Word" Grid.Row="RowSearchWord"/>

Upvotes: 7

Views: 7427

Answers (5)

Bigorca312
Bigorca312

Reputation: 1

To anyone coming here: the solution from mike works perfectly. Even updating in the visual studio Designer during designing. I created this account specifically to comment on this.

I compiled his GridHelper class into a separate assembly and referenced it in my main assembly. After adding the xmlns reference it works exactly as he described. Link to the specific comment with the GridHelper code:

https://stackoverflow.com/a/74534620/29094387

Make sure you define both the row and column just like mike's example. You can then use the standard Grid.RowSpan and Grid.ColumnSpan if needed.

Upvotes: 0

mike
mike

Reputation: 1744

Along the lines of the other answers I came up with this attached property solution that does not require using a custom Grid.

The code is largely redundant (for row & column) and can be used like this:

<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);
    }
}

Note: This also may not work in the designer - I've never tried using it...

Upvotes: 0

F-H
F-H

Reputation: 1075

Disclaimer: This answer is kind of a self-advertisement within the constraints alluded to by this meta post. It advertises a free open source project that I (at the time of writing this) do not earn any money with. The only gain is the knowledge that my time for writing the described control was not wasted if it helps some future visitors of this SO question.

I had exactly the same thoughts. That is why, not too long ago, I wrote a custom grid class that uses named columns and rows.

I put it on Codeplex under the MIT license: Name-Based Grid project

With that control, you can rewrite your Xaml source code as follows:

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

Advantage: You will be able to reference columns and rows (including column and row spans!) by name - no more counting of columns or rows, no more updating column or row spans when the layout changes.

Disadvantage: You will need to explicitly state names for all columns and rows, as numerical references are not supported at all in NameBasedGrid.

Upvotes: 7

Marco
Marco

Reputation: 11

I was looking for the same thing. Could not find exacly what I was looking for so i came up with my own solution using attached properties.

I created a specialized grid with attached properties for RowName and ColumnName. (In this example i only implemented RowName)

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);
        }
    }
}

It can be used in xaml like this.

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

It does not display correctly in the designer but works in runtime.

Upvotes: 1

grantnz
grantnz

Reputation: 7423

Nice idea but since the Grid.Row attached property is an integer this is not possible.

See http://msdn.microsoft.com/en-us/library/system.windows.controls.grid.row.aspx

However, it may possible to create a helper that takes the name of the grid row, finds the row object and returns its row index.

Upvotes: 2

Related Questions