60

Ok I really would like to know how expert MVVM developers handle an openfile dialog in WPF.

I don't really want to do this in my ViewModel(where 'Browse' is referenced via a DelegateCommand)

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

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

Because I believe that goes against MVVM methodology.

What do I do?

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
Jose
  • 10,891
  • 19
  • 67
  • 89
  • See blog post of Ollie Riches where is explained how to pass messages in WPF with a clean separation of the View & ViewModel. – amutter Apr 24 '13 at 10:04

7 Answers7

57

Long story short:
The solution is to show user interactions from a class, that is part of the view component.
This means, such a class must be a class that is unknown to the view model and therefore can't be invoked by the view model.
The solution of course can involve code-behind implementations as code-behind is not relevant when evaluating whether a solution complies with MVVM or not.

Beside answering the original question, this answer also tries to provide an alternative view on the general problem why controlling a UI component like a dialog from the view model violates the MVVM design pattern and why workarounds like a dialog service don't solve the problem.

1 MVVM and dialogs

1.1 Critique of common suggestions

Almost all answers are following the misconception that MVVM is a pattern, that targets class level dependencies and also requires empty code-behind files. But it's an architectural pattern, that tries to solve a different problem - on application/component level: keeping the business domain decoupled from the UI.
Most people (here on SO) agree that the view model should not handle dialogs, but then propose to move the UI related logic to a helper class (doesn't matter if it's called helper or service), which is still controlled by the view model.
This (especially the service version) is also known as dependency hiding. Many patterns do this. Such patterns are considered anti-patterns. Service Locator is the most famous dependency hiding anti-pattern.

That is why I would call any pattern that involves extraction of the UI logic from the view model class to a separate class an anti-pattern too. It does not solve the original problem: how to change the application structure or class design in order to remove the UI related responsibilities from a view model (or model) class and move it back to a view related class.
In other words: the critical logic remains being a part of the view model component.

For this reason, I do not recommend to implement solutions, like the accepted one, that involve a dialog service (whether it is hidden behind an interface or not). If you are concerned to write code that complies with the MVVM design pattern, then simply don't handle dialog views or messaging inside the view model.

Introducing an interface to decouple class level dependencies, for example an IFileDialogService interface, is called Dependency Inversion principle (the D in SOLID) and has nothing to do with MVVM. When it has no relevance in terms of MVVM, it can't solve an MVVM related problem. When room temperature does not have any relevance whether a structure is a four story building or a skyscraper, then changing the room temperature can never turn any building into a skyscraper. MVVM is not a synonym for Dependency Inversion.

MVVM is an architectural pattern while Dependency Inversion is an OO language principle that has nothing to do with structuring an application (aka software architecture). It's not the interface (or the abstract type) that structures an application, but abstract objects or entities like components or modules e.g. Model - View - View Model. An interface can only help to "physically" decouple the components or modules. It doesn't remove component associations.

1.2 Why dialogs or handling Window in general feels so odd?

We have to keep in mind that the dialog controls like Microsoft.Win32.OpenFileDialog are "low level" native Windows controls. They don't have the necessary API to smoothly integrate them into a MVVM environment. Because of their true nature, they have some limitations the way they can integrate into a high level framework like WPF. Dialogs or native window hosts in general, are a known "weakness" of all high level frameworks like WPF.

Dialogs are commonly based on the Window or the abstract CommonDialog class. The Window class is a ContentControl and therefore allows styles and templates to target the content.
One big limitation is, that a Window must always be the root element. You can't add it as a child to the visual tree and e.g. show/launch it using triggers or host it in a DataTemplate.
In case of the CommonDialog, it can't be added to the visual tree, because it doesn't extend UIElement.

Therefore, Window or CommonDialog based types must always be shown from code-behind, which I guess is the reason for the big confusion about handling this kind of controls properly.
In addition, many developers, especially beginners that are new to MVVM, have the perception that code-behind violates MVVM.
For some irrational reason, they find it less violating to handle the dialog views in the view model component.

Due to it's API, a Window looks like a simple control (in fact, it extends ContentControl). But underneath, it hooks into to the low level of the OS. There is a lot of unmanaged code necessary to achieve this. Developers that are coming from low level C++ frameworks like MFC know exactly what's going on under the hoods.

The Window and CommonDialog class are both true hybrids: they are part of the WPF framework, but in order to behave like or actually to be a native OS window, they must be also part of the low level OS infrastructure.
The WPF Window, as well as the CommonDialog class, is basically a wrapper around the complex low level OS API. That's why this controls have sometimes a strange feel (from the developer point of view), when compared to common and pure framework controls.
That Window is sold as a simple ContentControl is quite deceptive. But since WPF is a high level framework, all low level details are hidden from the API by design.
We have to accept that we have to handle controls based on Window and CommonDialog using C# only - and that code-behind does not violate any design pattern at all.

If you are willing to waive the native look and feel and the general OS integration to get the native features like theming and task bar, you can improve the handling by creating a custom dialog e.g., by extending Control or Popup, that exposes relevant properties as DependencyProperty. You can then set up data bindings and XAML triggers to control the visibility, like you usually would.

1.3 Why MVVM?

Without a sophisticated design pattern or application structure, developers would e.g., directly load database data to a table control and mix UI logic with business logic. In such a scenario, changing to a different database would break the UI. But even worse, changing the UI would require to change the logic that deals with the database. And when changing the logic, you would also need to change the related unit tests.

The real application is the business logic and not the fancy GUI.
You want to write unit tests for the business logic - without being forced to include any UI.
You want to modify the UI without modifying the business logic and unit tests.

MVVM is a pattern that solves this problems and allows to decouple the UI from the business logic i.e. data from views. It does this more efficiently than the related design patterns MVC and MVP.

We don't want to have the UI bleed into the lower levels of the application. We want to separate data from data presentation and especially their rendering (data views). For example, we want to handle database access without having to care which libraries or controls are used to view the data. That's why we choose MVVM. For this sake, we can't allow to implement UI logic in components other than the view.

1.4 Why moving UI logic from a class named ViewModel to a separate class still violates MVVM

By applying MVVM, you effectively structuring the application into three components: model, view and view model. It is very important to understand that this partitioning or structure is not about classes. It's about application components.
You may follow the widely spread pattern to name or suffix a class ViewModel, but you must know that the view model component usually contains many classes of which some are not named or suffixed with ViewModel - View Model is an abstract component.

Example:
when you extract functionality, like creating a data source collection, from a big class named MainViewModel and you move this functionality to a new class named ItemCreator, then this class ItemCreator is logically still part of the view model component.
On class level the functionality is now outside the MainViewModel class (while MainViewModel now has a strong reference to the new class, in order to invoke the code). On application level (architecture level), the functionality is still in the same component.

You can project this example onto the often proposed dialog service: extracting the dialog logic from the view model to a dedicated class named DialogService doesn't move the logic outside the view model component: the view model still depends on this extracted functionality.
The view model still participates in the UI logic e.g by explicitly invoking the "service" to control when dialog is shown and to control the dialog type itself (e.g., file open, folder select, color picker etc.).
This all requires knowledge of the UI's business details. Knowledge, that per definition does not belong into the view model component. Of course, such knowlegde introduces a coupling/dependency from the view model component to the view component.

Responsibilities simply don't change because you name a class DialogService instead of e.g. DialogViewModel.

The DialogService is therefore an anti-pattern, which hides the real problem: having implemented view model classes, that depend on UI and execute UI logic.

1.5 Does writing code-behind violates the MVVM design pattern?

MVVM is a design pattern and design patterns are per definition library independent, framework independent and language or compiler independent. Therefore, code-behind is not a topic when talking about MVVM.

The code-behind file is absolutely a valid context to write UI code. It's just another file that contains C# code. Code-behind means "a file with a .xaml.cs extension". It's also the only place for event handlers. And you don't want to stay away from events.

Why does the mantra "No code in code-behind" exist?
For people that are new to WPF, UWP or Xamarin, like those skilled and experienced developers coming from frameworks like WinForms, we have to stress that using XAML should be the preferred way to write UI code. Implementing a Style or DataTemplate using C# (e.g. in the code-behind file) is too complicated and produces code that is very difficult to read => difficult to understand => difficult to maintain.
XAML is just perfect for such tasks. The visually verbose markup style perfectly expresses the UI's structure. It does this far better, than for example C# could ever do. Despite markup languages like XAML may feel inferior to some or not worth learning it, it's definitely the first choice when implementing GUI. We should strive to write as much GUI code as possible using XAML.

But such considerations are absolutely irrelevant in terms of the MVVM design pattern.

