0
votes

There are tons and tons of articles around the internet about this topic, but I just can't wrap my head around it. Most articles use code behind, but I want to stick to "pure" MVVM since I try to learn it. Also, I explicitly don't want to use any other framework (MVVMlight, Ninject...). I just want to stick to what WPF has to offer. I know this got asked a lot, but what I found either was not mvvm or was not specific enough.

My task is simple: I want to see the most simple solution of opening a modal dialog, send it a string, and get a string from the dialog back upon closing it.

Therefore I set up my MainWindow.xaml with a text input field (TextBox), a button (that should open the modal dialog) and a textblock that will show the message I intend to receive from the dialog.

The dialog has a TextBlock, showing the user-input from MainWindow.xaml, and a TextBox to enter some text, and a button. You guessed it: you press the button, and the message I typed into the textfield get's returned to MainWindow.xaml. Please refer also to the images I've included - I think it's pretty self-explanatory.

MainWindow.xaml

<Window x:Class="Dialogs.MainWindow"
     ...
Title="First View (Main Window)" Height="240" Width="630">
<Grid>
    <StackPanel>
        <StackPanel Orientation="Horizontal" Margin="10">
            <TextBlock Text="Main View sayz: "/>
            <TextBox Width="360" Margin="10,0,0,30"/>
        </StackPanel>

        <Button Content="Send to Second View" Command="{Binding SendToSecondViewCommand}" Width="200"/>

        <StackPanel Orientation="Horizontal" Margin="10,30,10,10">
            <TextBlock Text="Second View replies: "/>
            <TextBlock Width="360"/>
        </StackPanel>           

    </StackPanel>

</Grid>
</Window>

SecondView.xaml

<UserControl x:Class="Dialogs.SecondView"
    ...
d:DesignHeight="240" d:DesignWidth="630" Background="BlanchedAlmond">
<Grid>
    <StackPanel>
        <StackPanel Orientation="Horizontal" Margin="10">
            <TextBlock Text="This is what First View sayz: "/> 
            <TextBlock Width="360"/>
        </StackPanel>

        <StackPanel Orientation="Horizontal" Margin="10">
            <TextBlock Text="Second View replies: "/>
            <TextBox Width="360" Margin="10,0,0,30"/>
        </StackPanel>

            <Button Content="Reply to First View" Command="{Binding ReplyToFirstViewCommand}" Width="200"/>

    </StackPanel>
</Grid>
</UserControl>

Here is how I implemented INotifyPropertyChanged (It's actually a .cs file named BaseClasses; I know it's not named properly...)

public abstract class NotifyPropertyChangedBase : INotifyPropertyChanged
{        
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged<T>(ref T variable, T value,
                    [CallerMemberName] string propertyName = null)
    {
        variable = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

And here my base class for relay commands:

public class CommandDelegateBase : ICommand
{        
    public delegate void ExecuteDelegate(object parameter);

    public delegate bool CanExecuteDelegate(object paramerter);

    private ExecuteDelegate execute;

    private CanExecuteDelegate canExecute;

    public CommandDelegateBase(ExecuteDelegate _execute, CanExecuteDelegate _canExecute = null)
    {
        execute = _execute;
        canExecute = _canExecute;
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public bool CanExecute(object parameter)
    {
        return canExecute?.Invoke(parameter) ?? true;
    }

    public void Execute(object parameter)
    {
        execute.Invoke(parameter);
    }

}

Lastly my ViewModels: FirstViewModel:

public class FirstViewViewModel: NotifyPropertyChangedBase
{
    private string _sendText;
    public string SendText
    {
        get { return _sendText; }
        set
        {
            _sendText = value;

            OnPropertyChanged(ref _sendText, value);
        }
    }

    public ICommand SendToSecondViewCommand { get; set; }

    public FirstViewViewModel()
    {
        SendToSecondViewCommand = new CommandDelegateBase(SendExecuteCommand, SendCanExecuteCommand);
    }

    private bool SendCanExecuteCommand(object paramerter)
    {
        return true;
    }

    private void SendExecuteCommand(object parameter)
    {
        //Do stuff to :
        // a) show the second view as modal dialog
        // b) submit what I just wrote (SendText)
    }
}

SecondViewModel:

public class SecondViewViewModel : NotifyPropertyChangedBase
{
    private string _replyText;
    public string ReplyText
    {
        get { return _replyText; }
        set
        {
            _replyText = value;

            OnPropertyChanged(ref _replyText, value);
        }
    }

    public ICommand ReplyToFirstViewCommand { get; set; }

    public SecondViewViewModel()
    {
        ReplyToFirstViewCommand = new CommandDelegateBase(ReplyExecuteCommand, ReplyCanExecuteCommand);
    }

    private bool ReplyCanExecuteCommand(object paramerter)
    {
        return true;
    }

    private void ReplyExecuteCommand(object parameter)
    {
        //Do stuff to :
        // a) close the second view 
        // b) reply what I just wrote (ReplyText) back to First View.
    }
}       

I have a folder called "Models" in my solution but for the sake of simplicity it's empty.

I know there are solutions with helper classes or services - what ever pertains mvvm will do. I also do know that doing this for such a simple task as what I want is quiet "overkill", and has a lot more writing code coming with it than it would be justifyable for this purpose. But again: I'd like to learn this, and understand what I am doing.

Thank you so much in advance!

MainWindow.xaml SecondView.xaml Structure of my solution

2
You may have a look at my CleanArchitecture project where I just added the Dialog featureSir Rufo
Thank you for your link - noob question: is there any folder I should pay certain attention to? Like "Desktop.WpfApp" or Desktop.Application? The way I understand this, these folders represent complete wpf-solutions to opened in Visual Studio?SelfBiased_Resistor
It is a single WPF application with separated projects for the layers (Application, Infrastructure, Presentation and the WPF-Platform).Sir Rufo

2 Answers

1
votes

I wrote an article about this subject and provided a library and sample application. The article itself is long...because it's not a trivial topic...but causing a dialog box to appear can be as simple as this:

this.Dialogs.Add(new CustomDialogBoxViewModel()); // dialog box appears here

UPDATE: I just noticed that my MvvmDialogs library in that package is actually referencing MvvmLite. That's a vestigial remnant from when I was developing it though, the library itself doesn't need it, so you can remove the reference altogether.

-1
votes

Finding an MVVM pure solution to a programming problem, which may be straightforward in other contexts, is often not a simple task. However, creating a library of helper classes is a "write once, use many times" scenario, so no matter how much code is required, you don't have to reproduce it for every usage.

My preferred method for handling message dialogs in MVVM is a two part service module.

  1. The View registers its data context (its ViewModel) with the DialogService as potentially wanting to display a dialog - the service will use the View's UI context to do so when it does.

  2. The ViewModel calls the injected dialog service each time a dialog should be displayed. Calls to the MessageDialog service are made using the async / await pattern, rather than requiring some other form of callback in the ViewModel.

So now, displaying a MessageDialog from a ViewModel is as simple as

await _dialogService.ShowMessageAsync(this, "Hello from the dialog service.", perDialogIcon.Information, "Mvvm Dialog Service").ConfigureAwait(false);

or

var response = await _dialogService.ShowDialogAsync(this, perDialogButton.YesNo, "Do you want to continue?", perDialogIcon.Question, "Mvvm Dialog Service").ConfigureAwait(false);

I covered this in more detail on a blog post.

As an aside, your ViewModel properties look a bit wierd - you're setting the backing-field value, then passing it into your OnPropertyChanged() method where the value is set again.