Tamir Dresher

Creating a Dialog service for Windows Store apps using Caliburn.Micro

28 Nov 2014

This post was originally published on 28 Nov 2014 at http://blogs.microsoft.co.il/iblogger/2014/11/28/creating-dialog-service-windows-store-apps-using-caliburn-micro/

When you want to display a dislog screen in windows store apps, you can use Flyout, MessageDialog or Popup

the first two should be used for the simple cases – message prompts, question, basic user interaction.

But if you need to display a richer dialog then Popup is the right fit.

The MVVM way to open those dialogs is by using some kind of DialogService that will be called from you ViewModels.

in all the sample i’ve seen on using Popup it always has a strong coupling to the View itself, meaning the Popup was embedded inside the view layout and was triggered to appear by changing the IsOpen property to true.

I wanted to create a service that creates the popup for me, no matter from where i call it. for me, the right approach is to open the popup above the application Frame.

Since im a Caliburn.Micro fan and since Caliburn is the framework with i did it in Caliburn style.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
public class DialogService : IDialogService
{
    
    public virtual Task ShowPopupAsync<T>(object context = null, IDictionary<string, object> settings = null) where T : IScreen
    {
        var rootModel = IoC.Get<T>();
        return ShowPopupAsync(rootModel, context, settings);
    }
     
    public virtual Task ShowPopupAsync(IScreen rootModel, object context = null, IDictionary<string, object> settings = null)
    {
        var taskCompletionSource = new TaskCompletionSource<bool>();
 
        var popup = CreatePopup(rootModel, settings);
        var view = ViewLocator.LocateForModel(rootModel, popup, context);
 
        popup.Child = view;       
 
        ViewModelBinder.Bind(rootModel, popup, null);
        Caliburn.Micro.Action.SetTargetWithoutContext(view, rootModel);
 
        SetPopupPosition(popup, view);
        
        var activatable = rootModel as IActivate;
        if (activatable != null)
        {
            activatable.Activate();
        }
 
        var deactivator = rootModel as IDeactivate;
 
        popup.Closed += delegate
        {
 
            try
            {
                rootModel.CloseView = null;
                if (deactivator != null)
                {
                    deactivator.Deactivate(true);
                }
            }
            finally
            {
 
                taskCompletionSource.SetResult(true);
            }
 
        };
 
 
 
        popup.IsOpen = true;
        return taskCompletionSource.Task;
    }
 
    private void SetPopupPosition(Popup popup, UIElement popupContent)
    {
        var windowContnt = Window.Current.Content as UIElement;
 
        popupContent.Measure(new Size(Window.Current.Bounds.Width, Window.Current.Bounds.Height));
        popupContent.Arrange(new Rect(0, 0, popupContent.DesiredSize.Width, popupContent.DesiredSize.Height));
 
        var horizontalOffset = Math.Max(0, Window.Current.Bounds.Width / 2 - popupContent.DesiredSize.Width / 2);
        var verticalOffset = Math.Max(0, Window.Current.Bounds.Height / 2 - popupContent.DesiredSize.Height / 2);
 
        popup.HorizontalOffset = horizontalOffset;
        popup.VerticalOffset = verticalOffset;
    }
 
 
    protected virtual Popup CreatePopup(object rootModel, IDictionary<string, object> settings)
    {
        var popup = new Popup();
 
    popup.IsLightDismissEnabled = true;
        ApplySettings(popup, settings);
 
        return popup;
    }
 
 
 
    bool ApplySettings(object target, IEnumerable<KeyValuePair<string, object>> settings)
    {
        if (settings != null)
        {
            var type = target.GetType();
 
            foreach (var pair in settings)
            {
                var propertyInfo = type.GetPropertyCaseInsensitive(pair.Key);
 
                if (propertyInfo != null)
                {
                    propertyInfo.SetValue(target, pair.Value, null);
                }
            }
 
            return true;
        }
 
        return false;
    }
 
}

One intersting thing about Popup is that it supports light dismiss, meaning i can close the popup by clicking outside its region, I decide to turn this option by default, you can always override it by passing false inside the settings dictionary.

Caliburn knows how to make the connection between the IScreen.TryClose() method and the Popup IsOpen property so i dont need to handle it by myself.