首页 > 解决方案 > Canvas ActualWidth 和 ActualHeight 以 MVVM 方式传入 ViewModel

问题描述

最近我遇到了一个看似简单的问题:我想在我正在处理的一个应用程序中使用 Mode=OneWayToSource 将画布的 Width 和 Height 参数推送到 ViewModel 中。原来 Mode=OneWayToSource 对此不起作用,根据微软的说法,这是 WPF 的一个功能。好的。

无论如何,一段时间以来,我一直在思考如何在遵守 MVVM 原则的同时以最好的方式(即最短和最不混乱)做到这一点。我想出了以下几点:

/// <summary>
/// Sends graph dimensions to <see cref="GraphViewModel"/>
/// </summary>
public class SendGraphDimensionsToViewModelProperty : BaseAttachedProperty<SendGraphDimensionsToViewModelProperty, bool>
{
    public override void OnValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        if (!(sender is Canvas canvas))
            return;

        canvas.SizeChanged += (sender, e) =>
        {
            if (canvas.DataContext is GraphViewModel graph)
            {
                graph.Width = canvas.ActualWidth;
                graph.Height = canvas.ActualHeight;
            }
        };
    }
}

然后我只是将属性附加到带有 GraphViewModel 实例的 DataContext 的画布上:

local:SendGraphDimensionsToViewModelProperty.Value="True"

这按预期工作。基本上,附加属性的值在加载时从 null 更改为 true,这会触发 SizeChanged 事件,该事件监视 Canvas 的 ActualHeight 和 ActualWidth。

我的问题是:您是否建议对我的代码进行任何改进(例如与可能的内存泄漏等有关)?我只想在学习新东西的同时“MinMax”。我是一名初级开发人员,我不能真正向 IRL 咨询这个问题,因为我认识的人都没有 MVVM。我正在尝试以模块化方式编写所有内容,这意味着我想编写一次,然后在我再次需要时复制它以加快我开发应用程序的速度。

期待您的建议。

标签: c#wpfmvvm

解决方案


我对这个问题的解决方案是一组附加属性,它们可以应用于任何 FrameworkElement 实例。

public static class perSizeBindingHelper
{
    public static readonly DependencyProperty ActiveProperty = DependencyProperty.RegisterAttached(
        "Active",
        typeof(bool),
        typeof(perSizeBindingHelper),
        new FrameworkPropertyMetadata(OnActiveChanged));

    public static bool GetActive(FrameworkElement frameworkElement)
    {
        return (bool) frameworkElement.GetValue(ActiveProperty);
    }

    public static void SetActive(FrameworkElement frameworkElement, bool active)
    {
        frameworkElement.SetValue(ActiveProperty, active);
    }

    public static readonly DependencyProperty BoundActualWidthProperty = DependencyProperty.RegisterAttached(
        "BoundActualWidth",
        typeof(double),
        typeof(perSizeBindingHelper));

    public static double GetBoundActualWidth(FrameworkElement frameworkElement)
    {
        return (double) frameworkElement.GetValue(BoundActualWidthProperty);
    }

    public static void SetBoundActualWidth(FrameworkElement frameworkElement, double width)
    {
        frameworkElement.SetValue(BoundActualWidthProperty, width);
    }

    public static readonly DependencyProperty BoundActualHeightProperty = DependencyProperty.RegisterAttached(
        "BoundActualHeight",
        typeof(double),
        typeof(perSizeBindingHelper));

    public static double GetBoundActualHeight(FrameworkElement frameworkElement)
    {
        return (double) frameworkElement.GetValue(BoundActualHeightProperty);
    }

    public static void SetBoundActualHeight(FrameworkElement frameworkElement, double height)
    {
        frameworkElement.SetValue(BoundActualHeightProperty, height);
    }

    private static void OnActiveChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        if (!(dependencyObject is FrameworkElement frameworkElement))
        {
            return;
        }

        if ((bool) e.NewValue)
        {
            frameworkElement.SizeChanged += OnFrameworkElementSizeChanged;
            UpdateObservedSizesForFrameworkElement(frameworkElement);
        }
        else
        {
            frameworkElement.SizeChanged -= OnFrameworkElementSizeChanged;
        }
    }

    private static void OnFrameworkElementSizeChanged(object sender, SizeChangedEventArgs e)
    {
        if (sender is FrameworkElement frameworkElement)
        {
            UpdateObservedSizesForFrameworkElement(frameworkElement);
        }
    }

    private static void UpdateObservedSizesForFrameworkElement(FrameworkElement frameworkElement)
    {
        frameworkElement.SetCurrentValue(BoundActualWidthProperty, frameworkElement.ActualWidth);
        frameworkElement.SetCurrentValue(BoundActualHeightProperty, frameworkElement.ActualHeight);
    }
}

用法是...

<Grid ...
    vhelp:perSizeBindingHelper.Active="True"
    vhelp:perSizeBindingHelper.BoundActualHeight="{Binding GridHeight, Mode=OneWayToSource}"
    vhelp:perSizeBindingHelper.BoundActualWidth="{Binding GridWidth, Mode=OneWayToSource}">

推荐阅读