Preparing to Re-launch Squirrel Clock

Earlier this year I’m launched Squirrel Clock, it has undergone a short invitation-only beta and stayed closed for new customers ever since because of the poor results of that beta.

I’m now working hard on improving Squirrel Clock, my on-line time-clock/ punch clock application in order to re-launch it.

I going over all the pages in the application and checking every field, table, link and button – this is resulting in a lot of tiny UI improvements that make the app much more pleasant to use.

I’m also going to shorten the sign-up sequence from 3 pages (where one required quite a bit of data entry) to just one not-so-long form, this will let new users get to the actual app faster and hopefully make a better impression.

There is also a whole new reporting system I’m integrating with it, this means the new Squirrel Clock will produce some very good looking reports.

And last but not least, there will be none of this closed beta nonsense, the new Squirrel Clock will be available to everyone from day one.

If you want progress updates and to know when the new version is released you can subscribe to this blog (even via e-mail, see the link on the blog’s right sidebar) of follow @nirdobovizki (that’s me) on Twitter.

Buy yaTimer now, because it’s the biggest discount ever

yaTimer is features on bits du jour (a software deal of the day site) and it’s your only chance to get yaTimer for 60% off – yes, less than half price, only for 24 hours and only from this page

Buy yaTimer tomorrow – save 60%

Roger from Bits du hour somehow convinced me to offer a 60% discount on yaTimer (only if you buy from this page and only for 24 hours).

I have listed yaTimer on bits du jour before, but never with such a huge discount – so you better take advantage of it before I regain my sanity.

The sale starts midnight EST

Your software architecture is not more important that producing working software

Designing software is hard, producing internal design for the software (the software “architecture”) that both supports the user requirements and makes it easier for developers to work on the software is a real challenge.

There are a lot of “best practices” (I really hate that term), frameworks, libraries, software design principles and design patterns that are supposed to help with this challenge.

The idea behind those is that some very smart and very experienced people already solved the problem for you, they found the correct way to structure software and indentified all the pitfalls, they then wrote some easy to follow rules – if you follow those rules you will produce quality software.

Software developers are very logical people – people that are used to simple black-and-white rules (because basically that’s all the computer can understand and perform) – so software developers take those rules written by people smarter and more experienced then them and follow them religiously.

And then everything breaks down.

Every software project is different, there’s is almost nothing in common between high end graphic software and normal everyday business software or between word processors and computer games and even sometimes between normal everyday business software built for different industries.

So those software design principles, formulated by very smart and very experienced people, truly describe a near-perfect pitfall free software design – for the specific project that smart and experienced person worked on when he wrote that principle (or, in the best case, for the kind of projects that he usually works on).

And large groups of software developers all around the world, working on a large variety of different software projects, all blindly following the one true software design – and most of them hit some completely obvious limitations of that design, simply because they are doing something that didn’t exist in the project that influenced that design.

But the smart and experienced guru is never wrong, and the failing design principle always looks so logical and right (because it is right, just not for your specific project).

So those poor developers keep religiously following the one true design, building one huge patch after the other working around the limitation of the one true design, doing more and more work, making the software more and more complicated (and adding more and more bugs in the process).

And so, I have one request for my fellow software developers – never value your precious architecture more than actually producing working software that your customers can user.

If you need a global messaging system to do something that should be one method call you’re doing it wrong and should rethink your architecture.

If your design uses some design patterns that is not supported by your platform you’re doing it wrong and should rethink your architecture.

If you’re using a library for something it wasn’t designed to do you’re doing it wrong and should rethink your architecture.

And in general if you are doing something that should be easy but just hit a wall, don’t start coding some clever and complicated solution, first stop and think how to keep the simple things simple – even if you have to change your architecture.

Easy form layout in WPF Part 3 – Adding Groups

This is the third and final post in a series, you may want to start from the beginning:

  1. Easy form layout in WPF Part 1 – Introducing FormPanel.
  2. Easy form layout in WPF Part 2 – How to deal with more complicated scenarios
  3. Easy form layout in WPF Part 3 – Adding Groups (You are here).

Let’s divide the controls from out previous example into groups and produce this dialog box:

Putting multiple FormPanel panels in the window, each in its own GroupBox, is simple – but we need somehow to synchronize the sizes across multiple controls (and we still want to minimize typing).

Let’s look at the XAML for this window (only the form panels part this time)

