I've decided to keep the static Show class from Caliburn. So, when you wish to display a message box, you just have to call

public IEnumerable<IResult> Display()
{
    yield return Show.MessageBox(text,caption, Answer.Ok);
}

The Caliburn's version of the MessageBoxResult returned from Show.MessageBox calls ShowMessageBox from the IWindowManager interface, which basically creates a new window "on-the-fly" and sets the content of this window with an instance of the QuestionDialogViewModel class.

I'm going to keep the principle, but instead of displaying the message box in a new window, I will set a property of type IQuestionDialog on my shell view-model. The view of the shell will have a ContentControl, which will have a dependency property of type View.Model bound to the property. That ContentControl will be collapsed by default, and using a converter, we will display it when the property of the view-model is not null. Putting the ContentControl above all the other controls of the shell view-model will then just be a matter of some XAML coding.

Let's begin to write the IQuestionDialog__ interface:

public interface IQuestionDialog : IScreen
{
    void Setup(string caption, Question question);

    event EventHandler<EventArgs> OnClose;
}

From the original implementation, I now use only one question, and I added an OnClose event.

The QuestionDialogViewModel is implemented as:

public class QuestionDialogViewModel : Screen, IQuestionDialog
{
    public QuestionDialogViewModel()
    {
        Caption = "Shell";
    }

    public Question Question { get; set; }
    public string Caption { get; set; }

    public BindableCollection<ButtonModel> Buttons
    {
        get { return Question.Buttons; }
    }

    public void Setup(string caption, Question question)
    {
        Caption = caption;
        Question = question;
    }

    public event EventHandler<EventArgs> OnClose = delegate { };
        
    public void SelectAnswer(Answer answer)
    {
        Question.Answer = answer;

        OnClose(this, EventArgs.Empty);
    }
}

The code is much more simpler than in the original version, because I removed the multi-questions, and because the NotifyOfPropertyChanged calls have disappeared, thanks to PostSharp. You have to notice that I raise the OnClose event when the SelectAnswer method is called.

Here is the XAML code of the view:

<UserControl x:Class="Emidee.Basket.Views.QuestionDialogView" 
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:Micro="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro" 
             mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300">
    <Grid HorizontalAlignment="Center" VerticalAlignment="Center" MinHeight="100" MinWidth="300" MaxWidth="400" >
        <Border Background="White" CornerRadius="6">
            <Border.BitmapEffect>
                <DropShadowBitmapEffect ShadowDepth="20" Softness="45" Direction="-60" Opacity="0.5" />
            </Border.BitmapEffect>
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="*" />
                    <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>

                <TextBlock Text="{Binding Caption}" Margin="10" FontWeight="Bold" />

                <TextBlock Text="{Binding Question.Text}" Grid.Row="1" TextWrapping="Wrap" Margin="10" />

                <ItemsControl HorizontalAlignment="Right" ItemsSource="{Binding Question.PossibleAnswers}" Grid.Row="2">
                    <ItemsControl.ItemsPanel>
                        <ItemsPanelTemplate>
                            <StackPanel Orientation="Horizontal" />
                        </ItemsPanelTemplate>
                    </ItemsControl.ItemsPanel>
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <Button Content="{Binding}" Micro:Message.Attach="SelectAnswer($dataContext)" Margin="0 10 10 10" MinWidth="70" MinHeight="30" />
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                </ItemsControl>
            </Grid>
        </Border>
    </Grid>
</UserControl>

This is a basic UserControl which will be resized automatically, according to the length of the question. It will display the Caption on top, the Question.Text in the middle, and the buttons (bound to the Question.PossibleAnswers) are in the bottom, aligned on the right. There are also some cosmetics improvements, like rounded borders, and a drop shadow. Also, you will see that clicking on a button will call the SelectAnswer method of QuestionDialogViewModel.

Now is the time to modify the shell view, to add the ContentControl:

<Window x:Class="Emidee.Sports.Base.Views.ShellView" 
		  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
		  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
		  xmlns:Micro="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro">
	<Window.Resources>
		<gui:IsNullConverter x:Key="isNullConverter" />
	</Window.Resources>
	<Grid>
		<Grid.RowDefinitions>
			<RowDefinition Height="Auto" />
			<RowDefinition Height="*" />
		</Grid.RowDefinitions>

		<Menu />

		<ContentControl Grid.Row="1" Micro:View.Model="{Binding ActiveItem}" HorizontalContentAlignment="Center" VerticalContentAlignment="Stretch" />

		<Grid Grid.Row="0" Grid.RowSpan="2" Background="#8000">
			<Grid.Style>
				<Style>
					<Setter Property="Grid.Visibility" Value="Collapsed"/>
					<Style.Triggers>
						<DataTrigger Binding="{Binding QuestionDialogViewModel, Converter={StaticResource isNullConverter}}" Value="False">
							<Setter Property="Grid.Visibility" Value="Visible" />
						</DataTrigger>
					</Style.Triggers>
				</Style>
			</Grid.Style>
			<ContentControl Micro:View.Model="{Binding QuestionDialogViewModel}" />
		</Grid>
	</Grid>
</Window>

As you can see, I add a new Grid, with a semi-transparent color, which will cover the entire view. I define the visibility of this grid to Collapsed by default. The IsNullConverter will trigger the modification of the visibility, making it Visible if the QuestionDialogViewModel property is not-null. In this grid, I've put the ContentControl, which, like I already said, will display the content of the QuestionDialogViewModel property.

Speaking about the IsNullConverter:

public class IsNullConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return (value == null);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new InvalidOperationException("IsNullConverter can only be used OneWay.");
    }
}

The last step is to write the MessageBoxResult class:

public class MessageBoxResult : IResult
{
    private readonly string caption = "Shell";
    private readonly Answer[] possibleAnswers = new[] {Answer.Ok};
    private readonly string text;

    public MessageBoxResult(string text, string caption, params Answer[] possibleAnswers)
    {
        this.text = text;
        this.caption = caption;
        this.possibleAnswers = possibleAnswers;
    }

    public string Text { get { return text; } }

    public string Caption { get { return caption; } }

    public Answer[] PossibleAnswers { get { return possibleAnswers; } }

    public Answer Answer { get; set; }

    [Inject]
    public IShellViewModel ShellViewModel { get; set; }

    [Inject]
    public IQuestionDialog QuestionDialog { get; set; }

    public void Execute(ActionExecutionContext context)
    {
        var question = new Question(Text, possibleAnswers);

        QuestionDialog.Setup(Caption, question);

        QuestionDialog.OnClose += (s, e) =>
        {
            Answer = question.Answer;

            ShellViewModel.QuestionDialogViewModel = null;

            Completed(this, new ResultCompletionEventArgs());
        };
            
        ShellViewModel.QuestionDialogViewModel = QuestionDialog;
    }

    public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };
}

In the Execute method, after CM has injected the dependencies of the result (and then got the instance of IShellViewModel, and a new instance of IQuestionDialog), I first set the property of the shell view-model, but I don't raise the Completed event now. Instead, it is raised when the user has clicked on one of the buttons of the QuestionDialogViewModel. If you remember, clicking on a button will call the SelectAnswer method of QuestionDialogViewModel, which will raise the OnClose event. Here I subscribe to this event, so I can unset the QuestionDialogViewModel property of IShellViewModel, to hide the message box, and finally to complete the result, in order for the caller to continue the execution of the co-routine.

As a last note, I remind you not to forgive to configure your dependency injection framework to resolve IShellViewModel in a singleton scope. That will help you to save some time ;)

I hope you enjoyed this article.

See you soon!