0
votes

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?

2
Why not create a property in the view model for the data you want to store?zaggler
Well, the problem is i am using ViewModel of my Dialog in parent-ViewModel class. I would like to get some Data provided by user, stored in dialog-ViewModel in parent-ViewModel (after calling ShowDialog, like "ParentProperty = errorDialogVM.Data;" After calling ShowDialog method, Expand() continues executing and the input data is not even created yet. I need some idea to inform parentVM that data is ready to catch and somehow deliver it to the VM.Bulchsu
View model shouldn't handle view elements. A dialog is a view element. A better solution would be to 1. Catch the exception 2. Raise an error event with an error model as event args 3. The view that has registered to the error event shows a dialog to collect user input and stores them in the previously received error model 4. View executes a command on the view model to pass back the error model 5. View model can process the error model (user input).BionicCode
You can add a DialogDataReady event to your dialog view model. The parent view model can subscribe to this event and handle it. But as I said before this is not good design as it violates MVVM. You introduced a dependency to your view by handling dialogs inside your view models. I think it's absolutely not necessary and very cheap to avoid. See my answer. I tried to show some alternatives that are not violating MVVM.BionicCode
Dialog Boxes are problematic with MVVM, but it can be done. You might want to read my article about this, I've provided a library too that makes it all much easier.Mark Feldman

2 Answers

1
votes

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.

0
votes

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()
    }
}