I am not quite sure, where my problem/mistake is. I am using WPF in combination with the MVVM pattern and my problem is at the login.
My first attempt worked fine. I had several windows, each with their own ViewModel. In the Login ViewModel I had following code running:
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";
That part connects to an Active Directory and first checks if the login was succesfull and then, if the user has a certain ad group (in method isSupport)
I have a display in the view, which is like a progress bar. It is active when PanelLoading equals true.
Until now everything worked.
Then I created a main window with a contentcontrol in it and changed my views to user controls, so I could swap them. (The intention was, not to open/create a new window for every view).
When I execute the code now, my GUI blocks, until said part is executed. I have tried several ways...
Moving the code snippet into an additional method and starting it as an own thread:
Thread t1 = new Thread(() => loginThread()); t1.SetApartmentState(ApartmentState.STA); t1.Start();
When I do it this way, I get an error that a ressource is owned by an another thread and thus cannot be accessed. (the calling thread cannot access this object because a different thread owns it)
Then, instead of an additional thread, trying to invoke the login part; login containing the previous code snippet
Application.Current.Dispatcher.Invoke((Action)(() => { login(); }));
That does not work. At least not how I implemented it.
After that, I tried to run only the main part of the login snippet in a thread and after that finished, raising an previously registered event, which would handle the change of the content control. That is the part, where I get the error with the thread accessing a ressource owned by another thread, so I thought, I could work around that.
void HandleThreadDone(object sender, EventArgs e) { if (_isValid) { PanelLoading = false; _mainwindowviewmodel.switchToQuestionView(_isSupportUser); } else PanelMainMessage = "Verbindung zum Server konnte nicht hergestellt werden"; }
And in the login method I would call ThreadDone(this, EventArgs.Empty); after it finished. Well, I got the same error regarding the ressource owned by an another thread.
And now I am here, seeking for help...
I know that my code isn't the prettiest and I broke at least two times the idea behind the mvvm pattern. Also I have little understanding of the Invoke method, but I tried my best and searched for a while (2-3 hours) on stackoverflow and other sites, without succeeding.
To specify where the error with thread occurs:
_mainwindowviewmodel.switchToQuestionView(_isSupportUser);
which leads to the following method
public void switchToQuestionView(bool supportUser)
{
_view.ContentHolder.Content = new SwitchPanel(supportUser);
}
This is also one occasion, where I am not using Data Binding. I change the content of my contentcontrol:
<ContentControl Name="ContentHolder"/>
How would I implement this with Data Binding. Should the property have the type ContentControl? I couldn't really find an answer to this. And by changing this to DataBinding, would the error with the thread ownage be solved?
The project structure is as following: Main View is entry point, in the constructor the data context is set to the mainviewmodel, which is created at that time. the main view has a contentcontrol, where I swap between my usercontrols, in this case my views.
from my mainviewmodel I set the content of the contentcontrol in the beginning at the usercontrol login, which creates a viewmodel in its contructors and sets it as datacontext.
The code snippets are from my loginviewmodel. Hope this helps.
I thought I found a workaround, but it still does not work. I forgot, how the timer works in the background, so it can be solved that way either.