我不太确定,我的问题/错误在哪里。我将WPF与MVVM模式结合使用,我的问题是登录。
我的第一次尝试工作正常。我有几个窗口,每个窗口都有自己的ViewModel。在Login 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并首先检查登录是否成功,然后,如果用户具有某个广告组(在方法中是支持)
我在视图中有一个显示,就像一个进度条。当PanelLoading等于true时,它处于活动状态。
到现在为止一切正常。
然后我创建了一个带有contentcontrol的主窗口,并将我的视图更改为用户控件,因此我可以交换它们。 (目的是,不要为每个视图打开/创建一个新窗口)。
当我现在执行代码时,我的GUI会阻塞,直到所述部分被执行。我尝试了几种方法......
Thread t1 = new Thread(() => loginThread());
t1.SetApartmentState(ApartmentState.STA);
t1.Start();
当我这样做时,我得到一个错误,即资源由另一个线程拥有,因此无法访问。 (调用线程无法访问此对象,因为另一个线程拥有它)Application.Current.Dispatcher.Invoke((Action)(() =>
{
login();
}));
这不起作用。至少不是我如何实现它。void HandleThreadDone(object sender, EventArgs e)
{
if (_isValid)
{
PanelLoading = false;
_mainwindowviewmodel.switchToQuestionView(_isSupportUser);
}
else
PanelMainMessage = "Verbindung zum Server konnte nicht hergestellt werden";
}
在login方法中我会调用ThreadDone(this,EventArgs.Empty);完成后。好吧,我对另一个线程所拥有的资源有同样的错误。现在我在这里,寻求帮助......
我知道我的代码不是最漂亮的,我至少打破了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类型吗?我真的找不到答案。通过将其更改为DataBinding,是否可以解决线程所有权的错误?
项目结构如下:Main View是入口点,在构造函数中,数据上下文设置为mainviewmodel,该时间是在此时创建的。主视图有一个contentcontrol,我在我的usercontrols之间交换,在这种情况下我的视图。
在我的mainviewmodel中,我在usercontrol login的开头设置了contentcontrol的内容,在其构造函数中创建了一个viewmodel并将其设置为datacontext。
代码片段来自我的loginviewmodel。希望这可以帮助。
我以为我找到了一个解决方法,但它仍然无效。我忘了,计时器如何在后台运行,所以它也可以这样解决。
问题是WPF或XAML框架通常不允许从其他线程修改主线程上的可视元素。为了解决这个问题,你应该区分哪个代码是从第二个线程更新视图的部分。在你的情况下,我可以看到:
_view.ContentHolder.Content = new SwitchPanel(supportUser);
改变观点。为了解决这个问题,你可以尝试这个answer。其中我使用同步上下文来进行线程之间的通信。
解决它的另一种方法(并且它可能是调度程序的错误用法)是使用调度程序将“修改视图”的操作“发送”到主线程。有点像这样:
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和DataBinding。
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();
}
}
这是LoginViewModel
。
// ViewBaseModel is a basic implementation of ViewModel and INotifyPropertyChanged interface
// and which implements OnPropertyChanged method to notify the UI that a property changed
public class LoginViewModel : ViewModelBase<LoginViewModel> {
private IAuthService authService;
public LoginViewModel(IAuthService authService) {
// Inject authService or your Context, whatever you use with the IoC
// framework of your choice, i.e. Unity
this.authService = authService
}
private AsyncRelayCommand loginCommand;
public ICommand LoginCommand {
get {
return loginCommand ?? (loginCommand = new AsyncCommand(Login));
}
}
private string username;
public string Username {
get { return this.username; }
set {
if(username != value) {
username = value;
OnPropertyChanged("Username");
}
}
}
private string password;
public string Password {
get { return this.password; }
set {
if(password != value) {
password = value;
OnPropertyChanged("Password");
}
}
}
private async Task Search() {
return await Task.Run( () => {
// validate the credentials
PanelMainMessage = "username und passwort werden überprüft";
// for ViewModel properties you don't have to invoke/dispatch anything
// Only if you interact with i.e. Observable Collections, you have to
// run them on the main thread
_isValid = pc.ValidateCredentials(this.Username, this.Password);
PanelMainMessage = "gruppe wird überprüft";
_isSupportUser = isSupport(Username, pc);
}
} );
}
}
现在将Username
和Password
属性绑定为文本字段的双向绑定,并将LoginCommand
命令绑定到登录按钮。
最后但并非最不重要的是,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));
}
}
}
最后的一些评论:正如您已经提到的,上面的代码有几个问题。您从ViewModel引用View。这几乎打破了整个过程,如果你开始从ViewModel引用视图,你可以完全跳过MVVM并使用WPF的CodeBehind。
此外,您应该避免从ViewModel引用其他ViewModel,因为这会紧密地耦合它们并使单元测试非常困难。
要在Views / ViewModels之间导航,通常会实现NavigationService。您可以在模型中定义NavigationService的接口(即INavigationService
)。但是NavigationService的实现发生在表示层(即视图所在的位置/项目),因为这是您可以实现NavigationService的唯一地方。
导航服务非常特定于应用程序/平台,因此需要为每个平台实现新的(桌面,WinRT,Silverlight)。对于显示Dialog消息/弹出窗口的DialogService也是如此。