Using multiple shells/windows in a Prism application - c#

I'm trying to create an application that would use multiple windows. For starters I'd like it to have a splash screen window and a "main window". I came along this article: http://southworks.com/blog/2010/01/26/creating-a-multi-shell-application-in-prism-v2/ however it doesn't seem to suit my needs. Author is creating 2 windows at the same time, also showing both right from the start. What I want to do is to create this splash screen window (with some nice loading indicators) and only when it's underlying logic will complete its tasks, I want to show another window
protected override DependencyObject CreateShell() {
//return Container.Resolve<MainShell>();
return Container.Resolve<SplashScreenShell>();
}
protected override void InitializeShell() {
base.InitializeShell();
App.Current.MainWindow = (Window)Shell;
App.Current.MainWindow.Show();
}
Another issue is that when I use this code, all my modules (so even those used only by MainShell) are getting loaded and initialized, and that's totally not what I want. Mainly because Prism looks for RegionName that is not present on SplashScreenShell (but is present on the second shell).
I'm using Prism 6.1.0 and .NET 4.6.

Why not just show your splash screen before you call MainWindow.Show? Just show it as ShowDialog to stop the bootstrapper form continuing to process until you close your splash screen.
protected override void InitializeShell()
{
var sc = new SplashScreen();
sc.ShowDialog();
App.Current.MainWindow = (Window)Shell;
App.Current.MainWindow.Show();
}

The good patern is to stick to one window for the whole application. My proposal is to create splash screen as UserControl and then kind of mess around with visibility.
Your MainView may be looking as follows
<Grid>
<ContentControl Content="{Binding CurrentViewModel}"/>
<userControl:SplashScreen/>
</Grid>
At this point your splash screen covers view and once the view is loaded you make the SplashScreen invisible. In order to do so create another class which will expose boolean property like IsVisible, and method Show/Hide for making splash screen visible and invisible respectively. Subsequently, you set SplashScreen's DataContext to that class, bind Visibility property to IsVisible and make use of BooleanToVisibilityConverter, provided by WPF by default. For appearance sake, you can set splash sreen opacity to, for instance 0.75, so that it would show through splash screen displaying some destination view simultaneously.
Whenever you want to show splash screen you invoke Show method, within method you set IsVisible to true, PropertyChanged event is raised and changes are reflected at view. As a result Visibility is set to true and Splash screen shows up on the top covering everything else.
If you follow these steps you should acquire similar outcome. Message being displayed can be explicitly adjusted as well, as a replacement for hardcoded "Loading".

Related

MahApps Metro DialogCoordinator: Display Dialog to span UserControl only (instead of entire window)?

