简体   繁体   English

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

[英]Expose a ViewModel event for binding to a custom DependencyProperty

Is it possible to expose a public event from my ViewModel is such a way as to allow it to be bound to a custom DependencyProperty in my View? 是否可以通过允许将其绑定到View中的自定义DependencyProperty的方式来公开ViewModel中的公共事件?

My application is written in C# using the .NET 4.5 framework. 我的应用程序是使用.NET 4.5框架以C#编写的。 It has a MVVM architecture with no code-behind in the view and custom DependencyProperty classes to bind WPF-specific behvaiours of the View to properties exposed by the ViewModel. 它具有MVVM体系结构,在视图中没有任何代码,并且自定义DependencyProperty类将视图的WPF特定行为绑定到ViewModel公开的属性。

There is a set of properties that I would like the ViewModel to be able to expose that represent events to which the View needs to respond. 我希望ViewModel能够提供一组属性,这些属性表示View需要响应的事件。 For example, when a top level ViewModel object is about to be Disposed I would like the WPF View implementation to respond by closing the corresponding Window. 例如,当将要放置顶级ViewModel对象时,我希望WPF View实现通过关闭相应的Window来响应。 This could occur when a configuration process has displayed a Dialog Window, the user has enetered and confirmed the information and the ViewModel has passed it to the Model and is no longer required. 当配置过程显示了一个对话框窗口,用户已经确认并确认信息并且ViewModel已将其传递给模型并且不再需要时,可能会发生这种情况。

I am aware that there are many questions that are specific to solving the 'show dialog from ViewModel' question; 我知道有很多问题是解决“从ViewModel显示对话框”问题的; this is not one of them and I have a solution to that one. 这不是其中之一,我对此有解决方案。

I've read through the MSDN documentation for DependencyProperties and can't find anything specific to binding to event properties. 我已经阅读了有关DependencyProperties的MSDN文档,但是找不到绑定到事件属性的任何特定内容。

What I would like to achieve is something similar to the code below. 我想要实现的是与下面的代码相似的东西。 This code builds, but results in a typical System.Windows.Data Error: 40 : BindingExpression path error: 'RequestCloseEvent' property not found error when the MainWindow is shown. 这段代码会生成,但会导致典型的System.Windows.Data Error: 40 : BindingExpression path error: 'RequestCloseEvent' property not found显示MainWindow时System.Windows.Data Error: 40 : BindingExpression path error: 'RequestCloseEvent' property not found错误。

I am aware that there are many questions that go along the lines of 'please help me debug my System.Windows.Data Error: 40 issue'; 我知道有很多问题与“请帮助我调试System.Windows.Data错误:40问题”有关; this is (probably) not one of these either. 这(可能)也不是其中之一。 (But I'd be happy if that's all it really is.) (但仅此而已,我会很高兴。)

Source for the custom DependencyProperty in WindowBindableProperties.cs: 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
    }
}

Source for MainWindow.xaml: (This example contains code-behind to simplify the Stop Button implementation.) 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>

Source for MainWindow.xaml.cs (code behind): 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();
            }
        }
    }
}

Source for the MainWindowViewModel.cs: 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);
            }
        }
    }
}

Source for App.xaml: 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>

Source for App.xaml.cs 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();
        }
    }
}

It appears that the public event ViewModelTerminatingEventHandler RequestCloseEvent; 似乎public event ViewModelTerminatingEventHandler RequestCloseEvent; syntax is not sufficient to allo the data binding to occur. 语法不足以允许发生数据绑定。 A similar problem is see if the public String CloseCommandName { get; private set; } 类似的问题是看是否public String CloseCommandName { get; private set; } public String CloseCommandName { get; private set; } public String CloseCommandName { get; private set; } is declared as public String CloseCommandName; public String CloseCommandName { get; private set; }被声明为public String CloseCommandName; without the { get; private set; } 没有{ get; private set; } { get; private set; } { get; private set; } . { get; private set; } However, there is no { get; private set; } 但是,没有{ get; private set; } { get; private set; } { get; private set; } for events, which use the {add{} remove{}} syntax (and that does not solve the problem either). { get; private set; }事件,这些事件使用{add{} remove{}}语法(而且也不能解决问题)。

Is what I'm attempting possible and if so, what have I missed? 我正在尝试的可能吗?如果可以,我错过了什么?

View closing means window closing event. 视图关闭表示窗口关闭事件。 So you basically want react on events in the view. 因此,您基本上想对视图中的事件做出反应。 I read recently this arcticle , there was a very good image 我最近读了这个书信 ,有一个很好的形象

在此处输入图片说明

and also mentioned EventBehavior existence. 并且还提到了EventBehavior存在。

Your best bet, if you don't want any code behind, is to use behaviors. 如果您不想要任何代码,那么最好的选择就是使用行为。 Behavior is a simple attached property, which can perform actions, to example rising application-wide commands, which ViewModel can then catch without MVVM issues. 行为是一个简单的附加属性,可以执行操作,例如上升的应用程序级命令,然后ViewModel可以捕获这些事件而不会出现MVVM问题。

Here is an example of behavior: 这是一个行为示例:

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();
        }
    }
}

it's used like this 这样使用

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

It can be attached to any freezable to freeze it. 它可以连接到任何可冻结的文件以冻结它。 In your case you want to subscribe to event and invoke command or set property, or whatever to inform ViewModel . 在您的情况下,您想要订阅事件并调用command或set属性,或者通过任何方法通知ViewModel

What you are asking for is kinda weird, but I'm not going to get into a big long discussion about that.... 您的要求有点奇怪,但我不会对此进行长时间的讨论。

You don't bind to events - you expose them and the view can add handlers for the events. 您无需绑定到事件-您可以公开事件,并且视图可以为事件添加处理程序。

Of course this means you will have to put some code behind into the view - but this is fine provided it is UI related. 当然,这意味着您将不得不在视图中放置一些代码-但这很好,只要它与UI相关即可。 To complete the decoupling your view should only handle the viewmodel as an interface, this means you can easily swap out viewmodels at a later stage. 要完成分离,您的视图只应将视图模型作为接口来处理,这意味着您可以在以后的阶段轻松换出视图模型。

(Note that I've avoided talking about event triggers ). (请注意,我避免谈论事件触发器 )。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM