Хорошая или плохая практика для диалогов в wpf с MVVM?

В последнее время у меня возникла проблема создания диалогов добавления и редактирования для моего приложения wpf.

Все, что я хочу сделать в своем коде, было примерно таким. (Я в основном использую viewmodel первый подход с mvvm)

ViewModel, которая вызывает диалоговое окно:

var result = this.uiDialogService.ShowDialog("Dialogwindow Title", dialogwindowVM);
// Do anything with the dialog result

Как это работает?

Сначала я создал диалоговую службу:

public interface IUIWindowDialogService
{
    bool? ShowDialog(string title, object datacontext);
}

public class WpfUIWindowDialogService : IUIWindowDialogService
{
    public bool? ShowDialog(string title, object datacontext)
    {
        var win = new WindowDialog();
        win.Title = title;
        win.DataContext = datacontext;

        return win.ShowDialog();
    }
}

WindowDialog это специальное, но простое окно. Мне нужно, чтобы держать мой контент:

<Window x:Class="WindowDialog"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    Title="WindowDialog" 
    WindowStyle="SingleBorderWindow" 
    WindowStartupLocation="CenterOwner" SizeToContent="WidthAndHeight">
    <ContentPresenter x:Name="DialogPresenter" Content="{Binding .}">

    </ContentPresenter>
</Window>

Проблема с диалогами в wpf заключается вdialogresult = true может быть достигнуто только в коде. Вот почему я создал интерфейс для моегоdialogviewmodel реализовать это.

public class RequestCloseDialogEventArgs : EventArgs
{
    public bool DialogResult { get; set; }
    public RequestCloseDialogEventArgs(bool dialogresult)
    {
        this.DialogResult = dialogresult;
    }
}

public interface IDialogResultVMHelper
{
    event EventHandler<RequestCloseDialogEventArgs> RequestCloseDialog;
}

Всякий раз, когда моя ViewModel думает, что пришло времяdialogresult = true, а затем поднять это событие.

public partial class DialogWindow : Window
{
    // Note: If the window is closed, it has no DialogResult
    private bool _isClosed = false;

    public DialogWindow()
    {
        InitializeComponent();
        this.DialogPresenter.DataContextChanged += DialogPresenterDataContextChanged;
        this.Closed += DialogWindowClosed;
    }

    void DialogWindowClosed(object sender, EventArgs e)
    {
        this._isClosed = true;
    }

    private void DialogPresenterDataContextChanged(object sender,
                              DependencyPropertyChangedEventArgs e)
    {
        var d = e.NewValue as IDialogResultVMHelper;

        if (d == null)
            return;

        d.RequestCloseDialog += new EventHandler<RequestCloseDialogEventArgs>
                                    (DialogResultTrueEvent).MakeWeak(
                                        eh => d.RequestCloseDialog -= eh;);
    }

    private void DialogResultTrueEvent(object sender, 
                              RequestCloseDialogEventArgs eventargs)
    {
        // Important: Do not set DialogResult for a closed window
        // GC clears windows anyways and with MakeWeak it
        // closes out with IDialogResultVMHelper
        if(_isClosed) return;

        this.DialogResult = eventargs.DialogResult;
    }
 }

Теперь, по крайней мере, я должен создатьDataTemplate в моем файле ресурсов (app.xaml или что-то):

<DataTemplate DataType="{x:Type DialogViewModel:EditOrNewAuswahlItemVM}" >
        <DialogView:EditOrNewAuswahlItem/>
</DataTemplate>

Ну вот и все, теперь я могу вызывать диалоги из моих моделей:

 var result = this.uiDialogService.ShowDialog("Dialogwindow Title", dialogwindowVM);

Теперь мой вопрос, вы видите какие-либо проблемы с этим решением?

Редактировать: для полноты. ViewModel должен реализоватьIDialogResultVMHelper и тогда он может поднять его в течениеOkCommand или что-то вроде этого:

public class MyViewmodel : IDialogResultVMHelper
{
    private readonly Lazy<DelegateCommand> _okCommand;

    public MyViewmodel()
    {
         this._okCommand = new Lazy<DelegateCommand>(() => 
             new DelegateCommand(() => 
                 InvokeRequestCloseDialog(
                     new RequestCloseDialogEventArgs(true)), () => 
                         YourConditionsGoesHere = true));
    }

    public ICommand OkCommand
    { 
        get { return this._okCommand.Value; } 
    }

    public event EventHandler<RequestCloseDialogEventArgs> RequestCloseDialog;
    private void InvokeRequestCloseDialog(RequestCloseDialogEventArgs e)
    {
        var handler = RequestCloseDialog;
        if (handler != null) 
            handler(this, e);
    }
 }

РЕДАКТИРОВАТЬ 2: я использовал код отсюда, чтобы сделать мой регистр EventHandler слабым:
http://diditwith.net/2007/03/23/SolvingTheProblemWithEventsWeakEventHandlers.aspx
(Веб-сайт больше не существует,WebArchive Mirror)

