RSS 2.0
# Monday, August 18, 2008
HowManyOfMe.com
Logo There are
1
or fewer people with my name in the U.S.A.

How many have your name?

Monday, August 18, 2008 12:15:25 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0] -
Misc
# Thursday, July 17, 2008

In the several months I've been procrastinating on the media player remote project, the industry has continued to advance. I just ran across Mirage, a remote control application that appears to target the exact problem I am trying to solve with my little application. But, I'm too cheap to shell out $200 for something I know I can write myself. Let's see if this adds any motivation to the development effort.

Thursday, July 17, 2008 7:00:16 AM (Pacific Standard Time, UTC-08:00)  #    Comments [0] -

# Wednesday, July 09, 2008

Update - The source for this article is available at http://www.cavinconsulting.com/Code/MediaPlayerRemote7a.zip.

The current version of the Smart Client Software Factory comes with a clever WCF service proxy recipe called the Disconnected Service Agent recipe. This recipe is a much nicer version of the webservice proxy shipped with the prior version of the toolkit. One of the nice parts of this recipe is the automatic online/offline detection capabilities. At its root, the recipe uses a RequestManager to queue up and dispatch webservice requests. Attached to the RequestManager is an IConnectionMonitor. By default, this connection monitor automatically keeps track of a client's connection status.

Unfortunately, this only determines if a client has a connection to the Internet, not whether the client can actually call the webservice of interest. What we want is to make sure the client is really "Online" with respect to the webservice it is calling. There are a couple of ways we can do this. The toolkit allows us to plug in our own service that implements IConnecitionMonitor. We could implement IConnectionMonitor in our own custom service and add an attribute to the ConnectionMonitor tag in the app.config file that would make the framework spin up our own, custom definition of what it means to be "online". That's probably the right way to do things, but I decided to implement a WebserviceMonitor service that simply polls the desired webservice to make sure it is available.

I added IConnectionMonitor.cs to Infrastructure.Interface/Services as an interface with a single, read only property -- IsConnected, and a single event -- ConnectionStatusChanged. Then, I added ConnectionMonitor.cs to Infrastructure.Module/Services as a class that implements both IConnectionMonitor and IBuilderAware:

public class WebserviceMonitor : IWebserviceMonitor, IBuilderAware
{ ... }

The constructor of the class uses the existing IConnectionMonitor as a service dependency:

private IConnectionMonitor _connectionMonitor;
public WebserviceMonitor([ServiceDependency] IConnectionMonitor connectionMonitor)
{
    _connectionMonitor = connectionMonitor;
    _connectionMonitor.ConnectionStatusChanged += new EventHandler(connectionMonitor_ConnectionStatusChanged);
}

The IConnectionMonitor manages whether the machine is online or offline. So, we need to manage the other half (whether we can get to the webservice). So, we implement a member method to answer the question:

private void UpdateConnectedStatus()
{
    bool wasConnected = _connected;
    _connected = _connectionMonitor.IsConnected && WebserviceAvailable();
    if (wasConnected != _connected)
        OnConnectionStatusChanged(_connected);
}

With that, we can call UpdateConnectedStatus on creation as well as any time ConnectionStatusChanged is fired by the connection manager. When we sense a connection change, we fire our own ConnectionStatusChanged event using the built-in event mechanism:

[EventPublication(EventTopicNames.OnlineOfflineUpdate,PublicationScope.Global)]
public event EventHandler<EventArgs<bool>> ConnectionStatusChanged;

But this doesn't solve the problem completely. Why not? The webservice status is only updated in the event of a connection status change as fired by the IConnectionMonitor. So, how do we make sure the webservice status is updated periodically, even if the connection status doesn't change? One solution is to use a thread timer in the service to make periodic calls to the webservice. We can implement this by taking advantage of our IBuilderAware methods:

public void OnBuiltUp(string id)
{
    _connectionCheckTimer = new System.Threading.Timer(new System.Threading.TimerCallback(Tick), null, 0, PingDelay);  
}

