Archive for the ‘User Interface’ Category

Structure of a WPF view

Friday, June 5th, 2009

Commuter View in Blend The Commuter View Model interface is written. The next step is to hook it to a view.

I like to think of the view as just a convenience. If the user could access properties directly, all they would need is the View Model interface. Thinking about the View Model this way helps me ensure that it is complete and targeted.

So I created the interface as a way of describing what would be on the view. A designer given the interface and a wireframe could build a view. Not having a designer handy, I did it myself. Here's how.

Create a mock
The first step was to create a mock implementation of the View Model interface. In truth, it's not just one interface; it's a set of interrelated interfaces. So I have interrelated mocks. Most of them are simple data access objects: collections of properties with no behavior. Create a read/write property for every property of the interface, even if the interface property is read-only. So for the IPodcastEpisode interface:

public interface IPodcastEpisode
{
    string Name { get; }
    string PodcastName { get; }
    string DurationAsString { get; }
}

I created a PodcastEpisodeSample class:

public class PodcastEpisodeSample : IPodcastEpisode
{
    public string Name { get; set; }
    public string PodcastName { get; set; }
    public string DurationAsString { get; set; }
}

The root class has a little more to it. It has to initialize itself and its children in its constructor. I don't like putting code in a constructor, but you don't have another chance to set your properties.

public class CommuterViewModelSample : ICommuterViewModel
{
    public CommuterViewModelSample()
    {
        // Set up some sample data.
        List<IPlaylist> playlists = new List<IPlaylist>();
        playlists.Add(new PlaylistSample() { Name = "Favorites" });
        PlaylistSample commutePlaylist = new PlaylistSample() { Name = "Commute" };
        playlists.Add(commutePlaylist);
        playlists.Add(new PlaylistSample() { Name = "Energy Songs" });
        Playlists = playlists;
        SelectedPlaylist = commutePlaylist;

        List<IPodcast> podcasts = new List<IPodcast>();
        podcasts.Add(new PodcastViewModel(new Podcast() { Name = ".NET Rocks!", Selected = false, Rank = 0 }));
        podcasts.Add(new PodcastViewModel(new Podcast() { Name = "Hanselminutes", Selected = true, Rank = 3 }));
        podcasts.Add(new PodcastViewModel(new Podcast() { Name = "Deep Fried Bytes", Selected = true, Rank = 4 }));
        podcasts.Add(new PodcastViewModel(new Podcast() { Name = "They Might Be Giants", Selected = true, Rank = 5 }));
        Podcasts = podcasts;

        List<IPodcastEpisode> queue = new List<IPodcastEpisode>();
        queue.Add(new PodcastEpisodeSample() { Name = "5-A", PodcastName = "They Might Be Giants", DurationAsString = "9:30" });
        queue.Add(new PodcastEpisodeSample() { Name = "JavaFX and the Web's Four Virtual Machines", PodcastName = "Hanselminutes", DurationAsString = "40:24" });
        PodcastEpisodeSample deepFriedBytesEpisode = new PodcastEpisodeSample() { Name = "New Ideas for the Web with Thomas Krotkiewski", PodcastName = "Deep Fried Bytes", DurationAsString = "53:15" };
        queue.Add(deepFriedBytesEpisode);
        Queue = queue;
        SelectedEpisode = deepFriedBytesEpisode;

        TotalDurationAsString = "1:43:09";
    }

    public IEnumerable<IPlaylist> Playlists { get; set; }
    public IPlaylist SelectedPlaylist { get; set; }
    public IEnumerable<IPodcast> Podcasts { get; set; }
    public IEnumerable<IPodcastEpisode> Queue { get; set; }
    public IPodcastEpisode SelectedEpisode { get; set; }
    public string TotalDurationAsString { get; set; }
    public ICommand Skip { get; set; }
}

You can hook up the mock in Blend. In the Window's properties, press the "New" button next to DataContext. Select the CommuterViewModelSample class from the solution. Now Blend will show the data from your mock object while you edit the view.

Overall structure: Grid
The structure of the Window itself is defined by a Grid. Grid is the best container for a window, because it supports "star" sizing. You can be specific about the size of some components, and then leave the rest of the real estate to be carved up proportionally. A Grid is the default layout root that Blend puts into a window, but if you have something else there (like a StackPanel for example), you can right-click on it and select "Change Layout Type".

