Is there a way to reference a Grid Column by the x:Name as an alternative to its zero-based index?
In my code I can reference the Column using it's index. But I'd much rather use it's name. Is this possible? I have no idea. :D
<Window x:Class="WpfApplication1.Window1"
xmlns="h开发者_C百科ttp://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="800">
<Grid ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="LeftColumn" Width="200"></ColumnDefinition>
<ColumnDefinition x:Name="MiddleColumn" Width="200"></ColumnDefinition>
<ColumnDefinition x:Name="RightColumn" Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button x:Name="WelcomeButton" Grid.Column="1">Click me.</Button> //Here!
</Grid>
Edit:
If it isn't possible to reference it by name, what use is there to set a name for a column? (I'm new to WPF :P)
Yes! The beauty of WPF is how trivial it is to add the features you want. You can easily add named grid rows and columns using attached properties. For example, the code below will allow you to do this:
<Grid ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="LeftColumn" Width="200" />
<ColumnDefinition x:Name="MiddleColumn" Width="200" />
<ColumnDefinition x:Name="RightColumn" Width="*" />
</Grid.ColumnDefinitions>
<Button my:Grid_Named.Column="RightColumn">Click me.</Button>
</Grid>
Here's the code itself. It's quite straightforward and took only a few minutes to write:
public static class Grid_Named
{
// Define Grid_Name.Column property
public static string GetColumn(DependencyObject obj) { return (string)obj.GetValue(ColumnProperty); }
public static void SetColumn(DependencyObject obj, string value) { obj.SetValue(ColumnProperty, value); }
public static readonly DependencyProperty ColumnProperty = DependencyProperty.RegisterAttached("Column", typeof(string), typeof(Grid_Named), new UIPropertyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
if(e.OldValue!=null) BindingOperations.ClearBinding(obj, Grid.ColumnProperty);
if(e.NewValue!=null) BindingOperations.SetBinding(obj, Grid.ColumnProperty, _columnBinding);
},
});
// Define Grid_Named.Row property
public static string GetRow(DependencyObject obj) { return (string)obj.GetValue(RowProperty); }
public static void SetRow(DependencyObject obj, string value) { obj.SetValue(RowProperty, value); }
public static readonly DependencyProperty RowProperty = DependencyProperty.RegisterAttached("Row", typeof(string), typeof(Grid_Named), new UIPropertyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
if(e.OldValue!=null) BindingOperations.ClearBinding(obj, Grid.RowProperty);
if(e.NewValue!=null) BindingOperations.SetBinding(obj, Grid.RowProperty, _rowBinding);
},
});
// Construct bindings for Grid.Column and Grid.Row
private static BindingBase _columnBinding = BuildBinding(grid => grid.ColumnDefinitions, ColumnProperty);
private static BindingBase _rowBinding = BuildBinding(grid => grid.RowDefinitions, RowProperty);
private static BindingBase BuildBinding(Func<Grid, IList> getDefinitions, DependencyProperty nameProperty)
{
var binding = new MultiBinding
{
Converter = new NameLookupConverter { GetDefinitions = getDefinitions }
};
binding.Bindings.Add(new Binding { Path = new PropertyPath(nameProperty), RelativeSource = RelativeSource.Self });
binding.Bindings.Add(new Binding { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Grid), 1) });
return binding;
}
// Converter to take a row/column name and a Grid, and find the index of that row/column in the grid
private class NameLookupConverter : IMultiValueConverter
{
public Func<Grid, IList> GetDefinitions;
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var name = values[0] as string;
var grid = values[1] as Grid;
if(grid==null || name==null) return 0;
object namedObject = grid.FindName(name);
if(namedObject==null) return 0;
int index = GetDefinitions(grid).IndexOf(namedObject);
return index<0 ? 0 : index;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
As you can see, any time you add an attached property Grid_Named.Column or Grid_Named.Row, it simply creates a binding for Grid.Column or Grid.Row that uses a IMultiValueConveter to select the appropriate column or row number.
Simple, no?
No, it isn't.
If you really wanted to, you could write a custom ValueConverter or attached property to fake it, but I wouldn't recommend it.
No, it's not possible. Name is here to access it in code behind so that you can resize it, or bind it with other elements xaml to resize it at runtime.
I really like the solution provided by Ray Burns, but I think it can be simplified a bit by binding to the element itself, rather than the name path. This allows us to get the grid via the Parent property of the row/column definition and then simply do an IndexOf on the bound value rather than having to search for the element.
xaml
<Grid ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="LeftColumn" Width="200" />
<ColumnDefinition x:Name="MiddleColumn" Width="200" />
<ColumnDefinition x:Name="RightColumn" Width="*" />
</Grid.ColumnDefinitions>
<Button my:GridByElement.Column="{Binding ElementName=RightColumn}">Click me.</Button>
</Grid>
codebehind
public static class GridByElement
{
// Define Grid_Name.Column property
public static DefinitionBase GetColumn(DependencyObject obj)
{
return (DefinitionBase)obj.GetValue(ColumnProperty);
}
public static void SetColumn(DependencyObject obj, DefinitionBase value)
{
obj.SetValue(ColumnProperty, value);
}
public static readonly DependencyProperty ColumnProperty
= DependencyProperty.RegisterAttached("Column", typeof(DefinitionBase), typeof(GridByElement), new UIPropertyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
if (e.OldValue != null) BindingOperations.ClearBinding(obj, Grid.ColumnProperty);
if (e.NewValue != null) BindingOperations.SetBinding(obj, Grid.ColumnProperty, _columnBinding);
},
});
// Define Grid_Named.Row property
public static DefinitionBase GetRow(DependencyObject obj)
{
return (DefinitionBase)obj.GetValue(RowProperty);
}
public static void SetRow(DependencyObject obj, DefinitionBase value)
{
obj.SetValue(RowProperty, value);
}
public static readonly DependencyProperty RowProperty
= DependencyProperty.RegisterAttached("Row", typeof(DefinitionBase), typeof(GridByElement), new UIPropertyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
if (e.OldValue != null) BindingOperations.ClearBinding(obj, Grid.RowProperty);
if (e.NewValue != null) BindingOperations.SetBinding(obj, Grid.RowProperty, _rowBinding);
},
});
// Construct bindings for Grid.Column and Grid.Row
private static BindingBase _columnBinding = BuildBinding(grid => grid.ColumnDefinitions, ColumnProperty);
private static BindingBase _rowBinding = BuildBinding(grid => grid.RowDefinitions, RowProperty);
private static BindingBase BuildBinding(Func<Grid, IList> getDefinitions, DependencyProperty nameProperty)
{
var binding = new Binding
{
Converter = new NameLookupConverter() { GetDefinitions = getDefinitions },
Path = new PropertyPath(nameProperty),
RelativeSource = RelativeSource.Self
};
return binding;
}
// Converter to take a row/column definition, and find the index of that row/column in the grid
private class NameLookupConverter : IValueConverter
{
public Func<Grid, IList> GetDefinitions;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is DefinitionBase def && def.Parent is Grid grid)) return 0;
int index = Math.Max(0, GetDefinitions(grid).IndexOf(def));
return index;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
精彩评论