web-dev-qa-db-ja.com

ファイルを開くダイアログMVVM

OK.

私はViewModelでこれを本当にやりたくない(「Browse」はDelegateCommandを介して参照される)

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

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

MVVMの方法論に反すると信じているからです。

私は何をしますか?

43
Jose

ここで行う最も良いことは、サービスを使用することです。

サービスは、サービスの中央リポジトリからアクセスする単なるクラスであり、多くの場合IOCコンテナ。サービスはOpenFileDialogのような必要なものを実装します。

したがって、UnityコンテナにIFileDialogServiceがあると仮定すると、次のことができます...

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

    string path = fileDialogService.OpenFileDialog();

    if (!string.IsNullOrEmpty(path))
    {
        //Do stuff
    }
}
36

答えの1つについてコメントしたかったのですが、残念ながら、私の評判はそうするほど高くはありません。

OpenFileDialog()などの呼び出しがあると、ビューモデルのビュー(ダイアログ)を暗示するため、MVVMパターンに違反します。ビューモデルはGetFileName()のようなものを呼び出すことができます(つまり、単純なバインドでは不十分な場合)が、ファイル名の取得方法を気にする必要はありません。

9
JAB

私は、たとえばviewModelのコンストラクターに渡したり、依存関係の注入によって解決したりできるサービスを使用しています。例えば.

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

そして、内部でOpenFileDialogを使用してそれを実装するクラス。 viewModelでは、インターフェイスのみを使用するため、必要に応じてモック/置換を行うことができます。

7
Botz3000

ViewModelは、ダイアログを開いたり、ダイアログの存在を知ってはいけません。 VMが別のDLLに格納されている場合、プロジェクトにはPresentationFrameworkへの参照を含めることはできません。

共通ダイアログのビューでヘルパークラスを使用するのが好きです。

ヘルパークラスは、ウィンドウがXAMLでバインドするコマンド(イベントではなく)を公開します。これは、ビュー内でRelayCommandを使用することを意味します。ヘルパークラスはDepencyObjectであるため、ビューモデルにバインドできます。

class DialogHelper : DependencyObject
{
    public ViewModel ViewModel
    {
        get { return (ViewModel)GetValue(ViewModelProperty); }
        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>
6
Paul Williams

サービスを持つことは、viewmodelからビューを開くようなものです。ビューにDependencyプロパティがあり、プロパティの変更時に、FileDialogを開いてパスを読み取り、プロパティを更新し、VMのバインドされたプロパティを更新します

2
Jilt

私はこのようにそれを解決しました:

  • ViewModelでインターフェイスを定義し、ViewModelで操作します
  • Viewでこのインターフェイスを実装しました。

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..
        });
    }
}

見る:

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>
2
Rekshino