Вопрос

Я только начал изучать шаблон MVVM для WPF.Я врезался в стену: что вы делаете, когда вам нужно показать OpenFileDialog?

Вот пример пользовательского интерфейса, на котором я пытаюсь его использовать:

alt text

При нажатии кнопки обзор должен появиться OpenFileDialog.Когда пользователь выбирает файл из OpenFileDialog, в текстовом поле должен отображаться путь к файлу.

Как я могу сделать это с помощью MVVM?

Обновить:Как я могу сделать это с помощью MVVM и сделать его доступным для модульного тестирования?Приведенное ниже решение не работает для модульного тестирования.

Это было полезно?

Решение

Что я обычно делаю, так это создаю интерфейс для прикладной службы, которая выполняет эту функцию.В моих примерах я предполагаю, что вы используете что-то вроде MVVM Toolkit или что-то подобное (чтобы я мог получить базовую ViewModel и RelayCommand).

Вот пример чрезвычайно простого интерфейса для выполнения базовых операций ввода-вывода, таких как OpenFileDialog и OpenFile.Я показываю их здесь оба, чтобы вы не подумали, что я предлагаю вам создать один интерфейс с одним методом, чтобы обойти эту проблему.

public interface IOService
{
     string OpenFileDialog(string defaultPath);

     //Other similar untestable IO operations
     Stream OpenFile(string path);
}

В вашем приложении вы бы предоставили реализацию этой службы по умолчанию.Вот как бы вы это употребляли.

public MyViewModel : ViewModel
{
     private string _selectedPath;
     public string SelectedPath
     {
          get { return _selectedPath; }
          set { _selectedPath = value; OnPropertyChanged("SelectedPath"); }
     }

     private RelayCommand _openCommand;
     public RelayCommand OpenCommand
     {
          //You know the drill.
          ...
     }

     private IOService _ioService;
     public MyViewModel(IOService ioService)
     {
          _ioService = ioService;
          OpenCommand = new RelayCommand(OpenFile);
     }

     private void OpenFile()
     {
          SelectedPath = _ioService.OpenFileDialog(@"c:\Where\My\File\Usually\Is.txt");
          if(SelectedPath == null)
          {
               SelectedPath = string.Empty;
          }
     }
}

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

[Test]
public void OpenFileCommand_UserSelectsInvalidPath_SelectedPathSetToEmpty()
{
     Mock<IOService> ioServiceStub = new Mock<IOService>();

     //We use null to indicate invalid path in our implementation
     ioServiceStub.Setup(ioServ => ioServ.OpenFileDialog(It.IsAny<string>()))
                  .Returns(null);

     //Setup target and test
     MyViewModel target = new MyViewModel(ioServiceStub.Object);
     target.OpenCommand.Execute();

     Assert.IsEqual(string.Empty, target.SelectedPath);
}

Вероятно, это сработает у вас.