<l:FormGroupHost>
    <l:FormGroup Header="General Details">
        <TextBlock Text="Title:"/>
        <TextBox/>
        <TextBlock Text="Area:"/>
        <ComboBox/>
        <TextBlock Text="Category:"/>
        <ComboBox/>
        <TextBlock Text="Assigned To:"/>
        <ComboBox/>
        <TextBlock Text="Status:"/>
        <ComboBox/>
        <TextBlock Text="Estimate:"/>
        <TextBox/>
    </l:FormGroup>
    <l:FormGroup Header="User Defined Fields">
        <TextBlock Text="Tags:"/>
        <TextBox/>
        <TextBlock Text="Version:"/>
        <TextBox/>
    </l:FormGroup>
</l:FormGroupHost>

Ok, what’s going on here? there’s a new FormGroup that works like a FormPanel except it has an Header property that probably draws the GroupBox and a FormPanelHost that displays all the groups one after the other and maybe takes care of synchronizing the sizes between panels.

Let’s look at the code for FormGroup:

    public class FormGroup : HeaderedItemsControl
    {
    }

That is one short class, let’s look at it’s XAML style:

<Style TargetType="l:FormGroup">
    <Setter Property="ItemsPanel">
        <Setter.Value>
            <ItemsPanelTemplate>
                <l:FormPanel/>
            </ItemsPanelTemplate>
        </Setter.Value>
    </Setter>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="l:FormGroup">
                <GroupBox Header="{TemplateBinding Header}" Padding="5">
                    <ItemsPresenter/>
                </GroupBox>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
 

So, what do we have here? FormGroup is an HeaderedItemsControl and it has a control template that simply put all it’s children inside a GroupBox, it also has a panel template that makes is use a FormTemplate.

FormGroup is just a FormPanel inside a GroupBox, the clever part is that children of an ItemsControl are added to it’s panel so we don’t have to type the group box and panel XAML for every group.

Now let’s look at FormGroupHost:

    public class FormGroupHost : ItemsControl, IFormPanelCoordinator
    {
        protected override Size MeasureOverride(Size constraint)
        {
            Size result = base.MeasureOverride(constraint);
            DoSizing();
            return result;
        }

        private void DoSizing()
        {
            List groups = new List();

            for (int i = 0; i < Items.Count; ++i)
            {
                var group = ItemContainerGenerator.ContainerFromIndex(i) as FormGroup;
                if (group != null && group.Items.Count > 0)
                {
                    var container = group.ItemContainerGenerator.ContainerFromIndex(0);
                    var panel = VisualTreeHelper.GetParent(container) as FormPanel;
                    if (panel != null)
                    {
                        panel.Coordinator = this;
                        groups.Add(panel);
                    }
                }
            }

            double labelMaxWidth = 0;
            double labelMaxHeight = 0;
            double controlMaxWidth = 0;
            double controlMaxHeight = 0;

            foreach (var current in groups)
            {
                labelMaxWidth = Math.Max(labelMaxWidth, current.LabelSize.Width);
                labelMaxHeight = Math.Max(labelMaxHeight, current.LabelSize.Height);
                controlMaxWidth = Math.Max(controlMaxWidth, current.ControlSize.Width);
                controlMaxHeight = Math.Max(controlMaxHeight, current.ControlSize.Height);
            }

            foreach (var current in groups)
            {
                current.LabelSize = new Size(
                    labelMaxWidth, labelMaxHeight);
                current.ControlSize = new Size(
                    controlMaxWidth, controlMaxHeight);
            }
        }

        #region IFormPanelCoordinator Members

        void IFormPanelCoordinator.ControlOrLabelSizeChanged(FormPanel sender)
        {
            DoSizing();
        }

        #endregion
    }

finally some code, now it’s really quite simple:

  1. Iterate over all FormGroups, extract form panel, get label and control size and set myself as the coordinator (finally we find out what the coordinator is for) for the panel.
  2. Iterate over all panels again, set the maximum label and control size

We do this on MeasureOverride (when the system tells us our layout changed) and when one of the panels notifies us (via the IFormPanelCoordinator interface) that its sizing changed.

FormGroupHost is also an ItemsControl but it ahs no style, by default this will put all the children in a StackPanel (we can change that with a style or by setting ItemsPanel if we want).

