Вопрос по mvvm, wpf – Диалог открытия файла MVVM

42

Хорошо, я действительно хотел бы знать, как опытные разработчики MVVM обрабатывают диалог openfile в WPF.

Я действительно не хочу делать это в моей ViewModel (где на "Browse" ссылается посредством DelegateCommand)

<code>void Browse(object param)
{
    //Add code here
    OpenFileDialog d = new OpenFileDialog();

    if (d.ShowDialog() == true)
    {
        //Do stuff
    }
}
</code>

Потому что я считаю, что это идет вразрез с методологией MVVM.

Что я делаю?

Смотрите сообщение в блоге Олли Ричес & lt;awkwardcoder.blogspot.nl/2012/03/…& GT; где объясняется, как передавать сообщения в WPF с чистым разделением View & amp; ViewModel. amutter

Ваш Ответ

6   ответов
9

но, увы, моя репутация недостаточно высока, чтобы сделать это.

Наличие вызова, такого как OpenFileDialog (), нарушает шаблон MVVM, поскольку подразумевает представление (диалог) в модели представления. Модель представления может вызывать что-то вроде GetFileName () (то есть, если простого связывания недостаточно), но не должно заботиться о том, как получить имя файла.

2

In ViewModel I have defined an interface and work with it in ViewModel In View I have implemented this interface.

CommandImpl не реализован в коде ниже.

ViewModel:
namespace ViewModels.Interfaces
{
    using System.Collections.Generic;
    public interface IDialogWindow
    {
        List<string> ExecuteFileDialog(object owner, string extFilter);
    }
}

namespace ViewModels
{
    using ViewModels.Interfaces;
    public class MyViewModel
    {
        public ICommand DoSomeThingCmd { get; } = new CommandImpl((dialogType) =>
        {
            var dlgObj = Activator.CreateInstance(dialogType) as IDialogWindow;
            var fileNames = dlgObj?.ExecuteFileDialog(null, "*.txt");
            //Do something with fileNames..
        });
    }
}
View:
namespace Views
{
    using ViewModels.Interfaces;
    using Microsoft.Win32;
    using System.Collections.Generic;
    using System.Linq;
    using System.Windows;

    public class OpenFilesDialog : IDialogWindow
    {
        public List<string> ExecuteFileDialog(object owner, string extFilter)
        {
            var fd = new OpenFileDialog();
            fd.Multiselect = true;
            if (!string.IsNullOrWhiteSpace(extFilter))
            {
                fd.Filter = extFilter;
            }
            fd.ShowDialog(owner as Window);

            return fd.FileNames.ToList();
        }
    }
}

XAML:

<Window

    xmlns:views="clr-namespace:Views"
    xmlns:viewModels="clr-namespace:ViewModels"
>    
    <Window.DataContext>
        <viewModels:MyViewModel/>
    </Window.DataContext>

    <Grid>
        <Button Content = "Open files.." Command="{Binding DoSomeThingCmd}" CommandParameter="{x:Type views:OpenFilesDialog}"/>
    </Grid>
</Window>
34

Служба - это просто класс, к которому вы получаете доступ из центрального хранилища служб, часто из контейнера IOC. Затем сервис реализует то, что вам нужно, например OpenFileDialog.

Итак, если у вас естьIFileDialogService в контейнере Unity вы можете сделать ...

void Browse(object param)
{
    var fileDialogService = container.Resolve<IFileDialogService>();

    string path = fileDialogService.OpenFileDialog();

    if (!string.IsNullOrEmpty(path))
    {
        //Do stuff
    }
}
Как вы говорите XAML использовать этот сервис?
В зависимости от вашей инфраструктуры MVVM вы можете использовать команду или действие для выполнения кода на основе нажатия кнопки или какого-либо другого события.
@CameronMacFarland Я немного медленный, не могли бы вы показать мне пример интерфейса и как он вызывается в средстве просмотра моделей?
Хотя решение является правильным, оно представляет собой анти-шаблон Service Locator. Вместо разрешенияIFileDialogService, это должно быть передано как зависимость в ctor.
Есть ли способ указать окно, которому принадлежит диалог?
6

ии. Если виртуальная машина размещена в отдельной DLL, проект не должен иметь ссылку на PresentationFramework.

Мне нравится использовать вспомогательный класс в представлении для общих диалогов.

Вспомогательный класс предоставляет команду (не событие), с которой окно связывается в XAML. Это подразумевает использование RelayCommand в представлении. Вспомогательный класс является DepencyObject, поэтому он может связываться с моделью представления.

class DialogHelper : DependencyObject
{
    public ViewModel ViewModel
    {
        get { return (ViewModel)GetValue(ViewMo,delProperty); }
        set { SetValue(ViewModelProperty, value); }
    }

    public static readonly DependencyProperty ViewModelProperty =
        DependencyProperty.Register("ViewModel", typeof(ViewModel), typeof(DialogHelper),
        new UIPropertyMetadata(new PropertyChangedCallback(ViewModelProperty_Changed)));