Code-behind is simply a compiler concept, realized by the partial directive (in C#). That's why code-behind has nothing to do with any design pattern. That's why neither XAML nor C# can't have anything to do with any design pattern.


2 Solution

Like the OP correctly concludes:

"I don't really want to do this [open a file picker dialog] in my ViewModel(where 'Browse' is referenced via a DelegateCommand). Because I believe that goes against MVVM methodology.

2.1 Some fundamental considerations

  • A dialog is a UI control: a view.
  • The handling of a dialog control or a control in general e.g. showing/hiding is UI logic.
  • MVVM requirement: the view model does not know about the existence of an UI or users. Because of this, a control flow that requires the view model to actively wait or call for user input, really requires some re-design: it is a critical violation and breaks the architectural boundaries dictated by MVVM.
  • Showing a dialog requires knowledge about when to show it and when to close it.
  • Showing the dialog requires to know about the UI and user, because the only reason to show a dialog is to interact with the user.
  • Showing the dialog requires knowledge about the current UI context (in order to choose the appropriate dialog type).
  • It is not the dependency on assemblies or classes like OpenFileDialog or UIElement that breaks the MVVM pattern, but the implementation or reference of UI logic in the view model component or model component (although such a dependency can be a valuable hint).
  • For the same reasons, it would be wrong to show the dialog from the model component too.
  • The only component responsible for UI logic is the view component.
  • From an MVVM point of view, there is nothing like C#, XAML, C++ or VB.NET. Which means, there is nothing like partial or the related infamous code-behind file (*.xaml.cs). The concept of code-behind exists to allow the compiler to merge the XAML part of a class with its C# part. After that merge, both files are treated as a single class: it's a pure compiler concept. partial is the magic that enables to write class code using XAML (the true compiler language is still C# or any other IL compliant language).
  • ICommand is an interface of the .NET library and therefore not a topic when talking about MVVM. It's wrong to believe that every action has to be triggered by an ICommand implementation in the view model.
    Events are still a very useful concept that conform with MVVM, as long as the unidirectional dependency between the components is maintained. Always forcing the use of ICommand instead of using events leads to unnatural and smelly code like the code presented by the OP.
  • There is no such "rule" that ICommand must only be implemented by a view model class. It can be implemented by a view class too.
    In fact, views commonly implement RoutedCommand (or RoutedUICommand), which both are implementions of ICommand, and can also be used to trigger the display of a dialog from e.g., a Window or any other control.
    We have data binding to allow the UI to exchange data with the view model (anonymously, from the data source point of view). But since data binding can't invoke operations (at least in WPF - e.g., UWP allows this), we have ICommand and ICommandSource to realize this.
  • Interfaces in general are not a relevant concept of MVVM. Therefore, introducing an interface (e.g., IFileDialogService) can never solve a MVVM related problem.
  • Services or helper classes are not a concept of MVVM. Therefore, introducing services or helper classes can never solve a MVVM related problem.
  • Classes an their names or type names in general are not relevant in terms of MVVM. Moving view model code to a separate class, even if that class is not named or suffixed with ViewModel, can't solve a MVVM related problem.

2.2 Conclusion

The solution is to show user interactions from a class, that is part of the view component.
This means, such a class must be a class that is unknown to the view model and therefore can't be invoked by the view model.

This logic could be implemented directly in the code-behind file or inside any other class (file). The implementation can be a simple helper class or a more sophisticated (attached) behavior.

The point is: the dialog i.e. the UI component must be handled by the view component alone, as this is the only component that contains UI related logic. Since the view model does not have any knowledge of a view, it can't act actively to communicate with the view. Only passive communication is allowed (data binding, events).

We can always implement a certain flow using events raised by the view model that can be observed by the view in order to take actions like interacting with the user using a dialog.

There exist solutions using the view-model-first approach, which is does not violate MVVM in the first place. But still, badly designed responsibilities can turn this solution into an anti-pattern too.

3 How to fix the need for certain dialog requests

Most of the times, we can eliminate the need to show dialogs from within the application by fixing the application's design.

Since dialogs are a UI concept to enable interaction with the user, we must evaluate dialogs using UI design rules.
Maybe the most famous design rules for UI design are the 10 rules postulated by Nielsen and Molich in the 90's.

One important rule is about error prevention: it states that

a) we must prevent any kind of errors, especially input related, because
b) the user does not like his productivity to be interrupted by error messages and dialogs.

a) means: input data validation. Don't allow invalid data to enter the business logic.
b) means: avoid showing dialogs to the user, whenever possible. Never show a dialog from within the application and let the user trigger dialogs explicitly e.g., on mouse click (no unexpected interruption).

Following this simple rule certainly always eliminates the need to show a dialog triggered by the view model.

From the user's perspective, an application is a black box: it outputs data, accepts data and processes the input data. If we control the data input to guard against invalid data, we eliminate undefined or illegal states and ensure data integrity. This would mean that there is no need to ever show a dialog to the user from inside the application. Only those explicitly triggered by the user.

For example, a common scenario is that our model needs to persist data in a file. If the destination file already exists, we want to ask the user to confirm to overwrite this file.

Following the rule of error prevention, we always let the user pick files in the first place: whether it is a source file or a destination file, it's always the user who specifies this file by explicitly picking it via a file dialog. This means, the user must also explicitly trigger the file operation, for example by clicking on a "Save As" button.

This way, we can use a file picker or file save dialog to ensure only existing files are selected. As a bonus, we additionally eliminate the need to warn the user about overwriting existing files.

Following this approach, we have satisfied a) "[...]prevent any kind of errors, especially input related" and b) "[...]the user does not like to be interrupted by error messages and dialogs".

Update

Since people are questioning the fact that you don't need a view model to handle the dialog views, by coming up with extra "complicated" requirements like data validation to proof their point, I am forced to provide more complex examples to address these more complex scenarios (that were not initially requested by the OP).

4 Examples

4.1 Overview

The scenario is a simple input form to collect a user input like an album name and then use the OpenFileDialog to pick a destination file where the album name is saved to.
Three simple solutions:

Solution 1: Very simple and basic scenario, that meets the exact requirements of the question. Solution 2: Solution that enables to use data validation in the view model. To keep the example simple, the implementation of INotifyDataErrorInfo is omitted.
Solution 3: Another, more elegant solution that uses an ICommand and the ICommandSource.CommandParameter to send the dialog result to the view model and execute the persistence operation.

Solution 1

The following example provides a simple and intuitive solution to show the OpenFileDialog in a MVVM compliant way.
The solution allows the view model to remain unaware of any UI components or logic.

You can even consider to pass a FileStream to the view model instead of the file path. This way, you can handle any errors, while creating the stream, directly in the UI e.g., by showing a dialog if needed.

View

MainWindow.xaml

