Archive for the ‘Commuter’ Category

Commuter launch

Wednesday, April 7th, 2010

I have officially launched Commuter, a podcast playlist manager for iTunes. Commuter finds all of your podcast subscriptions and builds a continuous playlist of episodes. Listen to the playlist on your way to work. Dock your iPod when you get home.

commuter

I started this project almost a year ago. Along the way, I have learned WPF, enhanced Update Controls, and built Correspondence to support this application. Here are some of the stories from the journey.

There is still much to say about what I’ve learned from working on this application. Be expecting posts about inertial navigation, 3D modeling in WPF, and synchronization.

Multi-threaded view model using Update Controls

Wednesday, June 10th, 2009

Multithreaded programming doesn't have to be hard. You just need to learn a few patterns.

Here's a common problem. How do you notify the UI thread that something has changed on a background thread? The INotifyPropertyChanged contract requires that the PropertyChanged event be fired on the UI thread. There are ways of marshalling that event from the background to the foreground, but wouldn't it be nice if you didn't have to?

Update Controls takes care of this problem for you. You make sure your code is thread-safe, and Update Controls will make sure that dependents are updated, no matter what thread they are on.

A thread-safe model
For Update Controls to work properly -- and indeed for your entire application to work properly -- you need your data model to be thread safe. That means that anything that can change is protected with a lock. If it can change (and you are using Update Controls), then it should have an Independent sentry. Just be sure to call OnGet and OnSet inside the lock.

public class Playlist
{
    private int _id;
    private string _name;

    public Playlist(int id)
    {
        _id = id;
    }

    public int ID
    {
        get { return _id; }
    }

    #region Independent properties
    // Generated by Update Controls --------------------------------
    private Independent _indName = new Independent();

    public string Name
    {
        get
        {
            lock (this)
            {
                _indName.OnGet();
                return _name;
            }
        }
        set
        {
            lock (this)
            {
                _indName.OnSet();
                _name = value;
            }
        }
    }
    // End generated code --------------------------------
    #endregion
}

When dealing with collections, we need to be a little careful. If you return an IEnumerable that traverses a collection, that IEnumerable will leave the lock. This will cause problems, as another thread can come in and modify the collection while you are traversing it. To be safe, we need to create a copy of the collection within the lock.

public class MusicLibrary
{
    private List<Playlist> _playlists = new List<Playlist>();

    #region Independent properties
    // Generated by Update Controls --------------------------------
    private Independent _indPlaylists = new Independent();

    public Playlist GetPlaylist(int playlistID)
    {
        lock (this)
        {
            // This method changes the list.
            _indPlaylists.OnSet();

            // See if we already have this one.
            Playlist playlist = _playlists.Where(p => p.ID == playlistID).FirstOrDefault();
            if (playlist != null)
                return playlist;

            // If not, create it.
            playlist = new Playlist(playlistID);
            _playlists.Add(playlist);
            return playlist;
        }
    }

    public void DeletePlaylist(Playlist playlist)
    {
        lock (this)
        {
            _indPlaylists.OnSet();
            _playlists.Remove(playlist);
        }
    }

    public IEnumerable<Playlist> Playlists
    {
        get
        {
            lock (this)
            {
                _indPlaylists.OnGet();
                // Return a copy of the list so that it can be accessed in a
                // thread-safe manner.
                return new List<Playlist>(_playlists);
            }
        }
    }
    // End generated code --------------------------------
    #endregion
}

With this, your model is safe to access from multiple threads. So let's create one.

Create a thread to communicate with external systems
The UI thread is for the user. If you use it to communicate with external systems, it might hang. Then the user will have to decide whether to "Continue Waiting" or "Switch To...". Switch to what, a Mac? But seriously, it's better if you spawn a new thread to do all of your communication with external systems.

Commuter communicates with iTunes. So I created a separate thread to synchronize between iTunes and my data model. It wakes up every 10 seconds and refreshes the playlists.

public class ITunesSynchronizationService
{
    private MusicLibrary _musicLibrary;
    private Thread _thread;
    private ManualResetEvent _stop;
    private bool _first = true;

    private string _lastError = string.Empty;
    private Independent _indLastError = new Independent();

    public ITunesSynchronizationService(MusicLibrary musicLibrary)
    {
        _musicLibrary = musicLibrary;
        _thread = new Thread(ThreadProc);
        _thread.Name = "iTunes synchronization service";
        _stop = new ManualResetEvent(false);
    }

    public void Start()
    {
        _thread.Start();
    }

    public void Stop()
    {
        _stop.Set();

        // Give it 30 seconds to shut down.
        _thread.Join(30000);
    }

    public string LastError
    {
        get
        {
            lock (this)
            {
                _indLastError.OnGet();
                return _lastError;
            }
        }

        private set
        {
            lock (this)
            {
                _indLastError.OnSet();
                _lastError = value;
            }
        }
    }

    private void ThreadProc()
    {
        while (ShouldContinue())
        {
            try
            {
                // Connect to iTunes.
                iTunesApp itApp = new iTunesAppClass();

                // List all of the playlists.
                List<Playlist> oldPlaylists = _musicLibrary.Playlists.ToList();
                foreach (IITUserPlaylist itPlaylist in itApp.LibrarySource.Playlists.OfType<IITUserPlaylist>())
                {
                    string name = itPlaylist.Name;
                    Playlist playlist = _musicLibrary.GetPlaylist(itPlaylist.playlistID);
                    if (name != null && playlist.Name != name)
                        playlist.Name = name;
                    oldPlaylists.Remove(playlist);
                }

                // Delete all that are no longer in iTunes.
                foreach (Playlist playlist in oldPlaylists)
                    _musicLibrary.DeletePlaylist(playlist);

                LastError = string.Empty;
            }
            catch (Exception ex)
            {
                LastError = ex.Message;
            }
        }
    }
    private bool ShouldContinue()
    {
        // Don't wait the first time.
        if (_first)
        {
            _first = false;
            return true;
        }

        // Wake up every 10 seconds, or when the thread should stop.
        return !_stop.WaitOne(10000);
    }
}

Notice the LastError property. This is a thread-safe way for the service to communicate its status with the user. This property is bound to a TextBlock on the UI.

The iTunes synchronization service makes changes directly to the data model. Because the data model is thread-safe, the UI thread and the background thread can share this resource. Update Controls will notify the UI thread whenever the background thread changes the data model.

Try it out
Download the code. When you run Commuter, it will launch iTunes. Even as iTunes launches, the Commuter user interface is responsive. If you are fast enough, you can open the Playlist drop-down before it is populated. But even if COM is faster than the mouse, you can try a few experiments.

Select a playlist in Commuter. Then switch to iTunes and rename that playlist. Within 10 seconds you'll see the selected playlist's name change.

Bring iTunes to the front, but leave Commuter visible underneath it. Place your mouse on the drop-down. Press Ctrl+N to create a new playlist, then click the mouse to bring Commuter to the front and open the drop-down. Within 10 seconds, "untitled playlist" will appear in the list.

Multi-threaded programming still requires some care, but Update Controls can at least take care of updating the UI when the background thread changes the data model.

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.

A View Model based approach to star ratings

Thursday, May 28th, 2009

While researching for Commuter, I found several implementations of star-rating controls on the web. Here are a few:

There's some good work here, so give them a read. However, a common theme was to control the stars with code. I wanted to do a purely View Model driven design to limit the amount of code necessary.

The View Model is dependent behavior
Commuter View The first thing to realize is that a star rating control is a combination of independent and dependent behavior. While the rating itself is independent, the state of each star is dependent. The independent data lives in the model, and the dependent data lives in the View Model. So I changed the podcast View Model contract to this:

public interface IPodcast
{
    string Name { get; }
    bool Selected { get; set; }
    bool Rank1 { get; set; }
    bool Rank2 { get; set; }
    bool Rank3 { get; set; }
    bool Rank4 { get; set; }
    bool Rank5 { get; set; }
}

This lets me just drop five ordinary check boxes and bind them to the five properties. Well, not ordinary check boxes. Mine look like cars. Here's a step-by-step guide for styling a control. Use the same technique to create star checkboxes, if you are so inclined.

Implement the View Model contract
A star -- er a car -- is on if the rating is greater than or equal to its value. When a car is clicked, it sets the rank to its value. This logic is encoded in the View Model like this:

public class PodcastViewModel : IPodcast
{
    private Podcast _model;

    public PodcastViewModel(Podcast model)
    {
        _model = model;
    }

    public string Name
    {
        get { return _model.Name; }
        set { _model.Name = value; }
    }

    public bool Selected
    {
        get { return _model.Selected; }
        set { _model.Selected = value; }
    }

    public bool Rank1
    {
        get { return _model.Rank >= 1; }
        set { _model.Rank = 1; }
    }
    public bool Rank2
    {
        get { return _model.Rank >= 2; }
        set { _model.Rank = 2; }
    }
    public bool Rank3
    {
        get { return _model.Rank >= 3; }
        set { _model.Rank = 3; }
    }
    public bool Rank4
    {
        get { return _model.Rank >= 4; }
        set { _model.Rank = 4; }
    }
    public bool Rank5
    {
        get { return _model.Rank >= 5; }
        set { _model.Rank = 5; }
    }
}

The View Model does not maintain any state of its own. It delegates everything to the model. It is just a translator that converts model values into view behaviors.

Implement INotifyPropertyChanged
Oh, wait! You don't need to do that. Just use Independent sentries in your data model:

public class Podcast
{
    private string _name = string.Empty;
    private bool _selected = false;
    private int _rank = 1;

    #region Independent properties
    // Generated by Update Controls --------------------------------
    private Independent _indName = new Independent();
    private Independent _indSelected = new Independent();
    private Independent _indRank = new Independent();

    public string Name
    {
        get { _indName.OnGet(); return _name; }
        set { _indName.OnSet(); _name = value; }
    }

    public bool Selected
    {
        get { _indSelected.OnGet(); return _selected; }
        set { _indSelected.OnSet(); _selected = value; }
    }

    public int Rank
    {
        get { _indRank.OnGet(); return _rank; }
        set { _indRank.OnSet(); _rank = value; }
    }
    // End generated code --------------------------------
    #endregion
}

And then wrap the view model for the view (requires Update Controls release 2.0.4.0 or better):

public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();
        DataContext = ForView.Wrap(new CommuterViewModel(new CommuterModel()));
    }
}

Now when you click on any of the cars, it will set the Rank property in the data model. Since all of the check boxes depend upon that one Rank, they are all updated accordingly.

How to draw a car in Expression Blend

Sunday, May 24th, 2009

For the Commuter application, I want to rate podcasts. But instead of giving them a star rating, I decided to give them a "car" rating.

My inspiration I drive a blue Beetle, so naturally I decided that that would make the perfect template for my rating buttons. Here's how to draw a Beetle in Expression Blend.

Start with two circles for tires, and two circles for wheel wells. Choose the ellipse tool, and hold Shift while you drag from one corner to the opposite. Draw a small circle inside a big circle, then select both, hold Ctrl, and drag to create copies.

Wheels and wheel wells

Next, draw an ellipse for the body. Don't hold Shift while you drag, so the shape is unconstrained. The ellipse goes through the center of the front wheel, and is tangent with the back wheel. Switch to the rectangle tool to add a rounded rectangle for the bottom of the car.

Body and trim

Now we're going to use "Combine" operations to trim the shapes. When you combine two shapes, the originals are lost. We want to combine the rounded rectangle with several shapes, so we need to make copies of it.

The first operation will be to cut one wheel well with the rounded rectangle. Make a copy of the rectangle, select both it and one wheel well, and select "Combine: Intersect" from the "Object" menu. Repeat for the other wheel well, and for the body.

Trim all parts

We are done with the rounded rectangle, so we can delete the original. Next, select the two wheel wells and the body and "Combine: Unite" them. There will be a strange dip where the front wheel well meets the windshield. We'll straighten this out by adding a rectangle.