public void OnTearingDown()
{
    if (null != _connectionCheckTimer)
    {
        _connectionCheckTimer.Dispose();
        _connectionCheckTimer = null;
    }
}

There are the basic steps for producing a webservice monitor within the SCSF. I'm sure there are better ways of solving the problem, but this one works pretty well for my needs. Of course, subscribing to the service is as easy as getting a reference to the service or subscribing to the global event:

[EventSubscription(EventTopicNames.OnlineOfflineUpdate, ThreadOption.UserInterface)]
public void OnlineOfflineUpdateHandler(object sender, EventArgs<bool> e)
{
    _onlineOfflineLabel.Text = e.Data ? "Online" : "Offline";
}

Hope this helps.

Wednesday, July 09, 2008 3:44:04 AM (Pacific Standard Time, UTC-08:00)  #    Comments [4] -
Windows Media Player
# Tuesday, June 03, 2008

After using version 1.5 in the real world, I've realized it's time to make the solution more robust, with higher performance. The 1.5 implementation is so bad, in fact, that I'm not going to offer the source code as promised -- it just isn't release (even alpha) quality. As implemented, if the server goes down or there is a momentary network outage, I have to restart the client (the WCF channel faults and no longer functions). What I really need is a way to make the communication channel more fault tolerant. I can approach this several ways, but by drawing on the toolkits I know best, I have decided to use the Smart Client Software Factory as a base implementation framework for the product. This is probably overkill for what I'm doing, but I really like the automatic offline/online detection and the modular nature of the services layers.

Since we already have something resembling a UI, the next thing is to take a serious look at the business / data access layers. They're pretty co-mingled right now, and experts in the field tell me that isn't a good idea. For the Data Access layer, I see two different approaches: 1) Direct webservice access (like we've done in our previous examples) using the Disconnected Service Agent block provided by the SCSF, or 2) employing the services of 1) for real-time information (now playing) and handling the large volume of media library information via a local, cached data store, regularly updated via ADO.NET synchronization services.

Of course, 2) is my favorite, but we'll start with 1) and move to 2). The nice bit about the SCSF in this case is that changing the data access layer at a later time should have minimal impact on either the business layer or the UI. So long as the data access layer continues to implement the same service interface, the actual implementation shouldn't matter.

So, here goes. We'll start by downloading and installing the most recent version of SCSF, Guidance Automation Extensions (GAX), and Enterprise Library for VS2008.

With that out of the way, we start with a brand new smart client project with WPF UI components enabled. By default, if you want to enable WPF UI components, it creates a WinForms application that allows WPF parts to be hosted. I'm not sure I like this hybrid solution, and may look into using the techniques in the SCSFContrib CodePlex project to get a truly WPF application at the end of the day.

Here's the plan of attack:

  1. Implement the Data Access layer as an online/offline fault tolerant service.
  2. Implement some form of business layer using a yet-to-be-determined approach.
  3. Drop in the existing front-row clone as a UI layer for the time being.
  4. Implement a client-side cache for the media library (seldom changing) data.
  5. Replace the UI with a purely WPF implementation, possibly using the SCSFContrib libraries.

Sounds simple. Rather than walking through the entire solution, I'll post the areas where I have problems and (possibly) some solutions to these problems.

Tuesday, June 03, 2008 8:50:56 AM (Pacific Standard Time, UTC-08:00)  #    Comments [0] -
Windows Media Player
# Saturday, January 26, 2008

In segment 3 4 here of this series, I discovered a distinct problem with Visual Studio's treatment of WCF Library projects. Specifically, any WCF Library project in Visual Studio is automatically hosted in a test harness during a debug session -- even if you have explicitly told visual studio not to start that particular project or deleted any reference to WcfSvcHost.exe in any of the project files!

