【发布时间】:2020-06-04 19:11:52
【问题描述】:
创建了一个简单的附加属性来简化元素模板的绑定。 而不是这个:
<ItemsControl ItemsSource="{Binding Mode=OneWay, Source={StaticResource Points.Grid}}"
ItemsPanel="{StaticResource Grid.Panel}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="Point">
<Ellipse Fill="Coral"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Row" Value="{Binding Y}"/>
<Setter Property="Grid.Column" Value="{Binding X}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
你可以这样走:
<ItemsControl Grid.Row="1"
ItemsSource="{Binding Mode=OneWay, Source={StaticResource Points.Grid}}"
ItemsPanel="{StaticResource Grid.Panel}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="Point">
<Ellipse Fill="LightBlue"
pa:Grid.Row="{Binding Y}"
pa:Grid.Column="{Binding X}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
这是附加属性的完整代码:
public static partial class Grid
{
public static int GetRow(FrameworkElement element)
{
return (int)element.GetValue(RowProperty);
}
public static void SetRow(FrameworkElement element, int value)
{
element.SetValue(RowProperty, value);
}
// Using a DependencyProperty as the backing store for Row. This enables animation, styling, binding, etc...
public static readonly DependencyProperty RowProperty =
DependencyProperty.RegisterAttached("Row", typeof(int), typeof(Grid),
new FrameworkPropertyMetadata
(
0,
FrameworkPropertyMetadataOptions.AffectsParentArrange | FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
RowChanged
));
private static void RowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is FrameworkElement element))
throw new ArgumentException("Must be FrameworkElement", nameof(d));
FrameworkElement parent;
while ((parent = VisualTreeHelper.GetParent(element) as FrameworkElement) != null && !(parent is System.Windows.Controls.Grid))
element = parent;
if (parent is System.Windows.Controls.Grid grid)
element.SetValue(System.Windows.Controls.Grid.RowProperty, (int)e.NewValue);
}
private static void GridLoaded(object sender, RoutedEventArgs e)
=> ((System.Windows.Controls.Grid)sender).InvalidateMeasure();
public static int GetColumn(FrameworkElement element)
{
return (int)element.GetValue(ColumnProperty);
}
public static void SetColumn(FrameworkElement element, int value)
{
element.SetValue(ColumnProperty, value);
}
// Using a DependencyProperty as the backing store for Column. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ColumnProperty =
DependencyProperty.RegisterAttached("Column", typeof(int), typeof(Grid),
new FrameworkPropertyMetadata
(
0,
FrameworkPropertyMetadataOptions.AffectsParentArrange | FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
ColumnChanged
));
private static void ColumnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is FrameworkElement element))
throw new ArgumentException("Must be FrameworkElement", nameof(d));
FrameworkElement parent;
while ((parent = VisualTreeHelper.GetParent(element) as FrameworkElement) != null && !(parent is System.Windows.Controls.Grid))
element = parent;
if (parent is System.Windows.Controls.Grid grid)
element.SetValue(System.Windows.Controls.Grid.ColumnProperty, (int)e.NewValue);
}
}
该属性并不复杂。 使用 Canvas,同样可以正常工作。 而对于 Grid,在使用元素附加集合或将第一个元素添加到集合时会出现问题 - 元素显示在 Grid 中而不考虑它们的位置。 尽管在可视化树和属性浏览器中查看时,Grid.Row / Column 的附加属性设置正确。 并且在窗口发生最轻微的变化时,元素就会到位。
在我看来,一个坦率的错误。 但是怎么处理呢?
完整的 XAML 演示代码:
<Window x:Class="AttachedPropertiesWPF.BindParentWind"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:AttachedPropertiesWPF"
mc:Ignorable="d"
Title="BindParentWind" Height="450" Width="800"
xmlns:pa="clr-namespace:AttachedProperties;assembly=AttachedProperties">
<Window.Resources>
<x:Array x:Key="Points.Grid" Type="Point">
<Point X="1" Y="0"/>
<Point X="0" Y="2"/>
<Point X="2" Y="1"/>
</x:Array>
<ItemsPanelTemplate x:Key="Grid.Panel">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ItemsControl ItemsSource="{Binding Mode=OneWay, Source={StaticResource Points.Grid}}"
ItemsPanel="{StaticResource Grid.Panel}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="Point">
<Ellipse Fill="Coral"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Row" Value="{Binding Y}"/>
<Setter Property="Grid.Column" Value="{Binding X}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
<ItemsControl Grid.Row="1"
ItemsSource="{Binding Mode=OneWay, Source={StaticResource Points.Grid}}"
ItemsPanel="{StaticResource Grid.Panel}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="Point">
<Ellipse Fill="LightBlue"
pa:Grid.Row="{Binding Y}"
pa:Grid.Column="{Binding X}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
【问题讨论】:
-
这与简化完全相反。放下它,认真地继续使用 ItemContainerStyle 中的绑定。您的帮助程序类被命名为 Grid,这非常令人困惑。
-
除此之外,这不是错误。你的假设是错误的。设置
AffectsParentArrange无效,因为 Grid 不是 Ellipse 的直接父级(在其中设置附加属性)。中间有一个 ItemsPresenter。 -
"Grid 不是 Ellipse 的直接父级 ..." - 我知道。注意方法 Row/ColumnChanged。设置值发生在其中。我的属性只是访问基本 Grod.Row/Column 的一种便捷方式。它们附加到直接在面板(网格)中的元素(ContentPresenter)。在动态可视化树中,所有这些都是可见的,基本属性具有我所期望的含义。但外观与属性值不匹配。
-
总的来说,我同意@Clemens 和 Mark Feldman 的观点,即 Grid 助手和 AP 方法在这里并没有真正的好处,但是为了考虑用 AP 解决这个问题的想法: - 在代码中,我们可以看到在更改处理程序中,在 ContentPresenter 上设置了 Grid.Row/Column,就像在第一个版本中所做的那样(没有 AP)。 (虽然没有明确说明,因此很难在代码中发现这种行为) - 我在本地尝试过,如果我在绑定中将 IsAsync 设置为 true,它对我来说可以正常工作。显然这不是正确的解决方案,但可以指示真正的潜在问题是什么。
标签: wpf grid row attached-properties itemspaneltemplate