You can download the code, VS2008 project that targets .net 3.5SP1 here: FormPanelApp sample code.

The project also contains OkCancelFrame another typing saving control for MVVM that I may write about in the future.

Easy form layout in WPF Part 2 – How to deal with more complicated scenarios

This is the 2nd post in a series, you may want to start from the beginning, this series includes:

  1. Easy form layout in WPF Part 1 – Introducing FormPanel
    Easy form layout in WPF Part 2 – How to deal with more complicated scenarios (You are here).
    Easy form layout in WPF Part 3 – Adding Groups.

You can find the complete source code with a sample project at the end of the last post.

In the previous post I’ve described the wonderful work saving FormPanel that can take care of the layout of simple forms, I said that it’s ok it only handles simple forms because this is the most common case and for the rest we can use normal WPF layout.

But we all know life just isn’t that simple and even simple forms always have to special controls, if we take the bug tracking example I used the first post then we probably have to add a comment field in there, the comment field must be both wider and higher than normal fields and give us a dialog like this:

What do we do now? stop using FormPanel? extend it to support every layout under the sun?

We just put it in a Grid, put all the simple controls in the FormPanel, put the irregular controls directly in the grid and use data binding to synchronize sizing between them (remember we defined LabelSize and ControlSize as dependency properties?)

And the XAML for this form becomes:

<Window x:Class="FormPanelApp.Window4"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:l="clr-namespace:FormPanelApp"
    Title="Window4" Height="300" Width="500">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="{Binding ElementName=FormPanel,Path=RowSpacing}"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="{Binding ElementName=FormPanel,Path=LabelSize.Width}"/>
            <ColumnDefinition Width="{Binding ElementName=FormPanel,Path=LabelControlSpacing}"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <l:FormPanel Grid.ColumnSpan="3" x:Name="FormPanel">
            <TextBlock Text="Title:"/>
            <TextBox/>
            <TextBlock Text="Area:"/>
            <ComboBox/>
            <TextBlock Text="Category:"/>
            <ComboBox/>
            <TextBlock Text="Assigned To:"/>
            <ComboBox/>
            <TextBlock Text="Status:"/>
            <ComboBox/>
            <TextBlock Text="Estimate:"/>
            <TextBox/>
            <TextBlock Text="Tags:"/>
            <TextBox/>
            <TextBlock Text="Version:"/>
            <TextBox/>
        </l:FormPanel>
        <TextBlock Text="Comment:" Grid.Row="2"/>
        <TextBox Grid.Row="2" Grid.Column="2" TextWrapping="Wrap"/>
    </Grid>
</Window>

Unfortunately this is not as simple as out previous example but we still saved a lot of typing, and if we want to add more normal controls – or rearrange them - we don’t have to change the grid definitions at all.

The form panel is extremely versatile for such a simple control – but it can’t do everything – for example what do you do if you want the labels above the controls?

Simple, just modify the panel’s MeasureOverride and ArrangeOverride to set the new positions – it’s unlikely you’ll intentionally use two (or more) layout styles in the same application so it’s much simpler to write a panel that’s a perfect fit for your specific layout than to write a panel that support every possible option.

And what to do if I want to separate controls into different groups? we will do this in the next and final post.

Easy form layout in WPF Part 1 – Introducing FormPanel

The WPF layout system is extremely powerful, there’s almost nothing you can’t do with Grid and maybe a few DockPanel objects – but that power comes at a price and that price is a lot of typing.

I find it hard to believe that anyone who has written any form in WPF isn’t sick of  <RowDefinition Height="Auto"/> and Grid.Column=”1” Grid.Row=”1” – and of course things get worse when you have to add a new row at the beginning of the form and you have to manually update all those Grid.Row definitions.

So, in this series I will try to solve the problem.

Now it’s important to remember we are trying to simplify our code here, we will not write a powerful do-everything control, we will write something that will cover the simple cases (that are around 80% of cases) and the rest we will code with Grid.

This series has 3 parts:

  1. Easy form layout in WPF Part 1 – Introducing FormPanel (You are here).
  2. Easy form layout in WPF Part 2 – How to deal with more complicated scenarios.
  3. Easy form layout in WPF Part 3 – Adding Groups.

You can find the complete source code with a sample project at the end of the last post.

Let’s start from the desired end result and work back to the code:

I’m writing a bug tracking product and I want a form with the usual bug tracking fields:

