Как перерисовать ItemsControl ItemsPanel при изменении свойства в коллекции ItemsSource? - PullRequest
0 голосов
/ 09 октября 2018

Я создал собственный класс WeightedUniformGrid, который я использую в качестве ItemsPanel в ItemsControl.Каждый элемент сетки взвешивается с использованием свойства Weight в объектах в коллекции ItemsSource.

enter image description here

Но когда я изменяю свойство Weight в модели представления,это не сразу отображается в представлении.Мне нужно изменить размер окна для отображения WeightedUniformGrid с новыми значениями.

Как получить изменения свойств в коллекции ItemsSource, чтобы вызвать перерисовку ItemsControl?Я думаю, возможно, добавив DependencyProperty в WeightedUniformGrid, который будет и AffectsArrange, и AffectsMeasure.Но я не уверен, с чем я мог бы связать это.

Я долго искал это, и есть некоторые похожие вопросы (например, this one).Но я не смог адаптировать ни одну из них под свои нужды.

MainWindow.xaml:

<ItemsControl ItemsSource="{Binding Path=ElementGroupCollection}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <local:WeightedUniformGrid Rows="2" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Border BorderBrush="Black" BorderThickness="1"
                    RenderOptions.EdgeMode="Aliased">
                <Viewbox Stretch="Uniform">
                    <TextBlock Text="{Binding Path=Number}" Foreground="Black" FontSize="20" 
                                    HorizontalAlignment="Center" VerticalAlignment="Center"/>
                </Viewbox>
            </Border>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

MainWindowVM.cs:

public class MainWindowVM
{
    public MainWindowVM()
    {
        _elementGroupCollection = new ObservableCollection<ElementGroup>()
        {
            new ElementGroup(1, 60),
            new ElementGroup(2, 150),
            new ElementGroup(3, 90),
            new ElementGroup(4, 80),
            new ElementGroup(5, 60),
            new ElementGroup(6, 160)
        };
    }

    private ObservableCollection<ElementGroup> _elementGroupCollection;
    public ObservableCollection<ElementGroup> ElementGroupCollection
    {
        get { return _elementGroupCollection; }
    }
}

ElementGroup.cs:

public class ElementGroup : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public ElementGroup(int number, double? weight)
    {
        Number = number;
        Weight = (weight >= 0) ? weight : null;
    }

    public int Number { get; }

    private double? _weight;
    public double? Weight
    {
        get { return _weight; }
        set { SetNotify(ref _weight, value); }
    }

    public void SetNotify<T>(ref T storage,
                             T value,
                             [CallerMemberName] string propertyName = null)
    {
        storage = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

WeightedUniformGrid.cs:

public class WeightedUniformGrid : UniformGrid
{

    protected override Size MeasureOverride(Size constraint)
    {
        var size = base.MeasureOverride(constraint);
        double elementsPerRow = Math.Ceiling((double)Children.Count / Rows);
        double elementHeight = size.Height / Rows;
        double unweightedElementWidth = size.Width / elementsPerRow;
        for (int i = 0; i < Children.Count; ++i)
        {
            var child = (FrameworkElement)Children[i];
            var dc = child.DataContext;
            int rowNumber = (int)Math.Floor(i / elementsPerRow);
            double? weight = dc.GetType().GetProperty("Weight")?.GetValue(dc, null) as double?;
            if (weight == null) { weight = 100; }
            double weightedElementWidth = unweightedElementWidth * (double)weight / 100;
            child.Measure(new Size(weightedElementWidth, elementHeight));
        }
        return size;
    }

    protected override Size ArrangeOverride(Size arrangeSize)
    {
        var size = base.ArrangeOverride(arrangeSize);
        int elementsPerRow = (int)Math.Ceiling((double)Children.Count / Rows);
        double elementHeight = size.Height / Rows;
        double unweightedElementWidth = size.Width / elementsPerRow;
        double[] accumulatedWidthPerRow = new double[Rows];
        for (int i = 0; i < Children.Count; ++i)
        {
            var child = (FrameworkElement)Children[i];
            var dc = child.DataContext;
            int rowNumber = i / elementsPerRow;
            double? weight = dc.GetType().GetProperty("Weight")?.GetValue(dc, null) as double?;
            if (weight == null) { weight = 100; }
            double weightedElementWidth = unweightedElementWidth * (double)weight / 100;
            child.Arrange(new Rect(new Point(accumulatedWidthPerRow[rowNumber], rowNumber * elementHeight),
                                    new Point(accumulatedWidthPerRow[rowNumber] + weightedElementWidth, (rowNumber + 1) * elementHeight)));
            accumulatedWidthPerRow[rowNumber] += weightedElementWidth;
        }
        return size;
    }
}

1 Ответ

0 голосов
/ 09 октября 2018

Ваш WeightedUniformGrid не должен иметь прямого доступа к свойству Weight в DataContext его дочерних элементов.Помимо того, что это плохая практика, она не будет работать правильно, так как не существует механизма, который заставляет проход макета при изменении веса элемента модели представления.

Вместо этого должно быть присоединенное свойство, связанное с Weight.FrameworkPropertyMetadataOptions в прикрепленном свойстве заставит проход макета.

public class WeightedUniformGrid : UniformGrid
{
    public static readonly DependencyProperty WeightProperty =
        DependencyProperty.RegisterAttached(
            "Weight", typeof(double), typeof(WeightedUniformGrid),
            new FrameworkPropertyMetadata(double.NaN,
                FrameworkPropertyMetadataOptions.AffectsParentMeasure |
                FrameworkPropertyMetadataOptions.AffectsParentArrange));

    public static double GetWeight(UIElement element)
    {
        return (double)element.GetValue(WeightProperty);
    }

    public static void SetWeight(UIElement element, double value)
    {
        element.SetValue(WeightProperty, value);
    }

    protected override Size MeasureOverride(Size constraint)
    {
        var size = base.MeasureOverride(constraint);
        double elementsPerRow = Math.Ceiling((double)Children.Count / Rows);
        double elementHeight = size.Height / Rows;
        double unweightedElementWidth = size.Width / elementsPerRow;
        for (int i = 0; i < Children.Count; ++i)
        {
            var child = Children[i];
            int rowNumber = (int)Math.Floor(i / elementsPerRow);

            // get attached property value
            double weight = GetWeight(child);
            if (double.IsNaN(weight)) { weight = 100; }

            double weightedElementWidth = unweightedElementWidth * weight / 100;
            child.Measure(new Size(weightedElementWidth, elementHeight));
        }
        return size;
    }

    protected override Size ArrangeOverride(Size arrangeSize)
    {
        var size = base.ArrangeOverride(arrangeSize);
        int elementsPerRow = (int)Math.Ceiling((double)Children.Count / Rows);
        double elementHeight = size.Height / Rows;
        double unweightedElementWidth = size.Width / elementsPerRow;
        double[] accumulatedWidthPerRow = new double[Rows];
        for (int i = 0; i < Children.Count; ++i)
        {
            var child = Children[i];
            int rowNumber = i / elementsPerRow;

            // get attached property value
            double weight = GetWeight(child);
            if (double.IsNaN(weight)) { weight = 100; }

            double weightedElementWidth = unweightedElementWidth * (double)weight / 100;
            child.Arrange(new Rect(new Point(accumulatedWidthPerRow[rowNumber], rowNumber * elementHeight),
                                    new Point(accumulatedWidthPerRow[rowNumber] + weightedElementWidth, (rowNumber + 1) * elementHeight)));
            accumulatedWidthPerRow[rowNumber] += weightedElementWidth;
        }
        return size;
    }
}

Привязать вложенное свойство в ItemContainerStyle:

<ItemsControl ItemsSource="{Binding Path=ElementGroupCollection}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <local:WeightedUniformGrid Rows="2" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemContainerStyle>
        <Style TargetType="ContentPresenter">
            <Setter Property="local:WeightedUniformGrid.Weight" Value="{Binding Weight}"/>
        </Style>
    </ItemsControl.ItemContainerStyle>
    ...
</ItemsControl>
...