Showing posts with label XAML. Show all posts
Showing posts with label XAML. Show all posts

Friday, 14 November 2014

Extending WPF Control Templates with Attached Properties

One of the great things about WPF is that you can completely customise everything about the way a control looks. Most of the time then, creating a custom style is more than sufficient to get your controls looking just how you want them? But what if you need your control to change its appearance based on some criteria. At this point, I’d usually be tempted to create my own custom control. But that isn’t actually always necessary.

For example, I recently wanted to create a peak LED button in an audio application that was monitoring sound levels. The button would show the peak decibel level, and when the sound went above a certain threshold, would go red. To acknowledge the clipping, you could click the button and the it would revert to its default colour. So my button needed a boolean property that would indicate is clipped or not.

image

But how can you add a property to a class without inheriting it? Well in WPF, you can make use of attached properties. You’ve already used them if you’ve set Grid.Row for a control in your XAML. The control itself has no Grid.Row property, but you can associate a grid row value with that control, which enables it to be positioned correctly within the grid.

So we need an attached property that stores whether a peak has been detected or not. Attached properties are similar to dependency properties if you’ve ever created those before. I can never remember how to write them from scratch, but if you have an example handy, you can copy it. You need to inherit from DependencyObject, to register a static DependencyProperty, and to provide static getter and setter methods. Here’s the one I created:

public class PeakHelper : DependencyObject
{
    public static readonly DependencyProperty IsPeakProperty = DependencyProperty.RegisterAttached(
        "IsPeak", typeof (bool), typeof (PeakHelper), new PropertyMetadata(false));


    public static void SetIsPeak(DependencyObject target, Boolean value)
    {
        target.SetValue(IsPeakProperty, value);
    }

    public static bool GetIsPeak(DependencyObject target)
    {
        return (bool)target.GetValue(IsPeakProperty);
    }
}

Now we have our attached property, we can use it in our button template. The regular button template is simply a ContentPresenter inside a Border, and then we use a Trigger to set the border’s background and border colours when our attached property is true. Obviously this is a very simplistic button template otherwise, with no triggers for mouse-over, pressed, or disabled.

<ControlTemplate TargetType="Button" x:Key="PeakButtonControlTemplate" >
    <Border x:Name="PeakBorder" BorderBrush="Gray" 
            BorderThickness="2" Background="LightGray">
        <ContentPresenter HorizontalAlignment="Center">
        </ContentPresenter>        
    </Border>
    <ControlTemplate.Triggers>
        <Trigger Property="local:PeakHelper.IsPeak"
                 Value="True">
            <Setter 
                TargetName="PeakBorder"
                Property="BorderBrush"
                Value="Red"></Setter>
            <Setter 
                TargetName="PeakBorder"
                Property="Background"
                Value="Pink"></Setter>

        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

And that’s all there is to it. We can now use regular MVVM to set the IsPeak attached property to true, which will turn our button red:

<Button Margin="4" 
    Template="{StaticResource PeakButtonControlTemplate}" 
    Content="{Binding MaxVolume}"  
    local:PeakHelper.IsPeak="{Binding IsPeak}" 
    Command="{Binding PeakReset}" 
    Width="40" Height="20" />

Thursday, 6 November 2014

Styling a Vertical ProgressBar in WPF

Styling your own custom progress bar in WPF has always been a relatively straightforward task. I blogged about how I created a volume meter style several years ago. But recently I needed to create a style for a vertical progress bar, and it proved a lot more complicated than I anticipated. The root of the problem appears to be a breaking change in .NET 4, that meant your PART_Indicator’s Width rather than Height gets adjusted, irrespective of the orientation of the ProgressBar.

It means you end up with vertical progress bars looking like this:

image

Instead of what we want which is this:

image

The trick to fixing this is to use a LayoutTransform as described in this StackOverflow answer. The transform rotates the root element 270 degrees when the ProgressBar’s Orientation property is set to Vertical. If this sounds a bit of a hack to you, well it is, but it does seem to work.

The XAML for the style shown above is as follows:

<Style TargetType="ProgressBar">
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="ProgressBar" >
        <Grid x:Name="Root">
          <Border 
            Name="PART_Track" 
            CornerRadius="2" 
            Background="LightGreen"
            BorderBrush="Green"
            BorderThickness="2" />
          <Border 
            Name="PART_Indicator" 
            CornerRadius="2" 
            Background="ForestGreen" 
            BorderBrush="Green" 
            BorderThickness="2" 
            HorizontalAlignment="Left" />
          </Grid>
          <ControlTemplate.Triggers>
            <!-- Getting vertical style working using technique described here: http://stackoverflow.com/a/6849237/7532 -->
            <Trigger Property="Orientation" Value="Vertical">
              <Setter TargetName="Root" Property="LayoutTransform">
                <Setter.Value>
                  <RotateTransform Angle="270" />
                </Setter.Value>
              </Setter>

              <Setter TargetName="Root" Property="Width"
                Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Height}"
              />
              <Setter TargetName="Root" Property="Height"
                Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Width}"
              />
            </Trigger>            
          </ControlTemplate.Triggers>       
  
        </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

One word of caution. If you plan to plan to use the gradient fill and dock panel technique I described in my blog, then you’ll need another trigger to set the MinWidth property on the Mask element. This allows you to get the gradually revealed gradient fill in either Horizontal or Vertical alignment:

image

I’ve made the XAML for these styles available in a gist on GitHub.

Wednesday, 15 May 2013

Knockout - What MVVM should have been like in XAML

I've not got a lot of JavaScript programming experience, but I've been learning a bit recently, and decided to try knockout.js. I was pleasantly surprised at how quickly I was able to pick it up. In part this is because it follows a MVVM approach very similar to what I am used to with WPF. I've blogged a three-part MVVM tutorial here before as well as occasionally expressing my frustrations with MVVM. So I was interested to see what the MVVM story is like for Javascript developers. What I wasn't expecting, was to find that MVVM in JavaScript using knockout.js is a much nicer experience than I was used to with XAML + C#. In this post I'll run through a few reasons why I was so impressed with knockout.

Clean Data-Binding Syntax


The first impressive thing is how straightforward the binding syntax is. Any HTML element can have a data-bind property attached to it, and that can hold a series of binding expressions. Here's a simple binding expression in knockout that puts text from your viewmodel into a div:

<div data-bind="text: message" ></div>

To bind multiple properties, you can just add extra binding statements into the single data-bind attribute as follows:

<button data-bind="click: prev, enable: enablePrev">

In XAML the syntax for basic bindings isn't too cumbersome, but a bit more repetitive nonetheless:

<TextBox Text="{Binding Message}" 
Enabled="{Binding EnableEditMessage}" />

