Monday, June 02, 2008

Using the Google Chart API in a WPF application

If you haven't had a look at the Google Chart API I definitely recommend you do so. It can be a good, light alternative to investing in a more complete charting package. And honestly, most of the extra stuff offered by the 3rd party charting components out there is just eye candy that detracts from the information you chart should communicate.

So, with the Google Chart API you can easily put charts like this in your web application or page:

All you need to do is put an img on your page, and set it's src to a url that contains some Google Chart magic. The url for the chart above looks like this:

Granted, the query arguments are quite complex. But Google has some nice documentation for it, and in the end it's not so bad. Here's a simple example:

The url for this puppy is:

The way you talk to Google Chart is through a series of parameters, separated with an ampersand (&). The url above contains three parameters, as follows:

cht=lc Set the chart type to line chart.
chs=150x100 Set the size of the chart to a width of 150 and a height of 100.
chd=t:10,70,50,40,80,5,25,95 The data points for the chart, as comma-separated values.

So, as you can see, pretty simple stuff.

But what about using this in desktop applications, and WPF applications in particular? Since all Google Chart does is generates an image based on parameters you pass to it in the URL, it can be used in XAML through the Image control. Like this:

<Image Width="150" Height="100" Source="http://chart.apis.google.com/chart?cht=lc&amp;chs=150x100&amp;chd=t:10,70,50,40,80,5,25,95"/>

Note that the ampersands (&) had to be encoded as "&amp;" in the XAML.

Here's a screenshot of the XAML above being used in a little application:

image

Ok, so that works nicely. But dealing with the url, and in particular passing the data point for the chart to the API that way is a little bit ugly, and certainly feels very crude compared to how you usually do things in WPF. So what can be done to improve this? One thing that comes to mind is using a value converter to convert a list of values to the url-format that Google Chart wants. Here's an example of that:

[ValueConversion(typeof(IList), typeof(string))]
    public class ListToTextEncodingConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            IList valueList = (IList)value;
            string baseUrl = (string)parameter;
            if (valueList == null || baseUrl == null)
                return string.Empty;
 
            string valueString = "";
            CultureInfo usCulture = new CultureInfo("en-us");
 
            foreach (object item in valueList)
            {
                double? val = item as double?;
                if (val.HasValue)
                {
                    if (valueString.Length > 0)
                        valueString += ",";
                    valueString += val.Value.ToString(usCulture.NumberFormat);
                }
            }
 
            return baseUrl + "&chd=t:" + valueString;
        }
 
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return Binding.DoNothing;
        }
    }

It accepts something that implements IList, and tries to find doubles in it, and constructs the url string from that. In addition it accepts the rest of the parameters for Google Chart as a parameter.

And here's a snippet of XAML that uses that value converter to display a chart:

<Window x:Class="WpfSample.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfSample"
    xmlns:converters="clr-namespace:WpfSample.ValueConverters.ChartDataEncodingConverters"
    Title="Window1" Height="300" Width="300">
    <Window.Resources>
        <local:SampleData x:Key="SampleData"/>
        <converters:ListToTextEncodingConverter x:Key="ListToTextEncodingConverter"/>
    </Window.Resources>
    <Grid>
        <Image Width="250" Height="150"
               Source="{Binding Source={StaticResource SampleData}, 
                                Converter={StaticResource ListToTextEncodingConverter},
                                ConverterParameter='http://chart.apis.google.com/chart?cht=lc&amp;chs=250x150&amp;chds=0,30'}"/>
    </Grid>
</Window>

Oh, and please not that the ConverterParameter seems to screw up the WPF designer in Visual Studio 2008, I haven't looked into whether anything can be done about that. But it compiles and runs fine.

SampleData is just a set of values, you could of course use whatever you want for your data source, perhaps pull something from a database and use Linq to project it into a list of double values. Here's the class that creates the sample data:

