【问题标题】:Call NotifyPropertyChanged from different ViewModel从不同的 ViewModel 调用 NotifyPropertyChanged
【发布时间】:2018-02-27 14:35:57
【问题描述】:

我在 ItemsControl 中有一个 Canvas 设置,我用按钮填充它。每个按钮都有一个 X 和 Y 位置属性,用于在画布上定位按钮。

我试图做的是使项目位置相对于画布的大小。我的问题是当我调整画布大小时,按钮 ViewModel 没有收到 NotifyPropertyChanged 调用,因此它们的位置永远不会更新。

<ItemsControl ItemsSource="{Binding Buttons}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemContainerStyle>
        <Style TargetType="ContentPresenter">
            <Setter Property="Canvas.Top" Value="{Binding PosY, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
            <Setter Property="Canvas.Left" Value="{Binding PosX, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
        </Style>
    </ItemsControl.ItemContainerStyle>
    <ItemsControl.ItemTemplate>
        <DataTemplate DataType="{x:Type local:ToggleButton}">
            <local:ToggleButton DataContext="{Binding}"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

在 ToggleButtonViewModel 中:

    public double PosY {
        get {
            return (_Button.PositionVertical * Size[1]);
        }
    }

    public double PosX {
        get { return (_Button.PositionHorizontal * Size[0]); }
    }

Size 是一个 double[],具有画布的 X 和 Y 尺寸。我已经尝试将它存储在 CanvasViewModel 中,在这种情况下,我可以使用画布上的 SizeChanged 事件对其进行更新,但后来我不知道如何在大小更改时更新 ToggleButtonViewModel 中的 PosX 和 PosY。 我还尝试将大小作为静态参数存储在所有 ToggleButtonViewModel 中,但我无法使用带有静态参数的 NotifyPropertyChanged。

有什么想法可以让 PosX 和 PosY 在画布大小发生变化时更新?

【问题讨论】:

  • ItemsSource="{Binding Buttons}" 是您的项目控件中的数据上下文。所以 DataContext="{Binding}" 没用。如果你想使用 PosY 或 PosX,你已经在绑定过程中正确设置了数据上下文
  • 请注意,设置Mode=TwoWayUpdateSourceTrigger=PropertyChanged 是没有意义的。即使 UI 更改了 Top 或 Left 属性,对只读属性的双向绑定也毫无意义。而UpdateSourceTrigger=PropertyChanged与INotifyPropertyChanged接口的PropertyChanged事件完全没有关系。
  • 与其在 Canvas 大小变化时尝试更新 PosX 和 PosY,不如创建一个 custom Canvas 来处理 Left 和 Top(或其自己的一组附加属性)作为相对值范围 0..1。只需从 Canvas(或 Panel)派生并覆盖 ArrangeOverride。

标签: c# wpf inotifypropertychanged


【解决方案1】:

我认为这里的方法是实现一个覆盖您的排列逻辑的自定义面板,就像 Clemens 建议的那样,并为定位添加一些附加属性。 我为它做了一个非常快速的小解决方案,它似乎可以按您的意愿工作。

它是一个简单的面板“RelativePositionCanvas”,提供两个附加属性“RelativeX”和“RelativeY”,它们通常是双精度值范围从 0 到 1,用于设置左上角相对于面板大小的位置。

如果您想添加更多,例如将其对齐到右侧,或使大小相对,您只需为这些添加一些附加属性并在 ArrangeOverride 方法中使用它们。

public class RelativePositionCanvas : Panel
{
    #region Properties
    #region RelativeX
    public static readonly DependencyProperty RelativeXProperty =
        DependencyProperty.RegisterAttached(
                "RelativeX",
                typeof(double),
                typeof(RelativePositionCanvas),
                new FrameworkPropertyMetadata(
                        0.0d, 
                        new PropertyChangedCallback(OnPositioningChanged)));

