webentwicklung-frage-antwort-db.com.de

WPF OpenFileDialog mit dem MVVM-Muster?

Ich habe gerade angefangen, das MVVM-Muster für WPF zu lernen. Ich bin gegen eine Wand gestoßen: Was machst du, wenn du einen OpenFileDialog zeigen willst?

Hier ist ein Beispiel für eine Benutzeroberfläche, auf der ich sie verwenden möchte:

alt text

Wenn Sie auf die Schaltfläche "Durchsuchen" klicken, sollte ein OpenFileDialog angezeigt werden. Wenn der Benutzer eine Datei aus dem OpenFileDialog auswählt, sollte der Dateipfad im Textfeld angezeigt werden.

Wie kann ich das mit MVVM machen?

pdate: Wie kann ich das mit MVVM machen und es Unit testfähig machen? Die folgende Lösung funktioniert nicht für Komponententests.

94

Im Allgemeinen erstelle ich eine Schnittstelle für einen Anwendungsdienst, der diese Funktion ausführt. In meinen Beispielen gehe ich davon aus, dass Sie etwas wie das MVVM-Toolkit oder ähnliches verwenden (damit ich ein Basis-ViewModel und einen RelayCommand erhalten kann).

Hier ist ein Beispiel für eine extrem einfache Benutzeroberfläche, mit der grundlegende IO) - Operationen wie OpenFileDialog und OpenFile ausgeführt werden können. Ich zeige beide hier, damit Sie nicht glauben, dass Sie eine Benutzeroberfläche mit einer erstellen Methode, um dieses Problem zu umgehen.

public interface IOService
{
     string OpenFileDialog(string defaultPath);

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

In Ihrer Anwendung würden Sie eine Standardimplementierung dieses Dienstes bereitstellen. So würden Sie es konsumieren.

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

Das ist also ziemlich einfach. Nun zum letzten Teil: Testbarkeit. Dieser sollte offensichtlich sein, aber ich zeige Ihnen, wie Sie einen einfachen Test dafür durchführen können. Ich benutze Moq zum Stubben, aber du kannst natürlich alles verwenden, was du willst.

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

Dies wird wahrscheinlich für Sie arbeiten.

Auf CodePlex gibt es eine Bibliothek mit dem Namen "SystemWrapper" ( http://systemwrapper.codeplex.com ), die Sie möglicherweise davon abhalten könnte, ein lot davon zu machen Art der Sache. Es sieht so aus, als ob FileDialog noch nicht unterstützt wird, daher müssen Sie auf jeden Fall eine Schnittstelle für diese schreiben.

Hoffe das hilft.

Bearbeiten :

Ich scheine mich zu erinnern, dass Sie TypeMock Isolator für Ihr gefälschtes Framework favorisiert haben. Hier ist der gleiche Test mit 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);
}

Hoffe das ist auch hilfreich.

90
Anderson Imes

Das WPF Application Framework (WAF) bietet eine Implementierung für den Open- und SaveFileDialog.

Die Writer-Beispielanwendung zeigt, wie sie verwendet werden und wie der Code einheitlich getestet werden kann.

4
jbe

Aus meiner Sicht ist die beste Option die Prismenbibliothek und InteractionRequests. Die Aktion zum Öffnen des Dialogfelds verbleibt in der XAML und wird von Viewmodel ausgelöst, während das Viewmodel nichts über die Ansicht wissen muss.

Siehe auch

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

Als Beispiel siehe:

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

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

2
plainionist

Zunächst würde ich Ihnen empfehlen, mit einem WPF MVVM-Toolkit zu beginnen. Dies gibt Ihnen eine schöne Auswahl an Befehlen, die Sie für Ihre Projekte verwenden können. Eine Besonderheit, die seit der Einführung des MVVM-Musters bekannt geworden ist, ist der RelayCommand (es gibt natürlich viele andere Versionen, aber ich halte mich nur an die am häufigsten verwendeten). Es ist eine Implementierung der ICommand-Schnittstelle, mit der Sie einen neuen Befehl in Ihrem ViewModel erstellen können.

Zurück zu Ihrer Frage, hier ist ein Beispiel, wie Ihr ViewModel aussehen könnte.

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 und RelayCommand stammen beide aus dem MVVM Toolkit . So könnte die XAML aussehen.

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

und Ihr XAML.CS-Code dahinter.

DataContext = new OpenFileDialogVM();
InitializeComponent();

Das ist es.

Wenn Sie mit den Befehlen vertraut sind, können Sie auch Bedingungen festlegen, wann die Schaltfläche Durchsuchen deaktiviert werden soll usw. Ich hoffe, dass Sie in die gewünschte Richtung weisen.

2
Tri Q Tran

Meiner Meinung nach ist die beste Lösung, ein benutzerdefiniertes Steuerelement zu erstellen.

Das benutzerdefinierte Steuerelement, das ich normalerweise erstelle, setzt sich zusammen aus:

  • Textfeld oder Textblock
  • Schaltfläche mit einem Bild als Vorlage
  • String-Abhängigkeitseigenschaft, in die der Dateipfad eingeschlossen wird

Die * .xaml-Datei würde also so aussehen

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

Und die * .cs-Datei:

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

Am Ende können Sie es an Ihr Ansichtsmodell binden:

<controls:customFilePicker Text="{Binding Text}"/>
1