Flatten the dip 

Unite the rectangle with the outline of the car to complete the picture.

Finished path

Now we can use this path as the basis for rating buttons.

The Commuter View Model

Monday, May 11th, 2009

I'm working on an application to automatically manage an iTunes playlist. I've done the proof-of-concept and created a historic model. Now I want to describe the functionality of the user interface. I'll do this by drawing a UI mockup, and by defining an interface for the View Model.

The View Model is the name typically used in WPF for Martin Fowler's Presentation Model pattern. It is a class designed specifically for a view. It presents all of the information visible on the view, it accepts all of the input that the view gathers, and it provides all of the commands that the view exposes.

I like to think of a UI as merely a convenience. If the user had a compiler instead of a UI, he could still use your application. The View Model is the user's API to your program.

The View Model
An API is best defined as an interface. Here's the API that we are presenting to the user:

public interface IPlaylist
{
    string Name { get; }
}

public interface IPodcast
{
    string Name { get; }
    bool Selected { get; set; }
    int Rank { get; set; }
}

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

public interface ICommuterViewModel
{
    IEnumerable<IPlaylist> Playlists { get; }
    IPlaylist SelectedPlaylist { get; set; }

    IEnumerable<IPodcast> Podcasts { get; }

    IEnumerable<IPodcastEpisode> Queue { get; }
    IPodcastEpisode SelectedEpisode { get; set; }
    string TotalDurationAsString { get; }

    ICommand Skip { get; }
}

This API lets the user see all of their playlists, and select one. It lets them see all of their podcasts, and individually select and rank them. Then it shows their queue, lets them select an episode, and skip it.

The mockup
For the user's convenience, we will also provide this view (my code is much neater than my drawing):

Commuter Mockup

You can see how each of the View Model properties maps to the view. With this interface, mockup, and Blend, a designer can create a nice convenient UI for our user. Just so they don't have to get their hands dirty with a compiler. That's our next step.

Modeling an iTunes automation application

Saturday, May 9th, 2009

I listen to podcasts on the way to work. I've created a playlist called "Commute" that I use to make this hands-free. Here's my workflow:

  • Dock the iPod before bed, which launches iTunes and updates play counts.
  • Delete all episodes from the Commute playlist with a play count of 1.
  • iTunes (usually) downloads the latest episodes overnight. If not, I kick it in the morning.
  • Drag new episodes into the Commute playlist.

In the car, I just start the playlist and it takes me to work and back.

It has occurred to me on more than one occasion that this process could be automated. I finally decided to take on the task. After a quick POC with the iTunes COM interface, I decided that it was possible.

Commuter
The application, currently named "Commuter", will automatically queue selected podcasts in a specified playlist. Here's what a user can do:

  • See playlists and podcast subscriptions managed in iTunes.
  • Select a playlist into which to queue the episodes.
  • Select podcast subscriptions to use.
  • Rank subscriptions to influence the order in which they are queued.

From this information, the program manages the queue within the selected playlist.

The historic model
I prefer to use an analysis technique that I call Historic Modeling. This technique captures more information than data modeling alone. It includes all of the changes that can be made to the system, and the relationships among those changes. Here is the model I came up with:

commuter

Each bubble represents a fact. A fact is an entity that was created, a property that was changed, or an operation that was performed. The arrows between facts indicate that one has to occur before the other. The successor fact has knowledge of its predecessor.

A Commuter, for example, has knowledge of a MusicLibrary, and cannot be created before the MusicLibrary exists. A SelectedPlaylist records the fact that a Commuter has selected a Playlist. It also records the Commuter's prior selection, if any.

Facts are immutable. A Commuter can select a new Playlist, but the fact that he had once selected a particular Playlist will always be true.

Some of these facts are operations that the user performs, some of them represent iTunes synchronization, and some represent decisions of the program itself. All of these facts are interrelated. User actions are made with knowledge of iTunes objects, and program decisions are made because of user actions.

The next steps will be to mock up the UI and define a view model.