public class SampleData : List<double>
{
    public SampleData()
    {
        Add(10);
        Add(20);
        Add(15.2);
        Add(12);
        Add(21);
        Add(17);
        Add(11);
        Add(8);
        Add(12);
        Add(18);
    }
}

Here's what the application looks like:

image

Simple, but I guess you get the idea.

And finally, is it bad to use a web-based API for charting in a desktop application? Not really, in my opinion. Most desktop apps need to talk to servers anyway, and grabbing a few charts over the web is not going to be a significant performance issue.

Oh, one more thing. Of course this works great in Silverlight 2.0 as well.

Wednesday, February 20, 2008

A rating control in XAML

For a pet project I've been working on in WPF I needed a control that would let the user provide a rating between 0 and 5, for example like the one in iTunes and on numerous web sites:

image

After googling a bit I found some samples, including this one posted by Zhou Yong on msdn forums.

However, there was some issues with the functionality (how it reacts to clicks when you click a star that is already lit) and the visuals (the starts themselves and some other minor issues relating to sizing and scaling) so I decided to touch it up a bit. The end result looks like this:

image

It (like the original) is based on toggle buttons with a new template to redo the visuals. It quite nicely illustrates the power of templates in WPF, and what can be achieved just with a little bit of code in a user control. The template looks like this:

   1: <ControlTemplate x:Key="starTemplate"  TargetType="{x:Type ToggleButton}">
   2:     <Viewbox>
   3:         <Path Name="star" Fill="Gray" Data="F1 M 145.637,174.227L 127.619,110.39L 180.809,70.7577L 114.528,68.1664L 93.2725,5.33333L 70.3262,67.569L 4,68.3681L 56.0988,109.423L 36.3629,172.75L 91.508,135.888L 145.637,174.227 Z"/>
   4:     </Viewbox>
   5:     <ControlTemplate.Triggers>
   6:         <Trigger Property="IsChecked" Value="True">
   7:             <Setter TargetName="star" Property="Fill" Value="White"/>
   8:         </Trigger>
   9:     </ControlTemplate.Triggers>
  10: </ControlTemplate>

When working on this I realized how hard it is to find coordinates for a star. The ones I ended up using were lifted from a blog post by Jaime Rodriguez. They are a little bit crooked, but they were the best I could find.

 

Of course the whole control should ideally be implemented as a custom control so that it properly supports styles and templating, but I'll leave that exercise for later.

The code for this sample can be downloaded from here.

Wednesday, December 19, 2007

A WPF desktop alert control

Desktop alerts are a great feature in many applications. Whenever something important (and perhaps urgent) happens a nice little window appears at the bottom right corner of the screen and tells you about it. Everyone is familiar with what they look like in Outlook:

A desktop alert / notification popup

Even though these little notification popup windows can be quite annoying if used incorrectly (I'm thinking Messenger and a multitude of alerts here), they can most certainly be a good tool for making sure users are made aware of pressing issues they should be reacting to.

A few years ago I created a custom control for showing alerts like this in Windows Forms. However, I did find that different applications needed different types of alerts, with different functionality in them. Satisfying this need in Windows Forms always meant doing a whole bunch of coding and deriving a new custom control from my initial one. Now when Visual Studio 2008 was released, with decent support for Windows Presentation Foundation, I figured it would be a good time to revisit the old control and see how it could be implemented in WPF.

Since the thinking around controls in WPF is quite different from Windows Forms, and there is such a strong emphasis on the separation of behavior and appearance. First of all, is it even necessary to create a special control, or would a control template applied to for example a window be sufficient?

After thinking about it I came to the conclusion that there's enough behavior associated with a desktop alert to warrant making a custom control. It needs to be shown only for a short while, unless the user activates it (then it should stay visible until closed by the user). There can also be several alerts visible at the same time, and then they should be laid out in such a way that they don't obscure each other. Trying to achieve this with just a repeatable template would be difficult, but it should be possible to standardize these through a custom control. Some of the thinking that went into this was outlined in my previous post, "Closing a WPF custom control from a storyboard". At the same time I decided to create a small sample of this, and that is what you're reading now. :) The code itself is downloadable, look at the bottom of this post for a link.

Creating a desktop alert sample

Different applications need different looking alerts. The one used by Outlook contains both information about the email (sender, subject and a snippet of the email body) as well as commands you can perform on the email (delete, flag as urgent and so forth). Another application might want to provide links that when clicked open up different views of data in the application, depending on what the alert was about. These all sound like things that would be easy to provide specifically for each application by just showing application-specific XAML content in the alert window.

The next challenge was which existing WPF control should I base my control on, and how the control should be invoked and used. I decided to base it on Window, and allow deriving from the control to create other alert windows with different content in them like this:

(I'm sorry about the uneven lines and shaky text in the image above, but I'm writing this on an airplane...)

DesktopAlertBase is a basic alert window, that you derive from to create specific alert window types. In the diagram above there are two sample derived classes, SimpleAlert (which has a title and a text body) and ImageAlert (which has a title and shows an image as its body). Not very realistic (or good) alerts, but they will have to do for this sample.

I decided to go for derived classes despite the fact that this complicates using the control. But in the end it is actually the derived classes that should be reused, and not the DesktopAlertBase. Its purpose is to simplify the development of desktop alerts, not to be the actual alert that is used in an application. The fact is that the reason for doing this is that I didn't figure out a way to solve this with a control that could be placed inside a Window, since it is the actual window that has the desktop alert behavior. I may change this completely later, and then I'll post a new sample. :)

In Visual Studio I set up a project structure like this:

Project structure in Visual Studio 2008

The solution is split in two projects. The first, DesktopAlert, contains the reusable custom control and its default WPF template. It handles the fading in and out of the alert window, closes the alert when it's been up for a while, and also allows the user to close the alert window.

The second project, SandboxApplication, is a demo application that contains two derived classes (ImageAlert and SimpleAlert) and a little application that launches these.