I want all the labels to be the same size and all the text boxes and combo boxes to be the same size, I want the labels centered vertically and the text/combo boxes to fill all the available width, I want constant spacing between labels and controls and between row and columns.

And most of all I want everything without writing any code or XAML on every windows.

This is what I want the XAML for the window above to look like (a real app would obviously need some data binding code to fill the controls):

<Window x:Class="FormPanelApp.Window3"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:l="clr-namespace:FormPanelApp"
    Title="Window3" Height="300" Width="500">
    <l:FormPanel Margin="10">
        <TextBlock Text="Title:"/>
        <TextBox/>
        <TextBlock Text="Area:"/>
        <ComboBox/>
        <TextBlock Text="Category:"/>
        <ComboBox/>
        <TextBlock Text="Assigned To:"/>
        <ComboBox/>
        <TextBlock Text="Status:"/>
        <ComboBox/>
        <TextBlock Text="Estimate:"/>
        <TextBox/>
        <TextBlock Text="Tags:"/>
        <TextBox/>
        <TextBlock Text="Version:"/>
        <TextBox/>
    </l:FormPanel>
</Window>

So what am I going to do? easy, write a custom panel.

We will Create a FormPanel class that inherits from the WPF Panel.

using System;
using System.Windows.Controls;
using System.Windows;

namespace FormPanelApp
{
    public class FormPanel : Panel
    {

Now we will add some dependency properties for things we would like to be configurable in the panel.

The first and most important is the number of columns (each column is a label/control pair):

        public static readonly DependencyProperty ColumnsProperty =
            DependencyProperty.Register("Columns", typeof(int), typeof(FormPanel),
            new FrameworkPropertyMetadata(2, FrameworkPropertyMetadataOptions.AffectsMeasure));
        public int Columns
        {
            get { return (int)GetValue(ColumnsProperty); }
            set { SetValue(ColumnsProperty, value); }
        }

The spacing between rows and columns:

