I am working in WPF with a MainWindow that has a (Prism) region "MainViewRegion". This switches based off the User's desired view and when it does, the MainWindow resizes to snap to the new dimensions of the embedded view.
I have some code to keep the window fully visible on the desktop after the Region switch. Here's the code:
private void WindowModeChange(string uri)
{
IRegion mviewRegion = regionManager.Regions[RegionNames.MainViewRegion];
if (mviewRegion == null) return;
regionManager.RequestNavigate(mviewRegion.Name, new Uri(uri, UriKind.Relative));
//Get the MainWindow instance from the container
var uc = container.Resolve<MainWindow>(InstanceNames.MainWindowView);
//Make sure the entire window is visible onscreen
ShiftWindowOntoScreenHelper.ShiftWindowOntoScreen(uc);
}
The issue is that the "uc" variable will always equal the MainWindow parameters from before the region change. The "uc" is always one step behind what I want, so the "snap-to" code is always off.
What am I missing?
Often when you have a piece of code that executes upon some event, and you need to ensure UI has already responded to that event to some degree, your best bet is to use a Dispatcher
to defer the execution. Judging by the code you provided, the UI change is triggered by the regionManager.RequestNavigate
call. Now I cannot point you to a piece of documentation that states that, but I know from experience that the framework will process this request asynchronously, i.e. the control will be returned to your method (allowing it to proceed) before all the work caused by this request is finished (hence your problem). That is (as far as I can tell) accomplished internally by the framework by using the said Dispatcher
.
Depending on to what extent you need the framework to have processed the triggered change, you should use appropriate DispatcherPriority
enum value to ensure certain things are done with once your code is executed. Basing on your question, I think the DispatcherPriority.Loaded
is a good choice (from documentation the dispatcher will execute such code after the UI is rendered (which is crucial condition for you), but before any user input is processed). I personally tend to maximize the priority up to the point when it stops working (possibly avoiding some unexpected behavior caused by, for instance, user input).
So this modification to your code should be sufficient to accomplish your goal:
var uc = container.Resolve<MainWindow>(InstanceNames.MainWindowView);
uc.Dispatcher.InvokeAsync(() =>
ShiftWindowOntoScreenHelper.ShiftWindowOntoScreen(uc),
DispatcherPriority.Loaded);
Now I've noticed that you ultimately settled with Dispatcher.Invoke
rather than Dispatcher.InvokeAsync
- I guess it's ok in your case since it's the last instruction in your method, but generally the safe way is to use Dispatcher.InvokeAsync
(I've personally experienced situations where Dispatcher.Invoke
did not cut the mustard).