Select the Grid in the object pane (where you right-clicked to change the layout type) and expand the layout properties. Hit the ellipsis button to set the RowDefinitions property. For Commuter I added 8 rows. I set the height of all but one to "Auto"; the remaining one I left at "1 Star". This one row will resize with the windows, and the others will be the height of their contents.

I populated the rows with controls, alternating between TextBlocks for labels and other controls for inputs. To tell you the truth, it was easier to drop into XML for this part. If you've set the row sizes to "Auto", then there is no space into which to drop controls. And if you haven't you spend a lot of time resetting Margin, HorizontalAlignment, and VerticalAlignment after you drag a control into the right row. Here's the structure so far.

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="Commuter.Window1"
    Title="Commuter" Height="420" Width="540" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" xmlns:Commuter="clr-namespace:Commuter">
	<Window.DataContext>
		<Commuter:CommuterViewModelSample/>
	</Window.DataContext>
    <Grid>
    	<Grid.RowDefinitions>
    		<RowDefinition Height="Auto"/>
    		<RowDefinition Height="Auto"/>
    		<RowDefinition Height="Auto"/>
    		<RowDefinition Height="Auto"/>
    		<RowDefinition Height="Auto"/>
    		<RowDefinition Height="*"/>
    		<RowDefinition Height="Auto"/>
    		<RowDefinition Height="Auto"/>
    	</Grid.RowDefinitions>
	</Grid>
</Window>

Non-resizeable list: ListBox of Grid
The Podcasts list contains for each podcast a selection box, rating buttons, and the name of the podcast. The selection box and rating buttons are all fixed width. The only thing that varies is the width of the name. This sounds like a job for Grid again. The buttons can assume the width of their contents ("Auto"), while the name can consume all remaining space ("1 Star").

I dropped a ListBox into the appropriate row and set its Height property to a number I liked. Dragging the handle didn't do the right thing, because Blend couldn't tell whether I wanted to resize the row or not. When the property is set explicitly, it's obvious what you intend.

The next step was to bind the ItemsSource to the Podcasts property of the View Model. Click on the little gray dot to the right of the ItemsSource property and select "Data Binding". You'll want the "Explicit Data Context" tab, though I'm not sure why it's called that. You're using the DataContext inherited from the parent Window. Open up the View Model object and select the collection you want to show. I chose the Podcasts property.

Now you need to define how each item in the list looks. Right-click on the ListBox and select "Edit Control Parts (Template)", "Create Empty". Give the template an appropriate name (like PodcastsTemplate), leave "This Window" selected, and hit "OK". This creates a data template resource within the Window itself. If you had created it in the Application, it could be shared among windows. Since I don't see a need for that, I kept it local.

I'm not sure why this is called a "data template". It seems that it should be called a "view template". It's not a template for the data: that's defined by the bound interface property. Anyway...

Back Button From here, you can use a Grid layout just like you did for the Window, but set the ColumnDefinitions instead of the RowDefinitions. When you're done, click the button in the picture on the right to go back to the window. I can't tell you how long it took me to discover this thing.

You may notice that each of the rows is squashed at this point. Set the HorizontalContentAlignment property to "Stretch" to fix this.

Here's the resulting XAML.

<DataTemplate x:Key="PodcastsTemplate">
    <Grid Height="18">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="16"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="38"/>
            <ColumnDefinition Width="38"/>
            <ColumnDefinition Width="38"/>
            <ColumnDefinition Width="38"/>
            <ColumnDefinition Width="38"/>
        </Grid.ColumnDefinitions>
        <CheckBox Width="13" VerticalAlignment="Center" Grid.Column="0"/>
        <TextBlock Width="Auto" Text="{Binding Path=Name, Mode=Default}" Grid.Column="1" VerticalAlignment="Center"/>
        <CheckBox Grid.Column="2" Template="{DynamicResource CarCheckBox}" IsChecked="{Binding Path=Rank1, Mode=Default}"/>
        <CheckBox Grid.Column="3" Template="{DynamicResource CarCheckBox}" IsChecked="{Binding Path=Rank2, Mode=Default}"/>
        <CheckBox Grid.Column="4" Template="{DynamicResource CarCheckBox}" IsChecked="{Binding Path=Rank3, Mode=Default}"/>
        <CheckBox Grid.Column="5" Template="{DynamicResource CarCheckBox}" IsChecked="{Binding Path=Rank4, Mode=Default}"/>
        <CheckBox Grid.Column="6" Template="{DynamicResource CarCheckBox}" IsChecked="{Binding Path=Rank5, Mode=Default}"/>
    </Grid>
