A.D. A.D. - 4 months ago 20
C# Question

WPF UI change being postponed

Working on a WPF MVVM project.

Inside the VieModel, I want to hide a control before starting a long process.

The command code would be as follows:

private RelayCommand _DeleteCommand;
public RelayCommand DeleteCommand
{
get
{
return _DeleteReferenceCommand
?? (_DeleteReferenceCommand = new RelayCommand(
() =>
{


GridViewVisibility = false;

//Long process....

},
() => { return ReferencesGridWithPicsUC.SelectedReference != null; }

));
}
}


GridViewVisibility is bound to my control visibility into the view and uses a raiseevent.
It works fine except that the visibility is actually updated AFTER the command returns, so after the looong process.
I obviously want it to update ASAP.

I tried using a dispatcher and replaced:

GridViewVisibility = false;


with

Application.Current.Dispatcher.BeginInvoke(new Action(() =>
GridViewVisibility = false));


Same result.

How can I get it to work?

Answer

Your command execute method blocks the UI thread. You're doing something in there that causes a UI update, but you're not unblocking the UI thread to let the UI actually do what you ask. Instead you continue blocking the UI thread for a long time. When you stop blocking the UI thread, the UI becomes responsive again and the update takes place.

You need to launch a thread for the long process if you want the UI update to take place before the long process completes. In the old MFC days we used to pump the message loop, but then they repealed Prohibition and things are different now. And that method worked like s*** anyway, so in the end we had to write threads in C++, in snow up to our armpits, with nothing to eat but old socks. And we liked it.

Task.Run() should do nicely:

private RelayCommand _DeleteCommand;
public RelayCommand DeleteCommand
{
    get
    {
        return _DeleteReferenceCommand
            ?? (_DeleteReferenceCommand = new RelayCommand(
            () =>
            {
                GridViewVisibility = false;

                Task.Run(() =>
                {
                   //  Long process....
                });

            },
            () => { return ReferencesGridWithPicsUC.SelectedReference != null; }
        ));
    }
}

The Dispatcher.BeginInvoke() thing you've got doesn't do what you guessed it does. What that's for is that ordinarily, if a non-UI thread touches UI, you get an exception. That's not allowed. When you call Dispatcher.Invoke() or Dispatcher.BeginInvoke() from outside the UI thread, it executes your UI-touching code in the UI thread, so it all works.

You don't always need to invoke explicitly: If a control is updated via a Binding to a property that raises PropertyChanged, that'll do the invoke for you, behind the scenes. It's such a common pattern, they built it into the framework. However, INotifyCollectionChanged does not invoke for you. If you add items to an ObservableCollection, you'll need the Invoke. If you simply replace an ObservableCollection with a new one, that should be OK, though if it's a large collection that'll result in the UI going unresponsive for a bit. It's actually faster that way, but the user will perceive it differently than if he sees the items being added one by one to a list while a spinner spins. The human mind is a funny thing.