如何克服ItemsPanelTemplate中的Grid.Row/Column的错误? [英] How to defeat a bug with Grid.Row / Column in ItemsPanelTemplate?

查看:61
本文介绍了如何克服ItemsPanelTemplate中的Grid.Row/Column的错误?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

创建了一个简单的Attached属性,以简化与元素模板的绑定.代替这个:

Created a simple Attached property to simplify bindings from an element template. Instead of this:

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

这是附件属性的完整代码:

Here is the complete code the attached property:

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.Row/Column的附加属性设置正确.窗口中的任何变化,元素都​​就位.

The property is nothing complicated. With Canvas, the same works fine. And with the Grid, problems arise when attaching a collection with elements or when adding the first element to the collection - elements are displayed in the Grid without regard to their position. Although when viewing in the visual tree and in the properties browser, the attached properties of Grid.Row / Column are set correctly. And at the slightest change in the window, the elements fall into place.

我认为这是一个坦率的错误.但是如何处理呢?

In my opinion, a frank bug. But how to deal with it?

完整的XAML演示代码:

Full XAML Demo Code:

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

输出如下:

应该是这样的:

推荐答案

最初设置 Grid.Row Grid.Column 似乎是一个计时问题.在ContentPresenter上不会导致另一个布局周期.

It seems to be kind of a timing issue that initially setting Grid.Row and Grid.Column on the ContentPresenter won't result in another layout cycle.

虽然删除整个helper类并直接在ItemContainerStyle中设置Grid属性显然是一个好主意,但是一个丑陋的解决方法是异步设置Grid属性,如下所示:

While it would obviously be a good idea to drop the whole helper class and set the Grid properties directly in an ItemContainerStyle, an ugly workaround would be to asynchronously set the Grid properties, like shown here:

public class ContentPresenterHelper
{
    public static readonly DependencyProperty ColumnProperty =
        DependencyProperty.RegisterAttached(
            "Column", typeof(int), typeof(ContentPresenterHelper),
            new PropertyMetadata(0, ColumnPropertyChanged));

    public static readonly DependencyProperty RowProperty =
        DependencyProperty.RegisterAttached(
            "Row", typeof(int), typeof(ContentPresenterHelper),
            new PropertyMetadata(0, RowPropertyChanged));

    public static int GetRow(DependencyObject o)
    {
        return (int)o.GetValue(RowProperty);
    }

    public static void SetColumn(DependencyObject o, int value)
    {
        o.SetValue(ColumnProperty, value);
    }

    public static int GetColumn(DependencyObject o)
    {
        return (int)o.GetValue(ColumnProperty);
    }

    public static void SetRow(DependencyObject o, int value)
    {
        o.SetValue(RowProperty, value);
    }

    private static void ColumnPropertyChanged(
        DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        o.Dispatcher.InvokeAsync(() =>
            FindContentPresenterParent(o)?.SetValue(
                Grid.ColumnProperty, (int)e.NewValue));
    }

    private static void RowPropertyChanged(
        DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        o.Dispatcher.InvokeAsync(() =>
            FindContentPresenterParent(o)?.SetValue(
                Grid.RowProperty, (int)e.NewValue));
    }

    private static ContentPresenter FindContentPresenterParent(DependencyObject element)
    {
        if (element == null)
        {
            return null;
        }

        var parent = VisualTreeHelper.GetParent(element);

        return (parent as ContentPresenter) ?? FindContentPresenterParent(parent);
    }
}

这篇关于如何克服ItemsPanelTemplate中的Grid.Row/Column的错误?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