This caused problems when I was explicitly hosting my service in an outside application (like my WinForm app). If I had the service, service host, and client application in the same VS solution (and who wouldn't?), I couldn't use the same configuration information in the WCF host library as the WinForms host. If I did, one of two would fail to initialize, and it was always my hosting application that started up after the WcfSvcHost.exe application that failed. But, changing port numbers between the projects meant that I couldn't use the automatic "Update Service Reference" tool within Visual Studio without manually changing port numbers before I updated. Convincing VS that this wasn't a WCF Library (by removing the project type guid) meant that the automatic update tool wasn't even an option.

The solution was a side effect of my attempt to re-architect my application for re-use. The "Update Service Reference" tool uses the App.Config file within the project where the service reference lives. The applications use the App.Config within the project where the applications live. So, the simple solution is to extract the service references into a different .dll from the client application like this:

Project WCF Host Application WCF Library Service Access .dll Client Application
App.Config Port 3132 Port 3131 Port 3131 Port 3132

If you do that, then you can change the port numbers in the host application and client application projects without disturbing the port numbers in the WCF Library or the Service Access library. So, the "Update Service Reference" tool continues to work. The WCF service is still spun up twice on a debug session, but only once on the ports listed in the host application's app.config.

Saturday, January 26, 2008 3:46:31 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0] -
Windows Media Player
# Friday, January 25, 2008

This one may seem a little rudimentary, but it caused me to think a little about the right pattern for handling background worker threads. That is, in .NET 2.0, I could take advantage of the BackgroundWorker control. This control did a nice job of spawning a thread to do some work and alerting the application (on the UI thread) when it was complete. It also got Disposed when the containing control got disposed, alleviating the need for any end of life thread synchronization. In WPF, however, this control doesn't exist, but a class called BackgroundWorker does.

My usual approach to something like this is to create a class level ManualResetEvent and then initialize the BackgroundWorker in the constructor with something like this:

_backgroundWorker = new BackgroundWorker();
_threadStopEvent = new ManualResetEvent(false);
_backgroundWorker.DoWork += new DoWorkEventHandler(GetNowPlaying);
_backgroundWorker.WorkerSupportsCancellation = true;
_backgroundWorker.RunWorkerAsync();

Then, have the periodic worker do a timed wait on the ManualResetEvent. When the wait times out, perform the action, and when the wait is triggered, exit the loop (ending the worker thread). This usually looks something like this:

private void GetNowPlaying()
{
    while (!_threadStopEvent.WaitOne(500,false))
    {
        // Do some work
    }
}

In the normal case, I tell the thread to stop when the owning object's dispose method is called like this:

protected void Dispose(bool disposing)
{
    if (disposing)
    {
        if (!_disposed)
        {
            _threadStopEvent.Set();
            _disposed = true;
        }
    }
}

This didn't seem to work in our case. Another alternative was to call the BackgroundWorkder's CancelAsync() method. That didn't work either. The reason? Our Factory has a static NowPlaying object. So, when should we dispose our NowPlaying object? There are many instances of the Factory class created. We don't want each instance to have its own copy of the media library, so we share it across all instances. But, how do we know when to dispose of it?

It turns out, it doesn't really matter. The BackgroundWorker class does a pretty good job of cleaning up after itself. So, we can just get rid of the Dispose altogether. The BackgroundWorker takes care of terminating all of our background threads automatically. Best Practice? I'm not sure. It seems like we're not doing a very nice job of disposing of our resources, but it's pretty slick.

Friday, January 25, 2008 3:46:01 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0] -
Windows Media Player
# Thursday, January 24, 2008

While producing my new, pretty WMP remote control, I wanted to have clever navigation animations similar to those found in FrontRow. The example I used (/backRow) accomplished this in a pretty ugly way.  All of the controls were set up off-screen and flown on-screen according to some animation trigger. This worked, but it made it impossible to edit the contents of the pages with Expression Blend or the Visual Studio editor. So, I decided to use page navigation instead.

