是否可以从我的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{}}
语法(并且也没有解决问题)。
我尝试是否可能,如果是,我错过了什么?
答案 0 :(得分:3)
查看关闭意味着窗口关闭事件。所以你基本上想要对视图中的事件做出反应。我最近读了这个arcticle,有一个很好的形象
并且还提到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)。