    public static double GetRelativeX(DependencyObject d)
    {
        return (double)d.GetValue(RelativeXProperty);
    }
    public static void SetRelativeX(DependencyObject d, double value)
    {
        d.SetValue(RelativeXProperty, value);
    }
    #endregion

    #region RelativeY
    public static readonly DependencyProperty RelativeYProperty =
        DependencyProperty.RegisterAttached(
                "RelativeY",
                typeof(double),
                typeof(RelativePositionCanvas),
                new FrameworkPropertyMetadata(
                        0.0d,
                        new PropertyChangedCallback(OnPositioningChanged)));
    public static double GetRelativeY(DependencyObject d)
    {
        return (double)d.GetValue(RelativeYProperty);
    }
    public static void SetRelativeY(DependencyObject d, double value)
    {
        d.SetValue(RelativeYProperty, value);
    }
    #endregion

    private static void OnPositioningChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        UIElement uie = d as UIElement;
        if (uie != null)
        {
            RelativePositionCanvas p = VisualTreeHelper.GetParent(uie) as RelativePositionCanvas;
            if (p != null)
                p.InvalidateArrange();
        }
    }
    #endregion

    protected override Size MeasureOverride(Size availableSize)
    {
        Size childConstraint = new Size(Double.PositiveInfinity, Double.PositiveInfinity);

        foreach (UIElement child in InternalChildren)
        {
            if (child == null) { continue; }
            child.Measure(childConstraint);
        }

        return new Size();
    }

    protected override Size ArrangeOverride(Size arrangeSize)
    {
        var children = this.Children;
        foreach(UIElement child in children)
        {
            if (child == null) { continue; }

            var childRelativeX = GetRelativeX(child);
            var childRelativeY = GetRelativeY(child);

            var posX = childRelativeX * arrangeSize.Width;
            var posY = childRelativeY * arrangeSize.Height;
            child.Arrange(new Rect(new Point(posX, posY), child.DesiredSize));
        }
        return arrangeSize;
    }
}

要使用它,您必须将 ItemsPanelTemplate 替换为 RelativePositionCanvas,将 Canvas.TopCanvas.Left 属性替换为 RelativePositionCanvas.RelativeY RelativePositionCanvas.RelativeX 属性,应该没问题

【讨论】:

  • if (child == null) { continue; } 毫无意义。对于调用 InvalidateArrange 的 RelativeX/Y 属性,您也不需要 PropertyChangedCallback。只需向FrameworkPropertyMetadataOptions.AffectsArrange注册他们即可
  • 不对。属性上的 AffectsArrange 只会触发子级的重新排列,因为属性只是附加到它上,而不是在父级容器中,父级容器将负责在面板内排列按钮(RelativePositionCanvas 本身),所以在其他东西触发重新排列之前什么都不会发生。而且 Children 集合可能包含不是 UIElements 的条目,那您也可以在每个 .net 面板的源代码中找到此安全检查。
  • 然后使用AffectsParentArrange。 Children 或 InternalChildren 不会包含任何不是 UIElement 的内容。他们的类型是UIElementCollection
【解决方案2】:

Option1) 在 ToggleButtonViewModel 中,将 PosX 和 PosY 的定义更改为“正常”WPF 通知属性(我假设你知道我的意思),当更改时通知。当 Size 中的值发生变化时,请在 ViewModel 上设置属性。例如:

_viewModel.PosY = _Button.PositionVertical * Size[1];
_viewModel.PosX = _Button.PositionHorizontal * Size[0];

这很好地分离了关注点;但可能无法实现,具体取决于您在哪里持有哪些信息。

Option2) 向您的 ViewModel 添加一个方法,您可以调用该方法来指示 Size 已更改,您可以在其中:

RaiseNotifyPropertyChanged(nameof(PosX));
RaiseNotifyPropertyChanged(nameof(PosY));

(如果您需要,我可以为您提供 RaiseNotifyPropertyChanged 的​​实现,但它是您的 ViewModel 中可能已经拥有的非常标准的 WPF 样板代码。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-04-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-28
    相关资源
    最近更新 更多