Matt Gregory
Matt Gregory

Reputation: 8662

Dialog appears behind other windows

I'm using the Prism dialog service to create an unhandled exception dialog. I have a splash screen window that, while it doesn't have the TopMost property set (tried that and it was totally miserable :), I use Activate on it to get it to rise to the top so it appears over the main window as it loads up.

The problem is when an exception is thrown and the unhandled exception dialog that I made pops up, it appears behind the splash screen, leaving the user wondering what happened until they notice an extra button in the task bar.

The dialog is a UserControl, as instructed by the documentation, and it has no Activate function. And when I try to get a handle to the parent window in the constructor, the Parent property is null.

How can I bring a dialog to the top of other windows?

Upvotes: 0

Views: 732

Answers (1)

thatguy
thatguy

Reputation: 22089

Prism's dialog service sets the Owner of a dialog automatically to the first active Window that is found in the Application.Current.Windows collection if the dialog window does not already have set an owner. It does not expose any methods to access or activate windows directly. In an MVVM service you would not do that.

In Prism <= 7.x, you can only have a single dialog window host. By default this is a regular Window. You have to share this dialog host window type for all of your dialogs, so setting a different Owner in a custom Window is not an option. Since Owner is null when a Window is instantiated, its owner will be the first active window.

This behavior is problematic if you want the Owner of your dialog to be a different window like your splash screen, so it appears on top of it automatically or modally or have no owner at all.

You cannot activate the dialog in the constructor of your UserControl, as it cannot be set as the Content of the parent dialog host Window before it is instantiated. You either have to use the Application.Current.Windows collection to find the dialog or create a custom dialog service for now for both activating or setting an Owner.

Application Windows

If you want to find the dialog host window in Application.Current.Windows, you can filter this collection after showing the dialog and check the Content of each window, one will contain your UserControl type. If you only show the unhandled exception dialog once, there will be a single window instance with this UserControl. If you show it multiple times, you could identify the current instance via a property on its view model accessible by DataContext that you have set before by passing a DialogParameter in Show. Consider creating a service.

Custom Dialog Service

Personally, I prefer creating a custom dialog service, as it gives you more control over what you are doing. To create a custom interface that exposes the methods of the built-in IDialogService, as well as your own methods, depeding on your requirements.

public interface ICustomDialogService : IDialogService
{
   public void ShowOrphan(string name, IDialogParameters parameters, Action<IDialogResult> callback);
}

I simply create a new method ShowOrphan to show a dialog that has no owner and is activated automatically when shown, so it is in the foreground. Adapt it to your requirements, like passing in an explicit owner or just an owner type, so that the dialog service searches an instance in the application window collection itself.

Next, implement the ICustomDialogService in a CustomDialogService type. You can just copy the code from the public repository. Be sure to use the code for your version of Prism, see Tags.

You do not have to change much. Just pass appropriate parameters from your new public method down the ConfigureDialogWindowProperties method, which is responsible for setting the window owner. If activating the dialog window is really necessary apart from showing it, you can do that in ShowDialogInternal.

public class CustomDialogService : ICustomDialogService
{
   // ...other public members.

   public void ShowOrphan(string name, IDialogParameters parameters, Action<IDialogResult> callback)
   {
      // Appended a new parameter "isOrphan"
      ShowDialogInternal(name, parameters, callback, false, true);
   }

   // ...other private members.

   // Appended a new parameter "isOrphan"
   void ConfigureDialogWindowProperties(IDialogWindow window, FrameworkElement dialogContent, IDialogAware viewModel, bool isOrphan)
   {
      // ...other code.

      if (isOrphan)
         return;

      if (window.Owner == null)
         window.Owner = System.Windows.Application.Current.Windows.OfType<Window>().FirstOrDefault(x => x.IsActive);
   }
}

If you are done, you have to overwrite the registration for the old dialog service.

containerRegistry.RegisterSingleton <CustomDialogService>();
containerRegistry.Register<IDialogService, CustomDialogService>();
containerRegistry.Register<ICustomDialogService, CustomDialogService>();

Now you can resolve the ICustomDialogService and use its ShowOrphan method to show the window.

Upvotes: 3

Related Questions