C#WPF MVVM阻塞UI线程
我不太确定我的问题/错误在哪里。 我将WPF与MVVM模式结合使用,我的问题在于登录 我的第一次尝试很成功。我有几个窗口,每个窗口都有自己的ViewModel。 在登录视图模型中,我运行了以下代码:C#WPF MVVM阻塞UI线程,c#,wpf,multithreading,mvvm,blocking,C#,Wpf,Multithreading,Mvvm,Blocking,我不太确定我的问题/错误在哪里。 我将WPF与MVVM模式结合使用,我的问题在于登录 我的第一次尝试很成功。我有几个窗口,每个窗口都有自己的ViewModel。 在登录视图模型中,我运行了以下代码: PanelMainMessage = "Verbindung zum Server wird aufgebaut"; PanelLoading = true; _isValid = _isSupportUser = false; string server = Environment.GetEnv
PanelMainMessage = "Verbindung zum Server wird aufgebaut";
PanelLoading = true;
_isValid = _isSupportUser = false;
string server = Environment.GetEnvironmentVariable("CidServer");
string domain = Environment.GetEnvironmentVariable("SMARTDomain");
try
{
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, server + "." + domain))
{
// validate the credentials
PanelMainMessage = "username und passwort werden überprüft";
_isValid = pc.ValidateCredentials(Username, _view.PasswortBox.Password);
PanelMainMessage = "gruppe wird überprüft";
_isSupportUser = isSupport(Username, pc);
}
}
catch (Exception ex)
{
//errormanagement -> later
}
if (_isValid)
{
PanelLoading = false;
if (_isSupportUser)
_mainwindowviewmodel.switchToQuestionView(true);
else
_mainwindowviewmodel.switchToQuestionView(false);
}
else
PanelMainMessage = "Verbindung zum Server konnte nicht hergestellt werden";
该部分连接到Active Directory,首先检查登录是否成功,然后检查用户是否有特定的广告组(在方法Isupport中)
我在视图中有一个类似进度条的显示。当PanelLoading等于true时,它处于活动状态
到目前为止,一切都很顺利
然后,我创建了一个包含contentcontrol的主窗口,并将视图更改为用户控件,以便可以交换它们。(目的不是为每个视图打开/创建新窗口)
当我现在执行代码时,我的GUI会阻塞,直到执行了上述部分。我试过几种方法
- 将代码段移动到其他方法中,并将其作为自己的线程启动:
当我这样做时,我会得到一个错误,即一个ressource由另一个线程拥有,因此无法访问。(调用线程无法访问此对象,因为其他线程拥有它)Thread t1 = new Thread(() => loginThread()); t1.SetApartmentState(ApartmentState.STA); t1.Start();
- 然后,代替额外的线程,尝试调用登录部分;包含上一个代码段的登录名
这是行不通的。至少不是我如何实现的Application.Current.Dispatcher.Invoke((Action)(() => { login(); }));
- 在那之后,我尝试在线程中只运行登录代码段的主要部分,完成之后,引发一个以前注册的事件,该事件将处理内容控件的更改。这就是我在访问另一个线程拥有的ressource时遇到的错误,所以我想,我可以解决这个问题
在登录方法中,我将调用ThreadDone(this,EventArgs.Empty);完成之后。嗯,关于另一个线程拥有的ressource,我也遇到了同样的错误void HandleThreadDone(object sender, EventArgs e) { if (_isValid) { PanelLoading = false; _mainwindowviewmodel.switchToQuestionView(_isSupportUser); } else PanelMainMessage = "Verbindung zum Server konnte nicht hergestellt werden"; }
_mainwindowviewmodel.switchToQuestionView(_isSupportUser);
which leads to the following method
public void switchToQuestionView(bool supportUser)
{
_view.ContentHolder.Content = new SwitchPanel(supportUser);
}
这也是我不使用数据绑定的一种情况。我更改contentcontrol的内容:
<ContentControl Name="ContentHolder"/>
如何使用数据绑定实现这一点。属性是否应具有ContentControl类型?我真的找不到答案。通过将此更改为数据绑定,线程所有权的错误会得到解决吗
项目结构如下:
主视图是入口点,在构造函数中,数据上下文设置为当时创建的mainviewmodel。主视图有一个contentcontrol,我在其中交换我的UserControl,在本例中是我的视图
从mainviewmodel开始,我在usercontrol登录时设置contentcontrol的内容,这将在其构造函数中创建viewmodel并将其设置为datacontext
代码片段来自我的loginviewmodel。希望这有帮助
我以为我找到了一个解决办法,但它仍然不起作用。我忘记了计时器是如何在后台工作的,所以也可以通过这种方式来解决。问题是WPF,或者通常的XAML framawork,不允许修改主线程上其他线程的可视元素。为了解决这个问题,您应该区分代码中从第二个线程更新视图的部分。就你而言,我可以看出:
_view.ContentHolder.Content = new SwitchPanel(supportUser);
更改视图。
为了解决这个问题,你可以试试这个。在其中,我使用同步上下文来处理线程之间的通信
另一种解决方法(可能是错误地使用了dispatcher)是使用dispatcher将修改视图的操作“发送”到主线程。像这样的事情:
var dispatcher = Application.Current.Dispatcher;
//also could be a background worker
Thread t1 = new Thread(() =>
{
dispatcher .Invoke((Action)(() =>
{
login(); //or any action that update the view
}));
//loginThread();
});
t1.SetApartmentState(ApartmentState.STA);
t1.Start();
希望这有帮助……一种常见的方法是实现一个
AsyncRelayCommand
(在一些教程中也称为AsyncDelegateCommand
)并将其绑定到WPF视图
下面是我用于演示项目的一个示例实现,以熟悉WPF、MVVM和数据绑定
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
public class AsyncRelayCommand : ICommand {
protected readonly Func<Task> _asyncExecute;
protected readonly Func<bool> _canExecute;
public event EventHandler CanExecuteChanged {
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public AsyncRelayCommand(Func<Task> execute)
: this(execute, null) {
}
public AsyncRelayCommand(Func<Task> asyncExecute, Func<bool> canExecute) {
_asyncExecute = asyncExecute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) {
if(_canExecute == null) {
return true;
}
return _canExecute();
}
public async void Execute(object parameter) {
await ExecuteAsync(parameter);
}
protected virtual async Task ExecuteAsync(object parameter) {
await _asyncExecute();
}
}
现在,将用户名
和密码
属性作为双向绑定绑定到文本字段,并将登录命令
绑定到登录按钮
最后但并非最不重要的是,ViewModelBase
的一个非常基本的实现
public abstract class ViewModelBase<T> : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName) {
var handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
公共抽象类ViewModelBase:INotifyPropertyChanged{
公共事件属性更改事件处理程序属性更改;
受保护的虚拟void OnPropertyChanged([CallerMemberName]字符串propertyName){
var handler=PropertyChanged;
if(处理程序!=null){
处理程序(这是新的PropertyChangedEventArgs(propertyName));
}
}
}
最后的几句话:
如上所述,您的代码存在几个问题。您引用了ViewModel中的视图。这几乎破坏了整个过程,如果您开始引用ViewModel中的视图,您可以完全跳过MVVM并使用WPF的CodeBehind
此外,还应避免从ViewModel中引用其他ViewModel,因为这会使它们紧密耦合,并使单元测试非常困难
要在视图/视图模型之间导航,通常需要实现导航服务。您可以在模型中定义导航服务的接口(即INavigationService
),但导航服务的实现发生在表示层(即视图所在的位置/项目),因为这是唯一可以实现NavigationService的地方
导航服务非常特定于应用程序/平台,因此需要为每个平台实现一个新的导航服务(桌面、WinRT、Silverlight)
public abstract class ViewModelBase<T> : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName) {
var handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}