<Window>
  <Window.DataContext>
    <MainViewModel />
  </Window.DataContext>

  <StackPanel>
    <!-- The data to persist -->
    <TextBox Text="{Binding AlbumName}" />

    <!-- Show the file dialog. 
         Let user explicitly trigger the file save operation. 
         This button will be disabled until the required input is valid -->
    <Button Content="Save as" 
            Click="SaveAlbumNameToFile_OnClick" />
  </StackPanel>
</Window>

MainWindow.xaml.cs

partial class MainWindow : Window
{
  public MainWindow()
    => InitializeComponent();

  private void SaveAlbumNameToFile_OnClick(object sender, EventArgs e)
  {
    var dialog = new OpenFileDialog();

    if (dialog.ShowDialog() == true)
    {
      // Consider to create the FileStream here to handle errors 
      // related to the user's picked file in the view. 
      // If opening the FileStream succeeds, we can pass it over to the viewmodel.
      string destinationFilePath = dialog.FileName;
      (this.DataContext as MainViewModel)?.SaveAlbumName(destinationFilePath);
    }
  }
}

View Model

MainViewModel.cs

class MainViewModel : INotifyPropertyChanged
{
  // Raises PropertyChanged
  public string AlbumName { get; set; }
    
  // A model class that is responsible to persist and load data
  private DataRepository DataRepository { get; }
    
  public MainViewModel() => this.DataRepository = new DataRepository();
    
  // Since 'destinationFilePath' was picked using a file dialog, 
  // this method can't fail.
  public void SaveAlbumName(string destinationFilePath)
    => this.DataRepository.SaveData(this.AlbumName, destinationFilePath);
}

Solution 2

A more realistic solution is to add a dedicated TextBox as input field to enable collection of the destination file path via copy&paste.
This TextBox is bound to the view model class, which ideally implements INotifyDataErrorInfo to validate the file path before it is used.

An additional button will open the optional file picker view to allow the user to alternatively browse the file system to pick a destination.

Finally, the persistence operation is triggered by a "Save As" button:

View

MainWindow.xaml

<Window>
  <Window.DataContext>
    <MainViewModel />
  </Window.DataContext>    

  <StackPanel>

    <!-- The data to persist -->
    <TextBox Text="{Binding AlbumName}" />

    <!-- Alternative file path input, validated using INotifyDataErrorInfo validation 
         e.g. using File.Exists to validate the file path -->
    <TextBox x:Name="FilePathTextBox" 
             Text="{Binding DestinationPath, ValidatesOnNotifyDataErrors=True}" />

    <!-- Option to search a file using the file picker dialog -->
    <Button Content="Browse" Click="PickFile_OnClick" />

    <!-- Let user explicitly trigger the file save operation. 
         This button will be disabled until the required input is valid -->
    <Button Content="Save as" 
            Command="{Binding SaveAlbumNameCommand}" />
  </StackPanel>
</Window>

MainWindow.xaml.cs

partial class MainWindow : Window
{
  public MainWindow()
  {
    InitializeComponent();
  }

  private void PickFile_OnClick(object sender, EventArgs e)
  {
    var dialog = new OpenFileDialog();
    if (dialog.ShowDialog() == true)
    {
      this.FilePathTextBox.Text = dialog.FileName;

      // Since setting the property explicitly bypasses the data binding, 
      // we must explicitly update it by calling BindingExpression.UpdateSource()
      this.FilePathTextBox
        .GetBindingExpression(TextBox.TextProperty)
        .UpdateSource();
    }
  }
}

View Model

MainViewModel.cs

class MainViewModel : INotifyPropertyChanged, INotifyDataErrorInfo
{
  private string albumName;
  public string AlbumName
  {
    get => this.albumName;
    set
    {
      this.albumName = value;
      OnPropertyChanged();
    }
  }

  private string destinationPath;
  public string DestinationPath
  {
    get => this.destinationPath;
    set
    {
      this.destinationPath = value;
      OnPropertyChanged();

      ValidateDestinationFilePath();
    }
  }

  public ICommand SaveAlbumNameCommand => new RelayCommand(
    commandParameter => ExecuteSaveAlbumName(this.TextValue),
    commandParameter => true);

  // A model class that is responsible to persist and load data
  private DataRepository DataRepository { get; }

  // Default constructor
  public MainViewModel() => this.DataRepository = new DataRepository();

  private void ExecuteSaveAlbumName(string destinationFilePath)
  {
    // Use a aggregated/composed model class to persist the data
    this.DataRepository.SaveData(this.AlbumName, destinationFilePath);
  }
}

Solution 3

The following solution is a more elegant version of the second scenario. It uses the ICommandSource.CommandParameter property to send the dialog result to the view model (instead of the data binding used in the previous example).
The validation of the optional user input (e.g. copy&paste) is validated using binding validation:

View

MainWindow.xaml

<Window x:Name="Window">
  <Window.DataContext>
    <MainViewModel />
  </Window.DataContext> 

  <StackPanel> 

    <!-- The data to persist -->
    <TextBox Text="{Binding AlbumName}" />

    <!-- Alternative file path input, validated using binding validation 
         e.g. using File.Exists to validate the file path -->
    <TextBox x:Name="FilePathTextBox">
      <TextBox.Text>
        <Binding ElementName="Window" Path="DestinationPath">
          <Binding.ValidationRules>
            <FilePathValidationRule />
          </Binding.ValidationRules>
        </Binding>
      </TextBox.Text>
    </TextBox>

    <!-- Option to search a file using the file picker dialog -->
    <Button Content="Browse" Click="PickFile_OnClick" />

    <!-- Let user explicitly trigger the file save operation. 
         This button will be disabled until the required input is valid -->
    <Button Content="Save as" 
            CommandParameter="{Binding ElementName=Window, Path=DestinationPath}" 
            Command="{Binding SaveAlbumNameCommand}" />
  </StackPanel>