public delegate void UnregisterCallback<TE>(EventHandler<TE> eventHandler) 
    where TE : EventArgs;

public interface IWeakEventHandler<TE> 
    where TE : EventArgs
{
    EventHandler<TE> Handler { get; }
}

public class WeakEventHandler<T, TE> : IWeakEventHandler<TE> 
    where T : class 
    where TE : EventArgs
{
    private delegate void OpenEventHandler(T @this, object sender, TE e);

    private readonly WeakReference mTargetRef;
    private readonly OpenEventHandler mOpenHandler;
    private readonly EventHandler<TE> mHandler;
    private UnregisterCallback<TE> mUnregister;

    public WeakEventHandler(EventHandler<TE> eventHandler,
                                UnregisterCallback<TE> unregister)
    {
        mTargetRef = new WeakReference(eventHandler.Target);

        mOpenHandler = (OpenEventHandler)Delegate.CreateDelegate(
                           typeof(OpenEventHandler),null, eventHandler.Method);

        mHandler = Invoke;
        mUnregister = unregister;
    }

    public void Invoke(object sender, TE e)
    {
        T target = (T)mTargetRef.Target;

        if (target != null)
            mOpenHandler.Invoke(target, sender, e);
        else if (mUnregister != null)
        {
            mUnregister(mHandler);
            mUnregister = null;
        }
    }

    public EventHandler<TE> Handler
    {
        get { return mHandler; }
    }

    public static implicit operator EventHandler<TE>(WeakEventHandler<T, TE> weh)
    {
        return weh.mHandler;
    }
}

public static class EventHandlerUtils
{
    public static EventHandler<TE> MakeWeak<TE>(this EventHandler<TE> eventHandler, 
                                                    UnregisterCallback<TE> unregister)
        where TE : EventArgs
    {
        if (eventHandler == null)
            throw new ArgumentNullException("eventHandler");

        if (eventHandler.Method.IsStatic || eventHandler.Target == null)
            throw new ArgumentException("Only instance methods are supported.",
                                            "eventHandler");

        var wehType = typeof(WeakEventHandler<,>).MakeGenericType(
                          eventHandler.Method.DeclaringType, typeof(TE));

        var wehConstructor = wehType.GetConstructor(new Type[] 
                             { 
                                 typeof(EventHandler<TE>), typeof(UnregisterCallback<TE>) 
                             });

        IWeakEventHandler<TE> weh = (IWeakEventHandler<TE>)wehConstructor.Invoke(
                                        new object[] { eventHandler, unregister });

        return weh.Handler;
    }
}
 Moe4567301 июл. 2016 г., 01:05
Привет! Опоздавший здесь. Я не понимаю, как ваша служба имеет ссылку на WindowDialog. Какова иерархия ваших моделей? На мой взгляд, представление содержит ссылку на сборку Viewmodel и Viewmodel на сборки Service и Model. Таким образом, сервисный уровень не будет знать представления WindowDialog. Что мне не хватает?
 Matthew S22 янв. 2018 г., 23:00
Это может быть лучше опубликовано в codereview.stackexchange.com
 reggaeguitar25 апр. 2014 г., 00:07
 Adiel Yaacov02 янв. 2014 г., 12:47
вам, вероятно, не хватает xmlns: x = "schemas.microsoft.com/winfx/2006/xamlmsgstr "ссылка в вашем WindowDialog XAML.
 blindmeis17 мар. 2015 г., 12:26
я всегда использую подход viewmodel first с DataTemplates в моих приложениях, кроме как в главном окне. так что мне не нужен UserControl.DataContext в любом из моих просмотров :)
 reggaeguitar23 апр. 2014 г., 00:04
На самом деле пространство имен - это xmlns: x = "[http: //] schemas.microsoft.com/winfx/2006/xaml" без скобок
 Adil Mammadov17 мар. 2015 г., 07:10
Привет @blindmeis. Я знаю, что прошло почти 5 лет, но я застрял на закрытии диалоговой части. Можете ли вы объяснить эту часть?
 Hank31 янв. 2018 г., 07:54
Привет @blindmeis, просто пытаюсь обернуть голову вокруг этой концепции, я не думаю, что есть какой-нибудь пример проекта, который я могу выбрать? Есть много вещей, в которых я запутался.
 blindmeis17 мар. 2015 г., 07:28
Ваша viewmodel должна реализовать IDialogResultVMHelper и вызвать событие, конечно. смотри мое редактирование
 Adil Mammadov17 мар. 2015 г., 07:53
@blindmeis спасибо за ваш ответ. Я уже реализовал этот интерфейс. Моя проблема заключалась в том, что я также установил контекст данных в xaml моего пользовательского элемента управления. как <UserControl.DataContext> </UserControl.DataContext>. Из-за этого в части InitializeComponenet () был создан новый экземпляр этого класса, поэтому событие было удалено. После одного дня я удалил настройки контекста данных из xaml, и это работает как шарм. Спасибо за отличный пост. Это очень полезно.

Ответы на вопрос(3)