</DataTemplate>
...
<ListBox ItemsSource="{Binding Path=Podcasts, Mode=Default}" ItemTemplate="{DynamicResource PodcastsTemplate}" HorizontalContentAlignment="Stretch" Height="76" Grid.Row="3"/>

Resizable list: ListView
The list of episodes contains three string columns. It makes sense to let the user individually resize these, unlike the single text field among a bunch of fixed-width buttons. So I decided on a ListView for this control.

You'll probably have to click the chevrons on the bottom of the toolbar to find the ListView tool. It's not one that's visible out-of-the-box. Set the ItemsSource just as before, But this time, rather than defining a data template, you need to set the View. Expand the "View" property hiding in "Miscellaneous". Click on the "Columns" button to add three columns. Set the Header property and bind the DisplayMemberBinding.

Here's one frustrating part of Blend. Even though we've bound the ItemsSource, Blend presents us with the properties of the CommuterViewModelSample. It should be able to recognize that the ItemsSource is a collection of IPodcastEpisode and give us the properties of that interface. Since it can't we just have to manually enter a path expression. Check the box at the bottom and enter the property name.

Use custom path expression

<ListView ItemsSource="{Binding Path=Queue, Mode=Default}" SelectedItem="{Binding Path=SelectedEpisode, Mode=Default}" Grid.Row="5" HorizontalContentAlignment="Stretch">
    <ListView.View>
        <GridView>
            <GridViewColumn Width="300" Header="Name" DisplayMemberBinding="{Binding Name}"/>
            <GridViewColumn Width="140" Header="Podcast" DisplayMemberBinding="{Binding PodcastName}"/>
            <GridViewColumn Width="50" Header="Duration" DisplayMemberBinding="{Binding DurationAsString}"/>
        </GridView>
    </ListView.View>
</ListView>

Spacing: Style
By default, all of the controls in a grid are pushed right up next to each other. To add spacing, I defined a set of styles. Each style targets a control type in the grid, and sets the Margin property.

    <Window.Resources>
        <Style TargetType="{x:Type ComboBox}">
            <Setter Property="Margin" Value="3"/>
        </Style>
        <Style TargetType="{x:Type TextBlock}">
        	<Setter Property="Margin" Value="3"/>
        </Style>
        <Style TargetType="{x:Type ListBox}">
            <Setter Property="Margin" Value="3"/>
        </Style>
        <Style TargetType="{x:Type ListView}">
            <Setter Property="Margin" Value="3"/>
        </Style>
        <Style TargetType="{x:Type Button}">
            <Setter Property="Margin" Value="3"/>
        </Style>
    </Window.Resources>

Going back to Visual Studio
These patterns covered what I needed for this simple view. I suspect that they will cover most of my needs for data entry windows.

Overall, I find Blend to be a bit cumbersome to work in. It was great for the geometry of the car buttons, but it gets in the way when laying out a form. Dropping a control in the right place is not possible when the container size is Auto. Several extra properties get set on your behalf, which you then need to hunt down and reset. And even though the XAML that I need is small, the Blend UI hides many of the properties behind expanders, buttons, and tabs. I have to navigate several pages just to see what is immediately visible in a single line of XAML.

As a XAML editor, Blend is poor. It offers no intellisense the way that Visual Studio does. So now that I know the XAML that I need, I expect I'll be spending a lot more time in Visual Studio hand-crafting XAML.

Big (D)esign Conference 2009

Saturday, May 30th, 2009

The Big (D)esign Conference was an upbeat collaboration among designers, developers, and strategists. The common theme was finding ways to improve the quality of peoples lives through software. That's a theme that resonates with me, as I have made that my personal and professional mission.

This was a brand new conference. As such, there were some minor logistical problems, like a scarcity of water. But the attendees took to Twitter and the organizers responded. By the end of lunch, water bottles were abundant.