</Window>

FilePathValidationRule.cs

class FilePathValidationRule : ValidationRule
{
  public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    => value is string filePath && File.Exists(filePath)
      ? ValidationResult.ValidResult
      : new ValidationResult(false, "File path does not exist.");
}

MainWindow.xaml.cs

partial class MainWindow : Window
{
  public static readonly DependencyProperty DestinationPathProperty = DependencyProperty.Register(
    "DestinationPath",
    typeof(string),
    typeof(MainWindow),
    new PropertyMetadata(default(string)));

  public string DestinationPath
  {
    get => (string)GetValue(MainWindow.DestinationPathProperty);
    set => SetValue(MainWindow.DestinationPathProperty, value);
  }

  public MainWindow()
  {
    InitializeComponent();
  }

  private void PickFile_OnClick(object sender, EventArgs e)
  {
    var dialog = new OpenFileDialog();
    if (dialog.ShowDialog() == true)
    {
      this.DestinationPath = dialog.FileName;
    }
  }
}

View Model

MainViewModel.cs

class MainViewModel : INotifyPropertyChanged, INotifyDataErrorInfo
{
  private string albumName;
  public string AlbumName
  {
    get => this.albumName;
    set
    {
      this.albumName = value;
      OnPropertyChanged();
    }
  }

  public ICommand SaveAlbumNameCommand => new RelayCommand(
    commandParameter => ExecuteSaveAlbumName(commandParameter as string),
    commandParameter => true);

  // A model class that is responsible to persist and load data
  private DataRepository DataRepository { get; }

  // Default constructor
  public MainViewModel() => this.DataRepository = new DataRepository();

  private void ExecuteSaveAlbumName(string destinationFilePath)
  {
    // Use a aggregated/composed model class to persist the data
    this.DataRepository.SaveData(this.AlbumName, destinationFilePath);
  }
}
starball
  • 20,030
  • 7
  • 43
  • 238