Event Binding

One frustration with data binding in XAML is that you can't bind a function directly to an event. So for example when an animation finishes, it would be great to be able to call into a method on our ViewModel with something like this:

<Storyboard Completed="{Binding OnFinished}" />

Sadly, that is invalid XAML, but with knockout.js, binding to any event is trivial:

<div data-bind="click: handleItem"/>

Arbitrary expressions in binding syntax


XAML data binding does allow you to drill down into members of properties on your ViewModel. For example, you can do this:

<TextBlock Text="{Binding CurrentUser.LastName}" />

And it also does give you the ability to do a bit of string formatting, albeit with a ridiculously hard to remember syntax:

<TextBlock Text="{Binding Path=OrderDate, StringFormat='{}{0:dd.MM.yyyy}'}" />

But you can't call into a method on your ViewModel, or write expressions like this

<Button IsEnabled="{Binding Items.Count > 0}" />

However, in knockout.js, we have the freedom to write arbitrary bits of JavaScript right there in our binding syntax. It's simple to understand, and it just works:

<button data-bind="click: next, enable: index() < questions.length -1">Next</button>

This is brilliant, and it keeps the ViewModel from having to do unnecessary work just to manipulate data into the exact type and format needed for the data binding expression. (Anyone who's done MVVM with XAML will be all too familiar with the task of of turning booleans into Visibility values using converters)

Property Changed Notifications


Obviously, knockout needs some way for the ViewModel to report that a property has changed, so that the view can update itself. In the world of XAML this is done via the INotifyPropertyChanged interface, which is cumbersome to implement, even if you have a ViewModelBase class that you can ask to do the raising of the event for you.

private bool selected;
public bool Selected 
{
   get { return selected; }
   set   
   {
        if (selected != value)
        {
            selected = value;
            OnPropertyChanged("Selected");
        }
   }
}

Contrast this with the gloriously straightforward knockout approach which uses a ko.observable:

this.selected = ko.observable(false);

Now selected is a function that you call with no arguments to get the current value, and with a boolean argument to set its value. It's delightfully simple. I can't help but wonder if a similar idea could be somehow shoehorned into C#.

To be fair to the XAML MVVM world, you can alleviate some of the pain with Simon Cropp's superb Fody extension, which allows you to add in a compile time code weaver to automatically raise PropertyChanged events on your ViewModels. I use this on just about all my WPF projects now. It is a great timesaver and leaves your code looking a lot cleaner to boot. However, in my opinion, if you have to use code weaving its usually a sign that your language is lacking expressivity. I'd rather directly express myself in code.

Computed properties


Computed properties can be a pain in C# as well, as you have to remember to explicitly raise the PropertyChanged notification. (Although Fody is very powerful in this regard and can spot that a property getter on your ViewModel depends on other property getters.) Here's an example of a calculated FullName property in a C# ViewModel:

private string firstName;
public string FirstName 
{
   get { return firstName; }
   set   
   {
        if (firstName!= value)
        {
            firstName= value;
            OnPropertyChanged("FirstName");
            OnPropertyChanged("FullName");
        }
   }
}

public string FullName 
{
    get { return FirstName + " " + Surname; }
}

Knockout's solution to this is once again elegant and simple. You simply declare a ko.computed type on your ViewModel:
this.fullName = ko.computed(function() {
     return this.firstName() + " " + this.lastName();
}, this);

Elegant handling of binding to parent and root data contexts


Another area that causes regular pain for me with XAML databinding is when you need to bind to your parent's context or the root context. I think I've just about got the syntax memorized now, but I must have searched for it on StackOverflow hundreds of times before it finally stuck. You end up writing monstrosities like this:

...Binding="{Binding RelativeSource={RelativeSource FindAncestor, 
AncestorType={x:Type Window}}, Path=DataContext.AllowItemCommand}" ...

In knockout, once again, the solution is simple and elegant, allowing you to use $parent to access your parent data context (and grandparents with $parent[1] etc), or $root. Read more about knockout's binding context here.

<div data-bind="foreach: currentQuestion().answers">
    <div data-bind="html: answer, click: $parent.currentQuestion().select"></div>
</div>

Custom binding extensions!


Finally, the killer feature. If only we could add custom binding expressions to XAML, then maybe we could work around some of its limitations and upgrade it with powerful capabilities. Whilst I have a feeling that this is in fact technically possible, I don't know of anyone who has actually done it. Once again knockout completely knocks XAML out of the water on this front, with a very straightforward extensibility model for you to create your own powerful extensions. If you run through the knockout tutorial, you'll implement one yourself and be amazed at how easy it is.


Conclusion

I've only used knockout.js for a few hours (here's what I made) and all I can say is I am very jealous of its powers. This is what data binding in XAML ought to have been like. XAML has been around for some time now, but there have been very few innovations in the data-binding space (we have seen the arrival of behaviours, visual state managers, merged dictionaries, format strings, but all of them suffer from the same clunky, hard to remember syntax). And now we have another subtly different flavour of XAML to learn for Windows Store 8 apps, it seems that XAML is here to stay. Maybe it is time for us to put some pressure onto Microsoft to give XAML data binding power to rival what the JavaScript community seem to be able to take for granted.

Saturday, 15 September 2012

A XAML Loop Icon

I needed a XAML loop icon recently, so here’s what I came up with:

<Canvas>
  <Path Stroke="Black" StrokeThickness="2" Data="M 5,3 h 20 a 5, 5, 180, 1, 1, 0, 10 h -20 a 5, 5, 180, 1, 1, 0, -10 Z" />
  <Path Stroke="Black" Fill="Black" StrokeThickness="2"  StrokeLineJoin="Round" Data="M 11,0 l 8,3 l -8, 3  Z" />
</Canvas>

And this is what it looks like:

image

Friday, 14 September 2012

Using a WrapPanel with ItemsControl and ListView

I recently needed to put some items inside a WrapPanel in WPF, which I wanted to scroll vertically if there were too many items to fit into the available space. I was undecided on whether I wanted to use an ItemsControl or a ListView (which adds selected item capabilities), and discovered that when you switch between the two containers, the technique for getting the WrapPanel working is subtly different.

ItemsControl is the simplest. Just set the ItemsPanelTemplate to be a WrapPanel, and then put the whole thing inside a ScrollViewer (sadly you can’t put the ScrollViewer inside the ItemsPanelTemplate):