The trick was to figure out how to change the look and feel of the page navigation. By default, windows just appear over the old windows with no magical transitional WPF-ness. In comes the AnimationBehaviors Project on CodePlex. This clever little project allows me to control how a window is loaded and unloaded. So, in our case, we can define a page, the contents of which are an AnimationBehaviorHost, and set the AnimationBehaviorHost.LoadedBehavior to LoadedBehavior.SlideInFromRight.

That worked great, except that everything slide in from the right even if we were navigating backwards! It just didn't look right. What would be great is if the LoadedBehavior dynamically changed based on the direction of navigation. Sounds like a great used of Binding.

The first thing to do was to create a place to store the current animation behavior:

public class NavigationAnimation : INotifyPropertyChanged
{
    // Singleton class
    private NavigationAnimation() { }
    public enum NavigationDirection
    {
        Forward,
        Backward
    };
    private static NavigationAnimation _instance = new NavigationAnimation();
    public static NavigationAnimation Instance
    {
        get { return NavigationAnimation._instance; }
        set { NavigationAnimation._instance = value; }
    }
    public static void SetNavigationDirection(NavigationDirection direction)
    {
        if(NavigationDirection.Forward==direction)
        {
            Instance.AnimationBehavior = LoadedBehavior.SlideInFromRight;
        }
        else
        {
            Instance.AnimationBehavior = LoadedBehavior.SlideInFromLeft;
        }
    }
    private LoadedBehavior _animationBehavior = LoadedBehavior.SlideInFromRight;
    public LoadedBehavior AnimationBehavior
    {
        get { return _animationBehavior; }
        set {
            _animationBehavior = value;
            OnPropertyChanged("AnimationBehavior");
        }
    }
    private void OnPropertyChanged(string propertyName)
    {
        if (null != PropertyChanged)
        {
            PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);
            PropertyChanged(this,e);
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;
}

Next, we need to change the animation behavior based on our navigation state so we add an event handler to the NavigationFrame's Navigating event that looks something like this:

private void OnNavigating(object sender, NavigatingCancelEventArgs e)
{
    if (e.NavigationMode == NavigationMode.Back)
    {
        NavigationAnimation.SetNavigationDirection(NavigationAnimation.NavigationDirection.Backward);
    }
    else
    {
        NavigationAnimation.SetNavigationDirection(NavigationAnimation.NavigationDirection.Forward);
    }
}

Finally, we bind our AnimationBehaviorHost.LoadedBehavior to our current animation behavior.

private void OnNavigating(object sender, NavigatingCancelEventArgs e)
{
    if (e.NavigationMode == NavigationMode.Back)
    {
        NavigationAnimation.SetNavigationDirection(NavigationAnimation.NavigationDirection.Backward);
    }
    else
    {
        NavigationAnimation.SetNavigationDirection(NavigationAnimation.NavigationDirection.Forward);
    }
}

That's it! Navigating forward slides the windows in from the right, while navigating backward slides the windows in from the left. I may finally be getting the hang of this stuff!

Thursday, January 24, 2008 3:45:17 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0] -
Windows Media Player
# Wednesday, January 23, 2008

Once I figured out how to update data-bound collections from a background thread, things progressed rather smoothly. But, when it was time to implement the "Now Playing" pane, I wasn't working with a collection of items anymore. No worry, just implement INotifyPropertyChanged the same way we did INotifyCollectionChanged (with the SynchronizationContext "hack") and all was well. Or so I thought.

The NowPlayingService exposes the album art for the media item currently being played as a System.Bitmap. I chose that because it appeared to navigate nicely across WCF services. While that is true, they don't bind very nicely to any property of a WPF Image control. As far as I can tell, there are a few options to solve this problem: implement a System.Bitmap to System.Windows.Media.Imaging.BitmapSource TypeConverter. dig through the documented converter classes to see if there might actually be one implemented for you (still possible, but I couldn't find one), or just do the conversion in the business object and expose the image property as the appropriate System.Windows.Media.Imaging.BitmapSource.

