【问题标题】:How to redraw ItemsControl ItemsPanel when property in the ItemsSource collection is changed?更改 ItemsSource 集合中的属性时如何重绘 ItemsControl ItemsPanel?
【发布时间】:2019-03-13 14:35:23
【问题描述】:

我创建了一个自定义的 WeightedUniformGrid 类,我将其用作 ItemsControl 中的 ItemsPanel。每个网格元素都使用 ItemsSource 集合中对象中的 Weight 属性进行加权。

但是当我在视图模型中更改权重属性时,它不会立即显示在视图中。我必须更改要使用新值绘制的 WeightedUniformGrid 的窗口大小。

如何获取 ItemsSource 集合中的属性更改以导致 ItemsControl 被重绘?我在想也许在 WeightedUniformGrid 中添加一个 DependencyProperty,这将包括 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;
    }
}

【问题讨论】:

    标签: c# wpf itemscontrol


    【解决方案1】:

    您的 WeightedUniformGrid 不得直接访问其子元素的 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>
    

    【讨论】:

    • 为什么要附加 DP? AFAIK 一个普通的 DP 就足够了
    • @SirRufo 为什么 Grid.Row 是附加属性?
    • 谢谢你,克莱门斯。您的解决方案效果很好。 :-)
    猜你喜欢
    • 2012-04-10
    • 2021-11-22
    • 1970-01-01
    • 1970-01-01
    • 2016-01-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多