BionicCode
  • 1
  • 4
  • 28
  • 44
  • Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/224729/discussion-on-answer-by-bioniccode-open-file-dialog-mvvm). – Samuel Liew Nov 18 '20 at 13:43
  • 7
    Tbh. This answer/Article should be placed in Hall of Fame of "What MVVM is and What's not !" Thanks for such deep explanation I even understood MVVM in it's true sense today. Do you have some blog or tutorial site of yours ? would love to see that. Book marked ! Thanks again :) – Hammas Jan 14 '21 at 17:37
  • @BionicCode in solution 2 would it be possible to use a normal property instead of a dependency property? – Trey Apr 29 '22 at 04:51
  • 1
    @Trey Yes, if the property raises the `INotifyPropertyChanged.PropertyChanged` event. The property `DestinationPath` is modified and must update the Binding, so it must participate in the property changed infrastructure of the binding engine. But you must know that the performance of DependencyProperty is *far* better than a plain CLR property that raises `PropertyChanged`. – BionicCode Apr 29 '22 at 07:54
  • 1
    @Trey Dependency property and meta data are stored in a fast lookup table and therefore don't need to use any reflection to resolve the `Binding` parameters like `Source` and `Path`. Implementing `INotifyPropertyChanged` still requires the binding engine to use reflection in order to resolve the binding source. The overhead of defining a dependency property is negligible and should therefore be preferred over implementing the `INotifyPropertyChanged` interface. – BionicCode Apr 29 '22 at 07:55
  • @BionicCode Can you please tell me what would you do in this case? User loads some file with 2D shapes data. You process the file in your business logic and if the file contains canvas coordinates (optional), ask user if he wants to directly place them on canvas. – TheSpixxyQ Apr 15 '23 at 20:10
  • 1
    @TheSpixxyQ Without knowing further details: interacting with the user ==> View. Displaying elements ==> View. Managing element containers (e.g. Canvas) ==> View. The MVVM approach would be to create a control that manages the Canvas that displays those shape data and that shows dialogs to interact with the user. You bind the shape data to this control. The control decides how to treat this data based on the result of the user interaction: render or not render. If user wants to display the data and the data has associated coordinates ==> render. – BionicCode Apr 15 '23 at 20:17
  • @BionicCode Thank you, here is more details. I have two separate collections, `LoadedShapes` bound to ListView and another collection bound to canvas. User can load multiple files and those shapes are added to `LoadedShapes`, he can then one by one place them on the canvas. Even if he already has X shapes loaded, when he loads some with the coordinate data, they will be appended to the `LoadedShapes` *and* he will be asked if he wants to place them on the canvas right away. – TheSpixxyQ Apr 15 '23 at 23:02
  • 1
    Now you have the following flow: 1) user loads shapes from file which updates LoadedShapes in the view model. 2) LoadedShapes binds to custom control property ShapeItemsSource. This custom control detects source collection changes by observing CollectionChanged event of the ShapeItemsSource collection. 3) If change is an "add" action the custom control shows a dialog to ask if new the item should be displayed on the internal Canvas 4) If user confirms add new item to the CanvasItems collection, that is finally used to populate the Canvas. – BionicCode Apr 16 '23 at 00:19
  • @BionicCode This makes sense. My app is about manipulating shapes and then generating GCode, which is then sent to a machine, so I'll also need to bind those `CanvasItems` back. If the file processing service fails, how do I show a notification to the user? `ObservableCollection` doesn't have `INotifyDataErrorInfo`. Maybe event or messenger? Does messenger have any place in View layer? Last question (a bit OT) - My `CanvasControl` needs to know about my `Shape` model so it can render it and call methods like `Rotate()` etc, right? If not - how else? – TheSpixxyQ Apr 16 '23 at 12:45
  • @TheSpixxyQ I like to use an EventAggregator that allows to anonymously subscribe to the `IVIewModel.OperationFailed` event of all existing IVIewModel instances. Then in the view you have a Messenger that collect those error messages or error codes and allows the view to handle it (e.g. allow MainWindow to show an error message to the user). However, you can use events to notify the view that an operation has failed. Or make the view call view model methods directly where necessary (instead of using commands). You can then use the return value to indicate a failed operation. – BionicCode Apr 16 '23 at 16:34
  • @TheSpixxyQ Regarding your `CanvasControl`: it does not need to know about `Shape`. I would make `Shape` a plain data model. How does `CanvasControl` displays the shapes, do you use an ItemsControl? Do you know how to configure a `ListBox`? It has the `ItemsContainerStyle` to allow to setup a binding of the ListBoxItem to the item model. You have to implement something similar. Then use a MatrixTransform or RotateTransform to rotate the containers (inside `CanvasControl`). – BionicCode Apr 16 '23 at 16:35
  • @TheSpixxyQ If the `Shape` model has a `Angle` property, you would bind it by defining a Style. It's possible to design `CanvasControl` like `ListBox`: it doesn't know the data types it handles. Such details are configured via DataTemplate and Style. The control should be model type agnostic. But it is allowed to know the containers that are actually added to the `Canvas`. – BionicCode Apr 16 '23 at 16:35
  • @TheSpixxyQ Clean solutions always require some extra effort. – BionicCode Apr 16 '23 at 16:36
  • 1
    @TheSpixxyQ I originally had an example that uses EventAggregator to allow the View Model to notify the view about errors using error codes. The example used file handling. I removed it because I thought it was too complex and "scary". Looks like you would have perfectly benefit from it. But I found this in the edit history of this post. Maybe it helps you to understand how to implement a messaging pipe from view model to view: [MVVM messaging example](https://pastebin.com/MTYtm2HP), [IEventAggregator.cs](https://pastebin.com/iR04p1Se), [EventAggregator.cs](https://pastebin.com/Vd4rNmtL) – BionicCode Apr 16 '23 at 16:48
  • @TheSpixxyQ Just replace the EventAgregator class dummy implementation from the example with the IEventAggregator and EventAggregator class from the above pastebin links. – BionicCode Apr 16 '23 at 16:50
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/253179/discussion-between-thespixxyq-and-bioniccode). – TheSpixxyQ Apr 16 '23 at 19:11
37

The best thing to do here is use a service.

A service is just a class that you access from a central repository of services, often an IOC container. The service then implements what you need like the OpenFileDialog.

