MVVM:在 WPF NET 3.5 中完成线程池工作项后更新视图的控件可见性

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

我在 NET 3.5 和 Visual Studio 2008 下有一个 WPF MVVM 应用程序。视图中的一些控件绑定到视图模型上的属性,因此当这些属性更改时,它将通过INotifyPropertyChanged 的​​实施。

我在开头的 window 中间出现了一些字样 "Loading...",并且在从数据库请求某些数据时它保持可见。一旦从数据库请求数据,我想隐藏这个启动。

此启动画面绑定到一个属性,"IsSplashVisible" 在视图模型中因此将 属性 更新为 true,通知启动画面在开始时显示并将其设置为 false,通知启动画面被隐藏。

一开始将 属性 "IsSplashVisible" 设置为 true 没有问题,一旦排队的工作项完成,将 属性 设置为 false 时就会出现问题。一旦将此 属性 设置为 false,控件 (splash "Loading...") 就会收到通知,它会尝试隐藏但失败,因为这是一个与创建它的线程不同的线程,因此会抛出典型的异常。那么我该如何解决呢?

代码下方。

查看型号:

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
    }
}

Dispatcher.CurrentDispatcher 不是 UI 线程的 Dispatcher,因为它

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

你应该使用当前Application实例的Dispatcher:

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

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

假设您的 TestViewModel 构造函数在 UI 线程中调用,您可以如下所示编写它,其中 Dispatcher.CurrentDispatcher 在 UI 线程中调用而不是 ThreadPool线。但是,该字段完全是多余的。你可以随时调用 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;
            });            
        });
    }

    ...
}