WPF - MVVM Light - Unity - Events don't fire when reloading current usercontrol

227 Views Asked by At

I have an interesting problem with events not firing when a view is replaced with itself.

First, the setup: I'm creating the connection between the ViewModel and the View inside the App.xaml resource dictionary:

 <DataTemplate DataType="{x:Type vm:MyViewModel}">
     <v:MyView />
 </DataTemplate>

I have an application stage where I create my instance using ServiceLocator:

this.stageViewModel.ActiveStageViewModel = Util.GetInstanceFromDI<MyViewModel>();

And inside my stage, I use a content control bound to the ActiveStageViewModel to pull it all together:

<ContentControl Content="{Binding ActiveStageViewModel}" Margin="0" />

All this works great, and when I navigate to MyViewModel, the correct view shows up and looks fine. All of the load and resize events fire normally.

HOWEVER, if I try to navigate to the MyViewModel a second time, none of the events fire, except DataContextChanged.

I use an Observer to monitor size changed events so that I can decide on some styling and scrolling options, so I need for the load events to fire on the second load in order to refresh the values in my observer. In any case, not firing the events on a duplicate view load seems like a performance feature that is interfering rather than helping.

What it APPEARS to be doing is making a comparison between what is rendered and what will be rendered, and simply not firing the events if the rendering isn't changed.

I have tried a few things to force the updates, with little success, or a success which kind of breaks the MVVM approach I'm going for.

The first thing I tried was nulling out the ActiveStageViewModel before resetting the instance. That doesn't work at all. My guess is that because it never renders anything, it never drops the cached View.

The second thing I tried was forcing an FrameworkElement.InvalidateMeasure(), FrameworkElement.InvalidateArrange() and FrameworkElement.InvalidateVisual() when the DataContextChanged event fires. This doesn't work by itself, presumably because the Cached View comparison still happens.

Finally, what worked (but sucks), is attaching a DataContextChanged event to the View, and then setting the height on the element inside the event handler.

<DataTemplate DataType="{x:Type vm:MyViewModel}">
    <v:MyView DataContextChanged="FrameworkElement_DataContextChanged" />
</DataTemplate>




private void FrameworkElement_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    DispatcherTimer timer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(10) };
    var el = (FrameworkElement)sender;
    bool addOperation = false;
    double height = el.ActualHeight;
    timer.Tick += (s,ev) =>
    {
        el.Height = addOperation ? height + .001 : height - .001;
        addOperation = !addOperation;
        if (!addOperation)
        {
            timer.Stop();
        }
    };

    timer.Start();
}

This works because I'm effectively forcing a layout change inside the DataContextChanged event (twice, in 20 milliseconds, so it's not observable to the user), which apparently invalidates whatever view cache comparison that's going on. Obviously, adding an eventhandler inside the View/View Model declaration doesn't conform to MVVM's loose coupling scheme. Sure, it's at the application setup, but still seems awful.

Does anyone have any experience with this phenomenon, and a way to force re-rendering without such a brute force hack?

0

There are 0 best solutions below