<ScrollViewer>    
  <ItemsControl>
    <ItemsControl.ItemsPanel>
      <ItemsPanelTemplate>
        <WrapPanel />
      </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <Rectangle Margin="5" Width="100" Height="100" Fill="Beige" />
    <Rectangle Margin="5" Width="100" Height="100" Fill="PowderBlue" />
    <Rectangle Margin="5" Width="100" Height="100" Fill="#FF9ACD32" />    
    <Rectangle Margin="5" Width="100" Height="100" Fill="#FFFF6347" />
    <Rectangle Margin="5" Width="100" Height="100" Fill="#FF6495ED" />
    <Rectangle Margin="5" Width="100" Height="100" Fill="#FFFFA500" />
    <Rectangle Margin="5" Width="100" Height="100" Fill="#FFFFD700" />
    <Rectangle Margin="5" Width="100" Height="100" Fill="#FFFF4500" />
    <Rectangle Margin="5" Width="100" Height="100" Fill="#FF316915" />    
    <Rectangle Margin="5" Width="100" Height="100" Fill="#FF8E32A7" />
    <Rectangle Margin="5" Width="100" Height="100" Fill="#FFECBADC" />
    <Rectangle Margin="5" Width="100" Height="100" Fill="#FFE6D84F" />
  </ItemsControl>
</ScrollViewer>

this produces the following results:

image

But if you switch to an ItemsView instead, you’ll find that you get a single row of items with a horizontal scrollbar while the outer ScrollViewer has nothing to do.

image

The solution is to disable the horizontal scrollbar of the ListView itself:

<ListView ScrollViewer.HorizontalScrollBarVisibility="Disabled">

This allows our top-level ScrollViewer to work as with the ItemsControl and we have selection capabilities as well:

image

The full XAML for the ListView with vertically scrolling WrapPanel is:

<ScrollViewer>    
  <ListView ScrollViewer.HorizontalScrollBarVisibility="Disabled">
    <ListView.ItemsPanel>
      <ItemsPanelTemplate>
        <WrapPanel />
      </ItemsPanelTemplate>
    </ListView.ItemsPanel>
    <Rectangle Margin="5" Width="100" Height="100" Fill="Beige" />
    <Rectangle Margin="5" Width="100" Height="100" Fill="PowderBlue" />
    <Rectangle Margin="5" Width="100" Height="100" Fill="#FF9ACD32" />    
    <Rectangle Margin="5" Width="100" Height="100" Fill="#FFFF6347" />
    <Rectangle Margin="5" Width="100" Height="100" Fill="#FF6495ED" />
    <Rectangle Margin="5" Width="100" Height="100" Fill="#FFFFA500" />
    <Rectangle Margin="5" Width="100" Height="100" Fill="#FFFFD700" />
    <Rectangle Margin="5" Width="100" Height="100" Fill="#FFFF4500" />
    <Rectangle Margin="5" Width="100" Height="100" Fill="#FF316915" />    
    <Rectangle Margin="5" Width="100" Height="100" Fill="#FF8E32A7" />
    <Rectangle Margin="5" Width="100" Height="100" Fill="#FFECBADC" />
    <Rectangle Margin="5" Width="100" Height="100" Fill="#FFE6D84F" />
  </ListView>
</ScrollViewer>

Saturday, 10 March 2012

Creating Resizable Shape Controls in WPF

In WPF, you can create a resizable shape by using the Path control. Simply set up your shape’s path data and then set the Stretch property to Fill and your shape will resize itself to fit the space available. It even takes into account the stroke thickness.

But what if you want a shape that resizes differently? For example, you might want the corner radius of a rounded corner to stay constant, irrespective of how wide or tall you made your shape. In that case, things get quite a bit more complicated as you can’t use the Stretch=Fill technique.

I’ve recently built a small open source library of useful WPF shapes (available on NuGet), and in this post, I’ll briefly explain how to make your own resizable WPF shape.

Custom Shape Using Stretch.Fill

The first and simplest example is a shape that does use Stretch.Fill. If proportional stretching is all you want, then this is the simplest way to do it. Here’s a simple Diamond (Rhombus) shape:

public class Diamond : Shape
{
    public Diamond()
    {
        this.Stretch = Stretch.Fill;
    }

    protected override Geometry DefiningGeometry
    {
        get { return GetGeometry(); }
    }

    private Geometry GetGeometry()
    {
        return Geometry.Parse("M 100, 0 l 100, 100 l -100, 100 l -100, -100 Z");
    }
}

There are only two things we needed to do. First is to set the Stretch property to Fill by default, saving the users of our control the need to do that in XAML. And then override the DefiningGeometry. I like to use the XAML Path mini-language as I find it to be easy to use. Here I just draw a diamond, using “Z” to close the shape at the end. It doesn’t matter what size I draw the shape, since we are stretching to fit.

Adding Custom Properties

Another useful thing to be able to do is add custom properties to your Shape object. These should be dependency properties to fully benefit from the power of the WPF framework. For example, in my Triangle shape, I added a TriangleOrientation property that allows you to select what direction your triangle is pointing in.

public enum Orientation
{
    N,
    NE,
    E,
    SE,
    S,
    SW,
    W,
    NW
}

public Orientation TriangleOrientation
{
    get { return (Orientation)GetValue(TriangleOrientationProperty); }
    set { SetValue(TriangleOrientationProperty, value); }
}

// Using a DependencyProperty as the backing store for TriangleOrientation.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty TriangleOrientationProperty =
    DependencyProperty.Register("TriangleOrientation", typeof(Orientation), typeof(Triangle), new UIPropertyMetadata(Orientation.N, OnOrientationChanged));

private static void OnOrientationChanged(DependencyObject d, DependencyPropertyChangedEventArgs ek)
{
    Triangle t = (Triangle)d;
    t.InvalidateVisual();
}

Calling InvalidateVisual when the TriangleOrientation property changes causes DefiningGeometry to be called again. Now we simply have to return the correct geometry for the selected orientation:

private Geometry GetGeometry()
{
    string data;
    if (TriangleOrientation == Orientation.N)
        data = "M 0,1 l 1,1 h -2 Z";
    else if (TriangleOrientation == Orientation.NE)
        data = "M 0,0 h 1 v 1 Z";
    else if (TriangleOrientation == Orientation.E)
        data = "M 0,0 l 1,1 l -1,1 Z";
    else if (TriangleOrientation == Orientation.SE)
        data = "M 1,0 v 1 h -1 Z";
    else if (TriangleOrientation == Orientation.S)
        data = "M 0,0 h 2 l -1,1 Z";
    else if (TriangleOrientation == Orientation.SW)
        data = "M 0,0 v 1 h 1 Z";
    else if (TriangleOrientation == Orientation.W)
        data = "M 0,1 l 1,1 v -2 Z";
    else 
        data = "M 0,0 h 1 l -1,1 Z";
    return Geometry.Parse(data);
}

Controlling Resize Behaviour

As I mentioned at the start, controlling resize behaviour unfortunately is not nearly so simple. My first attempt was to use the control’s ActualHeight and ActualWidth property, or the RenderSize property while I created the DefiningGeometry. But that actually caused really strange resize behaviour with the shape growing larger as you resized the control smaller.

The trouble is caused by the fact that the parent container is asking the Shape “how much space do you need?” and the shape is asking the container “how much space have you got?”. Someone needs to make a decision what the size will be. The solution is for our Shape to say that it will take up the full amount of space it is being offered, by overriding MeasureOverride to simply pass back the constraint we are given:

protected override Size MeasureOverride(Size constraint)
{
    // we will size ourselves to fit the available space
    return constraint;
}

Now in the Geometry method, we can examine the ActualWidth and ActualHeight properties and use those to know how much space we have to draw the shape in. Unfortunately, we have to take the StrokeThickness into account ourselves, so make sure you leave a margin round the edge of at least StrokeThickness / 2 (it may even need to be more depending on your line join style and the angle of line joins). Here’s the code for my Label control, which is a rectangle with the top left and bottom left corners cut off. It has a CornerWidth dependency property to define the amount of corner to cut off, which stays constant however big you resize the control:

private Geometry GetGeometry()
{
    double cornerWidth = CornerWidth;
    double width = ActualWidth - StrokeThickness;
    double height = ActualHeight - StrokeThickness;

    return Geometry.Parse(String.Format("M {0},{1} h {3} v {4} h -{3} l -{2},-{2} v -{5} Z", 
        cornerWidth + StrokeThickness/2, StrokeThickness/2, cornerWidth, width-cornerWidth, height, height-(2 * cornerWidth)));
}

Try it out

You are welcome to try my shapes out for yourself. The code is available on CodePlex. I welcome any contributions to the library either of new shapes, or adding some more dependency properties onto the existing shapes to allow for better customisation. The current shapes are Speech Bubble, Diamond, Rounded Sides Rectangle, Hexagon, Label, Triangle (available in several orientations) and Chevron. Here’s what the shapes in the first version look like being resized:

shapes

Monday, 13 June 2011

Silverlight Star Rating Control

I found myself needing a star rating control for a Silverlight application recently, and although I found a few open source ones, none of them worked quite how I wanted them to. Besides, I designed my own star shape in XAML a while ago and wanted to use that as the basis.

StarRating

In particular I wanted the ability to have half-star ratings. I thought at first it would require me to use some kind of complicated clipping construct, with a rectangle hidden behind my star, but I realised I could cheat by creating a half-star shape. Each star is a UserControl made up of three shapes – a star fill, a half star on top and then an outline on top.

I also wanted the colours to change as you hovered the mouse over it, indicating what rating would be given were you to click at any moment. This required the background of the control to be painted with a transparent brush, otherwise mouse events are only received while you are over stars and not in between them.

The biggest difficulty was making resize work. Path objects can be a pain to resize. I ended up putting them on a Canvas and using a ScaleTransform to get them the right size. All the brushes are customisable (six of them), plus you can change the number of stars, the line joins (pointy or round edged stars) and the line thicknesses. You can also turn off editing to simply display a star rating.

The Silverlight Star Rating Control is open source and available on CodePlex. The easiest way to install it is using NuGet.

To have a play with the star rating control, you can try it in my (rather chaotic looking) test harness below:

Monday, 28 September 2009

Styling a WPF Volume Meter

In WPF, the complete separation of behaviour from appearance means that you can take a control such as the ProgressBar, and style it to look like any kind of meter. For a recent project, I wanted to make a audio volume meter. The look I wanted was a gradient bar, starting with green for low volumes and going to red for the highest volume:

wpf-volume-meter-1

When the volume is at half way, the progress bar should just show the left hand portion of the gradient:

wpf-volume-meter-2

Styling a ProgressBar in WPF is actually very easy. You can start with the very helpful “Simple Style” template that comes with kaxaml. You simply need to define the visuals for two parts, called PART_Track and PART_Indicator. The first is the background, while the second is the part that dynamically changes size as the current value changes.

At first it would seem trivial to create the look I am after – just create two rectangles on top of each other. The trouble is, that I only want the whole gradient to appear if the value is at maximum. Here’s it incorrectly drawing the entire gradient when the volume is low:

wpf-volume-meter-3

To work around this, I painted the entire gradient on the PART_Track. Then the PART_Indicator was made transparent. This required one further rectangle to cover up the part of the background gradient that I don’t want. I do this with a DockPanel. This allows the PART_Indicator to use its required space, while the masking rectangle fills up the remaining space on the right-hand side, covering up the background gradient.

<style targettype="{x:Type ProgressBar}" x:key="{x:Type ProgressBar}">
  <setter property="Template">
    <setter.value>
      <controltemplate targettype="{x:Type ProgressBar}">
        <grid minheight="14" minwidth="200">
          <rectangle name="PART_Track" stroke="#888888" strokethickness="1">
            <rectangle.fill>
              <lineargradientbrush startpoint="0,0" endpoint="1,0">
                <gradientstop offset="0" color="#FF00FF00"/>
                <gradientstop offset="0.9" color="#FFFFFF00"/>
                <gradientstop offset="1" color="#FFFF0000"/>
              </lineargradientbrush>
            </rectangle.fill>
          </rectangle>
          <dockpanel margin="1">
            <rectangle name="PART_Indicator">
            </rectangle>
            <rectangle name="Mask" minwidth="{TemplateBinding Width}" fill="#C0C0C0"/>
          </dockpanel>
        </grid>
      </controltemplate>
    </setter.value>
  </setter>
</style>

I think there may be an even better way to solve this using a VisualBrush, but I can’t quite get it working at the moment. I’ll post with the solution once I’ve worked it out.

Friday, 25 September 2009

Circular WPF Button Template

Here’s another WPF button template, since my first one continues to be one of the most popular posts on my blog. This one is in response to a question about how the button could be made circular. The basic background of the button is formed by superimposing three circles on top of each other:

<Grid Width="100" Height="100" Margin="5">
   <Ellipse Fill="#FF6DB4EF"/>
   <Ellipse>
      <Ellipse.Fill>
         <RadialGradientBrush>
            <GradientStop Offset="0" Color="#00000000"/>
            <GradientStop Offset="0.88" Color="#00000000"/>
            <GradientStop Offset="1" Color="#80000000"/>
         </RadialGradientBrush>
      </Ellipse.Fill>
   </Ellipse>
   <Ellipse Margin="10">
      <Ellipse.Fill>
         <LinearGradientBrush>
            <GradientStop Offset="0" Color="#50FFFFFF"/>
            <GradientStop Offset="0.5" Color="#00FFFFFF"/>
            <GradientStop Offset="1" Color="#50FFFFFF"/>
         </LinearGradientBrush>
      </Ellipse.Fill>
   </Ellipse>
</Grid>

Here’s what that looks like:

Circular Button

Now to turn it into a template, we follow a very similar process to before. We allow the Background colour to be overriden by the user if required. I couldn’t come up with a neat way of forcing the button to be circular, but it is not too much of a chore to set the Height and Width in XAML.

The focus rectangle has been made circular. For the IsPressed effect, I simply change the angle of the linear gradient of the inner circle a little, and move the ContentPresenter down a bit, which seems to work OK. I haven’t created any triggers yet for IsMouseOver, IsEnabled or IsFocused, mainly because I’m not quite sure what would create a the right visual effect.

<Page.Resources>
  <Style x:Key="MyFocusVisual">
     <Setter Property="Control.Template">
        <Setter.Value>
           <ControlTemplate TargetType="{x:Type Control}">
              <Grid Margin="8">
                 <Ellipse
                    Name="r1"
                    Stroke="Black"
                    StrokeDashArray="2 2"
                    StrokeThickness="1"/>
                 <Border
                    Name="border"
                    Width="{TemplateBinding ActualWidth}"
                    Height="{TemplateBinding ActualHeight}"
                    BorderThickness="1"
                    CornerRadius="2"/>
              </Grid>
           </ControlTemplate>
        </Setter.Value>
     </Setter>
  </Style>
  <Style x:Key="CircleButton" TargetType="Button">
     <Setter Property="OverridesDefaultStyle" Value="True"/>
     <Setter Property="Margin" Value="2"/>
     <Setter Property="FocusVisualStyle" Value="{StaticResource MyFocusVisual}"/>
     <Setter Property="Background" Value="#FF6DB4EF"/>
     <Setter Property="Template">
        <Setter.Value>
           <ControlTemplate TargetType="Button">
              <Grid>
                 <Ellipse Fill="{TemplateBinding Background}"/>
                 <Ellipse>
                    <Ellipse.Fill>
                       <RadialGradientBrush>
                          <GradientStop Offset="0" Color="#00000000"/>
                          <GradientStop Offset="0.88" Color="#00000000"/>
                          <GradientStop Offset="1" Color="#80000000"/>
                       </RadialGradientBrush>
                    </Ellipse.Fill>
                 </Ellipse>
                 <Ellipse Margin="10" x:Name="highlightCircle" >
                    <Ellipse.Fill >
                       <LinearGradientBrush >
                          <GradientStop Offset="0" Color="#50FFFFFF"/>
                          <GradientStop Offset="0.5" Color="#00FFFFFF"/>
                          <GradientStop Offset="1" Color="#50FFFFFF"/>
                       </LinearGradientBrush>
                    </Ellipse.Fill>
                 </Ellipse>
                 <ContentPresenter x:Name="content" HorizontalAlignment="Center" VerticalAlignment="Center"/>
              </Grid>
              <ControlTemplate.Triggers>
                 <Trigger Property="IsPressed" Value="True">
                    <Setter TargetName="highlightCircle" Property="Fill">
                       <Setter.Value>
                       <LinearGradientBrush StartPoint="0.3,0" EndPoint="0.7,1">
                          <GradientStop Offset="0" Color="#50FFFFFF"/>
                          <GradientStop Offset="0.5" Color="#00FFFFFF"/>
                          <GradientStop Offset="1" Color="#50FFFFFF"/>
                       </LinearGradientBrush>
                       </Setter.Value>
                    </Setter>
                    <Setter TargetName="content" Property="RenderTransform">
                       <Setter.Value>
                          <TranslateTransform Y="0.5" X="0.5"/>
                       </Setter.Value>
                    </Setter>
                 </Trigger>
              </ControlTemplate.Triggers>
           </ControlTemplate>
        </Setter.Value>
     </Setter>
  </Style>
</Page.Resources>

Here’s how we declare a few of these buttons of different sizes, and with different background colours:

<WrapPanel>
  <Button Width="100" Height="100" Style="{StaticResource CircleButton}">Hello World</Button>
  <Button Width="80" Height="80" Style="{StaticResource CircleButton}" Background="#FF9F1014">Button 2</Button>
  <Button Width="80" Height="80" Style="{StaticResource CircleButton}" Background="#FFD8C618">Button 3</Button>      
  <Button Width="80" Height="80" Style="{StaticResource CircleButton}" Background="#FF499E1E">Button 4</Button>
  <Button Width="80" Height="80" Style="{StaticResource CircleButton}" Background="Orange">Button 5</Button>
  <Button Width="80" Height="80" Style="{StaticResource CircleButton}" Background="#FF7C7C7C">Button 6</Button>
  <Button Width="80" Height="80" Style="{StaticResource CircleButton}" Background="Purple" Foreground="White">Button 7</Button>
  <Button Width="100" Height="100" Style="{StaticResource CircleButton}" Background="#FF3120D4" Foreground="White">Button 8</Button>
</WrapPanel>

Circular Buttons

Sadly, the use of ControlTemplate triggers means that this template can’t be used directly in Silverlight. I’ll maybe look into converting them to VisualStates for a future blog post.

Friday, 27 March 2009

Binding Combo Boxes in WPF with MVVM

I was recently creating a simple WPF application and was trying to use the MVVM pattern. In this pattern, all the controls on your form are data bound to properties on your “View Model” class. While there are lots of examples of how to do this with Text boxes, List boxes, and even master-detail views, it seems that examples for binding Combo boxes are a little thin on the ground.

What I wanted to do was bind the items in the ComboBox to a list in my ViewModel and to track the currently selected item. I found that there does not seem to be one “official” way of doing this. One approach bound both the ItemsSource and the SelectedValue properties of the Combo box to corresponding properties on ViewModel. The approach I went with uses a CollectionView which is a class included with .NET that encapsulates a list and the concept of a current item as well as supporting the INotifyPropertyChanged interface.

So here is the XAML first. I set the IsSychronizedWithCurrentItem to true to allow us to track the current item on the ItemsSource.

<ComboBox ItemsSource="{Binding Path=Queries}"                 
          IsSynchronizedWithCurrentItem="True"
          DisplayMemberPath="Name" />

The code in the ViewModel first creates the List, and then creates a CollectionView based on that list. This allows us to set the CurrentItem from the ViewModel as well as get notified whenever the CurrentItem changes.

public MainWindowViewModel()
{
    IList<Query> availableQueries = new List<Query>();
    // fill the list...

    Queries = new CollectionView(availableQueries);
    Queries.MoveCurrentTo(availableQueries[0]);
    Queries.CurrentChanged += new EventHandler(queries_CurrentChanged);
}

public CollectionView Queries { get; private set; }

void queries_CurrentChanged(object sender, EventArgs e)
{
    Query currentQuery = (Query)Queries.CurrentItem;
}

I’m not sure yet whether using CollectionView is a better approach than the alternatives I have seen which bind the SelectedValue or SelectedItem property. I would be interested to hear in the comments if you think either approach has benefits over the other. One consideration is that Silverlight doesn’t seem to support CollectionView at the moment.

Tuesday, 6 January 2009

XAML SoundWave Take 2

In my previous post, I described a couple of options for creating a sound-wave icon graphic in XAML. While my final solution worked OK, I have since thought up an even simpler approach by using the XAML Path mini-language.

The reason I didn’t initially go for this option was that I didn’t want to calculate the coordinates of each point. However, we can draw the shape rotated by 45 degrees first, which allows us to put each point nicely on a grid, and then use a RotateTransform afterwards to put it back.

The XAML Path is a very useful tool to master if you prefer to work directly with XAML instead of using a design tool like Expression Blend. Here’s the first path, to create a quadrant shape:

<Path Fill="Orange" Data="M 0,0 h 1 a 1,1 90 0 1 -1,1 Z" />

The M command starts us off by moving to the specified coordinates (0,0 in this case). A lower case h means a relative horizontal line – we move one unit to the right. Now the lower case a command means we are drawing an arc using relative coordinates. The first two arguments are the radius of the arc (1,1 = circle of radius 1), then comes the angle in degrees (90), then a flag indicating if this is a large arc – in our case, no (0), and then another flag indicating if we are going clockwise (1 = yes). Finally, we put the end coordinates (-1,1) which are relative to the starting point because we used a lower case a. This path is then closed with the Z command although this is not strictly necessary for us as we are not using a Stroke brush on this Path.

Now we can draw the other three sections, each of which consists of a horizontal line followed by an arc, then a vertical line, and finally another arc with a smaller radius. I don’t bother with the Z because I have reached my starting point again with the second arc.

<Path Fill="Orange" Data="M 2,0 h 1 a 3,3 90 0 1 -3,3 v -1 a 2,2 90 0 0 2,-2" />
<Path Fill="Orange" Data="M 4,0 h 1 a 5,5 90 0 1 -5,5 v -1 a 4,4 90 0 0 4,-4" />
<Path Fill="Orange" Data="M 6,0 h 1 a 7,7 90 0 1 -7,7 v -1 a 6,6 90 0 0 6,-6" />

We end up with the right graphics but with the wrong orientation:

image

A RotateTransform (plus a ScaleTransform) allows us to put this exactly how we want it:

image

Here’s the complete XAML:

<Page
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="#E0E0E0">
  <Grid Width="8" Height="8">
    
    <Grid.RenderTransform>
      <TransformGroup>
      <ScaleTransform ScaleX="10" ScaleY="10" />
      <RotateTransform Angle="-45" />
      </TransformGroup>
    </Grid.RenderTransform>
    
    <Path Fill="Orange" Data="M 0,0 h 1 a 1,1 90 0 1 -1,1 Z" />
    <Path Fill="Orange" Data="M 2,0 h 1 a 3,3 90 0 1 -3,3 v -1 a 2,2 90 0 0 2,-2" />
    <Path Fill="Orange" Data="M 4,0 h 1 a 5,5 90 0 1 -5,5 v -1 a 4,4 90 0 0 4,-4" />
    <Path Fill="Orange" Data="M 6,0 h 1 a 7,7 90 0 1 -7,7 v -1 a 6,6 90 0 0 6,-6" />
  </Grid>
</Page>

Friday, 19 December 2008

Soundwave XAML

I visited the NoiseTrade website this week and they had a neat looking soundwave icon they are using throughout the site. I doubt it is original to them, but I  it works nicely and I thought I would have a go at creating it in XAML.

Its very simple to do. You create a series of concentric circles and then mask off the bit you don’t want with a Polygon.

Soundwave in XAML

Here’s the XAML:

<Grid Width="100" Height="100">  
    <Ellipse Width="10" Height="10" Fill="Orange" />
    <Ellipse Width="30" Height="30" Stroke="Orange" StrokeThickness="5"  />
    <Ellipse Width="50" Height="50" Stroke="Orange" StrokeThickness="5"  />
    <Ellipse Width="70" Height="70" Stroke="Orange" StrokeThickness="5"  />
    <Polygon Stretch="Fill" Fill="White" Points="0,0, 1,1 -1,1 -1,-1 1,-1" />
  </Grid>

The only really clever thing going on here is that we use the Stretch property of the Polygon to centre our cut-out shape over the centre point of the circles. The Stretch property also means we can use nominal coordinates, making it easier to get the coordinates right. Alternatively we could have used the following:

<Polygon Fill="Red" Points="50,50, 100,100 0,100 0,0 100,0" />

The outer Grid must be sized to be a square so that the angles are 45 degrees when using the Stretch=Fill method. Here’s the whole thing with the cut-out in red to make it more obvious what is going on:

XAML Soundwave with clipping polygon

I then tried using Expression Blend to subtract the Polygon from the each of the Ellipses in turn, but it struggled to do what I wanted as it closed the new arcs giving the following effect (not sure why the right-hand sides look clipped):

Soundwave closed path

However, I was able to fix this by first going in and removing the final “z” from each newly created Path, which meant that the shapes were no longer closed. Then using the node editor tool, delete the extra node from each path. This gets us almost there, with the exception that Expression Blend has let the central circle drift slightly out of position:

XAML Soundwave in Blend

The resulting XAML isn’t quite so clean as our initial attempt, but it has the advantage of not relying on a white shape covering things up, meaning that it can be placed on top of any background image.

<Grid Width="100" Height="100">
  <Path HorizontalAlignment="Right" Margin="0,46.464,45,46.464" Width="5" Fill="Orange" Stretch="Fill" Data="M3.535533,0 C4.4403558,0.90481973 5,2.1548209 5,3.535533 C5,4.916245 4.4403558,6.1662469 3.535533,7.0710659 L0,3.535533 z"/>
  <Path Margin="0,38.661,34.5,38.339" Stretch="Fill" Stroke="Orange" StrokeThickness="5" Data="M11.338835,2.5 C13.600883,4.7620502 15,7.8870525 15,11.338835 C15,14.790609 13.600889,17.915615 11.338835,20.17767" Width="8.808" HorizontalAlignment="Right"/>
  <Path Margin="0,31.59,24.5,31.41" Stretch="Fill" Stroke="Orange" StrokeThickness="5" Data="M18.409901,2.5 C22.481602,6.5716963 25,12.19669 25,18.409901 C25,24.623108 22.481606,30.248114 18.409901,34.319801" Width="11.737" HorizontalAlignment="Right"/>
  <Path Margin="0,24.519,14.5,24.481" Stretch="Fill" Stroke="Orange" StrokeThickness="5" Data="M25.480968,2.5 C31.362314,8.3813419 35,16.506346 35,25.48097 C35,34.455612 31.362314,42.580608 25.480968,48.461937" Width="14.665" HorizontalAlignment="Right"/>
</Grid>

Interestingly, rendering this in Kaxaml, the inner segment now appears in the right place:

XAML Soundwave in Kaxaml

Thursday, 11 December 2008

List Filtering in WPF with M-V-VM

The first Windows Forms application I ever wrote was an anagram generator back in 2002. As part of my ongoing efforts to learn WPF and M-V-VM, I have been porting it to WPF, and adding a few new features along the way. Today, I wanted to add a TextBox that would allow you filter the anagram results to only show results that contained a specific sub-string.

The first task is to create a TextBox and a ListBox in XAML and set their binding properties. We want the filter to update every time a user types a character in, so we use the UpdateSourceTrigger to specify this.

<TextBox Margin="5" 
     Text="{Binding Path=Filter, UpdateSourceTrigger=PropertyChanged}" 
     Width="150" />
...
<ListBox Margin="5" ItemsSource="{Binding Phrases}" />

>Now we need to create the corresponding Filter and Phrases properties in our ViewModel. Then we need to get an ICollectionView based on our ObservableCollection of phrases. Once we have done this, we can attach a delegate to it that will perform our filtering. The final step is to call Refresh on the view whenever the user changes the filter box.

private ICollectionView phrasesView;
private string filter;

public ObservableCollection<string> Phrases { get; private set; }
                    
public AnagramViewModel()
{
   ...
   Phrases = new ObservableCollection<string>();
   phrasesView = CollectionViewSource.GetDefaultView(Phrases);
   phrasesView.Filter = o => String.IsNullOrEmpty(Filter) ? true : ((string)o).Contains(Filter); 
}

public string Filter 
{
   get
   {
      return filter;
   }
   set
   {
      if (value != filter)
      {
         filter = value;
         phrasesView.Refresh();
         RaisePropertyChanged("Filter");
      }
   }
}

And that's all there is to it. It can be kept nicely encapsulated within the ViewModel without the need for any code-behind in the view.

List filtering in WPF

Saturday, 8 November 2008

Model View View-Model (MVVM) in Silverlight

I watched an excellent screencast by Jason Dolinger recently, showing how to implement the Model View View-Model pattern in WPF. A lot of the documentation on the MVVM pattern seems unnecessarily complicated, but Jason's demonstration explains it very clearly.

The basic idea of MVVM is that we would like to use data binding to connect our View to our Model, but data binding is often tricky because our model doesn't have the right properties. So we create a View Model that is perfectly set up to have just the right properties for our View to bind to, and gets its actual data from the Model.

To try the technique out I decided to refactor a small piece of a Silverlight game I have written, called SilverNibbles, which is a port of the old QBasic Nibbles game, keeping the graphics fairly similar. At the top of the screen there is a scoreboard, whose role it is to keep track of the scores, number of lives, level, speed etc. This is the piece I will attempt to modify to use MVVM.

SilverNibbles Scoreboard

Let's have a look at the original code-behind for the Scoreboard user control. As you can see, there is a not lot going on here. Whenever a property on the Scoreboard is set, the appropriate changes are made to the graphical components. This is very similar to the typical code that would be written for a Windows Forms user control.

using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace SilverNibbles
{
    public partial class Scoreboard : UserControl
    {
        private int players;
        private int level;
        private int speed;
        private int jakeScore;
        private int sammyScore;

        public Scoreboard()
        {
            InitializeComponent();
        }

        public int Players
        {
            get 
            {
                return players; 
            }
            set 
            {
                players = value;
                jakeLives.Visibility = players == 2 ? Visibility.Visible : Visibility.Collapsed;
                jakeScoreLabel.Visibility = players == 2 ? Visibility.Visible : Visibility.Collapsed;
                jakeScoreTextBlock.Visibility = players == 2 ? Visibility.Visible : Visibility.Collapsed;
            }
        }

        public int Level
        {
            get
            {
                return level;
            }
            set
            {
                level = value;
                levelTextBlock.Text = value.ToString();
            }
        }

        public int Speed
        {
            get
            {
                return speed;
            }
            set
            {
                speed = value;
                speedTextBlock.Text = value.ToString();
            }
        }

        public int SammyScore
        {
            get
            {
                return sammyScore;
            }
            set
            {
                sammyScore = value;
                sammyScoreTextBlock.Text = value.ToString();
            }
        }

        public int JakeScore
        {
            get
            {
                return jakeScore;
            }
            set
            {
                jakeScore = value;
                jakeScoreTextBlock.Text = value.ToString();
            }
        }

        public int JakeLives
        {
            get
            {
                return jakeLives.Lives;
            }
            set
            {
                jakeLives.Lives = value;
            }
        }

        public int SammyLives
        {
            get
            {
                return sammyLives.Lives;
            }
            set
            {
                sammyLives.Lives = value;
            }
        }
    }
}

Instead of having all these properties, the Scoreboard will become a very simple view. Now the code-behind of Scoreboard is completely minimalised:

namespace SilverNibbles
{
    public partial class Scoreboard : UserControl
    {
        public Scoreboard()
        {
            InitializeComponent();
        }
    }
}

Now it is the job of whoever creates Scoreboard to give it a ScoreboardViewModel as its DataContext. It doesn't even have to be a ScoreboardViewModel either, so long as it has the appropriate properties. This means that a graphic designer could use an XML file instead to create dummy data to help while designing the appearance. Here's my code elsewhere in the project that sets up the data context of the Scoreboard with its View Model.

scoreData = new ScoreboardViewModel();
scoreboard.DataContext = scoreData;

Now I simply modify the scoreData object's properties, and the Scoreboard will update its view automatically.

What has changed in the Scoreboard user control's XAML? Well first, it has Binding statements for every property that gets its value from the view model. I was also able to remove all the x:Name attributes from the markup, which is a sign that MVVM has been done right. I also needed to make my Lives property on my LivesControl user control into a dependency property to allow it to accept the binding syntax as a value.

