19

Simple question for the MahApps Merry Men. I have implemented an application using your great metro styled controls using Caliburn.Micro for the MVVM stuff. The new message dialogs look great, but currently there is not clear way of launching these dialogs with out writing my own wrapper (which I am not against). However, has this been done or is there something I missing so that I can invoke a message box from a view model without any fuss?

Thanks for your time.

MoonKnight
  • 23,214
  • 40
  • 145
  • 277

4 Answers4

20

As of 1.1.3-ALPHA* (to become 1.2.0) MahApps provides a helper to launch dialogs from a VM, which works in a multiple Window setup:

1) Use an attached property in your Window to register your view model with the dialog sub-system.

Assuming your View’s DataContext is set to the view model from where you want to launch the dialog, add these attributes:

<Controls:MetroWindow 
    xmlns:Dialog="clr-namespace:MahApps.Metro.Controls.Dialogs;assembly=MahApps.Metro"
    Dialog:DialogParticipation.Register="{Binding}">

2) Grab/inject DialogCoordinator:

new MainWindowViewModel(DialogCoordinator.Instance);

3) Show your dialog from the view model. Use "this" as the context so MahApps can marry your view model to the correct window:

_dialogCoordinator.ShowMessageAsync(this, "Message from VM", "MVVM based dialogs!")
James Willock
  • 1,999
  • 14
  • 17
  • Do you mean to say 1.1.2.0 of MahApps.Metro because right now there's no 1.2 according to [MahApps.Metro Nuget Feed](https://www.nuget.org/packages/MahApps.Metro/1.1.2) – Maverik Oct 20 '15 at 14:22
  • 1.2 ...it's currently in alpha, so make sure you include pre release versions in your nuget search. – James Willock Oct 20 '15 at 19:19
  • Are you sure it's in Nuget feed and not in myget feed? Nuget feed only has MahApps.Metro 1.1.3-ALPHA198 right now from what I can see? In anycase, I was able to use your answer on 1.1.3-ALPHA series even and worked like a charm, so thank you for pointing this out :) – Maverik Oct 21 '15 at 14:17
  • Ah, OK, yeah sorry....1.1.3-ALPHA* is basically what will go into 1.2. Apologies. – James Willock Oct 21 '15 at 14:58
  • 1
    No problem. Perhaps amend your answer so that people reading this don't get confused until 1.2.0 is released? – Maverik Oct 21 '15 at 15:23
  • @JamesWillock Do you have an example on how to register the view through code behind? – Andres Ramos Apr 22 '16 at 22:02
19

I have created a wrapper to call the MahApps.Metro message dialog, cause I was having the same problem with my MVVM project. I had to create a list of opened windows, which the first window will always be my MainWindow.

Here's my DialogService code:

public async Task<MessageDialogResult> ShowMessage(string message, MessageDialogStyle dialogStyle)
{
    var metroWindow = (_openedViews.First() as MetroWindow);
    metroWindow.MetroDialogOptions.ColorScheme = MetroDialogColorScheme.Accented;

    return await metroWindow.ShowMessageAsync("MY TITLE", message, dialogStyle, metroWindow.MetroDialogOptions);
}

This code can be used to show dialogs with or without a result. You can notice that its return is a Task<MessageDialogResult>, so if you want to get the result, you can do just like that on your ViewModel:

MessageDialogResult result = await _dialog.ShowMessage("SOME MESSAGE HERE", MessageDialogStyle.AffirmativeAndNegative).ConfigureAwait(false);

if (result == MessageDialogResult.Affirmative)
{
    //Do something
}

By the way, if the method that calls the ShowMessage() needs a result, you MUST put async on the assignment, otherwise it won't work. (if you only want to show a message dialog, it's not necessary).

My project is using Framework 4.0, and I can only use async/await due to the package I had to install from NuGet. You can acces this link for the MSDN documentation of this package, and you can download the package here.

I hope it has solved your problem.

EDIT:

I have implemented a method on my DialogService to open any windows from any ViewModel. This method uses Microsoft Unity framework to instantiate my object, and then I call Show() to open itself. Before a call Show(), I add this window on a list.

See my code:

public void ShowView<T>(params ParameterOverride[] parameter)
{
    var window = UnityServiceConfigurator.Instance.Container.Resolve<T>(parameter) as MetroWindow;

    if (window != null)
    {
        if (Application.Current.MainWindow != window)
        {
            window.Owner = Application.Current.MainWindow;
            var ownerMetroWindow = (window.Owner as MetroWindow);

            if (!ownerMetroWindow.IsOverlayVisible())
                ownerMetroWindow.ShowOverlayAsync();
        }

        if (!_openedViews.Contains(window))
            _openedViews.Add(window);

        window.Show();
    }
}

This is how I call from my ViewModel:

_dialog.ShowView<MyView>();

If you have only one window on your entire software, you can save its reference and use it to show the ShowMessageAsync() without needing to create a List only to use the First. Like this:

var metroWindow = (Application.Current.MainWindow as MetroWindow);
Guilherme Oliveira
  • 2,008
  • 3
  • 27
  • 44
  • How do you get the list of open views from the view model with out breaking MVVM? – MoonKnight Mar 05 '14 at 22:51
  • @Killercam You can check my edit. I answered you there. – Guilherme Oliveira Mar 06 '14 at 11:13
  • Well, Thanks @Guilherme. I have created that message service already. But my message service would return a true or false depending on whether the user selected affirmative/negative option. I too am using .net 4.0 and am not willing to share the mahapps dll in my view models project. I am not sure how to block the call to show dialog in the Messaging Broker itself and then send back the yes/no to the view model. Any idea? – James Mar 10 '14 at 06:43
  • Sorry to spam. Creating an independent dialog is always an option but I was checking if the metro style dialogs (of mahapps) can be used for this purpose. Thanks – James Mar 10 '14 at 06:44
  • @James Actually, I didn't get your idea. You want to show a message dialog, but it will return a yes/no depending on the affirmative/negative, is that right? You could do this checking inside the method that calls the dialog. Or you could try using the async/await as I described on my answer. Btw, I wouldn't create a independent dialog as a message dialog. – Guilherme Oliveira Mar 10 '14 at 11:08
  • But how to make that message broker method NON "aync". I am struggling with that. I want that message service method to return a bool value. – James Mar 10 '14 at 14:32
  • @James Now I get it. If I were you, I would return Affirmative and Negative, instead of true and false, cause to use this `ShowMessageAsync`, the method has to be async. I asked on GitHub about this problem, you can read here: https://github.com/MahApps/MahApps.Metro/issues/1092 – Guilherme Oliveira Mar 10 '14 at 16:23
  • I don't mind returning even messagedialog. I will just need to put a result==MessageDialog.Affirmative to return true or false. But I am glad that you got my problem. The whole problem is async method's inevitability.Thanks. – James Mar 11 '14 at 05:19
  • I agree Yes/No is sort of restrictive. MessageDialog would let you return more options. But as I said I dont want my ViewModels to touch the MahApps dll. But thats not a problem, my message boroker can convert the mahApps's MessageDialog enum to another wrapper enum that my viewq models understand. – James Mar 11 '14 at 05:21
  • @James Or you could have two methods, one is public (you would call this one) and return true/false, the other one is private and return `MessageDialogResult`. Both must be async and you have to put await in front of the calling of both method. Then, from your ViewModel, you call the first one which returns true/false. I think it might work. You're welcome. – Guilherme Oliveira Mar 11 '14 at 11:13
  • An aysnc method either has to be void ot it has to return Task. Can you paste a few line of code here about what you are thinking. The implementations arent required but atleast the code responsible for async/await.Here, for instance is the interface i am talking of: public interface IMessageService { bool ConfirmFromUser(string header,string message) } – James Mar 11 '14 at 12:21
  • @James This method calls the `ShowMessage` that I put above on my answer: `public async Task ConfirmFromUser(string header, string message) { var result = await ShowMessage(message, MessageDialogStyle.AffirmativeAndNegative); return result == MessageDialogResult.Affirmative; }` From the ViewModel, you can call like this: `bool confirm = await _dialog.ConfirmFromUser("MY TITLE", "MY MESSAGE").ConfigureAwait(false);` I think it might help you. – Guilherme Oliveira Mar 11 '14 at 17:39
  • Where you take _openedViews from? – Oleksii Jun 12 '16 at 08:34
  • @Oleksii This is a private property on my DialogService. – Guilherme Oliveira Jun 13 '16 at 11:07
0

If you have only one instance of your window showing, you can try something like this:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Caliburn.Micro;
using MahApps.Metro.Controls;
using MahApps.Metro.Controls.Dialogs;

namespace Busyboy
{
    [Export(typeof(IShell))]
    class MainViewModel : PropertyChangedBase, IShell
    {
        public void StartPomodoro()
        {
            var mainview0 = System.Windows.Application.Current.Windows.OfType<MainView>().FirstOrDefault();
            mainview0.ShowInputAsync("New Pomodoro", "Enter a name for new pomodoro session.");
        }
    }
}

And, you should have a way of identifying each window so you can filter out windows. Please note the import "Metro.Controls.Dialogs" which contains the extensions.

Wickramaranga
  • 943
  • 2
  • 19
  • 35
0

I was able to get this working by first making the Dialog parent a Conductor<Screen> . Then in some VM action to launch the Dialog, I simply did the below:

public async Task LaunchDialog(MyDialogVM vm)
{
   var customDialog = new CustomDialog { Title = "Some Title" };
   var view = new MyDialogView{DataContext = vm};   // instance of the view user control
   customDialog.Content = view;
   // this registers the vm with CaliburnMicro, hence all life-cycle events are available
   ActivateItem(vm);    

   await _dialogCoordinator.ShowMetroDialogAsync(this, customDialog);

}
Purusartha
  • 992
  • 4
  • 19
  • 33