WPF OpenFileDialog z wzorcem MVVM? [duplikat]

to pytanie ma już odpowiedzi tutaj : Otwórz okno dialogowe pliku MVVM (7 odpowiedzi) Zamknięte 9 miesiące temu .

Właśnie zacząłem uczyć się wzorca MVVM dla WPF. I hit a wall: co zrobić, gdy trzeba pokazać OpenFileDialog?

Oto przykładowy interfejs użytkownika, na którym próbuję go użyć:

alt text

Po kliknięciu przycisku Przeglądaj należy wyświetlić OpenFileDialog. Gdy użytkownik wybierze plik z OpenFileDialog, ścieżka do pliku powinna być wyświetlona w polu tekstowym.

Jak mogę to zrobić z MVVM?

Update : Jak mogę to zrobić to z MVVM i zrobić to test jednostkowy-stanie? Poniższe rozwiązanie nie działa w przypadku testów jednostkowych.

Author: Eliahu Aaron, 2009-10-24

5 answers

To co zwykle robię to tworzenie interfejsu dla usługi aplikacji, która wykonuje tę funkcję. W moich przykładach zakładam, że używasz czegoś takiego jak zestaw narzędzi MVVM lub podobne rzeczy(więc mogę uzyskać podstawowy ViewModel i RelayCommand).

Oto przykład niezwykle prostego interfejsu do wykonywania podstawowych operacji IO, takich jak OpenFileDialog i OpenFile. Pokazuję je tutaj, więc nie myślisz, że sugeruję stworzenie jednego interfejsu z jedną metodą, aby obejść to problem.

public interface IOService
{
     string OpenFileDialog(string defaultPath);

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

W Twojej aplikacji podasz domyślną implementację tej usługi. Oto, jak byś to konsumował.

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;
          }
     }
}
To dość proste. Teraz ostatnia część: testowalność. To powinno być oczywiste, ale pokażę Ci, jak zrobić prosty test. Używam Moq do zacinania, ale możesz użyć, co chcesz, oczywiście.
[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);
}

To prawdopodobnie zadziała dla Ciebie.

Na Codeplexie znajduje się biblioteka o nazwie "SystemWrapper" ( http://systemwrapper.codeplex.com), które mogą uchronić Cię od konieczności zrobienia wiele tego rodzaju rzeczy. Wygląda na to, że FileDialog nie jest jeszcze obsługiwany, więc na pewno będziesz musiał napisać interfejs dla tego.

Mam nadzieję, że to pomoże.

Edit :

Chyba pamiętam, że faworyzowałeś TypeMock Isolator dla swojego fałszywego frameworka. Oto ten sam test z użyciem izolatora:

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

Mam nadzieję, że to również pomoże.

 101
Author: Anderson Imes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-05-18 06:11:11

The WPF Application Framework (WAF) zapewnia implementację dla Open i SaveFileDialog.

Przykładowa aplikacja Writer pokazuje jak z nich korzystać i jak można testować kod.

 6
Author: jbe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-26 08:39:24

Po pierwsze polecam zacząć od WPF MVVM toolkit . Daje to ładny wybór poleceń do wykorzystania w projektach. Szczególną cechą, która stała się sławna od czasu wprowadzenia wzorca MVVM, jest RelayCommand (są oczywiście inne wersje, ale po prostu trzymam się najczęściej używanych). Jest to implementacja interfejsu ICommand, który pozwala na skrzyni nowe polecenie w ViewModel.

Wracając do twojego pytania, oto przykład, jak może wyglądać Twój 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 i RelayCommand pochodzą z MVVM Toolkit. Oto jak może wyglądać XAML.

<TextBox Text="{Binding SelectedPath}" />
<Button Command="vm:OpenFileDialogVM.OpenCommand" >Browse</Button>
I twój XAML.Kod CS za.
DataContext = new OpenFileDialogVM();
InitializeComponent();
To wszystko.

Po zapoznaniu się z poleceniami, możesz również ustawić warunki, kiedy chcesz, aby przycisk przeglądania był wyłączony, itp. Mam nadzieję, że to wskazało ci kierunek, w którym chciałeś.

 2
Author: Tri Q Tran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-25 01:19:24

Z mojego punktu widzenia najlepszą opcją jest biblioteka prism i InteractionRequests. Operacja otwarcia okna dialogowego pozostaje w xaml i jest wywoływana przez Viewmodel, podczas gdy Viewmodel nie musi nic wiedzieć o widoku.

Zobacz też

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

Jako przykład zobacz:

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
Author: plainionist,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-19 20:48:03

Moim zdaniem najlepszym rozwiązaniem jest stworzenie niestandardowej kontroli.

Niestandardowa kontrola, którą zwykle tworzę, składa się z:

  • Textbox lub textblock
  • Przycisk z obrazkiem jako szablonem
  • właściwość zależności String, w której ścieżka do pliku zostanie zawinięta do

Więc *.plik xaml będzie wyglądał tak:

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

I *.plik 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;
    }
}

Na końcu możesz powiązać go z modelem widoku:

<controls:customFilePicker Text="{Binding Text}"/>
 1
Author: Daniel Angulo Duque,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-11-19 16:10:56