        public static readonly DependencyProperty ColumnSpacingProperty =
            DependencyProperty.Register("ColumnSpacing", typeof(double), typeof(FormPanel),
            new FrameworkPropertyMetadata(15.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
        public double ColumnSpacing
        {
            get { return (double)GetValue(ColumnSpacingProperty); }
            set { SetValue(ColumnSpacingProperty, value); }
        }

        public static readonly DependencyProperty RowSpacingProperty =
            DependencyProperty.Register("RowSpacing", typeof(double), typeof(FormPanel),
            new FrameworkPropertyMetadata(10.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
        public double RowSpacing
        {
            get { return (double)GetValue(RowSpacingProperty); }
            set { SetValue(RowSpacingProperty, value); }
        }

And the space between labels and controls:

        public static readonly DependencyProperty LabelControlSpacingProperty =
            DependencyProperty.Register("LabelControlSpacing", typeof(double), typeof(FormPanel),
            new FrameworkPropertyMetadata(5.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
        public double LabelControlSpacing
        {
            get { return (double)GetValue(LabelControlSpacingProperty); }
            set { SetValue(LabelControlSpacingProperty, value); }
        }

We will also create dependency properties for the size of labels and controls as calculated by the panel, in the next post we will see how useful this will be:

        public static readonly DependencyProperty LabelSizeProperty =
            DependencyProperty.Register("LabelSize", typeof(Size), typeof(FormPanel));
        public Size LabelSize
        {
            get { return (Size)GetValue(LabelSizeProperty); }
            set { SetValue(LabelSizeProperty, value); }
        }

        public static readonly DependencyProperty ControlSizeProperty =
            DependencyProperty.Register("ControlSize",typeof(Size),typeof(FormPanel));
        public Size ControlSize
        {
            get { return (Size)GetValue(ControlSizeProperty); }
            set { SetValue(ControlSizeProperty, value); }
        }

Now I'm going to add something called a coordinator, in the third post we will use it to quickly make the FormPanel even more useful - you can ignore it for now:

        public IFormPanelCoordinator Coordinator { get; set; }

All the actual work in a panel is done in two methods: MeasureOverride and ArrangeOverride.

The MeasureOverride method calculates the required size for the panel, in our case we just scan all the panel's children and look for the maximum label width and height and maximum control width and height.

We save the results in the LabelSize and ControlSize properties we defined earlier and calculate the required size based on those sizes, the number of columns and the spacing properties we defined.

        protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize)
        {
            double labelMaxWidth = 0;
            double labelMaxHeight = 0;
            double controlMaxWidth = 0;
            double controlMaxHeight = 0;
            for (int i = 0; i < Children.Count-1; i += 2)
            {
                Children[i].Measure(availableSize);
                Children[i + 1].Measure(availableSize);
                labelMaxWidth = Math.Max(labelMaxWidth, Children[i].DesiredSize.Width);
                labelMaxHeight = Math.Max(labelMaxHeight, Children[i].DesiredSize.Height);
                controlMaxWidth = Math.Max(controlMaxWidth, Children[i+1].DesiredSize.Width);
                controlMaxHeight = Math.Max(controlMaxHeight, Children[i+1].DesiredSize.Height);
            }

            var oldLabelSize = LabelSize;
            var oldControlSize = ControlSize;
            var newLabelSize = new Size(labelMaxWidth, labelMaxHeight);
            var newControlSize = new Size(controlMaxWidth, controlMaxHeight);
            LabelSize = newLabelSize;
            ControlSize = newControlSize;

            if (Coordinator != null &&
                (newLabelSize != oldLabelSize || newControlSize != oldControlSize))
            {
                Coordinator.ControlOrLabelSizeChanged(this);
            }

            return new Size(
                Columns * (LabelSize.Width + ControlSize.Width + LabelControlSpacing) + (Columns - 1) * ColumnSpacing,
                ((Children.Count/2) / Columns) * Math.Max(LabelSize.Height, ControlSize.Height) + (((Children.Count/2) / Columns) - 1) * RowSpacing);
        }

We also notify the coordinator if the size changed, but we will talk about that in the third post

The ArrangeOverride method actually places all the labels and controls, it just loops over all the panel's children and calculates their final location:

 

        protected override Size ArrangeOverride(Size finalSize)
        {
            double controlWidth = (finalSize.Width - (Columns - 1) * ColumnSpacing - Columns * (LabelSize.Width + LabelControlSpacing)) / Columns;
            double rowHeight = Math.Max(LabelSize.Height, ControlSize.Height) + RowSpacing;
            double columnWidth = LabelSize.Width + LabelControlSpacing + controlWidth + ColumnSpacing;
            for (int i = 0; i < Children.Count - 1; i += 2)
            {
                var labelRect = new Rect(
                    columnWidth * ((i / 2) % Columns), rowHeight * ((i / 2) / Columns),
                    LabelSize.Width, rowHeight - RowSpacing);
                Children[i].Arrange(
                    new Rect(
                        labelRect.Left, 
                        labelRect.Top+(labelRect.Height-Children[i].DesiredSize.Height)/2,
                        Children[i].DesiredSize.Width,Children[i].DesiredSize.Height));
                Children[i + 1].Arrange(new Rect(
                    columnWidth * ((i / 2) % Columns) + LabelSize.Width + LabelControlSpacing, rowHeight * ((i / 2) / Columns),
                    controlWidth, rowHeight - RowSpacing));
            }
            return new Size(finalSize.Width, rowHeight * ((Children.Count/2) / Columns + 1));
        }

and of course, we have to close the class:

    }
}

also, I'm listing the IFormPanelCoordinator interface here because the FormPanel wouldn't compile without it.

 

    public interface IFormPanelCoordinator
    {
        void ControlOrLabelSizeChanged(FormPanel sender);
    }

And, as you see, we wrote a completely trivial class that take care of the annoying task of manually setting the grid layout.

I said in the beginning this is a simple class that will only cover the common cases, in the next post we will see how this class still saves us a lot of typing in the not-so-simple case.

Blogging Software Updated

I’ve just finished upgrading my blogging software to the latest version, you shouldn’t notice any difference.

If you see any problems with the blog or the rest of the web site please e-mail me (the address is in the page footer) or use the contact form.

Thanks

Give your application the Hollywood user experience with WPF

Have you ever noticed the completely ridicules UI of computers in movies and TV shows?

You rarely see any windowing systems (but that’s ok, the hero never has to switch windows), the fonts and icons are huge (also ok, there’s never anything more complex than 3 text boxes and 2 buttons) except for lists that always display in a grid with 2 points font size (also ok, the hero never needs more than a second to scan the entire list).

But it sure is dramatic, logging into a secure system looks like opening a safe door, sending or receiving e-mail is accompanied by huge animated envelopes flying on screens and when you delete a file you see it slowly shredded (slowly – because the hero is on his way and has to arrive just in time to stop the deletion).

Today I’m going to give you a movie style login control.

Initially the control will display a user name and password controls inside a round area along with a semi circler login button surrounded by gray “closed doors” (if this was for production I would have used a brushed metal texture instead of gray – but I wanted to keep the download small).

If you try to login with incorrect credentials you will get a scary “access denied” message.

When you enter the correct user name and password the circular “lock” are will rotate and then the “doors” will slide open the reveal the application.

Now, we are all serious software engineers here, so this is going to be a proper WPF control, it’s going to be “lookless” with replaceable templates and it’s going to be fully MVVM friendly.

We start by creating a class, we will call it LoginControl and we will inherit from Control – because that’s were we get the control template support from.

Let’s start with the interface with the control template.

Our control needs a TextBox for entering the user name, a PasswordBox for entering password and a button for actually trying to log in.

There is no cancel button because it’s out of scope for this control (the hero will either successfully log in or be dragged away by evil security guards – he will never close the application).

To accommodate the TextBox we just need a string dependency property that can be bound to the TextBox.Text property (that way the control template writer can also replace the TextBox with a completely different control if needed) – we will call this property LoginName:

public static readonly DependencyProperty LoginNameProperty =
    DependencyProperty.Register("LoginName", typeof(string), typeof(LoginControl));
public string LoginName
{
    get { return (string)GetValue(LoginNameProperty); }
    set { SetValue(LoginNameProperty, value); }
}

The PasswordBox is a little tricky because you can’t data bind to the PasswordBox.Password property, so we will have to access the PasswordBox ourselves, we do that by specifying the control template must have a PasswordBox named PART_PasswordBox, this is called a template part and the convention is the start the name with an uppercase PART_, we specify this using the TemplatePart attribute on the control calss:

[TemplatePart(Name="PART_PasswordBox", Type=typeof(PasswordBox))]
public class LoginControl : Control
{

For the login button we will use a routed command – this will let the template writer use any control that can send commands instead of a button (hyperlink, for example) and thankfully it’s just one line of code:

public static readonly RoutedCommand TryLoginCommand = new RoutedCommand();

Ok, two lines, we also need to use CommandBindings to attach code to this command, we will do it in our constructor (we will see the TryLoginExecute method later):

public LoginControl()
{
    CommandBindings.Add(new CommandBinding(TryLoginCommand, TryLoginExecute));
}

That took care of the data and notifications the template have to send to the control, we also need two notifications from the control to the template: one for failed login attempts and one for successful login attempts.

When the user enters incorrect name or password we want to show a message like “Incorrect user name of password, please check your Caps Lock and try again” (or in the movies “ACCESS DENIED”) and continue, we don’t change the state of the control so a property that can be bound to doesn’t make sense – buy an event does (we use a routed event and set it’s routing strategy to “Tunnel” so it is sent into the control template and not to the parent window).

public static readonly RoutedEvent FailedLoginEvent = EventManager.RegisterRoutedEvent(
    "FailedLogin", RoutingStrategy.Tunnel, typeof(RoutedEventHandler), typeof(LoginControl));
public event RoutedEventHandler FailedLogin
{
    add { AddHandler(FailedLoginEvent, value); }
    remove { RemoveHandler(FailedLoginEvent, value); }
}

When the login is successful we want to run our unlock animation, we could use a property if we want but we will just deal with it the same way as the failed login:

public static readonly RoutedEvent SuccessfulLoginEvent = EventManager.RegisterRoutedEvent(
    "SuccessfulLogin", RoutingStrategy.Tunnel, typeof(RoutedEventHandler), typeof(LoginControl));
public event RoutedEventHandler SuccessfulLogin
{
    add { AddHandler(SuccessfulLoginEvent, value); }
    remove { RemoveHandler(SuccessfulLoginEvent, value); }
}

That’s everything the template needs, but we also need to communicate with the application hosting the control.

First we need a way to ask the application if the user name and password are valid, we will do it with an event (note that this time the routing strategy is “Bubble”):

public class CheckLoginEventArgs : RoutedEventArgs
{
    public string LoginName;
    public string Password;
    public bool LoginSuccessful;
}

public delegate void CheckLoginEventHandler(object sender, CheckLoginEventArgs e);

public static readonly RoutedEvent CheckLoginEvent = EventManager.RegisterRoutedEvent(
    "CheckLogin", RoutingStrategy.Bubble, typeof(CheckLoginEventHandler), typeof(LoginControl));
public event CheckLoginEventHandler CheckLogin
{
    add{AddHandler(CheckLoginEvent,value);}
    remove{RemoveHandler(CheckLoginEvent,value);}
}

But wait, I said this control will be MVVM friendly so we will also add a command options:

public static readonly DependencyProperty CheckLoginCommandProperty =
    DependencyProperty.Register("CheckLoginCommand", typeof(ICommand), typeof(LoginControl));
public ICommand CheckLoginCommand
{
    get { return (ICommand)GetValue(CheckLoginCommandProperty); }
    set { SetValue(CheckLoginCommandProperty, value); }
}

And last but not least, a command so signal that the login was successful in an MVVM friendly way:

public static readonly DependencyProperty SuccessfulLoginCommandProperty =
    DependencyProperty.Register("SuccessfulLoginCommand", typeof(ICommand), typeof(LoginControl));
public ICommand SuccessfulLoginCommand
{
    get { return (ICommand)GetValue(SuccessfulLoginCommandProperty); }
    set { SetValue(SuccessfulLoginCommandProperty, value); }
}

We could also add a bubbling successful login event, but it’s easy enough to attach to the tunneling event in code behind – so we won’t bother.

You may have noticed we wrote a whole lot of code that just defines properties, commands and events but we didn’t write any code that actually does anything, so it’s time to write the actual control logic.

The control logic is ridiculously simple:

  • if you have a CheckLoginCommand execute it, otherwise send the CheckLogin event.
  • It the command/event indicated the login was successful send the LoginSuccessful event and execute the SuccessfulLoginCommand.
  • Otherwise send the FailedLogin event.

or in code:

private void TryLoginExecute(object sender, ExecutedRoutedEventArgs e)
{
    CheckLoginEventArgs args = new CheckLoginEventArgs()
    {
        RoutedEvent = CheckLoginEvent,
        LoginName = LoginName,
        Password = GetPassword(),
    };

    var cmd = CheckLoginCommand;
    if (cmd != null)
    {
        cmd.Execute(args);
    }
    else
    {
        RaiseEvent(args);
    }

    if (args.LoginSuccessful)
    {
        var result = new RoutedEventArgs(SuccessfulLoginEvent);
        RaiseEvent(result);
        var successCommand = SuccessfulLoginCommand;
        if (successCommand != null)
        {
            successCommand.Execute(null);
        }
    }
    else
    {
        ClearPassword();
        var result = new RoutedEventArgs(FailedLoginEvent);
        RaiseEvent(result);
    }
}

There are also the GetPassword and ClearPassword methos we use to access the PasswordBox defined as a template part – note that we have to run correctly also when it doesn’t exist (even if there’s no point to do so in the control).

private string GetPassword()
{
    var pbox = Template.FindName("PART_PasswordBox", this) as PasswordBox;
    if (pbox != null) return pbox.Password;
    return string.Empty;
}
private void ClearPassword()
{
    var pbox = Template.FindName("PART_PasswordBox", this) as PasswordBox;
    if (pbox != null) pbox.Password = string.Empty;
}

And that’s all we need to apply our movie style template.

To see the control template and view the entire animation just download the project here (~20Kb). the control template is in the app.xaml file.

It’s a VS2010 project targeting .net 3.5SP1 but the code is fully compatible with VS2008, I’ve also included the sample executable so you can view the animation without compiling (the correct user name/password are “user”/”1234” without the quotes).

yaTimer 2.6 Now an auto-update

If you have automatic updates enabled and you are running version 2.5 or later you should get the updated version of yaTimer within a week.

If you can’t wait you can always click the “Check for updates” button located in the about window (accessed from the last option on the main menu) or the options window (Accessed from the bottom right of the main menu).

There were a few problems with the last auto update – and the reason it has been a full week between yaTimer’s release and it’s availability as an automatic update is that I’ve been working on fixing the problems – and hopefully everything will work now.

If you have any problems upgrading you can e-mail me (the address is on the bottom of every page on the site) or use the contact form.

As always, you can also upgrade manually from the upgrade page.