4
votes

I am developing a WPF-Application using PRISM/Unity and the MVVM approach. So far everything is fine, but I think I have a conceptual problem.

Since I need modal dialogs I implemented a dialogservice, which gets injected into the viewmodels and can be used to open up a modal dialog with a corresponding view. This dialog needs to be navigable ( = contains a region), so that I am able to switch views within the dialog. For this the service creates an instance of a window and navigates to the requestd view in a DialogRegion. This also works.

My problem now is, that at some point I need to open a second modal dialog which also needs to be navigable. If I use my dialogservice I would get a new instance of the dialog window and an exception is thrown, since the region was already registered with the regionmanager (It's done in the code behind of the dialog window). This makes sense, since the regionmanager would not be able to distinguish the regions otherwise, but prevents me from opening a second navigable dialog.

What would be a better approach? I can only think of creating a second dialog window with a different name for the region, but that seems to be a very ugly solution...


EDIT 1: Code for showing a dialog:

dialogService.ShowDialog<MyViewModel>();

Code in the dialog service for showing a dialog:

public void ShowDialog<TDialogViewModel>() where TDialogViewModel : class
{
    IModalDialog dialog = ServiceLocator.Current.TryResolve<IModalDialog>();
    dialog.Owner = Application.Current.MainWindow;
    regionManager.RequestNavigate(typeof(TDialogViewModel), Regions.DialogRegion);
    dialog.ShowDialog();
}

Code in the dialog-window:

public DialogWindow(IRegionManager regionManager) : this()
{
    RegionManager.SetRegionManager(this, regionManager);
    this.regionManager = regionManager;
}
private void Window_Unloaded(object sender, RoutedEventArgs e)
{
    regionManager.Regions.Remove(Regions.DialogRegion);
    regionManager.Regions.Remove(Regions.DialogStatusRegion);
}

Code in the view for construction, navigation and showing other dialogs

public MyViewModel(IRegionManager regionManager, IModalDialogService dialogService) : this()
{
    this.regionManager = regionManager;
    this.dialogService = dialogService;
}
void NavigateToSecondView()
{
    regionManager.RequestNavigate<MyViewModel2>(Regions.DialogRegion);
}
void ShowDialog2()
{
    // This is where a second dialog window with MyViewModel3 should be shown
    dialogService.ShowDialog<MyViewModel3>();
}

EDIT 2: XAML for the Window:

<Window.Resources>
    <DataTemplate DataType="{x:Type localModels:MyViewModel}">
        <localViewsProject:MyView/>
    </DataTemplate>
</Window.Resources>
<Grid>
    <ContentControl Grid.Row="0" x:Name="DialogContent" Margin="10,10,10,10" 
                    prismRegions:RegionManager.RegionName="{x:Static navi:Regions.DialogRegion}">
    </ContentControl>
</Grid>

EDIT 3 - Code for IModalDialog: I got the idea for IModalDialog somewhere in the web, but I can't remember where. The idea is to have an interface that is implemented by some window-instances and allows for interaction with the user. My dialog only has one content-region (plus a region for status/progress notifications) that can be filled through prism.

Here some code to explain the idea:

public partial class DialogWindow : Window, IModalDialog
{
    internal IRegionManager regionManager;

    public DialogWindow()
    {
        InitializeComponent();
    }

    public DialogWindow(IRegionManager regionManager)
        : this()
    {
        RegionManager.SetRegionManager(this, regionManager);
        this.regionManager = regionManager;
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        // Load Model, when window is loaded
        BaseViewModel model = this.DialogContent.Content as BaseViewModel;
        if (model != null)
        {
            model.LoadData();
        }
    }

    private void Window_Unloaded(object sender, RoutedEventArgs e)
    {
        regionManager.Regions.Remove(Regions.DialogRegion);
        regionManager.Regions.Remove(Regions.DialogStatusRegion);
    }
}

So in order to use it I have a static ModalDialogService, which receives a model and some action to be done after the dialog is closed. Something like this:

public void ShowDialog<TDialogViewModel>(Action<TDialogViewModel> initializeAction = null, Action<TDialogViewModel> actionAfterClose = null) where TDialogViewModel : class
{
    IModalDialog dialog = ServiceLocator.Current.TryResolve<IModalDialog>();
    if (actionAfterClose != null)
    {
        WeakEventManager<IModalDialog, EventArgs>.AddHandler(
                dialog,
                "Closed",
                (sender, e) => dialogView_Closed(sender, e, typeof(TDialogViewModel), Regions.DialogRegion, actionAfterClose));
        }

        dialog.Owner = Application.Current.MainWindow;
        regionManager.RequestNavigate(typeof(TDialogViewModel), Regions.DialogRegion);

        TDialogViewModel model = null;
        var region = regionManager.Regions[Regions.DialogRegion];

        foreach (var view in region.ActiveViews)
        {
            if (view is TDialogViewModel)
            {
                model = (TDialogViewModel)view;
                if (initializeAction != null)
                {
                    initializeAction(model);
                }
            }
        }

        // Attach an event listener to the closing event in order to prevent closing
        // if the form is not in a closeable state.
        if (typeof(IConfirmNavigationRequest).IsAssignableFrom(typeof(TDialogViewModel)))
        {
            WeakEventManager<IModalDialog, CancelEventArgs>.AddHandler(
                dialog,
                "Closing",
                (sender, e) => dialogView_Closing(sender, e, Regions.DialogRegion));
        }

        // This event can be published by a viewmodel in order to close the dialog
        eventAggregator.GetEvent<DialogCloseRequestedEvent>().Subscribe((containingDialog) =>
        {
            if (containingDialog == model)
            {
                dialog.Close();
            }
        });

        dialog.ShowDialog();
    }
}
1
I was trying to open up a dialog (only one level), and your code looks inspiring. But I am not able to understand about IModalDialog. Can you please show that part of code. It would be great help if you can perhaps share your code sampleJatin

1 Answers

0
votes

Instead of re-registering a Region with the RegionManager. You could reuse the initial Region that is registered and create multiple instances of it. These are known as Scoped Regions (under the section Creating Multiple Instances of a Region). For example:

// You may need to keep track of these scopes
IRegionManager scopedRegionManager = _regionManager.Regions["DialogRegion"].Add(view, viewName, true);
scopedRegionManager.Navigate(viewName);

EDIT

I believe it's as simple as changing the constructor of your DialogWindow to this:

public DialogWindow(IRegionManager regionManager) : this()
{
    var scopedRegionManager = regionManager.Regions[Regions.DialogRegion].Add(this, null, true);
    this.regionManager = scopedRegionManager;
}