通过MVVM模式缩放绘图?

时间:2014-01-16 17:04:18

标签: c# wpf mvvm

我有一个复杂的Plot RenderingControl,我已将其放入View中。处理MVVM模式的缩放的理想方法是什么?我希望用户能够通过在绘图上单击并拖动来进行缩放。

我看到的一种方法是获取Plot控件的MouseMove,MouseUp,MouseDown事件,并将它们连接到PlotViewModel中的命令。现在,为了响应命令,ViewModel可以更新它的ZoomLevel属性,该属性可以绑定到视图,并使视图放大。当用户点击并拖动时,我还想显示一个矩形,指示将要放置的区域被放大。在PlotViewModel中保留AnnotationViewModel以进行缩放预览是否有意义?

另一种方法是在视图中处理所有内容,而根本不涉及ViewModel。

我看到的主要区别是捕获ViewModel中的行为将使该行为比在View中更可重用。虽然我感觉底层的Plot控件和生成的View足够复杂,但无论如何都不会有太多重复使用的机会。你觉得怎么样?

1 个答案:

答案 0 :(得分:0)

我认为有几种方法可以解决您的问题。 HighCore对,当他说Zoom适用于View时,所以建议将它放在View边。但是有其他选择,我们在下面考虑它们。不幸的是,我没有处理Plot RenderingControl,所以我将描述一个基于抽象的解决方案,独立于控件。

AttachedBehavior

在这种情况下,我会尝试通过附加行为识别控件的所有可能工作,它非常适合MVVM模式,并且可以在Blend中使用。

工作示例

View中,定义了控件并附加了行为,如下所示:

<RenderingControl Name="MyPlotControl"
                  AttachedBehaviors:ZoomBehavior.IsStart="True" ... />

在代码隐藏中:

public static class ZoomBehavior
{
    public static readonly DependencyProperty IsStartProperty;

    public static void SetIsStart(DependencyObject DepObject, bool value)
    {
        DepObject.SetValue(IsStartProperty, value);
    }

    public static bool GetIsStart(DependencyObject DepObject)
    {
        return (bool)DepObject.GetValue(IsStartProperty);
    }

    static ZoomBehavior()
    {
        IsStartMoveProperty = DependencyProperty.RegisterAttached("IsStart",
                                                            typeof(bool),
                                                            typeof(ZoomBehavior),
                                                            new UIPropertyMetadata(false, IsStart));
    }

    private static void IsStart(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        UIElement uiElement = sender as UIElement;

        if (uiElement != null)
        {
            if (e.NewValue is bool && ((bool)e.NewValue) == true)
            {
                uiElement.MouseDown += new MouseButtonEventHandler(ObjectMouseDown);
                uiElement.MouseMove += new MouseEventHandler(ObjectMouseMove);
                uiElement.MouseUp += new MouseButtonEventHandler(ObjectMouseUp);
            }
        }
    }

    // Below is event handlers
}

一旦为属性IsStart设置为 true ,就会触发PropertyChanged处理程序,并为包含基本逻辑的事件设置处理程序。

为了在您的行为中传输其他数据,请注册其他依赖项属性,例如:

<RenderingControl Name="MyPlotControl"
                  AttachedBehaviors:ZoomBehavior.IsStart="True"
                  AttachedBehaviors:ZoomBehavior.ZoomValue="50" />

在代码隐藏中:

// ... Here registered property

public static void SetZoomValue(DependencyObject DepObject, int value)
{
    DepObject.SetValue(ZoomValueProperty, value);
}

public static int GetZoomValue(DependencyObject DepObject)
{
    return (int)DepObject.GetValue(ZoomValueProperty);
}

// ... Somewhere in handler

int value = GetZoomValue(plotControl);

要检索有关行为的数据,我使用单例模式。此模式表示对象的全局静态访问点,并且必须保证该类的单个实例的存在。

使用此模式的示例(取自行为,使用View中的时间显示):

public class TimeBehavior : INotifyPropertyChanged
{
    // Global instance
    private static TimeBehavior _instance = new TimeBehavior();

    public static TimeBehavior Instance
    {
        get
        {
            return _instance;
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private string _currentTime = DateTime.Now.ToString("HH:mm");

    public string CurrentTime
    {
        get
        {
            return _currentTime;
        }

        set
        {
            if (_currentTime != value)
            {
                _currentTime = value;

                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("CurrentTime"));
                }
            }
        }
    }

    private string _currentDayString = ReturnDayString();

    public string CurrentDayString
    {
        get
        {
            return _currentDayString;
        }

        set
        {
            if (_currentDayString != value)
            {
                _currentDayString = value;

                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("CurrentDayString"));
                }
            }
        }
    }

    private string _currentMonthAndDayNumber = ReturnMonthAndDayNumber();

    public string CurrentMonthAndDayNumber
    {
        get
        {
            return _currentMonthAndDayNumber;
        }

        set
        {
            if (_currentMonthAndDayNumber != value)
            {
                _currentMonthAndDayNumber = value;

                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("CurrentMonthAndDayNumber"));
                }
            }
        }
    }

    public static readonly DependencyProperty IsTimerStartProperty;

    public static void SetIsTimerStart(DependencyObject DepObject, bool value)
    {
        DepObject.SetValue(IsTimerStartProperty, value);
    }

    public static bool GetIsTimerStart(DependencyObject DepObject)
    {
        return (bool)DepObject.GetValue(IsTimerStartProperty);
    }

    static void OnIsTimerStartPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        if (e.NewValue is bool && ((bool)e.NewValue) == true)
        {
            DispatcherTimer timer = new DispatcherTimer();
            timer.Interval = TimeSpan.FromMilliseconds(1000);
            timer.Tick += new EventHandler(timer_Tick);
            timer.Start();
        }
    }

    static TimeBehavior() 
    {
        IsTimerStartProperty = DependencyProperty.RegisterAttached("IsTimerStart",
                                                                typeof(bool),
                                                                typeof(TimeBehavior),
                                                                new PropertyMetadata(new PropertyChangedCallback(OnIsTimerStartPropertyChanged)));
    }

    private static void timer_Tick(object sender, EventArgs e)
    {
        _instance.CurrentTime = DateTime.Now.ToString("HH:mm");
        _instance.CurrentDayString = ReturnDayString();
        _instance.CurrentMonthAndDayNumber = ReturnMonthAndDayNumber();
    }
}

访问View中的数据:

<TextBlock Name="WidgetTimeTextBlock"
           Text="{Binding Path=CurrentTime,
                          Source={x:Static Member=AttachedBehaviors:TimeBehavior.Instance}}" />

替代

通过界面在视图中工作

这种方式的一点是,我们通过View通过ViewModel调用方法,该方法执行所有工作,而他不了解View。这是通过界面的操作和这里描述的井完成的:

Talk to View

使用ServiceLocator

ServiceLocator允许您在ViewModel中工作,而不违反MVVM的原则。您有一个RegisterService方法,您可以在其中注册要提供的服务实例,以及用于获取所需服务的GetService方法。

可在此处找到更多信息:

Service Locator in MVVM