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;
});
});
}
...
}
我在 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;
});
});
}
...
}