user1624552 user1624552 - 3 years ago 60
C# Question

MVVM: Update view's control visibility after completion of threadpool worker item in WPF NET 3.5

I have a WPF MVVM app under NET 3.5 and Visual Studio 2008. Some controls in the view are bound to properties on the view model, so when these properties changes it will be notified to the view through the implementation of INotifyPropertyChanged.

I have some kind of splash saying "Loading..." that appears in the center of the window at the beginning, and it keeps visible while some data is being requested from database. Once data is requested from database, I want to hide this splash.

This splash is bound to a propert, "IsSplashVisible" in view model so updating the property to true, notify the splash to be shown at the beginnig and setting it to false, notify the splash to be hidden.

Setting property "IsSplashVisible" to true at the beginning there is no problem, the problem appears when setting the property to false once queued work item finishes. Once set this property to false, control (splash "Loading...") is notified and it tries to hide but fails as this is a different thread that the one who created it so the typical exception is thrown. So how can I solve this?

Below the code.

View model:

public class TestViewModel : BaseViewModel
{
private static Dispatcher _dispatcher;
public ObservableCollection<UserData> lstUsers

public ObservableCollection<UserData> LstUsers
{
get
{
return this.lstUsers;
}

private set
{
this.lstUsers= value;
OnPropertyChanged("LstUsers");
}
}

private bool isSplashVisible = false;

public bool IsSplashVisible
{
get
{
return this.isSplashVisible;
}

set
{
if (this.isSplashVisible== value)
{
return;
}

this.isSplashVisible= value;
OnPropertyChanged("IsSplashVisible");
}
}

public TestViewModel()
{
this.IsSplashVisible = true;

ThreadPool.QueueUserWorkItem(new WaitCallback((o) =>
{
var result = getDataFromDatabase();
UIThread(() =>
{
LstUsers = result;
this.IsSplashVisible = false; <---- HERE IT FAILS
});
}));
}

ObservableCollection<UserData> getDataFromDatabase()
{
return this.RequestDataToDatabase();
}

static void UIThread(Action a)
{
if(_dispatcher == null) _dispatcher = Dispatcher.CurrentDispatcher;
//this is to make sure that the event is raised on the correct Thread
_dispatcher.Invoke(a); <---- HERE EXCEPTION IS THROWN
}
}

Answer Source

Dispatcher.CurrentDispatcher is not the Dispatcher of the UI thread, because it

Gets the Dispatcher for the thread currently executing and creates a new Dispatcher if one is not already associated with the thread.

You should use the Dispatcher of the current Application instance:

ThreadPool.QueueUserWorkItem(o =>
{
    var result = getDataFromDatabase();

    Application.Current.Dispatcher.Invoke(() => 
    {
        LstUsers = result;
        IsSplashVisible = false;
    });            
});

Assuming that your TestViewModel constructor is called in the UI thread, you could have written it like shown below, where Dispatcher.CurrentDispatcher is called in the UI thread instead of a ThreadPool thread. However, the field is entirely redundant. You could always just call Application.Current.Dispatcher.Invoke().

public class TestViewModel
{
    private readonly Dispatcher _dispatcher = Dispatcher.CurrentDispatcher;

    public TestViewModel()
    {
        IsSplashVisible = true;

        ThreadPool.QueueUserWorkItem(o =>
        {
            var result = getDataFromDatabase();

            _dispatcher.Invoke(() => 
            {
               LstUsers = result;
               IsSplashVisible = false;
            });            
        });
    }

    ...
}
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download