I'm epxloring different ways to best show dialog windows in my application.
MahApp Metro's IDialogCoordinator seems quite useful, but I couldn't quite adjust it to my use case yet.
Say I'm creating a UserControl (view), whose ViewModel needs to be able to display dialogues.
These dialogues should, when displayed, overlay/span the UserControl only, NOT the entire Window in which the UserControl is hosted.
Is there any way to achieve this?
Default behavior always seems to span over the entire window, and I haven't found any way to change this yet.
So far, I've been using the Dialog coordinator in a very straightforward way, doing the following in my view:
<UserControl
xmlns:Dialog="clr-namespace:MahApps.Metro.Controls.Dialogs;assembly=MahApps.Metro"
Dialog:DialogParticipation.Register="{Binding}">
and set set the instance in my view's constructor by,
viewModel.Initialize(DialogCoordinator.Instance);
which I'd then call in the viewmodel via
IDialogCoordinator _DialogCoordinator; // set with viewModel.Initialize() called from the view
private async Task _SomeCmdExecute()
{
await _DialogCoordinator.ShowMessageAsync(this, "HEADER", "TEST");
}
Thanks!
Dialogs in MahApps.Metro are always at the window level (see the container PART_MetroActiveDialogContainer in the window's style.)
What you can do is changing the styling of dialogs, so they don't stretch horizontally accross the entire window. See the default template MetroDialogTemplate for reference.

MessageService for current window in Catel

I'm trying to create a Message in my WPF application using Catel. I have a problem, because I'm trying to show this message over additional window (not over mainWindow)
I'm using Catel with Orchestra.MahApps.
When I'm trying to execute code like this:
IMessageService messageService = ServiceLocator.Default.ResolveType<IMessageService>();
messageService.ShowInformation(message, title);
in my Additional Window ViewModel, it shows my message, but over my MainWindow. Is there a way to show it over my second window?
Right now I have a workaround with Events, but with my application growing, it will be hard to maintain everything.
There are a few options:
Create (or override) a custom version of the PleaseWaitService. When showing the busy indicator, check the current foreground window and show that one.
If you really want per/view busy indicator, it might be easier to create boolean values on your vm (IsBusy) and bind to that from within the view.

WPF Navigation and Rotating Backgrounds

I'm working on an application, and I'm using the MVVM approach.
Basically, there are currently two Pages, and 1 MainWindow.
I switch between the pages using a Frame inside MainWindow.
In the main window, there are 2 buttons which are basically global and should show in all pages; x (exit) and settings.
This is basically my 'shell', as I decided to not use a window border.
The problem is I'd like each page to have a different background and this is where it gets complicated:
- Settings page: Grey background.
- Main Page: Rotating background color that changes according to a property.
The thing is the background is being set in the main window, because it should apply to the global area as well (the top, where the exit and settings buttons are).
I first set the background (in MainWindow) as bound to a property the represents the current page (the value is then being translated into a color hex code with the help of a converter).
All in all, this results in a case where the background changes when a page is changed, but not when the property inside MainPage changes. I can clearly understand why, but I have no idea how to solve it.
The possible solutions I came up with so far:
Somehow causing the binding in MainWindow to update/refresh when the property is changed in MainPage.
Changing the background manually from inside each of the pages. (Although doesn't it negate the idea of mvvm?)
Move the background into each of the pages and set it from there, while making the global buttons on top of the page (which could be a bad thing in case controls end up overlapping).
If so, what would be the best solution to this problem?
If you haven't already, I'd suggest you install some package via NuGet to make MVVM style development more enjoyable. I personally prefer MVVMLight which is... well, light, but it also packs lot's of helpful features.
To communicate between ViewModels, you have (at least) two possible approaches.
1) ViewModelLocator (not recommended)
ViewModelLocator is central place holding references to all of your viewmodels. You could add a property that is then used by all of the viewmodels to get/set the background.
....
x:Name="Main"
DataContext="{Binding Source={StaticResource Locator}, Path=MainVM}">
....
<Grid Background="{Binding Background, Converter={StaticResource StringBrushConverter}}">
...
2) Messenger (recommended)
When ever property changes in your viewmodel(s) or method is executed, you could send a message that your MainViewModel is registered to listen to. Sending a message would be as easy as...
Messenger.Default.Send(new UpdateBackgroundMessage(new SolidColorBrush(Colors.Blue)));
And you'd register for this message in your MainViewModel's constructor:
Messenger.Default.Register<UpdateBackgroundMessage>(this, message =>
{
Background = message.Brush;
});
Actual message class would be:
public class UpdateBackgroundMessage : MessageBase
{
public UpdateBackgroundMessage(Brush brush)
{
Brush = brush;
}
public Brush Brush { get; set; }
}
I know I'm simplifying things here but I hope you got the idea. Both approaches are valid even if you decide not to use MVVMLight.
Edit:
Here's Git repo with example https://github.com/mikkoviitala/cross-viewmodel-communication
I think you should use Application Properties for storing background. There are various benefit of this :
1) Globally available
2) Easy to remember or store user preference
3) Automatically maintain separate profile for each user as it store values in AppData folder of user.
you can use Messenger to notify that background property has changed so that main window or shell could pull out new background value and update it.

WPF owner window on top of child window

