Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/320.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C#WPF MVVM阻塞UI线程_C#_Wpf_Multithreading_Mvvm_Blocking - Fatal编程技术网

C#WPF MVVM阻塞UI线程

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

我不太确定我的问题/错误在哪里。 我将WPF与MVVM模式结合使用,我的问题在于登录

我的第一次尝试很成功。我有几个窗口,每个窗口都有自己的ViewModel。 在登录视图模型中,我运行了以下代码:

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会阻塞,直到执行了上述部分。我试过几种方法

  • 将代码段移动到其他方法中,并将其作为自己的线程启动:

    Thread t1 = new Thread(() => loginThread());
    t1.SetApartmentState(ApartmentState.STA);
    t1.Start();
    
    当我这样做时,我会得到一个错误,即一个ressource由另一个线程拥有,因此无法访问。(调用线程无法访问此对象,因为其他线程拥有它)

  • 然后,代替额外的线程,尝试调用登录部分;包含上一个代码段的登录名

    Application.Current.Dispatcher.Invoke((Action)(() =>
        {
            login(); 
        }));
    
    这是行不通的。至少不是我如何实现的

  • 在那之后,我尝试在线程中只运行登录代码段的主要部分,完成之后,引发一个以前注册的事件,该事件将处理内容控件的更改。这就是我在访问另一个线程拥有的ressource时遇到的错误,所以我想,我可以解决这个问题

    void HandleThreadDone(object sender, EventArgs e)
    {
        if (_isValid)
        {
            PanelLoading = false;
            _mainwindowviewmodel.switchToQuestionView(_isSupportUser);
        }
        else
            PanelMainMessage = "Verbindung zum Server konnte nicht hergestellt werden";
    }
    
    在登录方法中,我将调用ThreadDone(this,EventArgs.Empty);完成之后。嗯,关于另一个线程拥有的ressource,我也遇到了同样的错误

现在我在这里,寻求帮助

我知道我的代码不是最漂亮的,我至少两次打破了mvvm模式背后的想法。我对Invoke方法也不太了解,但我尽了最大努力,在stackoverflow和其他站点上搜索了一段时间(2-3小时),但没有成功

要指定线程错误发生的位置,请执行以下操作:

_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));
        }
    }
}