Если вы говорите об диалоговых окнах, а не только о всплывающих окнах сообщений, рассмотрите мой подход ниже. Ключевые моменты:

Я передаю ссылку наModule Controller в конструктор каждогоViewModel (вы можете использовать инъекцию).ТотModule Controller имеет публичные / внутренние методы для создания диалоговых окон (просто создание, без возврата результата). Следовательно, чтобы открыть диалоговое окно вViewModel Я пишу:controller.OpenDialogEntity(bla, bla...)Каждое диалоговое окно уведомляет о своем результате (например,Хорошо, Сохранить, отменитьи т. д.) черезСлабые события, Если вы используете PRISM, то публиковать уведомления прощеэтот EventAggregator.Для обработки результатов диалога я использую подписку на уведомления (сноваСлабые события а такжеEventAggregator в случае ПРИЗМЫ). Чтобы уменьшить зависимость от таких уведомлений, используйте независимые классы со стандартными уведомлениями.

Плюсы:

Меньше кода. Я не против использования интерфейсов, но я видел слишком много проектов, в которых чрезмерное использование интерфейсов и уровней абстракции вызывает больше проблем, чем помощи.Открывать диалоговые окна черезModule Controller это простой способ избежать сильных ссылок и все еще позволяет использовать макеты для тестирования.Уведомление через слабые события уменьшает количество потенциальных утечек памяти.

Минусы:

Не легко отличить требуемое уведомление от других в обработчике. Два решения:отправьте уникальный токен при открытии диалогового окна и проверьте этот токен в подпискеиспользовать общие классы уведомлений<T> гдеT это перечисление сущностей (или для простоты это может быть тип ViewModel).Для проекта должно быть соглашение об использовании классов уведомлений для предотвращения их дублирования.Для очень больших проектовModule Controller могут быть перегружены методами создания окон. В этом случае лучше разбить его на несколько модулей.

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

Я использую почти идентичный подход уже несколько месяцев, и я очень доволен им (т.е. я еще не чувствовал желания полностью переписать его ...)

В моей реализации я используюIDialogViewModel который выставляет такие вещи, как заголовок, стандартные кнопки, чтобы показать (для того, чтобы иметь согласованный вид во всех диалогах),RequestClose событие, и несколько других вещей, чтобы иметь возможность контролировать размер окна и поведение

 blindmeis28 сент. 2010 г., 07:42
THX, заголовок должен действительно идти в моей IDialogViewModel. другие свойства, такие как размер, стандартная кнопка, которую я оставлю, потому что все это, по крайней мере, исходит из таблицы данных.
 Thomas Levesque04 дек. 2015 г., 17:12
@Thomas, объекты, представляющие кнопки. Вы никогда не должны ссылаться на объекты пользовательского интерфейса в ViewModel.
 Thomas04 дек. 2015 г., 16:49
@ThomasLevesque кнопки, содержащиеся в вашей ViewModel, они на самом деле объекты кнопок пользовательского интерфейса или объекты, представляющие кнопки?
 blindmeis28 сент. 2010 г., 14:38
MHH THX для этой информации :)
 Thomas Levesque28 сент. 2010 г., 11:15
Это то, что я сделал сначала, просто используйте SizeToContent, чтобы контролировать размер окна. Но в одном случае мне нужно было изменить размер окна, поэтому мне пришлось немного его настроить ...
Решение Вопроса

Это хороший подход, и я использовал подобные в прошлом. Действуй!

Одна небольшая вещь, которую я определенно сделаю, это заставит событие получать логическое значение, когда вам нужно установить «false» в DialogResult.

event EventHandler<RequestCloseEventArgs> RequestCloseDialog;

и класс EventArgs:

public class RequestCloseEventArgs : EventArgs
{
    public RequestCloseEventArgs(bool dialogResult)
    {
        this.DialogResult = dialogResult;
    }

    public bool DialogResult { get; private set; }
}
 blindmeis28 сент. 2010 г., 07:43
спасибо, я буду менять свое мероприятие :)
 Julian Dominguez10 февр. 2011 г., 18:57
Вы правы, я исправил пример кода. Спасибо
 Matthew S05 февр. 2018 г., 21:44
Что если вместо использования сервисов использовать своего рода Callback для облегчения взаимодействия с ViewModel и View? Например, View выполняет команду в ViewModel, затем, когда все сказано и сделано, ViewModel запускает функцию обратного вызова для View, чтобы отобразить результаты команды. Я все еще не могу заставить свою команду работать с использованием Служб для обработки диалоговых взаимодействий во ViewModel.
 Slauma09 февр. 2011 г., 21:41
Думаю вместоbool там должны быть пользовательские EventArgs, полученные из базыEventArgs класс, который содержитbool имущество.EventHandler Делегат имеет ограничение класса на универсальный параметр, который требует, чтобы тип был получен изEventArgs, Сbool как общий параметр, он не компилируется (по крайней мере, не в VS2010, я не знаю, изменилось ли это по сравнению с более ранними версиями).

Ваш ответ на вопрос