The DesktopAlertBase class is quite simple. It contains a couple of defaults for the alerts, and the necessary behavior related to opening and closing the alerts. The code for it looks like this:

   1: public abstract class DesktopAlertBase : Window
   2: {
   3:     private DoubleAnimation _fadeInAnimation;
   4:     private DoubleAnimation _fadeOutAnimation;
   5:     private DispatcherTimer _activeTimer;
   6:  
   7:     static DesktopAlertBase()
   8:     {
   9:         DefaultStyleKeyProperty.OverrideMetadata(typeof(DesktopAlertBase), new FrameworkPropertyMetadata(typeof(DesktopAlertBase)));
  10:     }
  11:  
  12:     // We need to add a public constructor so that we can set up our
  13:     // new window properly
  14:     public DesktopAlertBase()
  15:     {
  16:         Visibility = Visibility.Visible;
  17:  
  18:         // Let's set up a dummy window size
  19:         Width = 350;
  20:         Height = 75;
  21:  
  22:         // Set some default properties for the alerts.
  23:         // These can be changed by the derived alerts.
  24:         ShowInTaskbar = false;
  25:         WindowStyle = WindowStyle.None;
  26:         ResizeMode = ResizeMode.NoResize;
  27:         Topmost = true;
  28:         AllowsTransparency = true;
  29:         Opacity = 0.8;
  30:         BorderThickness = new Thickness(1);
  31:         BorderBrush = Brushes.Black;
  32:         Background = Brushes.White;
  33:  
  34:         // Set up the fade in and fade out animations
  35:         _fadeInAnimation = new DoubleAnimation();
  36:         _fadeInAnimation.From = 0;
  37:         _fadeInAnimation.To = 0.8;
  38:         _fadeInAnimation.Duration = new Duration(TimeSpan.Parse("0:0:1.5"));
  39:  
  40:         // For the fade out we omit the from, so that it can be smoothly initiated
  41:         // from a fade in that gets interrupted when the user wants to close the window
  42:         _fadeOutAnimation = new DoubleAnimation();
  43:         _fadeOutAnimation.To = 0;
  44:         _fadeOutAnimation.Duration = new Duration(TimeSpan.Parse("0:0:1.5"));
  45:  
  46:         Loaded += new RoutedEventHandler(DesktopAlertBase_Loaded);
  47:     }
  48:  
  49:     // When the template is applied to the control, look for a button called "PART_CloseButton".
  50:     // If such a button exists, hook up an event handler so that the alert can be closed when
  51:     // the user clicks the button.
  52:     public override void OnApplyTemplate()
  53:     {
  54:         ButtonBase closeButton = Template.FindName("PART_CloseButton", this) as ButtonBase;
  55:         if (closeButton != null)
  56:             closeButton.Click += new RoutedEventHandler(closeButton_Click);
  57:     }
  58:  
  59:     // In the Loaded-event handler we need to figure out where to place the alert window.
  60:     // Currently they are all placed in the bottom right corner, and can not take into
  61:     // account other currently open alert windows.
  62:     void DesktopAlertBase_Loaded(object sender, RoutedEventArgs e)
  63:     {
  64:         // Figure out where to place the window based on the current screen resolution
  65:         Rect workAreaRectangle = System.Windows.SystemParameters.WorkArea;
  66:         Left = workAreaRectangle.Right - Width - BorderThickness.Right;
  67:         Top = workAreaRectangle.Bottom - Height - BorderThickness.Bottom;
  68:  
  69:         _fadeInAnimation.Completed += new EventHandler(_fadeInAnimation_Completed);
  70:  
  71:         // Start the fade in animation
  72:         BeginAnimation(DesktopAlertBase.OpacityProperty, _fadeInAnimation);
  73:     }
  74:  
  75:     // When the fade in animation is completed, start another timer that fires an event when the
  76:     // window has been visible for 10 seconds
  77:     void _fadeInAnimation_Completed(object sender, EventArgs e)
  78:     {
  79:         _activeTimer = new DispatcherTimer();
  80:         _activeTimer.Interval = TimeSpan.Parse("0:0:10");
  81:  
  82:         // Attach an anonymous method to the timer so that we can start fading out the alert
  83:         // when the timer is done.
  84:         _activeTimer.Tick += delegate(object obj, EventArgs ea) { FadeOut(); };
  85:  
  86:         _activeTimer.Start();
  87:     }
  88:  
  89:     // Set up the fade out animation, and hook up an event handler to fire when it is completed.
  90:     private void FadeOut()
  91:     {
  92:         // Attach an anonymous method to the Completed-event of the fade out animation
  93:         // so that we can close the alert window when the animation is done.
  94:         _fadeOutAnimation.Completed += delegate(object sender, EventArgs e) { Close(); };
  95:  
  96:         BeginAnimation(DesktopAlertBase.OpacityProperty, _fadeOutAnimation, HandoffBehavior.SnapshotAndReplace);
  97:     }
  98:  
  99:     // If the user clicks the close button, stop the timer that counts how long the alert
 100:     // window has been open, and start the fading out of the window.
 101:     void closeButton_Click(object sender, RoutedEventArgs e)
 102:     {
 103:         _activeTimer.Stop();
 104:         FadeOut();
 105:     }

The template for the control (in Generic.xaml) is quite simple. It contains a template for the close button, and a template for the actual control. The control has a title area, and a content area in which the content of the derived class is displayed.

   1: <ResourceDictionary
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:local="clr-namespace:DesktopAlert">
   5:  
   6:     <ControlTemplate x:Key="CloseButtonTemplate" TargetType="{x:Type Button}">
   7:         <Grid Background="Transparent">
   8:             <Viewbox>
   9:                 <ContentPresenter/>
  10:             </Viewbox>
  11:         </Grid>
  12:         <ControlTemplate.Triggers>
  13:             <Trigger Property="IsMouseOver" Value="True">
  14:                 <Setter Property="BitmapEffect">
  15:                     <Setter.Value>
  16:                         <OuterGlowBitmapEffect GlowSize="12" GlowColor="White"/>
  17:                     </Setter.Value>
  18:                 </Setter>
  19:             </Trigger>
  20:             <Trigger Property="IsPressed" Value="True">
  21:                 <Setter Property="BitmapEffect">
  22:                     <Setter.Value>
  23:                         <OuterGlowBitmapEffect GlowSize="12" GlowColor="DarkGray"/>
  24:                     </Setter.Value>
  25:                 </Setter>
  26:             </Trigger>
  27:         </ControlTemplate.Triggers>
  28:     </ControlTemplate>
  29:     
  30:     <Style TargetType="{x:Type local:DesktopAlertBase}">
  31:         <Setter Property="Template">
  32:             <Setter.Value>
  33:                 <ControlTemplate TargetType="{x:Type local:DesktopAlertBase}">
  34:                     <Border Background="{TemplateBinding Background}"
  35:                             BorderBrush="{TemplateBinding BorderBrush}"
  36:                             BorderThickness="{TemplateBinding BorderThickness}"
  37:                             SnapsToDevicePixels="True">
  38:                         <Grid>
  39:                             <Grid.RowDefinitions>
  40:                                 <RowDefinition Height="20"/>
  41:                                 <RowDefinition/>
  42:                             </Grid.RowDefinitions>
  43:                             <Grid Grid.Row="0" Background="LightGray">
  44:                                 <Grid>
  45:                                     <Grid.ColumnDefinitions>
  46:                                         <ColumnDefinition/>
  47:                                         <ColumnDefinition Width="20"/>
  48:                                     </Grid.ColumnDefinitions>
  49:                                     <TextBlock Grid.Column="0" Margin="5, 0, 0, 0" FontWeight="Bold" FontSize="12" 
  50:                                                Opacity="1.0" Text="{TemplateBinding Title}"/>
  51:                                     <Button x:Name="PART_CloseButton" 
  52:                                                 Grid.Column="1"
  53:                                                 HorizontalAlignment="Right" VerticalAlignment="Top"
  54:                                                 Template="{StaticResource CloseButtonTemplate}"
  55:                                                 Width="18" Height="18" 
  56:                                                 FontWeight="Bold"
  57:                                                 Foreground="#FF222222"
  58:                                                 >x</Button>
  59:                                 </Grid>
  60:                             </Grid>
  61:                             <ContentControl Grid.Row="1" Content="{TemplateBinding Content}"/>
  62:                         </Grid>
  63:                     </Border>
  64:                 </ControlTemplate>
  65:             </Setter.Value>
  66:         </Setter>
  67:     </Style>
  68: </ResourceDictionary>

The derived classes contain very little code. The SimpleAlert type code behind implements a property called Message that contains the body of the alert. The XAML for SimpleAlert looks like this:

   1: <da:DesktopAlertBase x:Class="SandboxApplication.Alerts.SimpleAlert"
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:da="clr-namespace:DesktopAlert;assembly=DesktopAlert"
   5:     Title="SimpleAlert">
   6:     <Grid>
   7:         <TextBlock FontSize="12" Padding="5" TextWrapping="Wrap"
   8:                    Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=Message}"/>
   9:     </Grid>
  10: </da:DesktopAlertBase>