    private static void ViewModelProperty_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (ViewModelProperty != null)
        {
            Binding myBinding = new Binding("FileName");
            myBinding.Source = e.NewValue;
            myBinding.Mode = BindingMode.OneWayToSource;
            BindingOperations.SetBinding(d, FileNameProperty, myBinding);
        }
    }

    private string FileName
    {
        get { return (string)GetValue(FileNameProperty); }
        set { SetValue(FileNameProperty, value); }
    }

    private static readonly DependencyProperty FileNameProperty =
        DependencyProperty.Register("FileName", typeof(string), typeof(DialogHelper),
        new UIPropertyMetadata(new PropertyChangedCallback(FileNameProperty_Changed)));

    private static void FileNameProperty_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        Debug.WriteLine("DialogHelper.FileName = {0}", e.NewValue);
    }

    public ICommand OpenFile { get; private set; }

    public DialogHelper()
    {
        OpenFile = new RelayCommand(OpenFileAction);
    }

    private void OpenFileAction(object obj)
    {
        OpenFileDialog dlg = new OpenFileDialog();

        if (dlg.ShowDialog() == true)
        {
            FileName = dlg.FileName;
        }
    }
}

Вспомогательный класс нуждается в ссылке на экземпляр ViewModel. Смотрите словарь ресурсов. Сразу после построения устанавливается свойство ViewModel (в той же строке XAML). Это когда свойство FileName в классе помощника связано со свойством FileName в модели представления.

<Window x:Class="DialogExperiment.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DialogExperiment"
        xmlns:vm="clr-namespace:DialogExperimentVM;assembly=DialogExperimentVM"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <vm:ViewModel x:Key="viewModel" />
        <local:DialogHelper x:Key="helper" ,ViewModel="{StaticResource viewModel}"/>
    </Window.Resources>
    <DockPanel DataContext="{StaticResource viewModel}">
        <Menu DockPanel.Dock="Top">
            <MenuItem Header="File">
                <MenuItem Header="Open" Command="{Binding Source={StaticResource helper}, Path=OpenFile}" />
            </MenuItem>
        </Menu>
    </DockPanel>
</Window>
Либо будет работать, но свойства CLR легче, поэтому я предпочитаю их, где это возможно. Это не имеет большого значения, хотя. Помните, что объект, к которому вы привязываете, может быть простым свойством CLR. (Но, конечно, то, что вы связываете СО, должно быть DP, именно поэтому FileName является DP.)
Просто любопытно, (я все еще изучаю WPF), почему вы не сделали публичный ICommand OpenFile {get; приватный набор; } & Quot; свойство зависимости? Мне трудно понять, когда сделать что-то свойством зависимости, а когда все нормально, иметь простой объект .Net.
Я не согласен с вами, что "ViewModel не должен открывать диалоги или даже знать об их существовании". Существуют такие сценарии, когда в середине кода команды вам нужно открыть диалоговое окно файла или то же самое. Вам не нужно иметь ссылки на классы, определенные в слое View, но вы можете иметь ссылки для просмотра объектов во время выполнения иwork with interfaces defined in ViewModel and implemented in View, Увидеть:stackoverflow.com/questions/42931775/is-mvvm-pattern-broken
7

который я, например, могу передать в конструктор моего viewModel или разрешить с помощью внедрения зависимости. например

public interface IOpenFileService
{
    string FileName { get; }
    bool OpenFileDialog()
}

и класс, реализующий это, используя OpenFileDialog под капотом. В viewModel я использую только интерфейс и, таким образом, могу смоделировать / заменить его при необходимости.

Хотя это и правда, я согласен с оценкой Кэмерона здесь. Вы рискуете непреднамеренных побочных эффектов с подходом собственности. Я знаю, что это тот же шаблон, который использует .NET Framework, но это не обязательно аргумент для этого шаблона, просто он существует. Я предпочитаю использовать проверку на нулевое значение в возвращаемом значении, чтобы указать на какой-либо сбой (или пользователь нажал "Отмена").
В контейнерах внедрения зависимостей вы обычно можете зарегистрировать его, чтобы при каждом разрешении службы создавался новый экземпляр. Таким образом, вы можете создать новый сервис, использовать его и выбросить после этого. Или вы используете только метод OpenFileDialog () и позволяете ему вернуть строку, или ноль, если он был прерван.
Моя единственная проблема со свойствами экземпляра в сервисах - это как быть, если два потока вызывают OpenFileDialog одновременно? Не совсем проблема для файловых диалогов, но может быть проблема для других типов сервисов.
this is the same pattern that the .NET Framework uses Да, для этого открытый файлdialogs, Ни WinForms, ни WPF не поддерживают межпотоковый доступ, поскольку это реализовано в .NET Framework, и проблема в этом спорная.
2

что открывать вид из модели представления. Я имею в виду свойство Dependency, и в chnage свойства я открываю FileDialog и читаю путь, обновляю свойство и, следовательно, свойство bound виртуальной машины.

Похожие вопросы