Bulchsu
Bulchsu

Reputation: 690

How to correctly get output from dialog in WPF MVVM?

I tried to find answers to my question on the internet, but no answer satisfied me enough. I am writing WPF application and i'm trying to implement dialog mechanism. I have a simple ViewModel, and when some event happens, i would like to show a dialog, collect some output data from it and store it in "parent" View Model. My method in a view model looks like this:

    private void Expand()
    {

        ...

        catch(ArgumentNullException)
        {

            Shrink();

            var errorDialogVM = new DialogVM(new Dialog() { Type = DialogType.Error, Message = $"Unauthorized access to \"{FileManager.GetDirectoryName(Path)}\" directory!" });

            DialogService.ShowDialog(errorDialogVM);

            //Here i need output from dialog

        }

    }

Implementation of ShowDialog method:

    public void ShowDialog(DialogVM dialogVM)
    {

        var dialog = new DialogBox();
        var mainWindow = Application.Current.MainWindow as MainWindow;

        dialog.DataContext = dialogVM;

        dialog.Owner = mainWindow;

        dialog.Show();

    }

Now, let's imagine that i need some data from the dialog. How can i pass it to my ViewModel in a proper way?

Upvotes: 0

Views: 1791

Answers (2)

BionicCode
BionicCode

Reputation: 28968

View model shouldn't handle view elements. A dialog is a view element.
The view model can trigger user input by raising and event e.g., an error event with an data model as event args. The view that has registered to the event shows a dialog to collect user input and stores them in the previously received data model. The view then executes a command on the view model to pass back the data model.

Instead of an event you can also bind the view to a property of the view model e.g. of type bool. On property change show the dialog and return the result using a ICommand.

Alternatively let the view model expose a flag e.g. HasException and a property ExceptionDialogModel which can be used to bind a custom dialog or form. Then create a simple modal dialog yourself:

ExampleDialog

<Grid x:Name="ExampleDialog"
      Visibility="Visible"
      Panel.ZIndex="100"
      VerticalAlignment="Top">
  <Rectangle Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window}, Path=ActualWidth}"
             Height="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window}, Path=ActualHeight}"
             Fill="Gray"
             Opacity="0.7" />
  <Grid Width="400"
        Height="200">
    <Grid.RowDefinitions>
      <RowDefinition Height="*" />
      <RowDefinition Height="100" />
    </Grid.RowDefinitions>
    <Border Grid.RowSpan="2"
            Background="LightGray"
            BorderBrush="Black"
            BorderThickness="1">
      <Border.Effect>
        <DropShadowEffect BlurRadius="5"
                          Color="Black"
                          Opacity="0.6" />
      </Border.Effect>
    </Border>
    <TextBlock Grid.Row="0"
               TextWrapping="Wrap"
               Margin="30"
               Text="I am a modal dialog and my Visibility or Opacity property can be easily modified by a trigger or a nice animation" />
    <StackPanel Orientation="Horizontal"
                Grid.Row="1"
                HorizontalAlignment="Right"
                Height="50">
      <Button x:Name="OkButton"
              Content="Ok"
              Width="80" />
      <Button x:Name="CancelButton"
              Margin="30,0,30,0"
              Content="Cancel"
              Width="80" />
    </StackPanel>
  </Grid>
  <Grid.Triggers>
    <EventTrigger RoutedEvent="Button.Click">
      <BeginStoryboard>
        <Storyboard>
          <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ExampleDialog"
                                         Storyboard.TargetProperty="Visibility"
                                         Duration="0">
            <DiscreteObjectKeyFrame Value="{x:Static Visibility.Hidden}" />
          </ObjectAnimationUsingKeyFrames>
        </Storyboard>
      </BeginStoryboard>
    </EventTrigger>
  </Grid.Triggers>
</Grid>

You can put the Grid anywhere in your Window and toggle the Visibility. It will overlay the parent Window and has modal behavior.
Bind the DataContext to the ExceptionDialogModel so that the data is send back via TwoWay binding. Use a command to trigger a retry procedure (e.g., an OK or Retry button).
The Visibility can bind to the HasException property. You can animate this dialog and give it any look and feel you like.

Upvotes: 1

edwabr123
edwabr123

Reputation: 97

I believe you are doing this backwards. You should pass reference to your view model to the dialog, not the other way around because view model should be separate and not aware of the view's mechanics. Dialog, on the other hand, knows which properties of view model it needs to set. So it will be something like the following:

public class MyDialog : Dialog
{
    public MyDialog(DialogVM ViewModel) {
        this.InitializeComponent();
        this.DataContex = ViewModel;
        // TODO: Bind to view model's properties in XAML or set them on OnClose()
    }
}

Upvotes: 0

Related Questions