Domanda

Ho appena iniziato a studiare il modello MVVM per WPF. Ho colpito un muro: cosa fai quando devi mostrare un OpenFileDialog ?

Ecco un esempio dell'interfaccia utente su cui sto provando a usarlo:

alt text

Quando si fa clic sul pulsante Sfoglia, dovrebbe essere visualizzato un OpenFileDialog. Quando l'utente seleziona un file da OpenFileDialog, il percorso del file deve essere visualizzato nella casella di testo.

Come posso farlo con MVVM?

Aggiorna : come posso farlo con MVVM e renderlo testabile in unità? La soluzione seguente non funziona per i test unitari.

È stato utile?

Soluzione

Quello che generalmente faccio è creare un'interfaccia per un servizio applicativo che svolge questa funzione. Nei miei esempi assumerò che tu stia usando qualcosa come MVVM Toolkit o cose simili (così posso ottenere un ViewModel di base e un RelayCommand).

Ecco un esempio di un'interfaccia estremamente semplice per eseguire operazioni IO di base come OpenFileDialog e OpenFile. Li sto mostrando entrambi qui, quindi non pensi che sto suggerendo di creare un'interfaccia con un metodo per aggirare questo problema.

public interface IOService
{
     string OpenFileDialog(string defaultPath);

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

Nella tua applicazione, forniresti un'implementazione predefinita di questo servizio. Ecco come lo consumeresti.

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

Quindi è abbastanza semplice. Ora per l'ultima parte: testabilità. Questo dovrebbe essere ovvio, ma ti mostrerò come fare un semplice test per questo. Uso Moq per il mozzicone, ma ovviamente puoi usare quello che vuoi.

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

Questo probabilmente funzionerà per te.

Esiste una libreria su CodePlex chiamata " SystemWrapper " ( http://systemwrapper.codeplex.com ) che potrebbe impedirti di dover fare un lotto di questo tipo di cose. Sembra che FileDialog non sia ancora supportato, quindi dovrai sicuramente scrivere un'interfaccia per quello.

Spero che questo aiuti.

Modifica :

Mi sembra di ricordare che hai favorito TypeMock Isolator per il tuo finto framework. Ecco lo stesso test usando 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);
}

Spero che anche questo sia utile.

Altri suggerimenti

Il WPF Application Framework (WAF) fornisce un'implementazione per Open e SaveFileDialog.

L'applicazione di esempio di Writer mostra come usarli e come il codice può essere testato in unità.

Innanzitutto ti consiglio di iniziare con un WPF MVVM toolkit . Questo ti dà una buona selezione di comandi da usare per i tuoi progetti. Una caratteristica particolare che è stata resa famosa dall'introduzione del pattern MVVM è il RelayCommand (ci sono ovviamente molte altre versioni, ma mi limito a utilizzare le più comunemente usate). È un'implementazione dell'interfaccia ICommand che ti consente di creare un nuovo comando nel tuo ViewModel.

Tornando alla tua domanda, ecco un esempio di come potrebbe essere il tuo 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 e RelayCommand provengono entrambi da MVVM Toolkit . Ecco come potrebbe apparire XAML.

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

e il tuo codice XAML.CS dietro.

DataContext = new OpenFileDialogVM();
InitializeComponent();

Questo è tutto

Man mano che acquisisci familiarità con i comandi, puoi anche impostare le condizioni su quando vuoi che il pulsante Sfoglia sia disabilitato, ecc. Spero che ti abbia indirizzato nella direzione che volevi.

Dal mio punto di vista l'opzione migliore è la libreria di prismi e InteractionRequests. L'azione per aprire la finestra di dialogo rimane all'interno di xaml e viene attivata da Viewmodel mentre Viewmodel non ha bisogno di sapere nulla sulla vista.

Vedi anche

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

Come esempio vedi:

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

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

Secondo me la soluzione migliore è creare un controllo personalizzato.

Il controllo personalizzato che creo di solito è composto da:

  • Casella di testo o blocco di testo
  • Pulsante con un'immagine come modello
  • Proprietà di dipendenza stringa in cui verrà spostato il percorso del file

Quindi il file * .xaml sarebbe così

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

E il file * .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;
    }
}

Alla fine puoi associarlo al tuo modello di visualizzazione:

<controls:customFilePicker Text="{Binding Text}"/>
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top