That's just one example of the dynamic nature of this conference. It wasn't about people expecting a solution to be handed to them. It was about people finding solutions, responding to needs, and making the conference a success. The organizers did a great job, but it was the attitude of the attendees -- who really made the conference their own -- that made this day a success.

The Twitter hash tag was in full force during the conference. Many attendees Twittered salient points of the presentations, their own opinions of the topics, or just their sandwich selection. The Twitter stream was as lively a conversation as any occurring in the hall. Through this conversation, I made several new friends.

In addition to making new friends, I ran into some old ones. One of my old friends, T. Scott, is a designer. He recorded his notes and uploaded them for all to enjoy. So I decided to do the same, in my own way. My writing is not so beautiful, so here is a transcription of my notes from the sessions I attended.

Keynote: Norm Cox, Interaction Therapist
Titles are limiting. When we think of ourselves as developer, designer, business analyst, or architect, we tell ourselves (and others) that that is all we do. We limit our own thinking.

We should instead become jacks of many trades, masters of some. Know all of the disciplines related to solving someone's problem. Know which ones you are particularly good at, but don't let your skill set limit your solution.

Clients don't know what they need. Don't do exactly as they tell you to. Be a therapist. See the big picture. Find the real problem. Don't limit yourself to the client's expectations.

Chris Koenig: Touch Computing
A Natural User Interface (NUI) is touch, gesture, manipulation, and more. It doesn't replace the keyboard or the mouse. It is designed for different scenarios. And sometimes, it augments them.

NUI is good for free-form exploration and social interaction. It is not good for precision. It is not good for data density. Those are best left to the keyboard and mouse.

Touch is not click. Don't take old metaphors and idioms and apply them to NUI. This is a new space, with a new set of benefits. Add it to your tool belt, but don't replace your trusted tools.

Stephen P Anderson: The Art and Science of Seductive Interactions
Good usability lowers friction. But that's not enough. You also need to increase motivation. That can only be achieved by understanding psychology.

People respond to levels and rewards. LinkedIn rates the completeness of your profile, and tells you what to do to take it to the next level. That motivates people to behave the way LinkedIn wants. This behavior benefits LinkedIn, but it also benefits the individual.

Seduction is deliberately enticing a person to perform a desired behavior. It requires feedback. The person needs to see that their actions modify the results. Their curiosity will lead them to try other behaviors and see how the results change.

People respond to visual imagery. They look for patterns in everything they see. They recognize, even if they can't recall. Software that uses these traits to its advantage will successfully guide users to perform the desired behavior. So show people images and have them find patterns that give you information about them. Don't give them a blank slate and ask them to recall that information.

Bill Scott, design patterns you should know
Nuance is where a lot of good interaction is missed. Pay attention to the details to eliminate friction.

Make it direct. Where there is output, let there also be input. Let interactions be symmetric; don't enter a feature one way and leave it another.

Make it lightweight. Interactions have a "click-weight". Too many interactions, and your users are going to get tired and go away.

Avoid confusing and annoying anti-patterns. Don't hover-and-cover, popping up new content over old content, or hiding navigation. Avoid "mystery meat" navigation, when the user doesn't know what a click is going to do until they do it. Follow Fitts' Law and give the users a big enough target.

Stay on the page. Use overlays (pop-out) and inlays (slide-down) wisely. Don't stop the proceedings with idiocy. Use carousels, endless scrolling, and deep zoom where appropriate.

Offer an invitation. Play on your user's curiosity. Give them a call to action. Show them the steps involved. Walk them through. Make it discoverable.

Show transitions. Fade in and out. Animate zoom and motion to draw people to the next state.

Garrett Dimon, Feedback and Feature Requests
The creator of Sifter talks about how he solicits feedback from his customers, and how he rolls that in to new features of the product.

The lifecycle of feedback is: generate, receive, reply, absorb, and respond. Generate feedback by actively soliciting it from your users. Receive it in the form of analytics, web forms, email, phone calls, and anything else that is accessible to your users. Reply quickly to tell them whether you're looking into it, or if not why. Take the time to absorb the feature request and consider its full impact. Then respond by implementing the feature, a different feature, or giving the customer the results of your full consideration.

Don't be afraid to be transparent in your product development process. People will not steal your ideas. If your idea is really great, you are going to have to shove it down their throats.

