Pages

Monday, October 28, 2013

How to implement a login screen in WPF

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.

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.

No comments:

Post a Comment