В CodePlex есть библиотека под названием "SystemWrapper" (http://systemwrapper.codeplex.com) это могло бы избавить вас от необходимости выполнять лот о такого рода вещах.Похоже, FileDialog пока не поддерживается, так что вам определенно придется написать интерфейс для этого.

Надеюсь, это поможет.

Редактировать:

Кажется, я помню, что вы предпочитали TypeMock Isolator для своего фальшивого фреймворка.Вот тот же тест с использованием Isolator:

[Test]
[Isolated]
public void OpenFileCommand_UserSelectsInvalidPath_SelectedPathSetToEmpty()
{
    IOService ioServiceStub = Isolate.Fake.Instance<IOService>();

    //Setup stub arrangements
    Isolate.WhenCalled(() => ioServiceStub.OpenFileDialog("blah"))
           .WasCalledWithAnyArguments()
           .WillReturn(null);

     //Setup target and test
     MyViewModel target = new MyViewModel(ioServiceStub);
     target.OpenCommand.Execute();

     Assert.IsEqual(string.Empty, target.SelectedPath);
}

Надеюсь, это тоже будет полезно.

Другие советы

WPF Application Framework (WAF) обеспечивает реализацию для Open и SaveFileDialog.

Пример приложения Writer показывает, как их использовать и как можно тестировать код.

Во-первых, я бы порекомендовал вам начать с Инструментарий WPF MVVM . Это дает вам хороший выбор команд для ваших проектов. Одна особенность, которая стала известной с момента появления шаблона MVVM - это RelayCommand (конечно, есть и другие версии, но я просто придерживаюсь наиболее часто используемых). Это реализация интерфейса ICommand, который позволяет вам создать новую команду в вашей ViewModel.

Возвращаясь к вашему вопросу, вот пример того, как может выглядеть ваша ViewModel.

public class OpenFileDialogVM : ViewModelBase
{
    public static RelayCommand OpenCommand { get; set; }
    private string _selectedPath;
    public string SelectedPath
    {
        get { return _selectedPath; }
        set
        {
            _selectedPath = value;
            RaisePropertyChanged("SelectedPath");
        }
    }

    private string _defaultPath;

    public OpenFileDialogVM()
    {
        RegisterCommands();
    }

    public OpenFileDialogVM(string defaultPath)
    {
        _defaultPath = defaultPath;
        RegisterCommands();
    }

    private void RegisterCommands()
    {
        OpenCommand = new RelayCommand(ExecuteOpenFileDialog);
    }

    private void ExecuteOpenFileDialog()
    {
        var dialog = new OpenFileDialog { InitialDirectory = _defaultPath };
        dialog.ShowDialog();

        SelectedPath = dialog.FileName;
    }
}

ViewModelBase и RelayCommand оба из Инструментарий MVVM . Вот как может выглядеть XAML.

<TextBox Text="{Binding SelectedPath}" />
<Button Command="vm:OpenFileDialogVM.OpenCommand" >Browse</Button>

и ваш код XAML.CS.

DataContext = new OpenFileDialogVM();
InitializeComponent();

Вот и все.

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

С моей точки зрения, лучшим вариантом является библиотека призмы и запросы InteractionRequests. Действие по открытию диалога остается внутри xaml и запускается из Viewmodel, в то время как Viewmodel не нужно ничего знать о представлении.

Смотрите также

https://plainionist.github.io///Mvvm-Dialogs/

В качестве примера см .:

https: // github .com / plainionist / Plainion.Prism / блоб / ведущий / SRC / Plainion.Prism / Интерактивность / PopupCommonDialogAction.cs

https: / /github.com/plainionist/Plainion.Prism/blob/master/src/Plainion.Prism/Interactivity/InteractionRequest/OpenFileDialogNotification.cs

На мой взгляд, лучшим решением является создание пользовательского элемента управления.

Пользовательский элемент управления, который я обычно создаю, состоит из:

  • Текстовое поле или текстовый блок
  • Кнопка с изображением в качестве шаблона
  • Свойство String dependency, в которое будет перенесен путь к файлу

Таким образом, файл * .xaml будет выглядеть следующим образом

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="Auto"/>
    </Grid.ColumnDefinitions>
    <TextBox Grid.Column="0" Text="{Binding Text, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
    <Button Grid.Column="1" Click="Button_Click">
        <Button.Template>
            <ControlTemplate>
                <Image Grid.Column="1" Source="../Images/carpeta.png"/>
            </ControlTemplate>                
        </Button.Template>
    </Button>        
</Grid>

И файл *.cs:

public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text",
        typeof(string),
        typeof(customFilePicker),
        new FrameworkPropertyMetadata(null,
            FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal));

public string Text
{
    get
    {
        return this.GetValue(TextProperty) as String;
    }
    set
    {
        this.SetValue(TextProperty, value);
    }
}

public FilePicker()
{
    InitializeComponent();
}

private void Button_Click(object sender, RoutedEventArgs e)
{
    OpenFileDialog openFileDialog = new OpenFileDialog();

    if(openFileDialog.ShowDialog() == true)
    {
        this.Text = openFileDialog.FileName;
    }
}

В конце вы можете привязать его к вашей модели представления:

<controls:customFilePicker Text="{Binding Text}"/>
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top