Предоставить событие ViewModel для привязки к пользовательскому DependencyProperty

1

Возможно ли открыть публичное событие из моего ViewModel таким образом, чтобы он мог привязываться к пользовательскому DependencyProperty в моем представлении?

Мое приложение написано на С# с использованием платформы.NET 4.5. Он имеет архитектуру MVVM без кода в представлении и пользовательские классы DependencyProperty для привязки специфических для WPF структур видимости свойств View, открытых ViewModel.

Существует набор свойств, которые я бы хотел, чтобы ViewModel мог показывать, представляющие события, на которые View должен отвечать. Например, когда объект ViewModel верхнего уровня будет удален, я бы хотел, чтобы реализация WPF View ответила, закрыв соответствующее окно. Это может произойти, когда процесс конфигурации отобразил диалоговое окно, пользователь включил и подтвердил информацию, и ViewModel передал ее в Модель и больше не требуется.

Я знаю, что есть много вопросов, которые являются специфическими для решения вопроса "показать диалог из ViewModel"; это не один из них, и у меня есть решение для этого.

Я прочитал документацию MSDN для DependencyProperties и не могу найти ничего конкретного для привязки к свойствам событий.

То, что я хотел бы достичь, похоже на код ниже. Этот код строит, но приводит к типичной System.Windows.Data Error: 40: BindingExpression path error: 'RequestCloseEvent' property not found при отображении MainWindow.

Я знаю, что есть много вопросов, которые идут по строкам: "Пожалуйста, помогите мне отладить мою ошибку System.Windows.Data: 40 issue"; это (возможно) и не одно из них. (Но я был бы счастлив, если бы все это было на самом деле.)

Источник для настраиваемого DependencyProperty в WindowBindableProperties.cs:

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 event
                    var newEvent = (e.NewValue as ViewModelTerminatingEventHandler);
                    newEvent += new ViewModelTerminatingEventHandler(() => instance.Close());
                }
            }
        }

        #endregion
    }
}

Источник для MainWindow.xaml: (Этот пример содержит код для упрощения реализации кнопки Stop.)

<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; синтаксиса недостаточно для allo привязки данных. Аналогичная проблема возникает, если public String CloseCommandName { get; private set; } public String CloseCommandName { get; private set; } public String CloseCommandName { get; private set; } объявляется как public String CloseCommandName; без { get; private set; } { get; private set; } { get; private set; }. Однако нет { get; private set; } { get; private set; } { get; private set; } для событий, которые используют {add{} remove{}} синтаксис (и это также не решает проблему).

Я пытаюсь сделать это, и если да, то что я пропустил?

Теги:
wpf

2 ответа

3
Лучший ответ

Просмотр закрытия означает событие закрытия окна. Поэтому вы в основном хотите реагировать на события в представлении. Я недавно прочитал эту статью, было очень хорошее изображение

Изображение 174551

а также упоминается существование 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"/>

Он может быть прикреплен к любому замораживаемому, чтобы заморозить его. В вашем случае вы хотите подписаться на событие и вызвать команду или установить свойство или что угодно, чтобы сообщить ViewModel.

  • 0
    Да! Все, что мне нужно сделать, это изменить его тип на Boolean и событие PropertyChanged уже является частью фреймворка. К сведению: в моем приложении есть еще один DependencyProperty, который позволяет ViewModel подписаться на событие Closed из окна. Это механизм, который позволяет ViewModel указывать представлению, что окно больше не требуется и может быть закрыто.
  • 0
    ... Этот метод даже позволяет моему ViewModel предоставлять другое свойство ViewModel, которое представляет DataContext для нового представления, которое оно хочет отобразить. Богоявление! Я бы удвоил голосование, если бы мог.
1

То, о чем вы просите, это странно, но я не собираюсь подробно обсуждать это....

Вы не привязываетесь к событиям - вы их раскрываете, а представление может добавлять обработчики для событий.

Конечно, это означает, что вам нужно будет поместить некоторый код в представление - но это прекрасно, если он связан с пользовательским интерфейсом. Чтобы завершить развязку, ваш взгляд должен обрабатывать только viewmodel как интерфейс, это означает, что вы можете легко поменять местами модели просмотра на более позднем этапе.

(Обратите внимание, что я избегал говорить о триггерах событий).

  • 0
    Мое приложение действительно использует интерфейс, который может представлять событие. Интерфейс был удален здесь, чтобы упростить пример. Я посмотрю на триггеры событий. Я начинаю думать, что мой базовый класс 'NoCodeBehindWindow' должен расти и включать немного больше (не совсем) кода, чтобы избежать реального кода в объектах xaml, которые его создают.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню