Tuesday, 30 September 2008

Getting Started With MSBuild

I tried using MSBuild for the first time this week, and took a couple of wrong turnings along the way. Here's a brief summary of how I got started and how to perform a few basic tasks...

What is it?

MSBuild is the build system used by Visual Studio to compile your .NET projects. Check out this nice introduction to MSBuild on CodeProject. The .csproj files you may be familiar with from Visual Studio are in fact nothing more than MSBuild project files.

Why use it?

There are a few reasons why you might want to use MSBuild directly in preference to simply compiling your solution in Visual Studio:

  • You can use it on a dedicated build machine without the need to install Visual Studio.
  • You can use it to batch build several Visual Studio solutions.
  • You can use it to perform additional tasks such as creating installers, archiving, retrieving from source control etc.

It is the third reason that appeals to me, as I wanted to automate the creation of zip files for source code and runtime binaries, as well as run some unit tests.

Creating a project file

MSBuild project files are simply XML files. I created master.proj and put it in the root of my source code folder, alongside my .sln file.

Build Target

The first code I put in to my project file was an example I found on the web that performed a Clean followed by a Build. Here is a slightly modified version of the project file:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" DefaultTargets="Build"  
    xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <DeploymentProject>MyApplication</DeploymentProject>
    <OutputDirectory>$(DeploymentProject)\bin\$(Configuration)</OutputDirectory>
  </PropertyGroup>

  <Target Name="Clean">    
    <RemoveDir Directories="$(OutputDirectory)" 
            Condition="Exists($(OutputDirectory))"></RemoveDir>
  </Target>
  <Target Name="Build">
    <MSBuild 
      Projects="$(DeploymentProject)\MyApplication.csproj"
      Properties="Configuration=$(Configuration)" >      
    </MSBuild>
  </Target>
</Project>

I ran the build script using the following command line: MSBuild master.proj

It deleted all my source code! Aarrghh!! Fortunately I had a recent backup available.

The reason was that I had not put a default value for Configuration in the PropertyGroup, and my OutputDirectory property was missing the bin from its folder path. Note to self: always backup before running any script that can delete files for the first time.

Zip Target

After my unfortunate experience with clean, I was more determined than ever to create a target that would perform a source-code backup for me! To use the Zip task in MSBuild you first need to download and install MSBuild Community Tasks.

Then you create an ItemGroup defining which files you want to be included (and excluded) before using the Zip task to actually create the archive. Here's the two targets I created - one to make a release package, and one to make a source code archive:

<Target Name="Package" DependsOnTargets="Build">
    <ItemGroup>
      <!-- All files from build -->
      <ZipFiles Include="$(DeploymentProject)\bin\$(Configuration)\**\*.*"
         Exclude="**\*.zip;**\*.pdb;**\*.vshost.*" />
    </ItemGroup>
    <Zip Files="@(ZipFiles)"
         WorkingDirectory="$(DeploymentProject)\bin\$(Configuration)\"
         ZipFileName="$(ApplicationName)-$(Configuration)-$(Version).zip"
         Flatten="True" />
    </Target>

    <Target Name="Backup">
    <ItemGroup>
      <!-- All source code -->
      <SourceFiles Include="**\*.*" 
        Exclude="**\bin\**\*.*;**\obj\**\*.*;*.zip" />
    </ItemGroup>
    <Zip Files="@(SourceFiles)"
         WorkingDirectory=""
         ZipFileName="$(ApplicationName)-SourceCode-$(Version).zip" />
</Target>

See Ben Hall's blog for another example.

Test Task

My final task was to get some NUnit tests running. Again the MSBuild Community Tasks are required for this. It took me a while to get this working correctly, particularly because it seemed to have trouble detecting where my copy of NUnit was installed. Here's the XML:

<Target Name="Test" DependsOnTargets="Build">
    <NUnit Assemblies="@(TestAssembly)"
           WorkingDirectory="MyApplication.UnitTests\bin\$(Configuration)"
           ToolPath="C:\\Program Files\\NUnit 2.4.7\\bin"
           />
</Target>

Conclusion

It took me longer than I wanted to work out how to do these basic tasks with MSBuild, but even so, I am sure the time will be very quickly recouped as I will be able to reuse most of these tasks on future projects. The jury is still out on whether it is preferable to use MSBuild to NAnt though.

Friday, 26 September 2008

Don't Repeat Your Threading Code

Often when writing Windows Forms applications I will need to perform a long-running action on a background thread. So I write some code that can kick a delegate off in another thread (probably using the ThreadPool) and then some more code that handles when the action has completed. Then there is the code that handles any exceptions that were raised during that background action. Then there is the code that marshals back to the GUI thread so I can actually update controls on the form. Then I often end up adding a framework for reporting progress, and for aborting the action.

The end result is that the actual code that implements the task at hand has been obscured by a huge pile of threading related lines of code. And very often this code gets replicated every time a new action that needs to run on a background thread is written.

So I decided to see how far I could go with a DRY approach - ("Don't Repeat Yourself"), and decided to create a base BackgroundAction class that would hide a lot of the heavy lifting.

The first step was to create an interface, IBackgroundAction. The key components are the Begin method, that starts the action, and the Finished event, which indicates that the action has finished (successfully or not). I also put in a basic Progress reporting mechanism, as well as a way to request a Cancel of the action.

public interface IBackgroundAction
{
    void Begin();
    event EventHandler<ActionFinishedEventArgs> Finished;
    event EventHandler<ProgressEventArgs> Progress;
    void Cancel();
    ActionState State { get; }
}

public class ActionFinishedEventArgs : EventArgs
{
    public ActionState State { get; set; }
    public Exception Exception { get; set; }
}

public class ProgressEventArgs : EventArgs
{
    public string Message { get; set; }
}

public enum ActionState
{
    None,
    InProgress,
    Success,
    Cancelled,
    Error
}

The next step was to create an abstract base class, BackgroundAction that implements the interface, and does as much of the work as is possible:

abstract class BackgroundAction : IBackgroundAction
{
    protected volatile bool cancel;
    ActionState state = ActionState.None;
    public event EventHandler<ActionFinishedEventArgs> Finished;
    public event EventHandler<ProgressEventArgs> Progress;

    public void Begin()
    {
        if (state != ActionState.None)
        {
            throw new InvalidOperationException("Begin should only be called once");
        }
        state = ActionState.InProgress;
        ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc));
    }

    void ThreadProc(object state)
    {
        try
        {
            PerformBackgroundAction();
            state = cancel ? ActionState.Cancelled : ActionState.Success;
            ReportFinished();
        }
        catch (Exception e)
        {
            state = ActionState.Error;
            if (Finished != null)
            {
                Finished(this, new ActionFinishedEventArgs() { State = State, Exception = e });
            }
        }
    }

    protected abstract void PerformBackgroundAction();

    public void Cancel()
    {
        cancel = true;
    }

    public ActionState State
    {
        get { return state; }
    }

    void ReportFinished()
    {
        if (Finished != null)
        {
            Finished(this, new ActionFinishedEventArgs() { State = state });
        }
    }

    protected void ReportProgress(string message, params object[] args)
    {
        if (Progress != null)
        {
            Progress(this, new ProgressEventArgs() { Message = String.Format(message, args) });
        }
    }
}

This class manages the launching of the background action and the reporting of its completion (whether successful or not). It also includes a ReportProgress function as a helper for the concrete class to use.

Moving all this threading related code into a base class means that whenever we create a new action, the code we write is much cleaner, and only has the additional concerns of cancel checking and reporting progress. Here's an example from the project I was trying this out on. All I had to do was override the PerformBackgroundAction function to do the task at hand...

class DeserializeAllAction : BackgroundAction
{
    IEnumerable<BinarySearchResult> searchResults;

    public DeserializeAllAction(IEnumerable<BinarySearchResult> 
                                searchResults)
    {
        this.searchResults = searchResults;
    }

    protected override void PerformBackgroundAction()
    {
        ReportProgress("Preparing to deserialize");
        int count = 0;
        foreach (BinarySearchResult result in searchResults)
        {
            if (cancel)
            {
                break;
            }
            if (++count % 10 == 0)
            {
                ReportProgress("Deserialized {0}", count);
            }
            result.Deserialize();
        }
    }
}

The next step was to create a Progress Form that could take any IBackgroundAction, and run it, while showing progress updates and allowing the user to cancel. Here's a simple implementation:

public partial class ProgressForm : Form
{
    IBackgroundAction backgroundAction;
    bool finished;
    
    public ProgressForm(string title, IBackgroundAction 
                        backgroundAction)
    {
        this.backgroundAction = backgroundAction;
        backgroundAction.Finished += new 
            EventHandler<ActionFinishedEventArgs>
               (backgroundAction_Finished);
        backgroundAction.Progress += new 
            EventHandler<ProgressEventArgs>
                (backgroundAction_Progress);
        InitializeComponent();
        this.FormClosing += new 
            FormClosingEventHandler(ProgressForm_FormClosing);
        this.labelProgressMessage.Text = String.Empty;
        this.Text = title;
    }

    void ProgressForm_FormClosing(object sender, 
       FormClosingEventArgs e)
    {
        if (!finished)
        {
            backgroundAction.Cancel();
            buttonCancel.Enabled = false;
            e.Cancel = true;
        }
    }

    void backgroundAction_Progress(object sender, 
       ProgressEventArgs e)
    {
        if (this.InvokeRequired)
        {
            this.BeginInvoke(new 
               EventHandler<ProgressEventArgs>
                  (backgroundAction_Progress), sender, e);
            return;
        }
        labelProgressMessage.Text = e.Message;
    }

    void backgroundAction_Finished(object sender, 
        ActionFinishedEventArgs e)
    {
        if (this.InvokeRequired)
        {
            this.BeginInvoke(new  
               EventHandler<ActionFinishedEventArgs>
                  (backgroundAction_Finished), sender, e);
            return;
        }
        if(e.State == ActionState.Error)
        {
            MessageBox.Show(this, e.Exception.Message,
                this.Title,
                MessageBoxButtons.OK, MessageBoxIcon.Warning);
        }
        finished = true;
        this.Close();
    }

    private void ProgressForm_Load(object sender, EventArgs e)
    {
        backgroundAction.Begin();
    }
}

Now the code to create and run my background action becomes trivial:

private void buttonDeserializeAll_Click(object sender, EventArgs e)
{
    DeserializeAllAction deserializer = new DeserializeAllAction(searchResults);
    ProgressForm progressForm = new ProgressForm("Deserializing all results", deserializer);
    progressForm.ShowDialog(this);
}

So I was quite pleased with what I had achieved, but I started wondering whether I could take things one step further. In our PerformBackgroundAction function, we still have to perform periodic checks for cancellation and progress updates. These can't be moved down into the base class because it doesn't know when it can check and what it can report.

But then I considered the fact that most long-running background operations are in fact working their way through some kind of list. If I could insert the progress reporting and cancel checking into the enumerator, the PerformBackgroundAction function could focus entirely on doing the action at hand.

Here's my ProgressEnumerator class that injects progress reporting and cancel checking into any IEnumerable type. It calls back once every time round the loop, allowing a progress message to be emitted (if required), and allowing cancellation to be signalled (by returning true from the callback):

delegate bool ProgressCallback<T>(T value, int count);

class ProgressEnumerator<T>
{
    public static IEnumerable<T> Create(IEnumerable<T> items, ProgressCallback<T> callback)
    {
        ProgressEnumerator<T> enumerator = new ProgressEnumerator<T>(items, callback);
        return enumerator.Items;
    }

    IEnumerable<T> items;
    ProgressCallback<T> callback;
    int count;

    public ProgressEnumerator(IEnumerable<T> items, ProgressCallback<T> callback)
    {
        this.items = items;
        this.callback = callback;
        count = 0;
    }

    public IEnumerable<T> Items
    {
        get
        {
            foreach (T item in items)
            {
                if(callback(item,++count))
                    break;
                yield return item;
            }
        }
    }
}

Now we can revisit our PerformBackgroundAction function and remove the progress reporting and cancellation checking from inside the foreach loop, and moving it out into a separate function.

protected override void PerformBackgroundAction()
{
    ReportProgress("Preparing to deserialize");
    foreach (BinarySearchResult result in 
        ProgressEnumerator<BinarySearchResult>.Create(searchResults,ProgressCallback))
    {
        result.Deserialize();
    }
}

bool ProgressCallback(BinarySearchResult result, int count)
{
    if (count % 10 == 0)
    {
        ReportProgress("Deserialized {0}", count);
    }
    return cancel;
}

Finally I have achieved what I wanted. The GUI code that launches the background action is simple, and the code that performs the background action - the real 'business logic' - is unencumbered with threading related noise.

I'm sure that both my BackgroundAction and ProgressEnumerator classes have lots of ways in which they can be improved or streamlined further. Feel free to critique them in the comments.

Saturday, 12 July 2008

Styling a ListBox with Silverlight 2 Beta 2 (Part 3) - ListBoxItem Style

This is the third and final part of my updated series on how to style a ListBox in Silverlight 2 Beta 2. Here are the links to part 1 and part 2.

So far we have customised the appearance of a ListBox, including the ScrollBars. The one remaining missing piece is some kind of indication of which items are selected, and visual feedback when the mouse hovers over a ListBox item.
To achieve this we need to add some Storyboards to the custom ListBoxItem we created in my first post. There are eight states you can specify Storyboards for. The most important for our purposes are "Normal", "Selected", and "MouseOver". Note that we need to use two Visual State Groups. One will define Normal and MouseOver, while the other will define Selected and Unselected. This gives us the flexibility to combine animations if we require to show combined states such as Selected and MouseOver.

Unfortunately it can also mean conflict of interests. With the beta 1 model, I quite easily specified one colour for Normal, one colour for Selected and one colour for MouseOver. However, with the new model, the MouseOver and Unselected states compete to set the background colour. The solution I eventually came up with was to have two borders and control their opacity (partly inspired by this forum post). One border is used to show MouseOver state, and the other to show Selected state. Here is the updated ListBoxItem style containing the new border elements (without Visual State Groups):

<Style x:Key="ListBoxItemStyle1" TargetType="ListBoxItem"
    xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows">
  <Setter Property="Foreground"
      Value="{StaticResource ListBoxItemForegroundBrush}" />
  <Setter Property="Template">
  <Setter.Value>
  <ControlTemplate TargetType="ListBoxItem">
  <Grid x:Name="Root">
    <vsm:VisualStateManager.VisualStateGroups>
       ...
    </vsm:VisualStateManager.VisualStateGroups>
  
    <Border CornerRadius="5" Margin="1"
      x:Name="HoverBorder">
      <Border.Background>
        <SolidColorBrush
        x:Name="HoverBorderBackgroundBrush"
        Color="#51615B" />
      </Border.Background>
    </Border>
    <Border CornerRadius="5" Margin="1"
      x:Name="SelectedBorder">
      <Border.Background>
        <SolidColorBrush
        x:Name="SelectedBorderBackgroundBrush"
        Opacity="0"
        Color="#397F8F" />
      </Border.Background>
    </Border>
    <ContentPresenter
        Margin="4,1"                  
        Content="{TemplateBinding Content}"
        Foreground="{TemplateBinding Foreground}" />
    </Grid>                      
    </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

I have chosen to prioritise the Selected background above the MouseOver background, but you can easily change that by swapping the order of the borders.

The VisualStateGroups specify the two ColorAnimations I need - one for Selected and one for MouseOver. I also needed to add a DoubleAnimation for the opacity of the SelectedBorder.

<vsm:VisualStateManager.VisualStateGroups>

<vsm:VisualStateGroup x:Name="SelectionStates">
<vsm:VisualState x:Name="Unselected" />
<vsm:VisualState x:Name="Selected">
  <Storyboard>
    <DoubleAnimation
     Storyboard.TargetName="SelectedBorderBackgroundBrush"
     Storyboard.TargetProperty="Opacity"
     Duration="0"
     To="1.0"/>
   <ColorAnimation
     Storyboard.TargetName="SelectedBorderBackgroundBrush"
     Storyboard.TargetProperty="Color"
     Duration="0"
     To="#FF397F8F"/>
  </Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>

<vsm:VisualStateGroup x:Name="CommonStates">
<vsm:VisualState x:Name="Normal" />
<vsm:VisualState x:Name="MouseOver">
  <Storyboard>
    <ColorAnimation
      Storyboard.TargetName="HoverBorderBackgroundBrush"
      Storyboard.TargetProperty="Color"
      Duration="0"
      To="#898A8A"/>
  </Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>

Here's what it looks like (item 3 selected, item 5 mouse over):

Silverlight 2 ListBox Style with Mouse Over and Selected Item

OK, so my choice of colours is awful and there are lots of visual elements that could be improved (and we still await a fix for the vertical scrollbar bug), but hopefully I have demonstrated how it is possible to completely change every visual element of how your ListBox is rendered in Silverlight 2 beta 2.
I have uploaded my test project here which you can load in Expression Blend 2.5 or Visual Studio 2008.

Thursday, 26 June 2008

NAudio 1.2 Release Notes

I have released a new version of NAudio today on CodePlex. NAudio is an open source audio toolkit for .NET. It has been over a year since our last release, so let me run through a few of the highlights for this release.

WASAPI Output Model. We are now able to play audio using the new WASAPI output APIs in Windows Vista. We support shared mode and exclusive mode, and you can optionally use event callbacks for the buffer population. You may need to experiment to see what settings work best with your soundcard.

ASIO Output Model. We can also play back audio using any ASIO output drivers on your system. It is not working yet with all soundcards, but its working great with the ever-popular ASIO4All.

New DirectSound Output Model We have moved away from using the old managed DirectX code for DirectSound output, and done the interop ourselves. This gives us a much more reliable way to use DirectSound.

IWavePlayer simplifications. As part of our ongoing plans to improve the NAudio architecture, the IWavePlayer interface has gone on a diet and lost some unnecessary methods.

ResamplerDMO stream. Some Windows Vista systems have a Resampler DirectX Media Object that can be used to convert PCM and IEEE audio samples between different sample rates and bit depths. We have provided a managed wrapper around this, and it is used internally by the WASAPI output stream to do sample rate conversion if required.

ACM Enhancements - There have been a number of bugfixes and enhancements to the support for using the ACM codecs in your system.

BlockAlignmentReductionStream - This WaveStream helps to alleviate the problem of dealing with compressed audio streams whose block alignment means that you can't position exactly where you want or read the amount you want. BlockAlignmentReductionStream uses buffering and read-ahead to allow readers full flexibility over positioning and read size.

MP3 Playback - The MP3 File Reader Stream is now able to work with any wave output thanks to the BlockAlignmentReductionStream and playback MP3 files without stuttering. It uses any MP3 ACM decoder it can find on your system.

Custom WaveFormat Marshaler - The WaveFormat structure presents an awkward problem for interop with unmanaged code. A custom marshaler has been created which will be extended in future versions to allow WaveFormat structures to present their extra data.

NAudioDemo - One of the problems with NAudio has been that there are very few examples of how to use it. NAudioDemo has four mini-examples of using NAudio:

  • receiving MIDI input
  • playing WAV or MP3 files through any output
  • examining ACM codecs and converting files using them
  • recording audio using WaveIn

In addition the AudioFileInspector, MixDiff, MIDI File Splitter and MIDI File Mapper projects demonstrate other aspects of the NAudio framework.

Unit Tests - NAudio now has a small collection of unit tests, which we intend to grow in future versions. This will help us to ensure that as the feature set grows, we don't inadvertently break old code.

IWaveProvider Tech Preview - As discussed recently on my blog, we will be using a new interface called IWaveProvider in future versions of NAudio, which uses the WaveBuffer class. This code is available in the version 1.2 release, but you are not currently required to use it.

Alexandre Mutel - Finally, this version welcomes a new contributor to the team. In fact, Alexandre is the first contributor I have added to this project. He has provided the new implementations of ASIO and DirectSoundOut, as well as helping out with WASAPI and the new IWaveProvider interface design. His enthusiasm for the project has also meant that I have been working on it a little more than I might have otherwise!

It's A Beta - I do need to remind you that all these features should be thought of as being in "beta" state. We do not have the resources to exhaustively test with all the different soundcards that are available. Please report any bugs you encounter via the issue tracker on CodePlex.

What's Coming Up - NAudio tends to progress by adding features on an as-needed basis. The good news is that we have lots of ideas for NAudio's future, and we are hoping that version 1.3 will offer several features from the list below:

  • More extensive use of the new IWaveProvider interface
  • Audio Capture for WASAPI
  • Audio Capture for ASIO
  • NAudioDemo to be updated to demonstrate more features
  • Improving error handling and robustness of WASAPI, ASIO and DirectSound outputs
  • ... plus lots more goodies that we will announce later!

Anyway, just in case you haven't already worked out where to download it, here's the link: NAudio 1.2

Sunday, 22 June 2008

WaveBuffer - Casting Byte Arrays to Float Arrays

A while ago on my blog I wrote about a C# language feature that I wanted - reinterpret casts between arrays of structs. One reason this would be so useful to me is that I want to improve the design of my open source audio library NAudio, and create an IWaveProvider interface that allows people to output their audio data in whatever format is most convenient for them. Audio is sometimes in 16 bit integer format, sometimes in 32 bit floating point format, and sometimes in compressed blocks of bytes (other common scenarios include 24 bit integers and 64 bit double precision floating point audio).

In the world of C/C++, this isn't a problem. The Read method of IWaveProvider simply needs to take a void pointer that can be cast to a byte, short or float pointer as appropriate. In .NET things are not nearly so simple. True, there are 'unsafe' pointers in C#, but using them immediately excludes developers from other .NET languages such as VB.NET from using the framework. Also, when reading or writing data from files in .NET, you must work with the System.IO.Stream class that expects reads and writes to provide byte arrays, requiring a manual copy from the pointer to an array.

My initial idea was to simply provide a variety of Read functions for IWaveProvider, and provide helper functions in an abstract base class that would allow users just to implement the one Read method whose signature best fitted their needs:

interface IWaveProvider 
{
   int Read(byte[] buffer, int byteCount);
   int Read(short[] buffer, int sampleCount);
   int Read(float[] buffer, int sampleCount);
   ...

However, a new contributor to the NAudio project, Alexandre Mutel, has come up with an ingenious solution thanks to a brilliant piece of lateral thinking. Suppose we define a WaveBuffer class that uses an explicit structure layout:

[StructLayout(LayoutKind.Explicit, Pack=2)]
public class WaveBuffer : IWaveBuffer
{
   [FieldOffset(0)]
   public int numberOfBytes;
   [FieldOffset(4)]
   private byte[] byteBuffer;
   [FieldOffset(4)]
   private float[] floatBuffer;
   [FieldOffset(4)]
   private short[] shortBuffer;
   ...

This class has some interesting capabilities. You can set byteBuffer to point to a new byte array, but then access it using floatBuffer. Sounds dangerous? Well it compiles, and initial tests show that it works just fine. It is true that using the floatBuffer accessor will let you write beyond the end of available space, but so long as you never write more than the requested number of samples to the buffer, you are safe. This structure even survives garbage collections without any issues.

This allows us to simplify our IWaveProvider interface dramatically:

interface IWaveProvider
{
   int Read(IWaveBuffer buffer);
   ...

Implementers of the Read method then have a choice of which buffer they write into. If they simply want to write samples (whether 16 bit integers or 32 bit floats) that is fine, but equally if it is easier to provide their data as a byte array (for example when reading from a WAV file), then that can be done. The WaveBuffer trick effectively gives us the casting feature we need.

Sounds too good to be true? Well there are some potential concerns. This approach could be described as a bit of a "hack". Do we know for sure that in a future version of the .NET framework it will still work (or even compile)? Does it work with 64 bit Windows? Could there be a garbage collection scenario we have not yet encountered that would cause us pr oblems? Would people object to using a hack like this right at the core of the NAudio framework?

The solution to these concerns is fairly simple. We will use an interface, IWaveBuffer instead of using WaveBuffer itself. This allows us to create an alternative implementation if ever we find that WaveBuffer has any issues.

So the plan is that NAudio will be migrating to use IWaveProvider and IWaveBuffer in the future (not for version 1.2, but probably appearing in the following version), but if anyone can think of any problems with using the proposed WaveBuffer class, I would be interested to hear them.

Saturday, 21 June 2008

Styling a ListBox With Silverlight 2 Beta 2 (Part 2) - Scrollbars

In my first post, I explained how to create a very basic ListBox and ListBoxItem style that governed the appearance of a ListBox. It is still very rudimentary though, and doesn't give any visual feedback of selected items or mouse over. It also doesn't have any scrollbars. Here's what we have so far (with a few changes to the colours - although I'm not sure it's an improvement!):

image2

Adding scrollbars is actually quite simple. All we need to do is add a ScrollViewer to our ListBox style.

<Border Padding="3" Background="#E6BB8A" CornerRadius="5">
   <ScrollViewer x:Name="ScrollViewer"
       Padding="{TemplateBinding Padding}">
       <ItemsPresenter />
   </ScrollViewer>
</Border>

Which gives us scrollbars:

image3

Unfortunately, as nice as the default scrollbars look, they do not fit at all with the visual theme of our ListBox. We need another style which we will base on the default ScrollViewer style.

Here's the ScrollViewer template, slightly customised to remove the border and the square in the bottom right-hand corner. It looks a little intimidating because I left in most of the stuff from the default ScrollViewer style. We could have got rid of a lot of it, but as we are not planning to customise this part very much, we will leave it in there.

<Style TargetType="ScrollViewer" x:Key="ScrollViewerStyle1">
   <Setter Property="IsEnabled" Value="true" />
   <Setter Property="Foreground" Value="#FF000000" />
   <Setter Property="BorderBrush" Value="#FFA4A4A4" />
   <Setter Property="BorderThickness" Value="1" />
   <Setter Property="HorizontalContentAlignment" Value="Left" />
   <Setter Property="VerticalContentAlignment" Value="Top" />
   <Setter Property="Cursor" Value="Arrow" />
   <Setter Property="TextAlignment" Value="Left" />
   <Setter Property="TextWrapping" Value="NoWrap" />
   <!-- Cannot currently parse FontFamily type in XAML so it's being set in code -->
   <!-- <Setter Property="FontFamily" Value="Trebuchet MS" /> -->
   <Setter Property="FontSize" Value="11" />
   <Setter Property="VerticalScrollBarVisibility" Value="Visible" />
   <Setter Property="Template">
       <Setter.Value>
           <ControlTemplate TargetType="ScrollViewer">
               <Grid Background="{TemplateBinding Background}">
                   <Grid.ColumnDefinitions>
                       <ColumnDefinition Width="*"/>
                       <ColumnDefinition Width="Auto"/>
                   </Grid.ColumnDefinitions>
                   <Grid.RowDefinitions>
                       <RowDefinition Height="*"/>
                       <RowDefinition Height="Auto"/>
                   </Grid.RowDefinitions>
                   <!-- Fill="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" -->
                   <ScrollContentPresenter
                     x:Name="ScrollContentPresenter"
                     Grid.Column="0"
                     Grid.Row="0"
                     Content="{TemplateBinding Content}"
                     ContentTemplate="{TemplateBinding ContentTemplate}"
                     Cursor="{TemplateBinding Cursor}"
                     Background="{TemplateBinding Background}"
                     HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
                     TextAlignment="{TemplateBinding TextAlignment}"
                     TextDecorations="{TemplateBinding TextDecorations}"
                     TextWrapping="{TemplateBinding TextWrapping}"
                     VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
                     Margin="{TemplateBinding Padding}" />
                   <ScrollBar
                     x:Name="VerticalScrollBar"
                     Grid.Column="1"
                     Grid.Row="0"
                     Orientation="Vertical"
                     Cursor="Arrow"
                     Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"
                     ViewportSize="{TemplateBinding ViewportHeight}"
                     Minimum="0"
                     Maximum="{TemplateBinding ScrollableHeight}"
                     Value="{TemplateBinding VerticalOffset}"
                     Width="18"/>
                   <ScrollBar
                     x:Name="HorizontalScrollBar"
                     Grid.Column="0"
                     Grid.Row="1"
                     Orientation="Horizontal"
                     Cursor="Arrow"
                     Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"
                     ViewportSize="{TemplateBinding ViewportWidth}"
                     Minimum="0"
                     Maximum="{TemplateBinding ScrollableWidth}"
                     Value="{TemplateBinding HorizontalOffset}"
                     Height="18"/>
               </Grid>
           </ControlTemplate>
       </Setter.Value>
   </Setter>
</Style>

So now we need to tell our ScrollViewer to use this style.

<ScrollViewer x:Name="ScrollViewerElement"
   Style="{StaticResource ScrollViewerStyle1}"
   Padding="{TemplateBinding Padding}">
   <ItemsPresenter />
</ScrollViewer>

And this is what it looks like:

image8

Kind of disappointing, huh? That's because we haven't styled the scrollbars themselves yet. Now we need to take a look at the default scrollbar style. Now these really are intimidating. They contain hundreds of lines of XAML with plenty of VisualStates, Storyboards and gradients. Even more confusingly they contain templates within templates.

We will progress by looking at each of these sub-templates one by one. First up is HorizontalIncrementTemplate, which is the right-arrow at the end of the horizontal scrollbar. Here's a simplified version of the default implementation:

<ControlTemplate x:Key="HorizontalIncrementTemplate" TargetType="RepeatButton">
   <Grid x:Name="Root" Background="#00000000">
       <vsm:VisualStateManager.VisualStateGroups>
           <vsm:VisualStateGroup x:Name="CommonStates">
               <vsm:VisualStateGroup.Transitions>
                   <vsm:VisualTransition To="MouseOver" Duration="0:0:0.2" />
               </vsm:VisualStateGroup.Transitions>
               <vsm:VisualState x:Name="Normal" />
               <vsm:VisualState x:Name="MouseOver">
                   <Storyboard>
                       <ColorAnimation Storyboard.TargetName="ButtonColor" Storyboard.TargetProperty="Color" To="#FF557E9A" Duration="0" />
                   </Storyboard>
               </vsm:VisualState>
               <vsm:VisualState x:Name="Disabled">
                   <Storyboard>
                       <DoubleAnimation Storyboard.TargetName="ButtonVisual" Storyboard.TargetProperty="Opacity" To=".6" Duration="0" />
                   </Storyboard>
               </vsm:VisualState>
           </vsm:VisualStateGroup>                                           
       </vsm:VisualStateManager.VisualStateGroups>
       <Grid.ColumnDefinitions>
           <ColumnDefinition Width="35*"/>
           <ColumnDefinition Width="30*"/>
           <ColumnDefinition Width="35*"/>
       </Grid.ColumnDefinitions>
       <Grid.RowDefinitions>
           <RowDefinition Height="25*"/>
           <RowDefinition Height="50*"/>
           <RowDefinition Height="25*"/>
       </Grid.RowDefinitions>
       <Path x:Name="ButtonVisual" Grid.Column="1" Grid.Row="1" Stretch="Fill" Data="F1 M 511.047,352.682L 511.047,342.252L 517.145,347.467L 511.047,352.682 Z ">
           <Path.Fill>
               <SolidColorBrush x:Name="ButtonColor" Color="#FF313131" />
           </Path.Fill>
       </Path>
   </Grid>
</ControlTemplate>

Although this looks like a lot of code, all that is happening is simply a triangle is being drawn. The grid simply serves to give an appropriate margin around the triangle. The animations change the colour for mouse-over. I have removed the focus rectangle as I don't need it for my purposes. The actual background to the arrow is drawn by another part of the template. So for now I will leave these as they are and to move onto the "Thumb" components.

I have simplified the thumb component to be a rounded rectangle that changes colour as you hover over it. I also played with the margins to make it a little thinner. Here is my modified VerticalThumbTemplate:

<ControlTemplate x:Key="VerticalThumbTemplate" TargetType="Thumb">
   <Grid>
       <vsm:VisualStateManager.VisualStateGroups>
           <vsm:VisualStateGroup x:Name="CommonStates">
               <vsm:VisualStateGroup.Transitions>
                   <vsm:VisualTransition To="MouseOver" Duration="0:0:0.1" />
               </vsm:VisualStateGroup.Transitions>
               <vsm:VisualState x:Name="Normal" />
               <vsm:VisualState x:Name="MouseOver">
                   <Storyboard>
                       <ColorAnimation Storyboard.TargetName="ThumbForeground"
                                       Storyboard.TargetProperty="Color"
                                       To="{StaticResource ThumbHoverColor}" Duration="0" />
                   </Storyboard>
               </vsm:VisualState>
               <vsm:VisualState x:Name="Disabled">
                   <Storyboard>
                       <DoubleAnimation Storyboard.TargetName="ThumbVisual"
                                        Storyboard.TargetProperty="Opacity"
                                        To=".6" Duration="0" />
                   </Storyboard>
               </vsm:VisualState>
           </vsm:VisualStateGroup>
       </vsm:VisualStateManager.VisualStateGroups>

       <Grid x:Name="ThumbVisual">
           <Rectangle x:Name="Background"
               Margin="4.5,.5,4.5,.5"
               RadiusX="5" RadiusY="5" >
               <Rectangle.Fill>
                   <SolidColorBrush x:Name="ThumbForeground"
                   Color="{StaticResource ThumbForegroundColor}" />
                </Rectangle.Fill>
           </Rectangle>
       </Grid>
   </Grid>
</ControlTemplate>

With a similar change to the HorizontalThumbTemplate, our scrollbars are now finally beginning to change in appearance (n.b. To see the changes we have also had to modify our ScrollViewer style so that the two scrollbars it creates use the new ScrollBar style). Here's a screenshot showing the mouse hovering over the vertical thumb:

image31

Finally we are ready to update the remaining parts of the ScrollBar template - HorizontalRoot and VerticalRoot. Both are made up of a grid containing four repeat buttons and a thumb. Two of the repeat buttons are for the increment icons, while the other two are the invisible parts either side of the thumb that you can click for a large change.

Again, the change is simply to get rid of a load of unnecessary stuff. In our case we are simply putting a thin rounded rectangle behind the thumb and invisible repeat buttons, with no background behind the small increment buttons. Here's the new HorizontalRoot template:

<!-- Horizontal Template -->
<Grid x:Name="HorizontalRoot">
   <Grid.ColumnDefinitions>
       <ColumnDefinition Width="Auto" />
       <ColumnDefinition Width="Auto" />
       <ColumnDefinition Width="Auto" />
       <ColumnDefinition Width="*" />
       <ColumnDefinition Width="Auto" />
   </Grid.ColumnDefinitions>

   <Grid.RowDefinitions>
       <RowDefinition />
       <RowDefinition />
   </Grid.RowDefinitions>

   <!-- Track Layer -->
   <Rectangle Margin="1,6,1,6"
     Grid.RowSpan="2" Grid.Column="1"        
     Grid.ColumnSpan="3" Fill="#FF606060"
     RadiusX="3" RadiusY="3" />

   <!-- Repeat Buttons + Thumb -->
   <RepeatButton x:Name="HorizontalSmallDecrease" Grid.Column="0" Grid.RowSpan="2" Width="16" IsTabStop="False" Interval="50" Template="{StaticResource HorizontalDecrementTemplate}" />
   <RepeatButton x:Name="HorizontalLargeDecrease" Grid.Column="1" Grid.RowSpan="2" Width="0" Template="{StaticResource RepeatButtonTemplate}" Interval="50" IsTabStop="False" />
   <Thumb x:Name="HorizontalThumb" MinWidth="10" Width="20" Grid.Column="2" Grid.RowSpan="2" Template="{StaticResource HorizontalThumbTemplate}" />
   <RepeatButton x:Name="HorizontalLargeIncrease" Grid.Column="3" Grid.RowSpan="2" Template="{StaticResource RepeatButtonTemplate}" Interval="50" IsTabStop="False" />
   <RepeatButton x:Name="HorizontalSmallIncrease" Grid.Column="4" Grid.RowSpan="2" Width="16" IsTabStop="False" Interval="50" Template="{StaticResource HorizontalIncrementTemplate}" />
</Grid>

We make a similar change for the VerticalRoot and here's what it looks like:

image6

The bar alongside the vertical scrollbar is actually a very stretched horizontal scrollbar. For some reason, while it successfully uncollapses the VerticalRoot, it does not collapse the HorizontalRoot when you create a vertical scrollbar. After much frustration I discovered that this is a known bug in the current Silverlight beta and sadly it has not been fixed in Beta 2.

So finally we have completely customised scrollbars for our ListBox. Please excuse my hideous choice of colours. I am not a designer! I hope to add one further post to add the remaining two features to make our ListBox complete:

  • Selected Item Rendering
  • Mouse Over Rendering

Continue to Part 3...

Thursday, 19 June 2008

Styling a ListBox With Silverlight 2 Beta 2 (Part 1)

This post is an updated version of an earlier series I wrote on how to style a ListBox with Silverlight. I hope to update all three parts over the coming days. This tutorial is not about how to use Expression Blend, as there are plenty of other blogs that will explain that. This is for those who like to go right to the XAML.

One of the challenges of styling a control like the ListBox in Silverlight is the sheer number of subcomponents it uses. A ListBox has its own template which includes a ScrollViewer and an ItemPresenter. The ScrollViewer contains Scrollbars which contain RepeatButtons, Thumbs etc.

My original idea was to make copies of the default styles and slowly change them to look how I wanted, but I soon realised that the sheer quantity of XAML would get very confusing. So I decided to take the opposite approach. Start with a very basic template and slowly add features until it looks right. So here is the most basic of all ListBox styles which contains just a simple template:

<Style x:Key="ListBoxStyle1" TargetType="ListBox">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ListBox">
                <Grid x:Name="Root">
                    <ItemsPresenter />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

This simply presents the items, with no outline of any sort displayed. To test our style, let's add some items to a ListBox (notice that with Silverlight 2 Beta 2 I need to put TextBlocks inside my ListBoxItem instead of just text that worked fine in Beta 1)

<ListBox Margin="5" Width="160" Height="160"
 Style="{StaticResource ListBoxStyle1}">
    <ListBoxItem><TextBlock Text="Hello"/></ListBoxItem>
    <ListBoxItem><TextBlock Text="World"/></ListBoxItem>
    <ListBoxItem><TextBlock Text="Item 3"/></ListBoxItem>
    <ListBoxItem><TextBlock Text="Item 4 with a really long name"/></ListBoxItem>
    <ListBoxItem><TextBlock Text="Item 5"/></ListBoxItem>
    <ListBoxItem><TextBlock Text="Item 6"/></ListBoxItem>
    <ListBoxItem><TextBlock Text="Item 7"/></ListBoxItem>
    <ListBoxItem><TextBlock Text="Item 8"/></ListBoxItem>
    <ListBoxItem><TextBlock Text="Item 9 out of bounds"/></ListBoxItem>
    <ListBoxItem><TextBlock Text="Item 10 out of bounds"/></ListBoxItem>
</ListBox>

This is what it looks like so far:

ListBox Style Step 1

Next step is the border for our ListBox. I want all the ListBox items to be drawn on top of a filled rounded rectangle. To do this, I have added a Border control. I have given it some padding so we can actually see it, because the ListBoxItem template is currently drawing white rectangles over the top of it.

<Grid x:Name="Root">
  <Border Padding="5" Background="#E6BB8A" CornerRadius="5">
    <ItemsPresenter />
  </Border>
</Grid>

This is what it looks like now:

Listbox Style Step 2

So before we go doing anything further to the ListBox template itself (like adding scrollbars), let's sort out the item templates.

For this we need to create a new style - ListBoxItemStyle1. Here is a minimal ListBoxItem style:

<Style x:Key="ListBoxItemStyle1" TargetType="ListBoxItem">
    <Setter Property="Foreground" Value="#FF000000" />    
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ListBoxItem">
                <Grid x:Name="Root">
                    <ContentPresenter
                        Content="{TemplateBinding Content}"
                        Foreground="{TemplateBinding Foreground}"
                     />                    
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

As you can see, it is a little more involved than the basic ListBox template. We needed to have the Foreground property set to a sensible default value (Black) for anything to appear. And we have simply used one ContentPresenter control to show the contents of each ListBox item.

(n.b. Remember to define the ListBoxItemStyle before your ListBox style. Expression Blend will be OK with this, but when you come to run it, Silverlight will fail with one of its characteristically unhelpful errors.)

We can tell our ListBox to use it by modifying our ListBox Style as follows:

<Style x:Key="ListBoxStyle1" TargetType="ListBox">
    <Setter Property="ItemContainerStyle" Value="{StaticResource ListBoxItemStyle1}" />
    <Setter Property="Template">
        ...

Now we have got rid of those white backgrounds:

ListBox Style Step 3

Finally we will make the item template just a little more interesting. I've added a border to the item presenter, and modified some margins and colours:

<Border CornerRadius="5" Background="#51615B" Margin="1">
<ContentPresenter
    Margin="1"
    Content="{TemplateBinding Content}"
    Foreground="{TemplateBinding Foreground}"
 />                    
 </Border>

This leaves us with something looking like this:

ListBox Style Step 4

So that is at least a reasonable start. Our ListBox is missing at least three crucial features though, which I will tackle in a future blog-post:

  • Scrollbars
  • Selected Item Rendering
  • Mouse Over Rendering

Continue to Part 2...