0

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?

Servy
  • 202,030
  • 26
  • 332
  • 449
Moe45673
  • 854
  • 10
  • 20
  • 1
    Don't do that. Don't ever do that. There are a number of events you can respond to off the regionmanager and can event do something in the navigation callback. But, do not access the View in a VM. This type of logic belongs in the VIew's code-behind –  Mar 23 '17 at 22:44
  • Oh wow! Brian Lagunas! Appreciate the personal tip. I should add this method sits as a "controller" and not in the viewmodel – Moe45673 Mar 23 '17 at 23:01
  • @BrianLagunas: just to spark your notifications that I responded – Moe45673 Mar 24 '17 at 00:43
  • At what point the `WindowModeChange` method is called? Upon some UI event? Or maybe some container event? – Grx70 Mar 27 '17 at 19:12
  • 1
    I don't have the means to test it right now, but from the top of my head - try deferring the `ShiftWindowOntoScreenHelper.ShiftWindowOntoScreen` method execution with a `Dispatcher`, i.e. `uc.Dispatcher.InvokeAsync(()=>ShiftWindowOntoScreenHelper.ShiftWindowOntoScreen(uc))`. You might also need to adjust the priority (`DispatcherPriority.Loaded` seems to be a good start). This usually helps in scenarios where you need to "wait" for the framework to be done with certain things. – Grx70 Mar 27 '17 at 19:26
  • @Grx70: The user clicks a button which calls a command in the viewmodel. The viewmodel holds an enum that states the current window view. It changes the enum and sends out an event with the desired windowmode. The Controller subscribes to that event and calls this method. And yes, upon writing this, I see the issue with switching an enum and then using an event to change the mode without checking for failure :p – Moe45673 Mar 27 '17 at 20:33
  • @Grx70 I am currently testing your awesome suggestion about the Dispatcher and will report back. Thanks! – Moe45673 Mar 27 '17 at 20:37
  • @Grx70 thanks so much! That worked! How do I award you the bounty? – Moe45673 Mar 28 '17 at 17:38
  • 1
    If you've found the answer to your question then you should post it *as an answer*, not as an edit to the question. – Servy Mar 28 '17 at 17:45
  • @Servy No Prob, but I want to give Grx the bounty – Moe45673 Mar 28 '17 at 18:17
  • Regarding my comment a few posts above about the issue with switching an enum and then changing the view, I was able to get that to work appropriately by using a TaskCompletionSource instance. Now they will always be aligned. – Moe45673 Apr 24 '17 at 17:33

2 Answers2

1

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).

Grx70
  • 10,041
  • 1
  • 40
  • 55
  • Thanks for the amazing answer! I will switch to InvokeAsync then.... I will need to really ponder over what makes the most sense in my case. I don't want the user to do anything until after the Window is shifted so that it's fully within the virtualscreen. – Moe45673 Mar 28 '17 at 19:04
0

Thanks to Grx70 in the comments to my question, he gave me the solution. The body of the method now reads:

IRegion mviewRegion = regionManager.Regions[RegionNames.MainViewRegion];

        if (mviewRegion == null) return;


        mviewRegion.RequestNavigate(new Uri(uri, UriKind.Relative), (x =>
        {

            var uc = container.Resolve<MainWindow>(InstanceNames.MainWindowView);
            uc.Dispatcher.Invoke(() => ShiftWindowOntoScreenHelper.ShiftWindowOntoScreen(uc), DispatcherPriority.Loaded);

        }));

I am unsure as to why I had to call the Dispatcher in the callback parameter of the RequestNavigate(), but it won't work without it.

Moe45673
  • 854
  • 10
  • 20