I recently needed to implement an application with login screen and googled some best practice, but without luck. I've seen some solutions, but none of them could impress me. So, I decided to do it in my way and share with you.
First of all, my prerequisites were not to use two windows, because it looks lame when WPF closes one window and starts a new one. I also didn't want to use Popups or modal dialogs because they come after main window and they are part of the it. Furthermore main window (UserControl) will be loaded, initialized and rendered before you even see a login screen. If you don't put to much logic in your constructor it might be fast, but still unwanted at that moment. So, I think the best way is to use one Window with two UserControls.
Let's start with the contents, we need a LoginViewModel and a MainViewModel. Login will be shown first and if the credentials were accepted screen will be changed to main. Create a common interface, IFrameContent with an OnChangeRequest event handler member. Implement this interface as a FrameContent base class.
Not a big deal. ViewModelBase is a simple base class, which implement INotifyPropertyChanged. Okay, inherit this class and create our two screens: main and login.
MainViewModel is the same story. RelayCommand is an implemented ICommand interface. In the view bind the command to a button. What here happens is a command execution without any check. But don't worry, you can extend it later.
Finally create the FrameWindow, our only window and put everything together. Create two properties for our two view models and an extra for currently selected. Subscribe their events and in the handler change to the other content.
The trick is, that you need to bind the CurrentContent in the view as a ContentPrestener's content.
Override the CanDeactivated() method in content classes and build in some real check to prevent login in case of invalid credentials.
Of course you can (need to) extended bases classes with OnActivated(), OnDeactivated() etc methods in a real application, but the base concept does not change. You have one current content and change it in a frame window (SRP) without those two actually would known each other or without EventBus or EventAggregator (which are anti-pattern in my opinion).
You can download a full source code from here.
public abstract class
FrameContent : ViewModelBase, IFrameContent
{ public event
EventHandler
OnChangeRequested; protected void Deactivate() { if (this.CanDeactivated()) { var handler = OnChangeRequested; if (handler != null) { handler(this,
EventArgs
.Empty); } } }
public virtual bool CanDeactivated()
{
return true;
}
}
Not a big deal. ViewModelBase is a simple base class, which implement INotifyPropertyChanged. Okay, inherit this class and create our two screens: main and login.
public class
LoginViewModel : FrameContent
{ public
RelayCommand
LoginCommand { get; private set; } public LoginViewModel() { this.LoginCommand = new
RelayCommand
(Login, (p) => true); } private void Login(object parameter) { this.Deactivate(); } }
MainViewModel is the same story. RelayCommand is an implemented ICommand interface. In the view bind the command to a button. What here happens is a command execution without any check. But don't worry, you can extend it later.
Finally create the FrameWindow, our only window and put everything together. Create two properties for our two view models and an extra for currently selected. Subscribe their events and in the handler change to the other content.
public class
FrameWindowViewModel : ViewModelBase
{ private
LoginViewModel
_Login; private
LoginViewModel
Login { get { if (this._Login == null) { this._Login = new
LoginViewModel
(); this._Login.OnChangeRequested += Login_LoginSuccessful; } return this._Login; } } private
MainViewModel
_Main; private
MainViewModel
Main { get { if (this._Main == null) { this._Main = new
MainViewModel
(); this._Main.OnChangeRequested += Main_OnChangeRequested; } return this._Main; } } private
IFrameContent
_currentContent; public
IFrameContent
CurrentContent { get { return this._currentContent; } private set { this._currentContent = value; this.RaisePropertyChanged("CurrentContent"); } } public FrameWindowViewModel() { this.ChangeContent(this.Login); } private void Main_OnChangeRequested(object sender,
EventArgs
e) { ChangeContent(this.Login); } private void Login_LoginSuccessful(object sender,
EventArgs
e) { ChangeContent(this.Main); } private bool ChangeContent(
IFrameContent
content) { this.CurrentContent = content; return true; } }
The trick is, that you need to bind the CurrentContent in the view as a ContentPrestener's content.
<Window x:Class="LoginDemoClient.Views.FrameWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Demo client" Height="350" Width="525">
<ContentPresenter Grid.Row="1" Content="{Binding CurrentContent}" />
</Window>
Override the CanDeactivated() method in content classes and build in some real check to prevent login in case of invalid credentials.
Of course you can (need to) extended bases classes with OnActivated(), OnDeactivated() etc methods in a real application, but the base concept does not change. You have one current content and change it in a frame window (SRP) without those two actually would known each other or without EventBus or EventAggregator (which are anti-pattern in my opinion).
You can download a full source code from here.