So, assuming you have an IFileDialogService in a Unity container, you could do...

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

    string path = fileDialogService.OpenFileDialog();

    if (!string.IsNullOrEmpty(path))
    {
        //Do stuff
    }
}
BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
Cameron MacFarland
  • 70,676
  • 20
  • 104
  • 133
  • 3
    How do you tell the XAML to use this service? – Sheik Yerbouti Jan 25 '12 at 17:34
  • 1
    Depending on your MVVM framework you can use a command, or an action to execute code based on a button click or some other event. – Cameron MacFarland Jan 26 '12 at 14:16
  • 9
    Is there any way to specify the window that owns the dialog? – Collin K Jul 19 '13 at 20:12
  • @CollinK, yes. `Application.Current.MainWindow` – wingerse May 01 '16 at 10:52
  • 11
    Although the solution is correct, it represents a Service Locator anti-pattern. Instead of resolving the `IFileDialogService`, it should be passed as dependency in the ctor. – hyankov Dec 29 '16 at 10:36
  • 2
    Code-behind does not violate MVVM. MVVM isaiming to separate the business logic from the view. A dialog is clearly a UI component to collect user input like a `TextBox`. For the same reason you wouldn't (hopefully) recommend to handle the `TextBox` or its logic in the view model (e.g. hide/show, caret position) you shouldn't recommend to handle the dialog or its logic in the view model too. View model should only deal with data. The result of the interface/dialog is what the view model is concerned with. Like text input, the view must send this data to the view model for further handling. – BionicCode Sep 25 '20 at 16:32
  • 1
    There is no reason why you would want to explicitly delegate showing a control to the view model. It's not necessary - and violates the MVVM pattern. – BionicCode Sep 25 '20 at 16:32
  • @BionicCode The view model doesn't see a control. It sees a service that when it's called, returns a file path. How it gets that file path is unknown to the view model. Maybe a better name for the service would be `IFilePathService` and `GetFilePath` to hide the idea that this is an interactive UI service. – Cameron MacFarland Sep 26 '20 at 02:18
  • 2
    I understand this. But it should be the sole responsibility of the view to ask for input. That's the whole concept. The ideas of such a `IFilePathService` or `IDialogService` is wide spread. Sadly, because all it does is to hide a dependency to the UI. The same problem the Service Locator anti-pattern has. In a MVVM context you now have implicitly (the service is a hidden UI) inverted the dependency. Such a dependency inversion leads to a complete different application design, where the view model starts to ask the user for input or waiting for the user to take an action. – BionicCode Sep 26 '20 at 06:38
  • 1
    But the optimal implementation or MVVM design doesn't require the view model to ask for interaction. It just presents model data to the view for display. The view component usually modifies this data via the UI and the view model component sends this data back to the model. MVVM is an architectural design pattern. It is not about language level compiler concepts like `partial` classes (code-behind) or framework specific dependencies like `Window`. It's not about the concept of interface types or class types, which means it is irrelevant if you wrap a `Window` into a `IFilePathService`. – BionicCode Sep 26 '20 at 06:39
  • 1
    It's about responsibilities on a high level. In other words: you always want only the UI (or the user) to modify/display data. If you replace this user with a service e.g., an input generator, than this service becomes a member of the view component in MVVM terms. If the service interacts with the user then the service is a member of the view component and you shouldn't add a dependency to this service to the view model (which would invert the dependency: View <-- View Model). – BionicCode Sep 26 '20 at 06:39
  • 1
    MVVM requires the application to consist of three high level components: there is an input/output component, that only purpose is to modify (by user or service) and display the data provided by a presentation component, which prepares the business data of a hidden/decoupled business logic component. Your `IFilePathService` clearly falls into the domain of the input/output component from an architectural point of view, which is the only MVVM relevant point of view. – BionicCode Sep 26 '20 at 06:39
  • 1
    It's not about low level details or dependencies like `Window` or `UIElement`, but high level component dependencies or responsibilities. Avoiding dependencies of `Window` or `UIElement` in your view model component is only the result of a design that follows the high level concept. In this same way you would avoid a dependency to a service that interacts with the user (does what UI does). – BionicCode Sep 26 '20 at 06:40
  • 3
    All answers on this site are wrong as they all suggest that hiding the dialog behind an interface is what MVVM is about. This is class level thinking. But since MVVM is an architectural pattern, it doesn't care about class level details by definition. I decided to post this comment on your answer because it is the accepted answer. All this solutions are well suited to decouple the `Window` class from your view model. But they don't solve the problem of a view model invoking a view component, which is the only MVVM relevant part. – BionicCode Sep 26 '20 at 06:44
  • 4
    If we could only agree that code-behind is totally irrelevant in terms of MVVM, then we could at least agree upon that such a service is just adding noisy complexity to a trivial task: show a dialog. – BionicCode Sep 26 '20 at 06:50
  • 1
    @BionicCode: These have been a lot of comments. Are you saying the view's code-behind should actually show the dialog box, and only *then* (if confirmed with *Ok*) trigger the view-model to load data from the selected file path? Shouldn't that go into an answer of its own? – O. R. Mapper Nov 14 '20 at 22:54
  • 1
    @O.R.Mapper Yes lots of comments. In MVVM the view is not only the .xaml and its .xaml.cs file. All classes that are concerned with the UI are considered view in terms of MVVM. This are converters, markup extensions, attached behaviors, anything that extends `DependencyObject`, classes that a part of UI logic, resources etc. The dialog should be triggered and displayed by a view class and not by the view model or the model. This can be the code-behind file or any other UI related class. Question: why not showing the dialog from the model? – BionicCode Nov 16 '20 at 12:58
  • 1
    @O.R.Mapper For some reason a few people believe a service used by the view model solves the problem, instead of designing the application properly. This service "solution" is too popular here on SO and spreads like a virus. The misconception is that code-behind is bad or even worse, kills MVVM - this is pure nonsense. Now they implement this service solver the pseudo problem of code-behind and create a real problem: polluting the view model with UI logic. – BionicCode Nov 16 '20 at 12:59
  • 1
    @O.R.Mapper According to MVVM the view model (and the model) doesn't know that there is something like a view. Both components don't know about users or user interfaces to collect user input. That's the magic of the pattern: decoupling the UI completely from the business application. Showing/hiding controls is part of the UI logic and therefore not in the domain of the view model (or model). This question already has an accepted answer. Posting an answer may not have any relevance. – BionicCode Nov 16 '20 at 13:00
  • 4
    @BionicCode: "Posting an answer may not have any relevance." - I might upvote it, and so might other users. It would be the *right* place to provide this information, as opposed to comments, which are not meant to contain answers on their own. And lastly, it would make the information more readable due to more freedom in formatting (paragraphs, for a start, plus no length limitation). – O. R. Mapper Nov 16 '20 at 13:19
  • 2
    @BionicCode I agree with OR Mapper, the information and discussion is getting lost as your comments are no longer just about my answer. There's value in providing an alternative view, especially given how old my answer is. – Cameron MacFarland Nov 16 '20 at 13:23