As you can see, all it does is display a TextBlock that is data bound to the Message property. And why a relative binding? The truth is I was too tired to figure out another way of doing it at the time. :)

Let's have a look at what the alerts actually like. Here's the SimpleAlert:

simple alert

Not very pretty yet, but we'll get to that in another post. And how much code was needed to launch the alert? This much:

   1: SimpleAlert simpleAlert = new SimpleAlert();
   2: simpleAlert.Title = "My alert has a sample title";
   3: simpleAlert.Message = "This is my message! If it's long enough it will wrap. Is it long enough now?";

The ImageAlert is equally simple, so I won't post the code here. You can download it and have a look if you want.

Summing it all up

The code in this sample is by no means perfect. It's intended for exploring this way of implementing a desktop alert.

At this point I'm still not convinced that the approach outlined here is the right one, but at least it seems good enough for me now. I'll have to work on it a bit more, and try it out in a real project, before I know for sure. I'll also try to put together some proper alerts and make some nice designs for them to see what this will look like in real life. Also, this implementation doesn't yet support simultaneously appearing windows (they appear on top of each other), I hope to have time to fix that in a few days.

Please download the source code for this sample and have a look if you're interested. Please note that it comes with no warranties at all. If you like it (or do something with it), please let me know. You need Visual Studio 2008 to compile the sample (but compiled executables are included in the zip).