Is it possible for Owner window in WPF be on top of Child window when you click on it while Owner window is below Child window?
here is example how I call child window:
Window2 window = new Window2();
window.Owner = this;
window.Show();
Parent/Owner window will always be under child window.
To get the behavior you want, you don't want to set the Owner on either window.
You, of course will have to handle the logic yourself when closing either of the windows to close your imaginary "child" window.
There may be some other logic you'll have to implement related to minimizing, maximizing, etc.
Many of the answers on this page involve nulling-out the Window.Owner property for some or all of the (non-MainWindow) windows in your System.Windows.Application. While this is a simple and easy fix that does indeed, in isolation, fix the issue of Window overlap, unfortunately it also inhibits a lot of useful application-wide functionality that WPF seems otherwise eager to provide in the areas of:
Application acti­vation/deactivation (via mouse-click, desktop Alt-Tab switching, etc...),
correct observance of the Application.​ShutdownMode property, and generally,
orderly cleanup, resource disposal, and exit of your Application upon shutdown.
It is possible fix the Window overlap issue while still preserving these system-wide WPF features by instead designating a special invisible window instance as your Application.MainWindow.
Modify your application so that the first Window it creates--which is the Window that gets assigned to Application.MainWindow--is a special dummy Window that is then made invisible by setting its Visibility to Visibility.Hidden or calling Window.Hide(). This will not hide its owned windows. Then, ensure that your desired "main" window containing your true content, plus all the other windows, owned by this invisible window.
Once hidden, the dummy Window will not show in the Windows 10 taskbar. You can set the Window.ShowInTaskbar property on whichever of the visible windows you deem appropriate to simulate apparent special designation, as required.
This approach allows any of the visible windows in your Application to be on top of the others, while still preserving WPF features and behaviors for system-wide app activation. For example, when the Application is activated (by clicking on any one of the windows, or via Alt-tab), all of the application's windows are together brought above any other desktop app windows, while still preserving the most recent "in-app" Z-order. WPF shutdown functionality is also preserved, including correct observation of the Application.ShutdownMode logic in accordance with the invisible MainWindow (or all the others) being closed.
I ran into a similar situation. I solved this by simply removing the owner after showing the window.
Window2 window = new Window2();
window.Owner = this;
window.Show();
window.Owner = null;
Edit:
Someone replied to this, and while looking at it, I decided I wanted to make an extension method.
public static void ShowAsIfChildOf(this Window childWindow, Window parentWindow)
{
childWindow.Owner = parentWindow;
childWindow.Show();
childWindow.Owner = null;
}
Please note that the solution mentioned by Licht
Window2 window = new Window2();
window.Owner = this;
window.Show();
window.Owner = null;
seems to me to be the same as
Window2 window = new Window2();
window.Show();
i.e., as if no ownership relationship has been set, i.e., when you close the owner window the owned windows do not close, etc., and may not be a solution when one would wish to have all of the ownership relationship features except "An owner window can never cover an owned window."
Once this relationship is established, the following behaviours are exhibited:
If an owner window is minimized, all its owned windows are minimized as well.
If an owned window is minimized, its owner is not minimized.
If an owner window is maximized, both the owner window and its owned windows are restored.
An owner window can never cover an owned window.
Owned windows that were not opened using ShowDialog are not modal. The user can still interact with the owner window.
If you close an owner window, its owned windows are also closed.
The best solution here looks to me to be the one with hidden main dummy window described by Glenn Slayden, at the link below, though it would be nice if there was a simpler one.
https://stackoverflow.com/a/66110288/19683309
Don't set Owner for child window, then in MainWindow handle OnClosing event like this:
private void MainWindow_OnClosing(object? sender, CancelEventArgs e)
{
foreach (var window in Application.Current.Windows)
{
if (window is Window appWindow)
{
if(appWindow.Equals(Application.Current.MainWindow))
continue;
appWindow.Close();
}
}
}

Creating a Popup Balloon like Windows Messenger or AVG

How can I create a Popup balloon like you would see from Windows Messenger or AVG or Norton or whomever?
I want it to show the information, and then slide away after a few seconds.
Edit: It needs to be blocking like Form.ShowDialog() because the program exits after displaying the notification
You can use the notifyIcon control that's part of .NET 2.0 System.Windows.Forms. That allows you to place an icon for your application in the System Tray. Then, you can call the ShowBalloonTip(int timeOut) method on that. Be sure however to first set the text, and icon properties on the notifyIcon for it to work. Small code sample:
private void button1_Click(object sender, EventArgs e)
{
this.notifyIcon1.BalloonTipText = "Whatever";
this.notifyIcon1.BalloonTipTitle = "Title";
this.notifyIcon1.Icon = new Icon("icon.ico");
this.notifyIcon1.Visible = true;
this.notifyIcon1.ShowBalloonTip(3);
}
EDIT: Ok, so notifyIcon won't work for you. My second suggestion would then be to create your own control for this. Actually, I would use a form. A simple form, with no borders, and no control box and just have a timer running so you can set the Opacity for fade in/out. Then, you can easily get the bottom right of the screen using the Rectangle Screen.PrimaryScreen.WorkingArea. Then just show your form at that position.
Don't create a modal (blocking) balloon. Please. A big part of the design of these UIs is that they are not dialogs: they're transient, potentially non-interactive elements, intended to provide incidental information to a user without necessarily interrupting their workflow. A balloon that steals focus and blocks user input would be irritating at best - if you need a dialog, then use a dialog.
The .NET 1.1 Visual Basic Power Pack had a toaster control.

Categories