<UserControl x:Class="SilverNibbles.Scoreboard"
    xmlns="http://schemas.microsoft.com/client/2007" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:sn="clr-namespace:SilverNibbles"
     >
    <Grid x:Name="LayoutRoot" Background="LightYellow" ShowGridLines="False">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="20*" />
            <ColumnDefinition Width="35*" />
            <ColumnDefinition Width="25*" />
            <ColumnDefinition Width="10*" />
        </Grid.ColumnDefinitions>
        
        <!-- row 0 -->
        <TextBlock 
            Grid.Row="0" 
            Grid.Column="0"
            Text="High Score" 
            FontSize="18"
            FontFamily="teen bold.ttf#Teen" />
        <TextBlock 
            Grid.Row="0" 
            Grid.Column="1"
            Margin="5,0,5,0"
            Text="{Binding Record}" 
            FontSize="18"
            FontFamily="teen bold.ttf#Teen" />
        
        <TextBlock 
            Grid.Column="2" 
            Text="Level" 
            HorizontalAlignment="Right" 
            Margin="5,0,5,0"
            FontSize="18"
            FontFamily="teen bold.ttf#Teen" />
        <TextBlock 
            Grid.Column="3" 
            Margin="5,0,5,0"
            Text="{Binding Level}" 
            FontSize="18"
            FontFamily="teen bold.ttf#Teen" />
                
        <!-- row 1 -->        
        <TextBlock 
            Grid.Row="1"
            Text="Sammy" 
            FontSize="18"
            FontFamily="teen bold.ttf#Teen" />
       <sn:LivesControl
            Grid.Row="1"
            Grid.Column="1"
            HorizontalAlignment="Right"
           Fill="{StaticResource SammyBrush}"
           Lives="{Binding SammyLives}"
             />        
       <TextBlock 
            Grid.Row="1"
            Grid.Column="1"
            Margin="5,0,5,0"
            Text="{Binding SammyScore}" 
            FontSize="18"
            FontFamily="teen bold.ttf#Teen"
            />
        
       <TextBlock 
           Grid.Row="1" 
           Grid.Column="2" 
            Text="Speed" 
            HorizontalAlignment="Right" 
            Margin="5,0,5,0"
            FontSize="18"
            FontFamily="teen bold.ttf#Teen"
           />
        <TextBlock 
            Grid.Row="1"
            Grid.Column="3" 
            Margin="5,0,5,0"
            Text="{Binding Speed}" 
            FontSize="18"
            FontFamily="teen bold.ttf#Teen"
            />
        
        <!-- row 2 -->
        <TextBlock 
            Grid.Row="2"
            Grid.Column="0"
            Text="Jake" 
            FontSize="18"
            FontFamily="teen bold.ttf#Teen"
            Visibility="{Binding JakeVisible}"            
            />
        <sn:LivesControl
            Grid.Row="2"
            Grid.Column="1"
            HorizontalAlignment="Right"
            Fill="{StaticResource JakeBrush}"
            Visibility="{Binding JakeVisible}"
            Lives="{Binding JakeLives}"
             />
       <TextBlock 
            Grid.Row="2"
            Grid.Column="1"
            FontSize="18"
            Margin="5,0,5,0"
            Text="{Binding JakeScore}" 
            FontFamily="teen bold.ttf#Teen"
            Visibility="{Binding JakeVisible}"
           />
    </Grid>
</UserControl>

Here's what my ScoreboardViewModel looks like. The main thing to notice is that I have implemented the INotifyPropertyChanged interface. I rather lazily raise the changed event every time the setter is called irrespective of whether the value really changed. Notice also I have created a JakeVisible property. This highlights how the View Model can be used to create properties that are exactly what the View needs.

namespace SilverNibbles
{
    public class ScoreboardViewModel : INotifyPropertyChanged
    {
        private int players;
        private int level;
        private int speed;
        private int jakeScore;
        private int sammyScore;

        public int Players
        {
            get
            {
                return players;
            }
            set
            {
                players = value;
                RaisePropertyChanged("Players");
                RaisePropertyChanged("JakeVisible");
            }
        }

        public Visibility JakeVisible
        {
            get
            {
                return players == 2 ? Visibility.Visible : Visibility.Collapsed;
            }
        }

        public int Level
        {
            get
            {
                return level;
            }
            set
            {
                level = value;
                RaisePropertyChanged("Level");
            }
        }

        public int Speed
        {
            get
            {
                return speed;
            }
            set
            {
                speed = value;
                RaisePropertyChanged("Speed");
            }
        }

        public int SammyScore
        {
            get
            {
                return sammyScore;
            }
            set
            {
                sammyScore = value;
                RaisePropertyChanged("SammyScore");
            }
        }

        public int JakeScore
        {
            get
            {
                return jakeScore;
            }
            set
            {
                jakeScore = value;
                RaisePropertyChanged("JakeScore");
            }
        }

        int jakeLives;
        int sammyLives;

        public int JakeLives
        {
            get
            {
                return jakeLives;
            }
            set
            {
                jakeLives = value;
                RaisePropertyChanged("JakeLives");
            }
        }

        public int SammyLives
        {
            get
            {
                return sammyLives;
            }
            set
            {
                sammyLives = value;
                RaisePropertyChanged("SammyLives");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged(string property)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(property));
            }
        }
    }
}

I think there are alternatives to using INotifyPropertyChanged such as creating dependency properties or inheriting from DependencyObject. I don't know what the advantages or disadvantages of taking that approach would be. That is something for a future investigation.

There are lots of other parts of the SilverNibbles application that could be refactored to use this pattern, but that is also a task for another day. View the code at CodePlex, and play SilverNibbles here.

Tuesday, 14 October 2008

Styling a ListBox in Silverlight 2

Back in April, I started a series of posts here on how to completely customise the style of a ListBox in Silverlight 2 beta 1, including the scroll-bar styles. That was followed by a rather painful update to Silverlight 2 beta 2 (see Part 1, Part 2, Part 3). Now that Silverlight 2 has officially shipped, the code required one last update. Thankfully the changes were not so great this time. A few deprecated properties needed to be removed from the XAML, and some Duration attributes needed to be changed to GeneratedDuration, but that was about it.

There is some good news. From initial inspection it looks like the scroll-bar bug, where you saw a stretched horizontal scroll-bar behind your vertical scroll-bar has finally been fixed. And first impressions of the XAML preview in Visual Studio are good - you can actually see what it looks like without having to run the project.

What it hasn't fixed is my appalling colour selections and graphical design skills. I might take the time to work a bit more on the appearance of this thing now that I can be sure the syntax isn't going to change yet again.

Fully Styled Listbox in Silverlight 2

I've uploaded my example project which can be opened in Visual Studio 2008 (with Silverlight Tools) or in Expression Blend 2 SP1. Download it here.

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.

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...