公开ViewModel事件以绑定到自定义DependencyProperty

时间:2014-07-24 14:36:39

标签: c# wpf

是否可以从我的ViewModel公开一个公共事件,以便允许它绑定到我的View中的自定义DependencyProperty?

我的应用程序是使用.NET 4.5框架用C#编写的。它有一个MVVM架构,在视图中没有代码隐藏,自定义DependencyProperty类将View的WPF特定behvaiours绑定到ViewModel公开的属性。

我希望ViewModel能够公开一组属性,表示View需要响应的事件。例如,当顶级ViewModel对象即将被Disposed时,我希望WPF View实现通过关闭相应的Window来响应。当配置过程显示对话窗口,用户已经进入并确认信息并且ViewModel已将其传递给模型并且不再需要时,可能会发生这种情况。

我知道有许多问题专门用于解决ViewModel'的显示对话框。题;这不是其中之一,我有一个解决方案。

我已阅读了DependencyProperties的MSDN文档,但无法找到特定于绑定到事件属性的任何内容。

我想要实现的是类似于下面的代码。此代码构建,但在显示MainWindow时会导致典型的System.Windows.Data Error: 40 : BindingExpression path error: 'RequestCloseEvent' property not found错误。

我知道有很多问题需要帮助我调试我的System.Windows.Data错误:40问题&#39 ;;这可能(可能)也不是其中之一。(但如果确实如此,我会感到高兴。)

WindowBindableProperties.cs中自定义DependencyProperty的源代码:

using System;
using System.Threading;
using System.Windows;

namespace WpfEventBinding
{
    public static class WindowBindableProperties
    {
        #region ViewModelTerminatingEventProperty

        /// <summary>
        /// Register the ViewModelTerminatingEvent custom DependencyProperty.
        /// </summary>
        private static DependencyProperty _viewModelTerminatingEventProperty = 
            DependencyProperty.RegisterAttached
        (
            "ViewModelTerminatingEvent",
            typeof(ViewModelTerminatingEventHandler),
            typeof(WindowBindableProperties),
            new PropertyMetadata(null, ViewModelTerminatingEventPropertyChanged)
        );

        /// <summary>
        /// Identifies the ViewModelTerminatingEvent dependency property.
        /// </summary>
        public static DependencyProperty ViewModelTerminatingEventProperty
        { get { return _viewModelTerminatingEventProperty; } }

        /// <summary>
        /// Gets the attached ViewModelTerminatingEvent dependecy property.
        /// </summary>
        /// <param name="dependencyObject">The window attached to the WindowViewModel.</param>
        /// <returns>The ViewModelTerminatingEventHandler bound to this property</returns>
        public static ViewModelTerminatingEventHandler GetViewModelTerminatingEvent
        (DependencyObject dependencyObject)
        {
            return (dependencyObject.GetValue(ViewModelTerminatingEventProperty)
                as ViewModelTerminatingEventHandler);
        }

        /// <summary>
        /// Sets the ViewModelTerminatingEvent dependency property.
        /// </summary>
        public static void SetViewModelTerminatingEvent(
            DependencyObject dependencyObject,
            ViewModelTerminatingEventHandler value)
        {
            dependencyObject.SetValue(ViewModelTerminatingEventProperty, value);
        }

        /// <summary>
        /// Gets the ViewModelTerminatingEvent dependency property.
        /// </summary>
        private static void ViewModelTerminatingEventPropertyChanged(
            DependencyObject d, 
            DependencyPropertyChangedEventArgs e)
        {
            Window instance = d as Window;
            if (null != instance)
            {
                if (null != e.OldValue)
                {
                    throw new System.InvalidOperationException(
                    "ViewModelTerminatingEvent dependency property cannot be changed.");
                }

                if (null != e.NewValue)
                {
                    // Attach the Window.Close() method to the ViewModel's event
                    var newEvent = (e.NewValue as ViewModelTerminatingEventHandler);
                    newEvent += new ViewModelTerminatingEventHandler(() => instance.Close());
                }
            }
        }

        #endregion
    }
}

MainWindow.xaml的来源: (此示例包含代码隐藏以简化停止按钮实现。)

<Window x:Class="WpfEventBinding.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:v="clr-namespace:WpfEventBinding"
        v:WindowBindableProperties.ViewModelTerminatingEvent="{Binding Path=RequestCloseEvent}"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Button Content="{Binding Path=CloseCommandName}" Click="StopButton_Click" ></Button>
    </Grid>
</Window>

MainWindow.xaml.cs(代码隐藏)的来源:

using System.Windows;

