UWP ContentDialog Invocation
Asked Answered
L

2

8

I am using UWP and Template 10 to build a GUI app by following MVVM pattern. As a part of application I need to invoke a content dialog by pressing button on Main Page. So separate ContentDialog was created in standalone .xaml file for that purpose:

<ContentDialog
    x:Class="UWP1.Views.Speech"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:UWP1.Views"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Title="Dictate"
    PrimaryButtonText="Accept"
    SecondaryButtonText="Cancel"
    PrimaryButtonClick="ContentDialog_PrimaryButtonClick"
    SecondaryButtonClick="ContentDialog_SecondaryButtonClick"
    >
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="150" />
            <ColumnDefinition Width="150" />
        </Grid.ColumnDefinitions>
            <Button Margin="15" Content="Dictate" Grid.Row="0" Grid.Column="0" HorizontalAlignment="Stretch"/>
        <Button  Margin="15" Content="Clear Text" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Stretch"/>
        <TextBlock Grid.Row="1" Grid.ColumnSpan="2" Text="Tap 'Dictate', and speak" FontSize="12" />
            <TextBlock Margin="0 10 0 0" Grid.Row="2" Grid.ColumnSpan="2" Text="Message Dication" HorizontalAlignment="Center" FontSize="24"  />
        <ScrollViewer Grid.Row="3" Grid.ColumnSpan="2" Height="300">
            <TextBox Margin="5 5 5 10"  AcceptsReturn="True"  />
        </ScrollViewer>
    </Grid>
</ContentDialog>

What is the proper way to open/invoke it in my Main Page by pressing button (as I need to keep logic separated for view and viewmodel)?

How I do it now:

From Main Page I invoke DictateCommand, which in turn creates an instance of ContentDialog and shows it:

 <AppBarButton Grid.Column="1" Icon="Microphone" IsCompact="True" HorizontalAlignment="Right" Command="{Binding DictateCommand}"/>

        public ICommand DictateCommand { get; set; }

        public async void Dictate(object obj)
        {
            var contentDialog = new Speech();
            await contentDialog.ShowAsync();
        }

It looks like a MVVM pattern breach for me. Could you please help me to do it in a right way?

EDIT:

I have implemented dialog service and injected it in the main view model. However, I got another obstacle. For this dialog I created separate view model and property which encapsulates dialog text box value. When I press 'accept' button on the dialog - I need this value to be reflected on my Main View. So I need to pass dialog's text box value from dialog's view model to main view model. Should I perform another dependency injection to deal with it?

Location answered 5/1, 2016 at 23:35 Comment(0)
H
14

You have four options.

ONE The first is a service, just like @Ask-too-much explains. In fact, this is a beautiful solution if you like it.

The benefits of the first solution are that it is reusable. If you are not reusing this UI, a dedicated service might be overkill, to be honest.

TWO The second is a view-model event. That is to say, your Page can subscribe to your view-model's event (let's call it ShowContentDialog) and when it is raised by the view-model, your Page handle its presentation.

The benefits this approach is that, just like the first approach, you are offloading the effort to another class. In this case, you are creating what is likely a one-off solution without needing a service, a service interface, or injection of that service somehow. If you are not waiting for the result of the event, then I think this is idea for 99% of issues like yours.

THREE The third approach is to use a different control that can be bound to a property. For example, since you are already using Template 10, you can use the ModalDialog control which has an IsModal property. A property in your view-model (let's call it IsModalVisible) can be used to control the dialog without coupling to it.

The good part of this is that you get to invoke the dialog from the logic of your view-model, just like the first two approaches. But unlike the first, you do not need a service. Unlike the second, you do not need a handler. It's the most "data binding" way to do it, and likely what I would do.

FOUR The fourth way to do it is to use messaging. Messaging is the mechanism one view-model uses to communicate with another. IN this case you could use messaging from your view-model (with a message we might call ShowDialog) that is listened for, not in another view-model, but in your Page. This would work fine, too.

The down side to this is that you need a messaging solution, but you might already have that. The up-side is that the logic for handling the visual could be relocated at any time as Messaging is multicasted to anyone listening.

If I were you, I would consider number 3 first, perhaps. Without a little more understanding of your app scenario, I can't be sure. You are a developer, though. All four of those options are good. Just be sure not to be tempted into passing the UIElement into your view-model. That's needless nastiness :)

Best of luck!

Hedley answered 29/1, 2016 at 19:55 Comment(1)
With option Three, would the modal dialog be created the same as in Template 10's Search Sample project? I need a dialog where the user can press "yes" or "no" and I need to know in my ViewModel that invokes the Dialog which option they picked...but I'm unsure how to do that.Hectogram
C
7

Suggested solution in MVVM is don't create instance of Speech Dialog directly in ViewModel, create SpeechDialogService.

public interface ISpeechDialogService
{
    Task ShowAsync();
}

public class SpeechDialogService : ISpeechDialogService
{
    public async Task ShowAsync()
    {
        var contentDialog = new Speech();
        await contentDialog.ShowAsync();

    }
}

And inject this service in your ViewModel constructor

public class AbcViewModel
{
    readonly ISpeechDialogService _dialog;

    public AbcViewModel(ISpeechDialogService dialog)
    {
        _dialog = dialog;
    }

    public async void Dictate(object obj)
    {
        await _dialog.ShowAsync();
    }
}
Chare answered 6/1, 2016 at 0:28 Comment(1)
Where do you assign the Xaml Root to dialog? So it does not error.Exhaust

© 2022 - 2024 — McMap. All rights reserved.