12

I would have liked to comment on one of the answers, but alas, my reputation is not high enough to do so.

Having a call such as OpenFileDialog() violates the MVVM pattern because it implies a view (dialog) in the view model. The view model can call something like GetFileName() (that is, if simple binding is not sufficient), but it should not care how the file name is obtained.

JAB
  • 313
  • 2
  • 10
  • When GetFileName() opens the dialog, then the view model in fact shows a dialog. It doesn't matter if this is done implicitly or explicitly. In an application that is designed to follow MVVM the view model component's only concern is to present (passive act) the model's data to the view. It never asks (active act) for input. GetFileName at one point requires the view model component to maintain a strong reference to a view component. Data binding doesn't. – BionicCode Sep 26 '20 at 07:03
  • You can't compare invoking a method to data binding. Data binding was invented to solve the problem of getting data from an object (binding target) without introducing a dependency. – BionicCode Sep 26 '20 at 07:03
9

The ViewModel should not open dialogs or even know of their existence. If the VM is housed in a separate DLL, the project should not have a reference to PresentationFramework.

I like to use a helper class in the view for common dialogs.

The helper class exposes a command (not an event) which the window binds to in XAML. This implies the use of RelayCommand within the view. The helper class is a DepencyObject so it can bind to the view model.

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

The helper class needs a reference to the ViewModel instance. See the resource dictionary. Just after construction, the ViewModel property is set (in the same line of XAML). This is when the FileName property on the helper class is bound to the FileName property on the view model.

<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>
BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
Paul Williams
  • 3,099
  • 38
  • 34
  • Just curious, (I'm still learning WPF), why didn't you make the "public ICommand OpenFile { get; private set; }" a dependency property? I'm having a hard time knowing when to make something a dependency property and when it's just ok to have it a plain .Net object. – EbbnFlow Jun 09 '14 at 13:47
  • 1
    Either would work, but CLR properties are lighter weight so I prefer them where possible. It's not a big deal though. Remember that the object that you are binding TO may be a plain CLR property. (But of course the thing that you are binding FROM must be a DP, which is why FileName is a DP.) – Paul Williams Jun 09 '14 at 19:10
  • 1
    I disagree with you, that "ViewModel should not open dialogs or even know of their existence". There are such scenarios where in the middle of command code you need to open a file dialog or the same. You have not to have a references to classes defined in View layer, but you can have a references to view objects at run time and **work with interfaces defined in ViewModel and implemented in View**. See: http://stackoverflow.com/questions/42931775/is-mvvm-pattern-broken – Rekshino May 03 '17 at 06:52
  • **Code-behind does not violate MVVM!**. Both are different concepts that don't relate to each other. Showing a dialog from an event handler is fine. It doesn't couple the business logic (model) to the UI (view), which is the ONLY goal of MVVM. Your solution adds nothing but unnecessary complexity and lines of code. – BionicCode Sep 26 '20 at 07:09
  • @Rekshino There are no such scenarios if you are designing your application properly. MVVM is an architectural pattern. On this very high design level there are no classes and interfaces. MVVM is not about dependencies between types but components. A view model component should not be designed in a way that it **requires** to interact with the user. That's the whole point of the MVVM pattern. – BionicCode Sep 26 '20 at 07:13
8

I use a service which i for example can pass into the constructor of my viewModel or resolve via dependency injection. e.g.

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

and a class implementing it, using OpenFileDialog under the hood. In the viewModel, i only use the interface and thus can mock/replace it if needed.

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
Botz3000
  • 39,020
  • 8
  • 103
  • 127
  • 1
    My only problem with instance properties on services is what about if two threads call OpenFileDialog at the same time? Not really an issue for file dialogs but can be an issue for other types of services. – Cameron MacFarland Jun 25 '09 at 14:43
  • 1
    With dependency injection containers, you can usually register it so that each time the service is resolved, a new instance is created. So you could create a new service, use it and throw it away after that. Or, you only use the OpenFileDialog() method and let it return a string, or null if it was aborted. – Botz3000 Jun 25 '09 at 14:50
  • 1
    While this is true, I agree with Cameron's assessment here. You run the risk of unintended side-effects with the property approach. I know that this is the same pattern that the .NET Framework uses, but this isn't necessarily an argument for this pattern, just that it exists. I prefer the use of checking for null in the return value to indicate a failure of some sort (or user hitting "Cancel"). – Anderson Imes Nov 29 '09 at 20:46
  • 1
    *this is the same pattern that the .NET Framework uses* Yeah, for it's open file *dialogs*. Neither WinForms nor WPF support cross thread access so as far as this is implemented in the .NET Framework the concern is moot. – ta.speot.is Dec 14 '14 at 10:49
4

I have solved it for me this way:

  • In ViewModel I have defined an interface and work with it in ViewModel
  • In View I have implemented this interface.

CommandImpl is not implemented in code below.

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

View:

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>
Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
Rekshino
  • 6,954
  • 2
  • 19
  • 44
3

Having a service is like opening up a view from viewmodel. I have a Dependency property in view, and on the chnage of the property, I open up FileDialog and read the path, update the property and consequently the bound property of the VM

Jilt
  • 31
  • 1