namespace WpfEventBinding
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void StopButton_Click(object sender, RoutedEventArgs e)
        {
            MainWindowViewModel vm = (DataContext as MainWindowViewModel);
            if (null != vm)
            {
                vm.Stop();
            }
        }
    }
}

MainWindowViewModel.cs的来源:

using System;
using System.ComponentModel;

namespace WpfEventBinding
{
    public delegate void ViewModelTerminatingEventHandler();

    class MainWindowViewModel
        : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        // Raised by the ViewModel to indicate to the view that it is no longer required.
        // Causes System.Windows.Data Error: 40 : BindingExpression path error.  Is it
        // Possible to bind to an 'event' property?
        public event ViewModelTerminatingEventHandler RequestCloseEvent;

        // This has to have the public 'get' to allow binding.  Is there some way to
        // do the same thing for the 'event'?
        public String CloseCommandName { get; private set; }

        public MainWindowViewModel()
        {
            CloseCommandName = "Close";
        }

        internal void Stop()
        {
            ViewModelTerminatingEventHandler RaiseRequestCloseEvent =
                RequestCloseEvent;
            if (null != RaiseRequestCloseEvent)
            {
                RaiseRequestCloseEvent();
            }
        }

        internal void Start()
        {
            OnPropertyChanged("CloseCommandName");
            OnPropertyChanged("ViewModelTerminatingEvent");
        }

        private void OnPropertyChanged(String propertyName)
        {
            PropertyChangedEventHandler RaisePropertyChangedEvent = PropertyChanged;
            if (RaisePropertyChangedEvent != null)
            {
                var propertyChangedEventArgs = new PropertyChangedEventArgs(propertyName);
                RaisePropertyChangedEvent(this, propertyChangedEventArgs);
            }
        }
    }
}

App.xaml的来源:

<Application x:Class="WpfEventBinding.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             >
    <Application.Resources>
        <!-- Nothing to see here.  Move along... -->
    </Application.Resources>
</Application>

App.xaml.cs的来源

using System.Windows;

namespace WpfEventBinding
{
    public partial class App : Application
    {
        public App()
        {
            Startup += new StartupEventHandler(App_Startup);
        }

        void App_Startup(object sender, StartupEventArgs e)
        {
            MainWindowViewModel vm = new MainWindowViewModel();
            MainWindow window = new MainWindow();

            // Make sure this is set before attempting binding!
            window.DataContext = vm;
            vm.Start();
            window.Show();
        }
    }
}

似乎public event ViewModelTerminatingEventHandler RequestCloseEvent;语法不足以使数据绑定发生。类似的问题是,如果public String CloseCommandName { get; private set; }被声明为public String CloseCommandName;而没有{ get; private set; }。但是,事件没有{ get; private set; },它使用{add{} remove{}}语法(并且也没有解决问题)。

我尝试是否可能,如果是,我错过了什么?

2 个答案:

答案 0 :(得分:3)

查看关闭意味着窗口关闭事件。所以你基本上想要对视图中的事件做出反应。我最近读了这个arcticle,有一个很好的形象

enter image description here

并且还提到EventBehavior存在。

如果您不想要任何代码,最好的选择是使用行为。行为是一个简单的附加属性,它可以执行操作,例如上升的应用程序范围的命令,ViewModel可以在没有MVVM问题的情况下捕获。

以下是行为示例:

public static class FreezeBehavior
{
    public static bool GetIsFrozen(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsFrozenProperty);
    }
    public static void SetIsFrozen(DependencyObject obj, bool value)
    {
        obj.SetValue(IsFrozenProperty, value);
    }
    public static readonly DependencyProperty IsFrozenProperty =
        DependencyProperty.RegisterAttached("IsFrozen", typeof(bool), typeof(FreezeBehavior), new PropertyMetadata(OnIsFrozenChanged));

    private static void OnIsFrozenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if ((bool)e.NewValue)
        {
            var freezable = d as Freezable;
            if (freezable != null && freezable.CanFreeze)
                freezable.Freeze();
        }
    }
}

它像这样使用

<DropShadowEffect ShadowDepth="2" local:FreezeBehavior.IsFrozen="True"/>

它可以附加到任何freezable冻结它。在您的情况下,您希望订阅事件并调用命令或设置属性,或者通知ViewModel

答案 1 :(得分:1)

你所要求的是有点奇怪,但我不打算就此进行长时间的讨论......

您不绑定事件 - 您公开它们,视图可以为事件添加处理程序。

当然这意味着你必须在视图中加入一些代码 - 但如果与UI相关,这很好。要完成解耦,您的视图应该只将viewmodel作为接口处理,这意味着您可以在以后轻松交换视图模型。

(请注意,我已经避免谈论event triggers)。