If it needs instructions, then it's broken. Instead of explaining to people how to use your software, fix the problem. If you have Frequently Asked Questions, you have feedback. Fix it.

The proportion of criticism to encouragement is absurd. It will bring you down. But always be upbeat when you reply. It will turn the conversation around and open a constructive dialog.

Always think about the need, not the prescription. Find out what problem people have. Don't give them the solution they ask for. Don't abdicate software design to the users. Listen to them, then deliver a solution.

There are no takebacks with features. Consider whether it really belongs in your product before adding it. It may delight some people but confuse others. If you add it, you will have to support it for the life of the product.

Todd Wilkens, Saying "No" and Failing
Saying "No" limits the scope and gives you clear focus. First evaluate features based on importance and feasibility. Implement the ones that are important and feasible. Consider the ones in the middle. Ignore the ones at the low end of the scales.

Design is about specific customer value. Start with the things that you can do exceedingly well, and deliver that value completely. Each evolutionary step after that should provide complete customer value for a new problem, not a new part of an incomplete solution.

Failure is always an option. Everybody fails often. Just make sure you are failing forward. Fail early and often, and learn from each iteration.

If failure is not allowed, then risk is not allowed. The companies that succeed openly encourage failure internally. Separate personal feelings from ideas, so that the ideas can fail without damaging the people.

Have a conversation with the materials. Through the process of making something, we change the thing that we are making in response to the stuff that we are making it out of. Create an informal iterative process for failing often.

Thor Muller, Work Like the Network
Challenge the assumption of industry: that it is expensive to coordinate a large number of people to build something. When we move electrons instead of molecules, those economics no longer hold. The use of a physical product reduces its value. The use of an idea increases its value.

There are six ways to remodel the organization to act more like a network.

Move from To
Control Chaos
Process Flow
Documentation Collaboration
Fear Confidence
Hidden Shown
Ownership Stewardship

It was a relief to hear someone talk about agile processes and flow without being dogmatic about it. Thor just demonstrated the value of these principles, and showed how organizations have applied them successfully.

Christina Wasson, When Design Met Anthropology
Ethnographic anthropology is the study of cultures by becoming an observant participant. It is a systematic approach involving capturing information, coding it, and analyzing it. These techniques can be applied to software to give us a scientific basis for measuring the quality of a design.

Ironically, the case study that Christina shared was juxtaposed against the spirit of the conference. a study of Social TV, a Motorola project to join families in different living rooms around the viewing experience. The case study was punctuated by videos of people talking about how they negotiate over the remote, how television fits in with their lives, and how their family members react. It was depressing to see people still engaging in a one-sided conversation with old media, instead of using their talents to create something awesome.

I'm excited to have met such a fantastic group of people. I look forward to working with them to improve the quality of peoples lives through software. I'm looking forward to checking in next year to see how we've done.

Custom WPF and Silverlight ListBox ItemTemplates eat mouse clicks

Sunday, March 15th, 2009

A WPF or Silverlight ListBox can display anything. By default, it just displays the ToString() of the objects in the ItemsSource, but that can be adjusted. If you want to display a string other than the ToString(), you need to define an ItemTemplate:

<ListBox Height="200" ItemsSource="{Binding Posts}" SelectedItem="{Binding SelectedPost}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <ListBoxItem Content="{Binding Title}"/>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

Unfortunately, when you do this the string eats your mouse clicks. You can click anywhere around the string, but clicking on the string does not select the item.

Here's my solution
Through trial and error (and MSDN) I finally found a solution:

<ListBox Height="200" ItemsSource="{Binding Posts}" SelectedItem="{Binding SelectedPost}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <ListBoxItem IsHitTestVisible="False" Focusable="False" Content="{Binding Title}"/>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

If you set IsHitTestVisible and Focusable both to False on the ListBoxItem, then clicking the text selects the item, just like in the default ToString() scenario. Setting just one or the other does not change the behavior. Both must be changed.

This seems backwards to me. I would expect that you would have to set IsHitTestVisible to False on a TextBlock inside the ListBoxItem, not the ListBoxItem itself. Making the ListBoxItem invisible to hit tests seems like the opposite of what you want to do.

Nevertheless, this works, so I'll add those two attributes from now on. And BTW, my real XAML code uses Update Controls, but I thought I'd show you some Binding code just in case you haven't converted yet.

Update
Here's a simpler solution:

<ListBox Height="200" ItemsSource="{Binding Posts}" SelectedItem="{Binding SelectedPost}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Title}"/>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

It turns out that the content of a ListBox DataTemplate does not have to be a ListBoxItem. There's my old-school Win32 thinking kicking in. If you make it a TextBlock, then it responds correctly to mouse clicks.

Update Controls on Code Project

Friday, April 11th, 2008

I wrote a short article for The Code Project called A Simple Alternative to Windows Forms Data Binding. It's a real quick introduction to Update Controls. The goal here is just to get people trying it out to see how it simplifies Windows UI programming.

It's only been up for a day, but already I've gotten a comment from Marc Clifton himself! If you have spent any time on Code Project, you've read at least one of his articles. The man is a brilliant communicator. And he wears a nice hat.

Please check out the article. And if you haven't done so yet, check out Update Controls .NET.

Update Controls .NET released under LGPL

Tuesday, March 18th, 2008

I have released Update Controls .NET as an open source project.

Update Controls is a set of .NET Windows Forms controls that update themselves automatically. Instead of setting properties, you handle events. For example, the GetText event fires on an UpdateTextBox when it needs to update itself. The event returns the text string to display. Then, when any data that was used to calculate that string changes, the event fires again. Automatically.

Think of how you use a spreadsheet. You enter a formula in a cell, with references to other cells. When those other cells change, the formula is recalculated. You don't have to tell it to update, it just figures out when to do so.

I created the algorithm at the core of Update Controls almost ten years ago in C++. Since then, I've translated it first into Delphi, then Java, and finally C#. The Update Controls library is that core algorithm applied to the common Windows Forms controls. It also includes a handful of themed controls for better visual appeal.

Not data binding
For as long as Microsoft has done demos of VB, they've tried to convince us that we can write real software with zero lines of code. It works fine on the projector screen, but it fails to translate to a full-scale application. Once you get outside the box of direct column/control relationships, it is impossible to adapt data binding to your needs.

But Update Controls is completely flexible. You can start with a simple event that returns one value, but then modify it to combine two or more values as your needs change. Since you are writing real code, you are in complete control.

A data-bound control attaches directly to a column in the database, or a property of an object in the data model. It cannot call through more complex business logic.

But an Update Control can call any business logic that you specify. Whatever data the business logic touches in order to do its work, that data affects the control's behavior. When any of it changes, the event fires and calls the business logic again.

Not the observer pattern
The observer pattern is obsolete. It requires that you manually register each observer with each subject. To make it work, you have to know ahead of time all of the objects in the data model that affect each user interface component. A large portion of your source code is dedicated to making and breaking those connections.

Update Controls, on the other hand, discovers those dependencies on its own. It can see through business logic, so you don't have to directly tie your user interface to your data model. And if that business logic switches to a new path, the dependencies are updated as well.

The observer pattern also suffers from cascading redundant updates. If your observer depends upon two subjects and both are changed at the same time, the observer pattern will give you two updates. The problem gets even worse when you have indirect dependencies, as the redundancy rolls through the system like so many dominoes.

But Update Controls queues the updates until the last possible moment, so they occur only once. Even indirect dependencies are calculated in the best possible order, with each update being invoked just once as the information bubbles to the surface.

Please give it a try
The library works in C# and VB. It can be used in either Visual Studio 2005 or 2008, with .NET 2.0, 3.0, or 3.5. You can download the source code and build it yourself, or just run the convenient installer. Videos, demos, and documentation are all available to help you get started.

Once you write a Winforms app with Update Controls, you'll never want to code without them.

The metaphor is only skin deep

Tuesday, February 5th, 2008

One of the "new" apps that was made available for the iPod Touch is Notes. (Yes, I paid the $20 bounty on them rather than jailbreaking it. I just wasn't worth the drama.)

The app looks like a yellow legal pad. It uses a comic font to simulate handwriting (although my actual handwriting never looked as neat), it has hand-drawn icons for email and trash, and it plays a page turning animation when you switch from one note to another.

As I've said before, this kind of detail sells software. But there is one caveat. The metaphor should never limit the application.

In the early 90's, as software was becoming a commodity in the lives of average people, the graphical user interface was going mainstream. We saw several attempts to make software look like a real thing. The Macintosh and Windows both had desktop metaphors, some online "communities" actually looked like streets with buildings for different destinations, and of course there was Microsoft Bob.

As Alan Cooper tells us in the Myth of Metaphor, software should not limit itself based on the metaphor it chooses. It should be idioms to guide UI design. Idioms are patterns that users come to recognize, even though they have no basis in reality, and may not even be intuitive.

The iPod Touch employs several idioms that we were used to from previous iPods and other software. For example, you can scroll through a list, pick an item, and then the screen slides to the left to reveal a new list. When you go back, the prior list slides back in from the left. The Touch also created some new idioms thanks to its multi-touch screen. Now you can scroll through those lists with a flick of the finger. Most people have to be shown these idioms, but once they see them they apply them everywhere.

The iPod Touch Notes application is not a slave to its metaphor. For example, you can make a note as long as you like. It is not limited to the length of a piece of paper. If it strictly adhered to its metaphor, you would have to turn the page to keep writing. Thankfully, it's one page per note.

But the Notes application does adhere to the iPod Touch idioms. The notes appear in a list, which scrolls with a flick of the finger and slides to the left when you select a note. Notes themselves scroll, even though the "binder" stays fixed at the top of the screen. No physical legal pad in existence could work this way, but that doesn't keep us from understanding it.

The metaphor is only there to give the app a consistent visual design. The idiom gives it its usability.

Elevator Crowding and Human Behavior

Tuesday, January 15th, 2008

I don't know why, but elevators fascinate me. I guess it's because they represent a daily junction of people, process, and technology. They embody many of the problems we face with limited resources, queues, and traffic.

I've noticed that people tend to gather in front of doors that are about to open. It's probably preservation of your place in line so that the elevator doesn't fill up with people who haven't been waiting as long as you have.

The problem with this behavior is that the elevator is invariably filled with people wanting to get off. There is always a dance where the people trying to get out negotiate with the people trying to get in for a traffic lane. The dance lasts for about three seconds and it always ends with the people crowding around the doors retreating into the lobby to give the others a lane of egress.

Wouldn't it be better for everyone if people would stand away from the doors? You can be fairly certain, especially on the ground floor, that there are people trying to get off the elevator. There is limited space inside the car, but comparatively unlimited space in the lobby. It only makes sense to let them off first.

But people don't behave that way. The elevator represents a limited resource that they have already invested in. They have to preserve their access to that resource.

Here's my solution
Put the alert light and bell on a delay. Instead of alerting the people waiting for the elevator before the doors open, only give the indication after the people in the car have had the chance to get out.

What does this have to do with software?

Well, besides the trivial observation that modern elevators are run by software, this is an example of user interaction design. It recognizes human behavior and uses it to solve the problem. You might even call it a life hack.

So many software systems fight against human behavior. They put up punitive error messages to try to get the human to change their ways, or force the user through some complicated dance of gesticulations in order to get a simple task accomplished. But well-designed software recognizes the human as part of the system, and incorporates their behavior as-is into the solution.

Convenience and the Ground Floor Elevator Button

Thursday, January 3rd, 2008

Why don't elevator panels have a really big button for the first floor?

elevatorMost people ride the elevator up to a particular floor, conduct their business, and then ride it back down to the first floor. Rarely does anyone need to ride between two upper floors. So by inference, half of the time you step into an elevator you have to push the "1" button.

Making the ground floor button more convenient will certainly make the others less so. But not all of the buttons are equal. The minor increase in effort spent looking for an upper floor button is far outweighed by the decrease in effort on the way back down.

When we design software, we usually think in terms of functionality. We want to cram in everything that the user might want, and account for every possible situation. Instead, we should focus on the things that the user does most often and make them the most convenient. It's not the feature matrix that sells software, it's the big red button that gets you where you want to go.

User Interaction as Marketing

Monday, December 17th, 2007

Successful marketing makes a product more desirable. User interaction design makes software more desirable. So is user interaction design successful marketing for software? I think so.

With so many folks talking about the iPhone right now, it surprises me how few people remark on the effect of its lovely interaction design. Notice the difference between the advertisements for the iPhone and every other phone on the market. Other phones show people talking on the phone, snapping pictures of each other, dancing around, getting into kung fu battles with phones as weapons, everything except what the iPhone ads show you: how you use the phone. Think about that. They sell the iPhone by showing you thirty seconds of someone using it, assuming that this will make people want it … which it does, because people recognize that using a product with good interaction design feels good.

- Jonathan Korman, Intuition, pleasure, and gestures

If we intend to sell our software to the masses, we need to adopt the iPhone marketing model. Whether or not you agree, most people find the iPhone ads desirable. My friend's 5-year-old son will come running into the room to watch a commercial when he hears the distinctive iPhone music. I will stop my TiVo to watch an iPhone or iPod Touch ad. And I own an iPod Touch! The interface is so pleasurable that we want to watch people use it.

But even companies that don't have the money to run TV spots should aspire to the standard that Apple has set. Software sells by word-of-mouth. Every time a user fires up your software, they should be seeing an ad.

I'm not talking about ad-supported software. I'm talking about the user having such a great experience that they want to tell people about it. I'm talking about a user interface so fun that people want to watch it on YouTube.

The iPhone is sorely lacking in features. The iPod Touch has extremely limited capacity for the price and crashes fairly often. But the icons fly in when you hit the home button. And the album cover flips and rotates when you turn it. These are the things that you see in the commercials. These things are fun. These are the things that sell.

The Right Time to Update

Monday, November 26th, 2007

I use subversion for all of my personal and professional source code control. As I've stated before, I highly recommend that you set up an SVN repository for managing and sharing your own projects. My client of choice is Syncro SVN. But like all the other SVN clients that I've tried, it picks the wrong time to update.

When drilling down through the folders of a repository, all SVN clients pause each time a folder is opened to fetch data from the server. Syncro SVN keeps the tree populated while the program is running, so you can collapse and expand folders without penalty. However, once you close the program, it has to fetch folder contents all over again the next time you run it.

This pause for every folder puts a small tax on user navigation. It puts a speed bump on the road that the user takes to get something done. I hold in my head the place that I need to go and the thing that I need to do once I get there. A series of speed bumps, like the so-called "Chinese" water torture, can distract me from that goal and derail my progress.

An even more common example of the wrong time to update can be found in just about any program that downloads new versions of itself. Every once in a while, I start Firefox, Quicken, or iTunes to be asked if I want to install an update. The reason that I started this program was not to install an update. It was to get something accomplished. This question completely puts me off my goal and makes me worry. Is there some serious bug or security problem that I should let them fix? How long will this update take? What's the chance that it will fail and prevent me from accomplishing my goal altogether?

All of these programs let me say no, which I do by default. But then in most cases I'm just asked the question again the next time I try to get something done, so I eventually relent. Firefox is the best of a bad bunch, because it will hold off the update until the end of my session. Of course, it still has to ask, and then it has to tell me that it will update itself. I'd rather it just do that by default.

Here's my solution
I recently implemented automatic self update in a Windows program. I fashioned it after the Firefox model, but just made it work without any pop-ups.

First I created a launcher for the program. This launcher just runs the program, after doing some bookkeeping that I'll describe soon.

Then, when the program starts, it starts a background thread to go to the web site and check for newer versions. If it finds one, it downloads it in the background. If the user closes the program before the download completes, it is canceled. It will try again the next time the program starts.

If the download succeeds, then the new version is put in a sub folder for the launcher to find. This is the bookkeeping I was talking about. The next time the user runs the program, the launcher quickly (and silently) installs the new version before running it.

Update data in the background
The right time to update is when the user isn't looking. This works equally well for data updates as it does for software updates. Syncro SVN could start a background thread to pull down folder contents. This would get that processing off the main thread, and keep the user from experiencing a series of speed bumps.

But background processing isn't enough. If the background process is fired on demand, then the user is still waiting before going to the next step. You also need persistence. Once the folder contents are on the client, they should be accessible no matter what happens. If the user restarts the program, they should still be able to get instant access to folders that they've visited before. That means that those contents should be cached in someplace other than memory.

But wait! The user isn't seeing an up-to-date picture of the data! That's OK. It's fresh enough for the user to move forward toward his goal. Just refresh the data (in the background) when it gets too old. And give the user a manual refresh button if they don't see what they are looking for. Your user is smart. Just keep the speed bumps out of his way.