Saturday, July 28, 2007

Problems installing Windows Mobile 5.0 SDK R2 for Visual Studio 2007 Beta 2

I've had some trouble installing Visual Studio 2008 Beta 2 (I'm installing the Visual Studio Team System 2008 version). The installer fails when trying to install Windows Mobile 5.0 SDK R2 for Pocket PC, and I got the following error message:

"Installation failed for component Windows Mobile 5.0 SDK R2 for Pocket PC. MSI returned error code 1603"

What turned out to be the problem was that I already had a version of the Windows Mobile 5.0 SDK installed, namely the one that works with Visual Studio 2005. So apparently that one had to be uninstalled first. This does not seem to be mentioned in the installation instructions, or then I'm just crap at finding it.

Once I uninstalled the old version I tried going back and repairing the installation, but that didn't seem to try to reinstall the missing bits. What I ended up having to do was to go into the setup for VS 2008, select "Change or Remove Visual Studio 2008", and once the setup wizard has loaded press next and select "Add or Remove Features". The only missing bits were Crystal Reports for Visual Studio Beta 2. When I selected those, and started the installation, it also installed the missing stuff for Windows Mobile (and a lot of other things it seemed it had left uninstalled becuse of the error as well).

Friday, July 27, 2007

Visual Studio 2008 Beta 2 released

I just noticed on ScottGu's Blog that Beta 2 of Visual Studio 2008 (Orcas) has been released, and since I nowadays am an absolute WPF and LINQ fanboy I can't wait to try it out. I'll blog more once I've got the bits installed.

Saturday, June 30, 2007

Learning XAML

Lately I've been busy learning about WPF and XAML, and I have to say that cool doesn't even begin to describe it. After TechEd 2007 I installed the Visual Studio Orcas Beta 1, and it's been a pretty smooth ride so far.

One of the first things I've been playing around with is adding some better visualizations to some existing stuff we're doing at work, and for that I've been trying to figure out what can be done with the ListView control in WPF so that I won't have to resort to third party grids immediately. The reason for that is that first of all I want to really understand what WPF can do for me before I resort to third party stuff that tries to wrap everything up nicely for me, and in addition to that I've had a lot of really bad experiences with the different control packages out there (I could write an essay on Infragistics, but I think I won't).

One pretty good source on how to do cell templates for the list view I found was on Corrado's Blogs. It's in Italian, but the pictures and XAML speak for themselves.

One "funny" thing that happened was when I in the Orcas Beta 1 was trying to create a column that would show a checkbox in the cell. I was trying to set the cell template to bind to a property of the object that was being shown in the list, while having the designer open (Orcas shows both the design surface and the XAML at the same time by default). I was still typing the binding string for the IsChecked property of my check box-based data template, and switched to another window to check what I was supposed to bind to, switched back and got the following error message:

image

Note the nice way it shows the actual exception. It has a scrollbar for the completely unnecessary error message telling me that the designer just went completely insane, but the actual error message it refuses to even wrap. Fair enough, I didn't have a correct string set up. But getting rid of this windows turned out to be quite hard. I clicked OK, and it popped up again. One more OK just resulted in one more window. After about 20 tries I realised that this was quite a persistent little bugger, and tried to figure out what I could do to get around this.

What I ended up having to do is click OK, type one or two characters before it popped up again, click OK, type some more, and that way I was able to enter "true" into IsChecked, and finally it stopped.

Chardonnay helped me calm down.