[英]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.