I decided the last option was easiest. So, I exposed a BitmapSource property called AlbumArt and assigned it from the System.Bitmap with something like this:

private System.Windows.Media.Imaging.BitmapSource FromBitmap(System.Drawing.Bitmap bitmap)
{
    return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
        bitmap.GetHbitmap(), 
        IntPtr.Zero, 
        System.Windows.Int32Rect.Empty, 
        System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
}

That worked great, synchronously. When I started updating it in the background thread (on a periodic timer), I got a "System.Windows.Xaml.XamlParseException: System.Windows.Data.BindingExpression' value cannot be assigned to property 'Source' of object 'System.Windows.Controls.Image'. The calling thread cannot access this object because a different thread owns it.  Error at object 'System.Windows.Data.Binding' in markup file ...". What? I'm marshalling across threads correctly. Everything else works. What could be going on here, and why?

Non sequitur - In a previous life, I was a C++, Win32, COM, and ATL developer. I was also an ESRI ArcObjects (a pretty large COM API for automating ESRI ArcMap) developer. As soon as I started working with .NET, I grabbed a copy of Adam Nathan's excellent book on COM Interop. With that, I sought to understand as much as I could regarding the interoperability between COM and .NET for I wanted to take advantage of the .NET UI from within my COM business model.

The inclusion of things like .Interop. in the namespace and GetHbitmap() in the arguments immediately led me to the potential that the returned BitmapSource reference was a simple wrapper around the HBITMAP in the System.Bitmap reference. By the fact that the UI is complaining about accessing an object in a different thread, I surmise that the HBITMAP of the System.Bitmap is in Thread Local Storage rather than global memory. So, I altered the conversion process to something like this:

private System.Windows.Media.Imaging.BitmapSource BitmapSourceFromImage(System.Drawing.Image img)
{
    System.IO.MemoryStream memStream = new System.IO.MemoryStream();
    img.Save(memStream, System.Drawing.Imaging.ImageFormat.Png);
    System.Windows.Media.Imaging.PngBitmapDecoder decoder = 
            new System.Windows.Media.Imaging.PngBitmapDecoder(
            memStream, 
            System.Windows.Media.Imaging.BitmapCreateOptions.PreservePixelFormat, 
            System.Windows.Media.Imaging.BitmapCacheOption.Default);
    return decoder.Frames[0];
}

With the business object exposing a copy of the bitmap, all is well. However, I am finding myself, once again, questioning whether it should be the job of the business object to know whether it's being used from a different thread. I guess it's a question of determining who is responsible for ensuring thread safety. Ultimately, it was the business object's fault for exposing a wrapper around thread local storage rather than heap storage.

Wednesday, January 23, 2008 3:44:40 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0] -
Windows Media Player
# Tuesday, January 22, 2008

This is the eighth in a series, but the numbering is getting confusing, so I'm going to stop counting.

The first step I took in creating my pretty WMP remote control UI was to steal design the pretty UI. Once I had the UI, it was a matter of binding the media library entries to the appropriate WPF elements (a listbox). I used some pretty standard dataset creation / binding logic I found somewhere that makes use of the ObjectDataProvider. To implement this, I created a MediaLibrary and Factory class that looked something like this:

public class MediaLibrary : ObservableCollection<Song>
{
    public MediaLibrary()
    {
        GetSongs();
    }
    private void GetSongs()
    {
        IMediaLibraryService mediaLibrary = new MediaLibraryServiceClient();
        int librarySize = mediaLibrary.GetSongCount();
        int i = 0;
        while (i < (librarySize + _pageSize))
        {
            List<Guid> currentIds = mediaLibrary.GetSongs(i, _pageSize);
            foreach (Guid id in currentIds)
            {
                Song currentItem = mediaLibrary.GetMediaInfo(id);
                if (null != currentItem)
                {
                    this.Add(currentItem);
                }
            }
            i += _pageSize;
        }
    }
}
public class Factory
{
    /// <summary>
    /// 
    /// </summary>
    static Factory()
    {
        Songs = new MediaLibrary();
    }
    public static MediaLibrary Songs { get; private set; }
}

