从不同的ViewModel调用NotifyPropertyChanged

时间:2018-02-27 14:35:58

标签: c# wpf inotifypropertychanged

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

我想要做的是使项目位置相对于画布的大小。我的问题是当我调整画布大小时,按钮ViewModels没有得到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]); }
    }

尺寸是双倍[],具有画布的X和Y尺寸。我已经尝试将它存储在CanvasViewModel中,在这种情况下我可以在画布上使用SizeChanged事件更新它,但是当我改变尺寸时,我无法弄清楚如何更新ToggleButtonViewModel中的PosX和PosY。 我也尝试将大小存储为所有ToggleButtonViewModel中的静态参数,但我不能使用带有静态参数的NotifyPropertyChanged。

任何想法如何在画布大小改变时如何更新PosX和PosY?

2 个答案:

答案 0 :(得分:0)

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

它是一个简单的Panel&#34; RelativePositionCanvas &#34;,提供两个附加的属性&#34; RelativeX &#34;和&#34; RelativeY &#34;它们是双精度值,通常在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;
    }
}

要使用它,你必须用RelativePositionCanvas替换ItemsPanelTemplate,用 RelativePositionCanvas.RelativeY替换 Canvas.Top Canvas.Left 属性。 RelativePositionCanvas.RelativeX 属性,应该没问题

答案 1 :(得分:0)

选项1)在ToggleButtonViewModel中,将PosX和PosY的定义更改为“正常”WPF通知属性(我假设您知道我的意思)更改后通知。当“大小”中的值更改时,请在ViewModel上设置属性。例如:

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

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

选项2)向ViewModel添加一个方法,您可以调用该方法来指示大小已更改,您可以在其中:

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

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