And I bound the contents to the UI using syntax like this:

<Grid>
  <Grid.Resources>
    <ObjectDataProvider x:Key="FactoryDS" ObjectType="{x:Type local:Factory}"/>
    <DataTemplate x:Key="SongTemplate">
      <StackPanel>
        <TextBlock Text="{Binding Title}"/>
      </StackPanel>
    </DataTemplate>
  </Grid.Resources>
  <ListBox x:Name="songListBox" 
           ItemsSource="{Binding Songs, Mode=Default, Source={StaticResource FactoryDS}}" 
           RenderTransformOrigin="0.5,0.5" 
           DisplayMemberPath="Title" />
</Grid>

That worked great -- synchronously. But, as we already found out, a synchronous call to the media library webservice isn't the best option (we'd rather page through the results asynchronously, updating the UI while we go). So, I add a background thread, changing the constructor to something like this:

public MediaLibrary()
{
    System.Threading.Thread thread = 
        new System.Threading.Thread(new System.Threading.ThreadStart(GetSongs));
    thread.Priority = ThreadPriority.BelowNormal;
    thread.Start();
}

Run this, and you get a nasty cross-thread exception: "System.NotSupportedException: This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread." Huh? What does my business layer need to know about threading? I'm just updating a collection. But, not to be intimidated easily, I try other options. How about implementing INotifyCollectionChanged rather than inheriting from ObservableCollection? Same problem. So, Google reveals a nice discussion by ILoggable. He's as frustrated as I am. Really, should my data access layer know whether it's running on the UI thread? I don't think so. Shouldn't the Binding mechanism in WPF assume that I will be updating my business objects in background threads? After all, what's the cost of a 4 core processor nowadays?

I found a bunch of solutions to this problem that relied on explicitly making calls to Application.Current.Dispatcher.BeginInvoke. That seems sleazy to me. One, slightly less sleazy solution I found was to pass in a SynchronizationContext to the MediaLibrary class. This still sounds like the business objects need to know too much about the runtime, but at least SynchronizationContext is in the System.Windows.Threading namespace, so it doesn't completely destroy my hopes of separating UI, business, and data.

The solution I finally came up with (borrowed heavily from here) was a MediaLibrary with these changes:

   public classMediaLibrary : List<Song>, INotifyCollectionChanged
  
{
        private const int _pageSize = 5;
        privateIMediaLibraryService _mediaLibrary = newMediaLibraryServiceClient();
        privateSynchronizationContext _synchronizationContext;

        /// <summary>
        ///
        /// </summary>
       
publicMediaLibrary(SynchronizationContext sync)
        {
            _synchronizationContext = sync;
            System.Threading.Threadthread =
                newSystem.Threading.Thread(newSystem.Threading.ThreadStart(GetSongs));
            thread.Priority = ThreadPriority.BelowNormal;
            thread.Start();
        }
        private voidGetSongs()
        {
            IMediaLibraryService mediaLibrary = newMediaLibraryServiceClient();
            int librarySize = mediaLibrary.GetSongCount();
            int i = 0;
            while(i < (librarySize + _pageSize))
            {
                List<Guid> currentIds = mediaLibrary.GetSongs(i, _pageSize);
                foreach (Guid id incurrentIds)
                {
                    SongcurrentItem = mediaLibrary.GetMediaInfo(id);
                    if(null!= currentItem)
                    {
                        this.Add(currentItem);
                        SafeCollectionChangedNotification(currentItem);
                    }
                }
                i += _pageSize;
            }
        }

        private voidSafeCollectionChangedNotification(SongcurrentItem)
        {
            if(_synchronizationContext != null)
            {
                _synchronizationContext.Post(delegate
              
{
                    OnCollectionChanged(currentItem);
                }, null);
            }
            else
          
{
                OnCollectionChanged(currentItem);
            }
        }

        private voidOnCollectionChanged(SongcurrentItem)
        {
            if(null!= CollectionChanged)
            {
                CollectionChanged(this, newNotifyCollectionChangedEventArgs(
                    NotifyCollectionChangedAction.Add, currentItem));
            }
        }

        #regionINotifyCollectionChanged Members

        public eventNotifyCollectionChangedEventHandler CollectionChanged;

        #endregion

and passing in the context from the factory:

Songs = new MediaLibrary(
    new System.Windows.Threading.DispatcherSynchronizationContext(
        System.Windows.Application.Current.Dispatcher));

Of course, this means the factory needs to know what context to pass in, but I wasn't convinced the factory needed to be in the service layer anyway. Really, it's just there so WPF has some form of static class to bind to.

I'm still not sure what I think about this approach. While it works, and can even be used within a WinForms application (and probably even in ASP.NET), it seems like we're putting the onus on the event producer rather than the even consumer (who is, after all, the one who knows whether the event should be caught on a particular thread). I guess that is one of the things I like about the event model in the Smart Client Software Factory (SCSF). The producer just raises the even. If the consumer has specific thread constraints on consuming that event, they declare them explicitly. Hmm. Maybe I should start looking at using the SCSF to implement this thing...

Tuesday, January 22, 2008 3:43:45 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0] -
Windows Media Player

This is the seventh in a series.

As of Part 5, we had a functional end to end solution (let's call it version 1.0) that I deployed across my enterprise household. So, what's next? As with any product, we know things don't stop here -- the clients have additional expectations for functionality, the developers find new technology that would be cool to use, and the QA team finds bugs. Now that it is deployed, however, I'll have to pay special attention to version handling within our data and operation contracts.

Before making many changes, I decided it might be nice to have some unit tests to make sure I didn't break anything when rolling out new versions. A few tests later and I was glad I did. It turns out, I didn't write perfect code during my initial creation (shocking, I know). There were a few off by one errors, some stupid copy and paste errors, and a couple of functions that were just plain wrong. So, even though I didn't do test driven design, per say, I did gain the benefits of a module with reasonable unit test coverage.

So far, the biggest complaint observation from the user base has been that the client application sucks. While it's functional, it certainly isn't pretty. Additionally, it isn't the most robust implementation. For example, if you close the server application and start it back up, the client never reconnects. This is because, once a WCF service proxy faults, it stays faulted until you do something about it. So, the next phase will be to write a more robust, prettier version of the desktop client. Let's start with pretty.

The first step in creating a prettier application is, as is standard in our industry, to poach someone else's look and feel. While I certainly make my living within the Microsoft camp, the innovative UI solutions fall solidly within the Macintosh camp. So, let's take a look at Front Row. There's a great WPF implementation based on some of the Front Row functionality in a "tutorial" called /backRow. It's been a while since the author updated the article, and the Macintosh camp could do nothing but complain about the features that were missing. But, I'm no artist, so I figure I'll start with the look and feel as presented in his article but with a slightly different animation implementation. More on that later.

Source for this article will be available as it becomes ready. The next few segments will cover some of the challenges I ran into while developing the newer, prettier UI. In the mean time, here are a couple of screen shots:

MusicLibrary

NowPlaying

Tuesday, January 22, 2008 3:42:11 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0] -
Windows Media Player
Navigation
Categories
About the author/Disclaimer

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2012
Cavin Consulting
Sign In
Statistics
Total Posts: 28
This Year: 0
This Month: 0
This Week: 0
Comments: 4
All Content © 2012, Cavin Consulting
DasBlog theme 'Business' created by Christoph De Baene (delarou)