Delay's Blog

Silverlight, WPF, Windows Phone, Web Platform, .NET, and more...

Posts
  • Delay's Blog

    Turn your head and check out this post [How to: Easily rotate the axis labels of a Silverlight/WPF Toolkit chart]

    • 46 Comments

    When someone asked me how to rotate the axis labels of a chart from the Data Visualization package of the Silverlight Toolkit/WPF Toolkit earlier today, I realized it was time for a quick blog post. Because when I've answered a question two or three times, it's usually a pretty good sign that I'll keep on answering it for some time. I usually try to head that kind of thing off at the pass, so here's my post on the topic for the benefit of future generations. :)

    The typical scenario here is that someone has a chart and it's working well, but their axis labels are very long and end up overlapping - even after the default axis behavior of putting them in alternating rows to prevent such a problem kicks in:

    Overlapping axis labels

     

    The typical solution is to rotate the axis labels - and it's easy once you know where to look. The key here is to customize the Template of the AxisLabel instances that are used to render the labels. And it's quite simple to do so by providing a Style with a Template Setter for the AxisLabelStyle property of the Axis subclass in question:

    Rotated axis labels on WPF

    Yeah, it looks great on paper; but that description was a mouthful...

    It's probably easier to understand in XAML - here's the complete code for the sample above with the interesting part highlighted:

    <Window x:Class="WpfApplication1.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:charting="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit"
            xmlns:sys="clr-namespace:System;assembly=mscorlib"
            xmlns:spec="clr-namespace:System.Collections.Specialized;assembly=System"
            Title="RotatedAxisLabelsWPF"
            Width="500"
            Height="350">
        <Grid>
            <charting:Chart
                Title="Animals With Long Names">
                <charting:ColumnSeries
                    Title="Character count"
                    DependentValueBinding="{Binding Length}"
                    IndependentValueBinding="{Binding}">
                    <charting:ColumnSeries.ItemsSource>
                        <spec:StringCollection>
                            <sys:String>Bumblebee</sys:String>
                            <sys:String>Caterpillar</sys:String>
                            <sys:String>Hippopotamus</sys:String>
                            <sys:String>Rhinoceros</sys:String>
                            <sys:String>Velociraptor</sys:String>
                        </spec:StringCollection>
                    </charting:ColumnSeries.ItemsSource>
                    <charting:ColumnSeries.IndependentAxis>
                        <charting:CategoryAxis
                            Orientation="X">
                            <charting:CategoryAxis.AxisLabelStyle>
                                <Style TargetType="charting:AxisLabel">
                                    <Setter Property="Template">
                                        <Setter.Value>
                                            <ControlTemplate TargetType="charting:AxisLabel">
                                                <TextBlock Text="{TemplateBinding FormattedContent}">
                                                    <TextBlock.LayoutTransform>
                                                        <RotateTransform Angle="-60"/>
                                                    </TextBlock.LayoutTransform>
                                                </TextBlock>
                                            </ControlTemplate>
                                        </Setter.Value>
                                    </Setter>
                                </Style>
                            </charting:CategoryAxis.AxisLabelStyle>
                        </charting:CategoryAxis>
                    </charting:ColumnSeries.IndependentAxis>
                </charting:ColumnSeries>
            </charting:Chart>
        </Grid>
    </Window>

    Like I said, it's all pretty standard stuff once you know where to look. Of course, you can rotate the labels all the way to 90 degrees if you want them to take the least amount of space possible. But 60 degrees seemed like a suitably rakish angle. ;)

     

    Unfortunately, we can't declare "Mission Accomplished" quite yet... While the Data Visualization assembly itself works exactly the same on WPF and Silverlight, the platforms themselves aren't identical quite yet. Specifically, there's no support for LayoutTransform in Silverlight (and RenderTransform is simply not appropriate here). Fortunately, I've filled the LayoutTransform gap with my LayoutTransformer class - and it's already part of the Silverlight Toolkit!

    The syntax changes just a bit, but the concept is exactly the same:

    <UserControl x:Class="SilverlightApplication1.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:charting="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:toolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"
        xmlns:layout="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Layout.Toolkit">
        <Grid>
            <charting:Chart
                Title="Animals With Long Names">
                <charting:ColumnSeries
                    Title="Character count"
                    DependentValueBinding="{Binding Length}"
                    IndependentValueBinding="{Binding}">
                    <charting:ColumnSeries.ItemsSource>
                        <toolkit:ObjectCollection>
                            <sys:String>Bumblebee</sys:String>
                            <sys:String>Caterpillar</sys:String>
                            <sys:String>Hippopotamus</sys:String>
                            <sys:String>Rhinoceros</sys:String>
                            <sys:String>Velociraptor</sys:String>
                        </toolkit:ObjectCollection>
                    </charting:ColumnSeries.ItemsSource>
                    <charting:ColumnSeries.IndependentAxis>
                        <charting:CategoryAxis
                            Orientation="X">
                            <charting:CategoryAxis.AxisLabelStyle>
                                <Style TargetType="charting:AxisLabel">
                                    <Setter Property="Template">
                                        <Setter.Value>
                                            <ControlTemplate TargetType="charting:AxisLabel">
                                                <layout:LayoutTransformer>
                                                    <layout:LayoutTransformer.LayoutTransform>
                                                        <RotateTransform Angle="-60"/>
                                                    </layout:LayoutTransformer.LayoutTransform>
                                                    <TextBlock Text="{TemplateBinding FormattedContent}"/>
                                                </layout:LayoutTransformer>
                                            </ControlTemplate>
                                        </Setter.Value>
                                    </Setter>
                                </Style>
                            </charting:CategoryAxis.AxisLabelStyle>
                        </charting:CategoryAxis>
                    </charting:ColumnSeries.IndependentAxis>
                </charting:ColumnSeries>
            </charting:Chart>
        </Grid>
    </UserControl>

    Mission accomplished:

    Rotated axis labels on Silverlight

     

    There you have it: AxisLabelStyle is your new best friend. A friend with benefits, one might say, because there are other cool things you can do by customizing the AxisLabel Style.

    So please: go forth and enjoy your new friend!

  • Delay's Blog

    Bigger isn't always better [How to: Resize images without reloading them with WPF]

    • 5 Comments

    I've been doing some work with Windows Presentation Foundation lately and came across a scenario where an application needed to load a user-specified image and display it at a fairly small size for the entire life of the application. Now, WPF makes working with images easy and I could have simply used the Image class's automatic image scaling and moved on. But it seemed wasteful for the application to keep the entire (arbitrarily large!) image in memory forever when it was only ever going to be displayed at a significantly reduced size...

    What I really wanted was a way to shrink the source image down to the intended display size so the application wouldn't consume a lot of memory storing pixels that would never be seen. The WPF documentation notes that the most efficient way to load an image at reduced size is to set Image's DecodePixelWidth/DecodePixelHeight properties prior to loading it so WPF can decode the original image to the desired dimensions as part of the load process. However, the comments in one of the overview's samples explain why this isn't suitable for the aforementioned scenario:

    // To save significant application memory, set the DecodePixelWidth or
    // DecodePixelHeight of the BitmapImage value of the image source to the desired
    // height or width of the rendered image. If you don't do this, the application will
    // cache the image as though it were rendered as its normal size rather then just
    // the size that is displayed.
    // Note: In order to preserve aspect ratio, set DecodePixelWidth or
    // DecodePixelHeight but not both.

    Basically, the problem with this approach occurs when an application doesn't know the aspect ratio of the image before loading it: the application doesn't know whether to set DecodePixelWidth or DecodePixelHeight to constrain the larger dimension. If the application picks the right one (width vs. height), then the image will be properly resized to fit within the bounds of the application - but if it picks the wrong one, then the image will be resized some but will still be unnecessarily large (though less unnecessarily large than before!). While the application could arbitrarily pick one dimension to constrain, load the image, check if it guessed correctly, and reload the image with the other constraint when necessary, I was looking for something a little more deterministic. After all, sometimes you get only one chance to load an image - or someone else loads it for you - or the cost of loading it a second time is prohibitive, so it's nice to have a way to dynamically resize an already-loaded image.

    After a search of the documentation didn't turn up anything promising, I wrote a small helper function using the handy RenderTargetBitmap class to generate a new, properly sized image based on the original. The code for that method ended up being fairly simple:

    /// <summary>
    ///
    Creates a new ImageSource with the specified width/height
    /// </summary>
    ///
    <param name="source">Source image to resize</param>
    ///
    <param name="width">Width of resized image</param>
    ///
    <param name="height">Height of resized image</param>
    ///
    <returns>Resized image</returns>
    ImageSource CreateResizedImage(ImageSource source, int width, int height)
    {
      
    // Target Rect for the resize operation
      Rect rect = new Rect(0, 0, width, height);

      
    // Create a DrawingVisual/Context to render with
      DrawingVisual drawingVisual = new DrawingVisual();
      
    using (DrawingContext drawingContext = drawingVisual.RenderOpen())
      {
        drawingContext.DrawImage(source, rect);
      }

      
    // Use RenderTargetBitmap to resize the original image
      RenderTargetBitmap resizedImage = new RenderTargetBitmap(
          (
    int)rect.Width, (int)rect.Height,  // Resized dimensions
          96, 96,                             // Default DPI values
          PixelFormats.Default);              // Default pixel format
      resizedImage.Render(drawingVisual);

      
    // Return the resized image
      return resizedImage;
    }

    [Note: If you want to see this code in action, you can download the complete source code for a sample application (including Visual Studio 2008 solution/project files) that's attached to this post (click the WpfResizeImageSample.zip link below).]

    Basically, the CreateResizedImage method works by creating a new image of exactly the size specified and then drawing the original image onto the new, blank "canvas". WPF automatically scales the original image during the drawing operation, so the resulting image ends up being exactly the right (smaller) size. All that remains is for the calling application to do a bit of math on the original image's dimensions to determine how to scale it, pass that information along to CreateResizedImage to get back a properly sized image, and then discard the large original image. It's that easy.

    WPF and XAML make it easy to author compelling user interfaces. But sometimes it's worth a little extra effort to optimize some aspect of the user experience. So if you're looking to trim the fat from some of your in-memory images, consider something like CreateResizedImage to help you out!

  • Delay's Blog

    Confessions of a ListBox groupie [Using IValueConverter to create a grouped list of items simply and flexibly]

    • 50 Comments

    A customer recently asked how to implement a simple "grouped ListBox" experience in Silverlight (now available in desktop, mobile, and extra crispy flavor!), so I dashed off this sample to show one way that's pretty easy to work with.

    Well, actually, the first thing I did was ask if DataGrid (which supports grouping natively) was an option - but the customer felt strongly about using a ListBox, so here we are...

    The core of my solution is a custom IValueConverter implementation. If you've read my blog much, you were probably expecting that because I tend to be a pretty big fan. As usual, IValueConverter is convenient because it allows us to easily transform the source data into something that looks how we want without needing to modify the actual data source or values. In fact, the rest of the application doesn't really need to know what's going on - this is a (mostly) UI-only solution.

    Okay, not needing to modify the original data is a nice advantage. What else would we like to see in a good solution? Well, it would be nice if it were easy to customize the appearance of items and their group headers without writing any code. And it would be nice if the grouping logic were flexible enough to allow grouping on any criteria (ex: value of a property, value ranges, first letter of name, etc.). And of course we want the designer to have the flexibility to hook everything up in XAML.

     

    That seems like a pretty reasonable list of requirements - and we can handle them all without a problem. But first, let's see it in action:

    GroupingItemsControlConverter sample

    On the left is the original data in a simple ItemsControl, in the center is a grouped version of that same data (just like we wanted!), and on the right is the same grouping of that data - this time a little more fancy and in a ListBox so the items are selectable! The XAML for the middle example looks like this:

    <Grid.Resources>
        <delay:GroupingItemsControlConverter x:Key="GroupingItemsControlConverter"/>
    
        <delay:GroupingItemsControlConverterParameters x:Key="SimpleGroupingItemsControlConverterParameter">
            <delay:GroupingItemsControlConverterParameters.GroupSelector>
                <local:AnimalSpeciesGroupSelector/>
            </delay:GroupingItemsControlConverterParameters.GroupSelector>
    
            <delay:GroupingItemsControlConverterParameters.GroupHeaderTemplate>
                <DataTemplate>
                    <ContentControl Content="{Binding}" FontWeight="Bold"/>
                </DataTemplate>
            </delay:GroupingItemsControlConverterParameters.GroupHeaderTemplate>
    
            <delay:GroupingItemsControlConverterParameters.ItemTemplate>
                <DataTemplate>
                    <ContentControl Content="{Binding Name}" Padding="8 0 0 0"/>
                </DataTemplate>
            </delay:GroupingItemsControlConverterParameters.ItemTemplate>
        </delay:GroupingItemsControlConverterParameters>
    
    </Grid.Resources>
    
    <!-- ... -->
    
    <ItemsControl
        ItemsSource="{Binding Converter={StaticResource GroupingItemsControlConverter},
            ConverterParameter={StaticResource SimpleGroupingItemsControlConverterParameter}}"/>

     

    [Click here to download the complete source code for the GroupingItemsControlConverter sample as a Silverlight 4 Visual Studio 2010 solution.]

     

    How does it work? Fairly simply, actually! Once parameters have been validated, the GroupingItemsControlConverter class makes a call to Linq's GroupBy extension method using the custom grouping method specified and follows with a call to the OrderBy extension method. The results are then output as a sequence of ContentControl instances with a custom DataTemplate applied according to whether each thing is a group header or an item. This pattern should seem pretty familiar; it's the standard ItemsControl model mixed together with something kind of like implicit DataTemplates. The GroupingItemsControlConverterParameters class lets you specify the GroupHeaderTemplate, the ItemTemplate, and an class implementing the IGroupingItemsControlConverterSelector interface. And don't worry, the custom implementation of that interface is quite trivial - here's what the sample application uses:

    // Simple IGroupingItemsControlConverterSelector implementation for grouping by an Animal's species
    public class AnimalSpeciesGroupSelector : IGroupingItemsControlConverterSelector
    {
        public Func<object, IComparable> GetGroupSelector()
        {
            return (o) => ((Animal)o).Species;
        }
    }

     

    As you can see, the simple example really is pretty simple. :) The fancier example on the right is very similar, except that it uses a bit more XAML to get that "black is the new white" effect that's becoming so popular lately. And it makes use of my SetterValueBindingHelper implementation which adds support for specifying a Binding in the Value of a Setter on Silverlight to bind the ListBoxItem's IsEnabled property to another simple IValueConverter to disable the headers so they can't be clicked on or selected.

    Aside: Yes, I know that the very top group header is selectable when using the keyboard on current Silverlight bits. No, it's not my bug. Yes, I already reported it to the relevant people. :)

     

    Those of you familiar with my blog may be wondering why I haven't mentioned that everything here works on WPF, too... Okay, fine, I fully expect that what I've done here will work exactly the same on WPF as it does on Silverlight. :) However, WPF's support of additional features like implicit DataTemplates means that I'd probably implement this solution a little differently on WPF. If you're itching to use this code as-is on WPF, go right ahead; I don't anticipate any problems with that. But if you do, maybe spend just a bit of time thinking about how you would do things differently on WPF...

     

    Here's the complete implementation of GroupingItemsControlConverter and its helper classes for those who are interested:

    /// <summary>
    /// Class that implements simple grouping for ItemsControl and its subclasses (ex: ListBox)
    /// </summary>
    public class GroupingItemsControlConverter : IValueConverter
    {
        /// <summary>
        /// Modifies the source data before passing it to the target for display in the UI.
        /// </summary>
        /// <param name="value">The source data being passed to the target.</param>
        /// <param name="targetType">The Type of data expected by the target dependency property.</param>
        /// <param name="parameter">An optional parameter to be used in the converter logic.</param>
        /// <param name="culture">The culture of the conversion.</param>
        /// <returns>The value to be passed to the target dependency property.</returns>
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            // Validate parameters
            var valueAsIEnumerable = value as IEnumerable;
            if(null == valueAsIEnumerable)
            {
                throw new ArgumentException("GroupingItemsControlConverter works for only IEnumerable inputs.", "value");
            }
            var parameterAsGroupingItemsControlConverterParameter = parameter as GroupingItemsControlConverterParameters;
            if (null == parameterAsGroupingItemsControlConverterParameter)
            {
                throw new ArgumentException("Missing required GroupingItemsControlConverterParameter.", "parameter");
            }
            var groupSelectorAsIGroupingItemsControlConverterSelector =
                parameterAsGroupingItemsControlConverterParameter.GroupSelector as IGroupingItemsControlConverterSelector;
            if (null == groupSelectorAsIGroupingItemsControlConverterSelector)
            {
                throw new ArgumentException(
                    "GroupingItemsControlConverterParameter.GroupSelector must be non-null and implement IGroupingItemsControlConverterSelector.",
                    "parameter");
            }
    
            // Return the grouped results
            return ConvertAndGroupSequence(valueAsIEnumerable.Cast<object>(), parameterAsGroupingItemsControlConverterParameter);
        }
    
        /// <summary>
        /// Converts and groups the values of the specified sequence according to the settings of the specified parameters.
        /// </summary>
        /// <param name="sequence">Sequence of items.</param>
        /// <param name="parameters">Parameters for the grouping operation.</param>
        /// <returns>Converted and grouped sequence.</returns>
        private IEnumerable<object> ConvertAndGroupSequence(IEnumerable<object> sequence, GroupingItemsControlConverterParameters parameters)
        {
            // Validate parameters
            var groupSelector = ((IGroupingItemsControlConverterSelector)(parameters.GroupSelector)).GetGroupSelector();
            if (null == groupSelector)
            {
                throw new NotSupportedException("IGroupingItemsControlConverterSelector.GetGroupSelector must return a non-null value.");
            }
    
            // Do the grouping and ordering
            var groupedOrderedSequence = sequence.GroupBy(groupSelector).OrderBy(g => g.Key);
    
            // Return the wrapped results
            foreach (var group in groupedOrderedSequence)
            {
                yield return new ContentControl { Content = group.Key, ContentTemplate = parameters.GroupHeaderTemplate };
                foreach (var item in group)
                {
                    yield return new ContentControl { Content = item, ContentTemplate = parameters.ItemTemplate };
                }
            }
        }
    
        /// <summary>
        /// Modifies the target data before passing it to the source object. This method is called only in TwoWay bindings.
        /// </summary>
        /// <param name="value">The target data being passed to the source.</param>
        /// <param name="targetType">The Type of data expected by the source object.</param>
        /// <param name="parameter">An optional parameter to be used in the converter logic.</param>
        /// <param name="culture">The culture of the conversion.</param>
        /// <returns>The value to be passed to the source object.</returns>
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException("GroupingItemsControlConverter does not support ConvertBack.");
        }
    }
    
    /// <summary>
    /// Class that represents the input parameters to the GroupingItemsControlConverter class.
    /// </summary>
    public class GroupingItemsControlConverterParameters
    {
        /// <summary>
        /// Template to use for the header for a group.
        /// </summary>
        public DataTemplate GroupHeaderTemplate { get; set; }
    
        /// <summary>
        /// Template to use for the items of a group.
        /// </summary>
        public DataTemplate ItemTemplate { get; set; }
    
        /// <summary>
        /// Selector to use for determining the grouping of the sequence.
        /// </summary>
        public IGroupingItemsControlConverterSelector GroupSelector { get; set; }
    }
    
    /// <summary>
    /// Interface for classes to be used as a selector for the GroupingItemsControlConverterParameters class.
    /// </summary>
    public interface IGroupingItemsControlConverterSelector
    {
        /// <summary>
        /// Function that returns the group selector.
        /// </summary>
        /// <returns>Key to use for grouping.</returns>
        Func<object, IComparable> GetGroupSelector();
    }
  • Delay's Blog

    As the platform evolves, so do the workarounds [Better SetterValueBindingHelper makes Silverlight Setters better-er!]

    • 27 Comments

    Back in May, I mentioned that Silverlight 2 and 3 don't support putting a Binding in the Value of a Setter. I explained why this is useful (ex: MVVM, TreeView expansion, developer/designer separation, etc.) and shared a helper class I wrote to implement the intended functionality on Silverlight. My workaround supported setters for normal DependencyPropertys as well as attached ones, so it covered all the bases. It worked well on both flavors of Silverlight and a bunch of you went off and used SetterValueBindingHelper successfully in your own projects.

    The sun was shining, birds were chirping, and all was right with (that part of) the world...

    SetterValueBindingHelperDemo sample

     

    Now flash forward to a few days ago when I was contacted by fellow Silverlight team members RJ Boeke and Vinoo Cherian with a report that certain uses of SetterValueBindingHelper which worked fine on Silverlight 2 and 3 were likely to break if used in a possible future version of Silverlight that was more consistent with WPF's handling of such things. You can imagine my astonishment and dismay...

    Important aside: The Silverlight team takes backward compatibility very seriously, so running any Silverlight 2 or 3 application with SetterValueBindingHelper on such a future version of Silverlight would continue to work in the expected manner. The Silverlight team makes a concerted effort to ensure that each version of Silverlight is "bug compatible" with previous versions to prevent existing applications from suddenly breaking when a new version of Silverlight comes out. However, were someone to recompile such an application to target a newer release of Silverlight, that application would no longer be subject to the backwards compatibility quirks and would begin seeing the new (more correct/consistent) platform behavior.

    RJ and Vinoo pointed out that a more WPF-consistent handling of Styles would break one of the samples that was part of my original blog post. Specifically, the following example would not have the first Binding applied (note: per convention, code in italics is wrong):

    <Style TargetType="Button">
        <!-- WPF syntax:
        <Setter Property="Grid.Column" Value="{Binding}"/>
        <Setter Property="Grid.Row" Value="{Binding}"/> -->
        <Setter Property="local:SetterValueBindingHelper.PropertyBinding">
            <Setter.Value>
                <local:SetterValueBindingHelper
                    Type="System.Windows.Controls.Grid, System.Windows, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e"
                    Property="Column"
                    Binding="{Binding}"/>
            </Setter.Value>
        </Setter>
        <Setter Property="local:SetterValueBindingHelper.PropertyBinding">
            <Setter.Value>
                <local:SetterValueBindingHelper
                    Type="Grid"
                    Property="Row"
                    Binding="{Binding}"/>
            </Setter.Value>
        </Setter>
    </Style>
    

    What's important to note is that two Setters are both setting the same Property (local:SetterValueBindingHelper.PropertyBinding) and WPF optimizes this scenario to only apply the last Value it sees. Clearly, it was time to think about how tweak SetterValueBindingHelper so it would work with this theoretical future release of Silverlight...

    Tangential aside: This kind of platform change wouldn't affect just SetterValueBindingHelper - any place where multiple Setters targeted the same Property would behave differently. But that difference won't matter 99% of the time - SetterValueBindingHelper is fairly unique in its need that every Value be applied.

     

    One idea for a fix is to expose something like PropertyBinding2 from SetterValueBindingHelper and treat it just like another PropertyBinding. While that would definitely work, how do we know that two properties is enough? What if you need three or four? No, despite its simplicity, this is not the flexible solution we're looking for.

    Taking a step back, what we really want is to somehow provide an arbitrary number of Property/Binding pairs instead of being limited to just one. And if you read that last sentence and thought "Collection!", I like the way you think. :) Specifically, what if the same SetterValueBindingHelper class we're already using to provide the attached DependencyProperty and the data for it were also capable of storing a collection of other SetterValueBindingHelper objects? Yeah, sure, that would work!

     

    So let's lay a few ground rules to help guide us:

    • Every current use of SetterValueBindingHelper should continue to be valid after we make our changes. In other words, upgrading should be a simple matter of dropping in the new SetterValueBindingHelper.cs file and that's all.
    • The new SetterValueBindingHelper syntax should work correctly for the current Silverlight 3 release as well as this mythical future version of Silverlight with the WPF-consistent Style changes.
    • The new collection syntax should be easy to use and easy to understand.
    • Arbitrary nesting is unnecessary; either someone's using a SetterValueBindingHelper on its own, or else they're using it as a container for a single, nested layer of SetterValueBindingHelper children.
    • We could try to be fancy and let children inherit things from their parent, but it's not actually as useful as it seems. Let's not go there and instead keep everything simple and consistent.

    Keeping these guidelines in mind, the resulting changes to SetterValueBindingHelper give us the following alternate representation of the above XAML which works fine on Silverlight 3 today and will also give the desired effect on a possible future version of Silverlight with the WPF optimization:

    <Style TargetType="Button">
        <!-- WPF syntax:
        <Setter Property="Grid.Column" Value="{Binding}"/>
        <Setter Property="Grid.Row" Value="{Binding}"/> -->
        <Setter Property="delay:SetterValueBindingHelper.PropertyBinding">
            <Setter.Value>
                <delay:SetterValueBindingHelper>
                    <delay:SetterValueBindingHelper
                        Type="System.Windows.Controls.Grid, System.Windows, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e"
                        Property="Column"
                        Binding="{Binding}"/>
                    <delay:SetterValueBindingHelper
                        Type="Grid"
                        Property="Row"
                        Binding="{Binding}"/>
                </delay:SetterValueBindingHelper>
            </Setter.Value>
        </Setter>
    </Style>
    
    Aside: The two different ways of identifying Grid above are part of the original sample showing that both ways work - in practice, both instances would use the simple "Grid" form.

     

    Other than the namespace change to "delay" (for consistency with my other samples), the only change here is the extra SetterValueBindingHelper wrapper you see highlighted. Everything else is pretty much the same and now it works on imaginary versions of Silverlight, too! :) So if you're working on an app and you find yourself needing SetterValueBindingHelper, please use this latest version; you can rest assured that you're future-proof.

     

    [Click here to download the complete source code for SetterValueBindingHelper and its sample application.]

     

    Here's the updated code in its entirety. Please note that I have used a normal (i.e., non-observable) collection, so dynamic updates to the Values property are not supported. This was a deliberate decision to minimize complexity. (And besides, I've never heard of anyone modifying the contents of a Style dynamically.)

    /// <summary>
    /// Class that implements a workaround for a Silverlight XAML parser
    /// limitation that prevents the following syntax from working:
    ///    &lt;Setter Property="IsSelected" Value="{Binding IsSelected}"/&gt;
    /// </summary>
    [ContentProperty("Values")]
    public class SetterValueBindingHelper
    {
        /// <summary>
        /// Optional type parameter used to specify the type of an attached
        /// DependencyProperty as an assembly-qualified name, full name, or
        /// short name.
        /// </summary>
        [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods",
            Justification = "Unambiguous in XAML.")]
        public string Type { get; set; }
    
        /// <summary>
        /// Property name for the normal/attached DependencyProperty on which
        /// to set the Binding.
        /// </summary>
        public string Property { get; set; }
    
        /// <summary>
        /// Binding to set on the specified property.
        /// </summary>
        public Binding Binding { get; set; }
    
        /// <summary>
        /// Collection of SetterValueBindingHelper instances to apply to the
        /// target element.
        /// </summary>
        /// <remarks>
        /// Used when multiple Bindings need to be applied to the same element.
        /// </remarks>
        public Collection<SetterValueBindingHelper> Values
        {
            get
            {
                // Defer creating collection until needed
                if (null == _values)
                {
                    _values = new Collection<SetterValueBindingHelper>();
                }
                return _values;
            }
        }
        private Collection<SetterValueBindingHelper> _values;
    
        /// <summary>
        /// Gets the value of the PropertyBinding attached DependencyProperty.
        /// </summary>
        /// <param name="element">Element for which to get the property.</param>
        /// <returns>Value of PropertyBinding attached DependencyProperty.</returns>
        [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters",
            Justification = "SetBinding is only available on FrameworkElement.")]
        public static SetterValueBindingHelper GetPropertyBinding(FrameworkElement element)
        {
            if (null == element)
            {
                throw new ArgumentNullException("element");
            }
            return (SetterValueBindingHelper)element.GetValue(PropertyBindingProperty);
        }
    
        /// <summary>
        /// Sets the value of the PropertyBinding attached DependencyProperty.
        /// </summary>
        /// <param name="element">Element on which to set the property.</param>
        /// <param name="value">Value forPropertyBinding attached DependencyProperty.</param>
        [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters",
            Justification = "SetBinding is only available on FrameworkElement.")]
        public static void SetPropertyBinding(FrameworkElement element, SetterValueBindingHelper value)
        {
            if (null == element)
            {
                throw new ArgumentNullException("element");
            }
            element.SetValue(PropertyBindingProperty, value);
        }
    
        /// <summary>
        /// PropertyBinding attached DependencyProperty.
        /// </summary>
        public static readonly DependencyProperty PropertyBindingProperty =
            DependencyProperty.RegisterAttached(
                "PropertyBinding",
                typeof(SetterValueBindingHelper),
                typeof(SetterValueBindingHelper),
                new PropertyMetadata(null, OnPropertyBindingPropertyChanged));
    
        /// <summary>
        /// Change handler for the PropertyBinding attached DependencyProperty.
        /// </summary>
        /// <param name="d">Object on which the property was changed.</param>
        /// <param name="e">Property change arguments.</param>
        private static void OnPropertyBindingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            // Get/validate parameters
            var element = (FrameworkElement)d;
            var item = (SetterValueBindingHelper)(e.NewValue);
    
            if ((null == item.Values) || (0 == item.Values.Count))
            {
                // No children; apply the relevant binding
                ApplyBinding(element, item);
            }
            else
            {
                // Apply the bindings of each child
                foreach (var child in item.Values)
                {
                    if ((null != item.Property) || (null != item.Binding))
                    {
                        throw new ArgumentException(
                            "A SetterValueBindingHelper with Values may not have its Property or Binding set.");
                    }
                    if (0 != child.Values.Count)
                    {
                        throw new ArgumentException(
                            "Values of a SetterValueBindingHelper may not have Values themselves.");
                    }
                    ApplyBinding(element, child);
                }
            }
        }
    
        /// <summary>
        /// Applies the Binding represented by the SetterValueBindingHelper.
        /// </summary>
        /// <param name="element">Element to apply the Binding to.</param>
        /// <param name="item">SetterValueBindingHelper representing the Binding.</param>
        private static void ApplyBinding(FrameworkElement element, SetterValueBindingHelper item)
        {
            if ((null == item.Property) || (null == item.Binding))
            {
                throw new ArgumentException(
                    "SetterValueBindingHelper's Property and Binding must both be set to non-null values.");
            }
    
            // Get the type on which to set the Binding
            Type type = null;
            if (null == item.Type)
            {
                // No type specified; setting for the specified element
                type = element.GetType();
            }
            else
            {
                // Try to get the type from the type system
                type = System.Type.GetType(item.Type);
                if (null == type)
                {
                    // Search for the type in the list of assemblies
                    foreach (var assembly in AssembliesToSearch)
                    {
                        // Match on short or full name
                        type = assembly.GetTypes()
                            .Where(t => (t.FullName == item.Type) || (t.Name == item.Type))
                            .FirstOrDefault();
                        if (null != type)
                        {
                            // Found; done searching
                            break;
                        }
                    }
                    if (null == type)
                    {
                        // Unable to find the requested type anywhere
                        throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
                            "Unable to access type \"{0}\". Try using an assembly qualified type name.",
                            item.Type));
                    }
                }
            }
    
            // Get the DependencyProperty for which to set the Binding
            DependencyProperty property = null;
            var field = type.GetField(item.Property + "Property",
                BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Static);
            if (null != field)
            {
                property = field.GetValue(null) as DependencyProperty;
            }
            if (null == property)
            {
                // Unable to find the requsted property
                throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
                    "Unable to access DependencyProperty \"{0}\" on type \"{1}\".",
                    item.Property, type.Name));
            }
    
            // Set the specified Binding on the specified property
            element.SetBinding(property, item.Binding);
        }
    
        /// <summary>
        /// Returns a stream of assemblies to search for the provided type name.
        /// </summary>
        private static IEnumerable<Assembly> AssembliesToSearch
        {
            get
            {
                // Start with the System.Windows assembly (home of all core controls)
                yield return typeof(Control).Assembly;
    
                // Fall back by trying each of the assemblies in the Deployment's Parts list
                foreach (var part in Deployment.Current.Parts)
                {
                    var streamResourceInfo = Application.GetResourceStream(
                        new Uri(part.Source, UriKind.Relative));
                    using (var stream = streamResourceInfo.Stream)
                    {
                        yield return part.Load(stream);
                    }
                }
            }
        }
    }
    
  • Delay's Blog

    Get out of the way with the tray ["Minimize to tray" sample implementation for WPF]

    • 9 Comments

    "Minimize to tray" is a feature in some applications where minimizing the application removes its taskbar button and replaces it with a (much smaller) notification area icon. Clicking that notification icon restores the application's window - just like clicking its taskbar button would have done.

    Well, I was considering using this functionality in a project I'm working on, so I looked to see what my options were. This feature didn't seem to be directly supported by WPF, so I searched the web a bit. What I found after a minute or two of searching was plenty of questions about how to implement this, a few suggestions, and not a lot else. So I figured I'd put something together myself and post it to my blog...

     

    Here's the sample application I'm about minimize - note the (boring) green square of an application icon:

    About to minimize

    And here's what things look like just after the application is minimized to the tray:

    Notify balloon

    Aside from just looking cool, that notification bubble serves an important purpose: it helps to draw the user's attention to the new icon in the notification area and calls out the application's custom minimize behavior. (To avoid being annoying, the bubble is only shown the first time the application is minimized each time it's run.) This is a nice convenience on most versions of Windows, but is pretty much necessary due to the new Windows 7 behavior of hiding notification area icons as quickly as possible. Without a helpful indication like this bubble, users might "lose" the application when it minimizes to the tray.

    Aside: I understand why the Windows 7 team introduced this new behavior and I think it's perfectly reasonable. However, it has implications for this scenario, so it's good to think twice (or thrice!) about choosing to use "minimize to tray" in your application. The document I link to above has lots more guidance on the proper use of notification icons - interested parties are encouraged to review it!

    After Windows 7 hides the notification icon for the application, it can be accessed via the "Show hidden icons" popup. Clicking on the hidden notification icon restores the application just like you'd expect:

    Hidden by Windows 7
    Aside: If you really dislike the new hiding behavior, it's easy to disable - just click the "Customize..." link (shown in the image above) and you'll be presented with a window that lets you disable this behavior for specific applications - or disable it for all of them with a single checkbox.

     

    Notes:

    • I've implemented this functionality in a static MinimizeToTray class with a single Enable method that's super-easy to use. Just add a call to it in your Window's constructor and you're done:
      // Enable "minimize to tray" behavior for this Window
      MinimizeToTray.Enable(this);
      
    • WPF doesn't natively offer notify icon support, but there's nothing stopping us from using the NotifyIcon implementation that's part of WinForms! That's what MinimizeToTray does, so it's important to note that you'll need to change your project to add references to the System.Drawing and System.Windows.Forms .NET assemblies. (FYI, they're both part of the .NET Framework and are already present in the GAC (and NGEN-ed), so you don't need to worry about distributing them with your application.)
    • But it's also important to note that I've written my code such that MinimizeToTray doesn't cause either of these assemblies get loaded until the user first minimizes the application. This means neither assembly will impact the startup time of your application! (You can verify this by running the sample application, attaching the debugger, checking the module list to see that neither assembly is present, minimizing the application, and then noting that both assemblies just got loaded.)
    • I only use the most basic notify icon functionality in this sample, so the WinForms implementation is more than adequate for my needs. However, if you're looking for true WPF-style notification icon support, this implementation by Philipp Sumi looks quite promising (though I haven't tried it myself).

     

    As a final favor, I'll ask that people please use "minimize to tray" wisely - just because you can minimize your application to the tray doesn't mean you should. :) Minimizing to the tray is something that's only meaningful for a limited set of scenarios - but if you find yourself in one of them, I hope MinimizeToTray is helpful!

     

    [Click here to download the sample application and complete source code for MinimizeToTray.]

     

    The code is quite straightforward - here it is in its entirety:

    /// <summary>
    /// Class implementing support for "minimize to tray" functionality.
    /// </summary>
    public static class MinimizeToTray
    {
        /// <summary>
        /// Enables "minimize to tray" behavior for the specified Window.
        /// </summary>
        /// <param name="window">Window to enable the behavior for.</param>
        public static void Enable(Window window)
        {
            // No need to track this instance; its event handlers will keep it alive
            new MinimizeToTrayInstance(window);
        }
    
        /// <summary>
        /// Class implementing "minimize to tray" functionality for a Window instance.
        /// </summary>
        private class MinimizeToTrayInstance
        {
            private Window _window;
            private NotifyIcon _notifyIcon;
            private bool _balloonShown;
    
            /// <summary>
            /// Initializes a new instance of the MinimizeToTrayInstance class.
            /// </summary>
            /// <param name="window">Window instance to attach to.</param>
            public MinimizeToTrayInstance(Window window)
            {
                Debug.Assert(window != null, "window parameter is null.");
                _window = window;
                _window.StateChanged += new EventHandler(HandleStateChanged);
            }
    
            /// <summary>
            /// Handles the Window's StateChanged event.
            /// </summary>
            /// <param name="sender">Event source.</param>
            /// <param name="e">Event arguments.</param>
            private void HandleStateChanged(object sender, EventArgs e)
            {
                if (_notifyIcon == null)
                {
                    // Initialize NotifyIcon instance "on demand"
                    _notifyIcon = new NotifyIcon();
                    _notifyIcon.Icon = Icon.ExtractAssociatedIcon(Assembly.GetEntryAssembly().Location);
                    _notifyIcon.MouseClick += new MouseEventHandler(HandleNotifyIconOrBalloonClicked);
                    _notifyIcon.BalloonTipClicked += new EventHandler(HandleNotifyIconOrBalloonClicked);
                }
                // Update copy of Window Title in case it has changed
                _notifyIcon.Text = _window.Title;
    
                // Show/hide Window and NotifyIcon
                var minimized = (_window.WindowState == WindowState.Minimized);
                _window.ShowInTaskbar = !minimized;
                _notifyIcon.Visible = minimized;
                if (minimized && !_balloonShown)
                {
                    // If this is the first time minimizing to the tray, show the user what happened
                    _notifyIcon.ShowBalloonTip(1000, null, _window.Title, ToolTipIcon.None);
                    _balloonShown = true;
                }
            }
    
            /// <summary>
            /// Handles a click on the notify icon or its balloon.
            /// </summary>
            /// <param name="sender">Event source.</param>
            /// <param name="e">Event arguments.</param>
            private void HandleNotifyIconOrBalloonClicked(object sender, EventArgs e)
            {
                // Restore the Window
                _window.WindowState = WindowState.Normal;
            }
        }
    }
    
  • Delay's Blog

    We've secretly changed this control's DataContext - let's see if it notices! [Workaround for a Silverlight data binding bug affecting various scenarios - including DataGrid+ContextMenu]

    • 5 Comments

    I was contacted by Simon Weaver via Twitter about a problem where the Bindings for MenuItems of a ContextMenu on a DataGrid were acting as though they were associated with a different row than they really were. This seemed pretty weird until Yifung Lin, one of the original DataGrid developers, suggested this might be due to the row recycling behavior DataGrid uses to improve performance. It turns out he was right - but the rabbit hole goes much deeper than that...

    Sample application showing bug

    Working from an internal reproduction of the problem Brian Braeckel created, I found that I was able to simplify the scenario significantly - to the point of removing ContextMenu and DataGrid entirely! Signs were pointing strongly to this being a bug in the Silverlight framework, but before I started crying wolf, I wanted someone to review my work to be sure I hadn't over-simplified things. Fortunately, RJ Boeke was around and not only confirmed the validity of my repro, but also pointed out further simplifications!

     

    When all is said and done, the bug is pretty easy to describe:

    If a Binding points to an element via its ElementName or Source parameters and the DataContext of a parent of that element changes, that change will NOT be propagated through the Binding. However, if the DataContext of the element itself changes, the change will be propagated correctly.

    RJ and I were both kind of surprised to find this bug in Silverlight 4, but when Sage LaTorra looked into this from the QA side, he reported the same problem with Silverlight 3. Which - in a weird kind of way - is actually good news because it means things haven't gotten worse with Silverlight 4 and existing applications won't break unexpectedly!

    Unfortunately, the DataGrid+ContextMenu scenario relies on this exact behavior to work correctly... When a ContextMenu is about to be displayed, a Popup is created, the menu UI is added to it, and it's shown. Because the Popup isn't in the visual tree, the ContextMenu won't see the right DataContext by default - so it sets up a Binding with its Source set to the ContextMenu's owner element. This works well and the DataContext then "flows" into the Popup where MenuItems can see it and bind to it successfully. The problem in the DataGrid scenario arises when DataGrid decides to recycle one of its rows by swapping one DataContext for another. Though this is a perfectly legitimate thing to do, it runs afoul of this bug and breaks the scenario.

     

    Fortunately, there's an easy workaround. Even better: it works for all known instances of the problem, not just the DataGrid+ContextMenu kind! Here's an example of code that demonstrates the bug when the DataContext of a parent element is changed (which you can do in the sample application by clicking on the red or green background):

    <!-- Simple example of the broken scenario -->
    <Grid x:Name="SimpleBroken" Grid.Column="0" Grid.RowSpan="2" Background="Red">
        <TextBlock Text="{Binding DataContext, ElementName=SimpleBroken}"/>
    </Grid>

    And here's what it looks like with the workaround applied:

    <!-- Simple example of the workaround -->
    <delay:DataContextPropagationGrid x:Name="SimpleWorking" Grid.Column="1" Grid.RowSpan="2" Background="Green">
        <TextBlock Text="{Binding DataContext, ElementName=SimpleWorking}"/>
    </delay:DataContextPropagationGrid>

    Pretty similar, huh? The way this works is that the DataContextPropagationGrid class derives from Grid, listens for DataContext changes in the usual manner, then uses the fact that changes to the local value of DataContext work to "re-broadcast" the change to any Bindings targeting it via ElementName or Source. The important thing to note is that any Binding broken because of the underlying platform bug needs to be pointed at the DataContextPropagationGrid wrapper instead - and should then behave correctly.

    Aside: I haven't historically considered Grid for workaround scenarios like this. However, it seems like a good fit: Grid can be wrapped around nearly anything without side-effects, it allows multiple children for scenarios where that might be necessary, it's familiar to developers and designers, and it keeps the workaround code simple!

     

    Just to "close the loop", here's an example of a DataGrid+ContextMenu demonstrating the problem:

    DataGrid+ContextMenu showing bug

    To reproduce it yourself, run the sample application, right-click on every row of the red DataGrid (everything will be correct), then scroll to the end of the list and do the same - you'll quickly find a mis-match like I show above. The fix is as easy as introducing the DataContextPropagationGrid class (as I did for the green DataGrid) - the important thing is to be sure to attach the ContextMenu to the DataContextPropagationGrid so the workaround has a chance to do its thing:

    <!-- DataGrid+ContextMenu example of the workaround -->
    <sdk:DataGrid Grid.Column="1" Grid.Row="1" AutoGenerateColumns="False" Margin="10">
        <sdk:DataGrid.Columns>
            <sdk:DataGridTemplateColumn Header="Values">
                <sdk:DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <delay:DataContextPropagationGrid>
                            <toolkit:ContextMenuService.ContextMenu>
                                <toolkit:ContextMenu>
                                    <toolkit:MenuItem Header="{Binding}"/>
                                </toolkit:ContextMenu>
                            </toolkit:ContextMenuService.ContextMenu>
                            <TextBlock Text="{Binding}"/>
                        </delay:DataContextPropagationGrid>
                    </DataTemplate>
                </sdk:DataGridTemplateColumn.CellTemplate>
            </sdk:DataGridTemplateColumn>
        </sdk:DataGrid.Columns>
    </sdk:DataGrid>

     

    If you run into this Silverlight bug in the DataGrid+ContextMenu scenario, please apply the DataContextPropagationGrid workaround and things should work properly. And if you happen run into the bug in some other scenario, the good news is that the DataContextPropagationGrid workaround should work there, too! Just be mindful to point the Binding's ElementName or Source at the DataContextPropagationGrid element, and you're good to go.

     

    [Click here to download the complete source code for the SilverlightDataContextBugWorkaround sample.]

     

    PS - Here's the code for the workaround:

    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    
    namespace Delay
    {
        /// <summary>
        /// Class to help work around a Silverlight bug where DataContext changes to
        /// an element aren't propagated through Bindings on child elements that use
        /// ElementName or Source.
        /// </summary>
        public class DataContextPropagationGrid : Grid
        {
            /// <summary>
            /// Initializes a new instance of the DataContextPropagationGrid class.
            /// </summary>
            public DataContextPropagationGrid()
            {
                // Create a Binding to keep InheritedDataContextProperty correct
                SetBinding(InheritedDataContextProperty, new Binding());
            }
    
            /// <summary>
            /// Identifies the InheritedDataContext DependencyProperty.
            /// </summary>
            public static readonly DependencyProperty InheritedDataContextProperty =
                DependencyProperty.Register(
                    "InheritedDataContext",
                    typeof(object),
                    typeof(DataContextPropagationGrid),
                    new PropertyMetadata(null, OnInheritedDataContextChanged));
    
            /// <summary>
            /// Handles changes to the InheritedDataContext DependencyProperty.
            /// </summary>
            /// <param name="d">Instance with property change.</param>
            /// <param name="e">Property change details.</param>
            private static void OnInheritedDataContextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                DataContextPropagationGrid workaround = (DataContextPropagationGrid)d;
                // Update local value of DataContext to prompt Silverlight to update problematic Bindings
                workaround.DataContext = e.NewValue;
                // Unset local value of DataContext so it will continue to inherit from the parent
                workaround.ClearValue(FrameworkElement.DataContextProperty);
            }
        }
    }
  • Delay's Blog

    There are lots of ways to ask for a date [Creating custom DatePicker/TimePicker experiences is easy with the Windows Phone Toolkit]

    • 40 Comments
    DatePicker and TimePicker sample picker

    In my previous post announcing the release of the Windows Phone Toolkit, I explained the motivation for the PhoneApplicationPage-based design of the DatePicker and TimePicker controls. Though justified, the current implementation is fairly unusual and has the potential to trip people up if they're not ready for it. So I highly recommend reading the details at the end of that post if you're using DatePicker or TimePicker.

    [Waits patiently...]

    Okay, if you read the notes, then you understand what we've done and why. And while I'm happy to discuss the pros and cons of the current implementation, that's what we've got for now and I'm going to spend a little time highlighting its extensibility!

     

    As with the rest of the Phone Toolkit, our primary goal was to make it easy for developers to match the core Windows Phone 7 experience in their own applications. Toward that end, adding an instance of DatePicker or TimePicker to a page gives the same "small button expands to full-page date/time picker with infinite scrolly thingies" experience that's found in the core Settings, Calendar, and Alarms applications. But sometimes you want to do things a little differently, and maybe you've got a date/time picking interface in mind that's more relevant to your scenario...

     

    Aside: If what you want to do is change the format used to display the date/time on the face of the initial "button", then all you need to do is set the ValueStringFormat property to the standard date and time format string or custom date and time format string of your choice!

    But because format strings include "{" and "}", they're a little tricky to code in XAML; you need to escape the special characters. here's an example of what this looks like in its simplest form: ValueStringFormat="{}{0:d}"

    And here are a few practical examples (using the culture settings for USA):

    Description XAML Sample
    Short date format (DatePicker default) "{}{0:d}" 9/20/2010
    Short time format (TimePicker default) "{}{0:t}" 4:00 PM
    Long date format "{}{0:D}" Monday, September 20, 2010
    Long time format "{}{0:T}" 4:00:00 PM
    Explicit month/day/year format "{}{0:MM-dd-yyyy}" 09-20-2010
    Explicit 24-hour time format with text "{}The 24-hour time is {0:HH:mm}." The 24-hour time is 16:00.

     

    Customizing the full-screen picker page is as simple as setting the DatePicker.PickerPageUri or TimePicker.PickerPageUri property to point to a PhoneApplicationPage that implements the IDateTimePickerPage interface. The PickerPageUri property uses the standard assembly-relative syntax ("/assemblyShortName;component/resourceLocation"), so it should be familiar to most folks. The IDateTimePickerPage interface is a new interface, but I think you'll agree it's pretty basic:

    namespace Microsoft.Phone.Controls.Primitives
    {
        /// <summary>
        /// Represents an interface for DatePicker/TimePicker to use for communicating with a picker page.
        /// </summary>
        public interface IDateTimePickerPage
        {
            /// <summary>
            /// Gets or sets the DateTime to show in the picker page and to set when the user makes a selection.
            /// </summary>
            DateTime? Value { get; set; }
        }
    }

     

    When it's time to display the picker page, DatePicker and TimePicker navigate to the page specified by PickerPageUri via the NavigationService.Navigate("...") method, wait for the page to load, get a reference to its IDateTimePickerPage interface, and set its Value property to the starting DateTime. After that, the custom picker page is in complete control and can display any UI it wants. When the custom picker is done (either because the user picked a date/time or because the user cancelled the operation), the picker should set its Value property to the selected date/time (or null if cancelled) and then call NavigationService.GoBack to return to the previous page. DatePicker and TimePicker automatically detect this, update their own Value property (which automatically updates their UI), and fire their ValueChanged event.

    In some ways, it's actually kind of elegant! :) And the fact that the picker is entirely implemented by its own PhoneApplicationPage makes it really easy to work with at design-time in Visual Studio and Blend.

     

    To help illustrate the process, I created a sample application that uses custom pickers for DatePicker and TimePicker which let the user pick times relative to the current time:

    CustomDateTimePickerPage sample application Custom DatePickerPage example Custom TimePickerPage example

    The sample application shows the current date and time because what the emulator reports isn't always the same as what the host operating system reports! In my experience, it's often an hour or more off - so please keep in mind that the times used by the sample are relative to emulator time!

     

    Here's the XAML for the main page that hosts the DatePicker and TimePicker controls:

    <toolkit:DatePicker
        Value="10/10/2010"
        Header="Custom Date Picker Page"
        PickerPageUri="/CustomDateTimePickerPage;component/CustomDatePickerPage.xaml"/>
    
    <toolkit:TimePicker
        Value="{x:Null}"
        Header="Custom Time Picker Page"
        PickerPageUri="/CustomDateTimePickerPage;component/CustomTimePickerPage.xaml"/>
    Aside: Just for fun, I've explicitly set the Value properties here. Although the controls' default behavior of using the current date/time is likely to be what you want most of the time, applications are free to override the initial date/time (as DatePicker does above) or to provide a blank starting value (as TimePicker does with the x:Null markup extension).

     

    The implementation of the custom PhoneApplicationPage implementation is so basic, I'm going to show the complete date picker here! (The custom time picker implementation is nearly identical.) I've highlighted the particularly interesting bits, but by now they should be familiar.

    First the XAML, then the code:

    <phone:PhoneApplicationPage
        x:Class="CustomDateTimePickerPage.CustomDatePickerPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        FontFamily="{StaticResource PhoneFontFamilyNormal}"
        FontSize="{StaticResource PhoneFontSizeNormal}"
        Foreground="{StaticResource PhoneForegroundBrush}"
        SupportedOrientations="PortraitOrLandscape" Orientation="Portrait"
        mc:Ignorable="d" d:DesignHeight="800" d:DesignWidth="480">
    
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
    
            <TextBlock
                Text="CHOOSE CUSTOM DATE"
                Margin="12"
                FontWeight="Bold"/>
    
            <StackPanel
                Grid.Row="1"
                VerticalAlignment="Center">
                <Button Content="Tomorrow" Click="TomorrowButtonClick"/>
                <Button Content="Next Week" Click="NextWeekButtonClick"/>
                <Button Content="Next Month" Click="NextMonthButtonClick"/>
                <Button Content="[Cancel]" Click="CancelButtonClick"/>
            </StackPanel>
        </Grid>
    
    </phone:PhoneApplicationPage>
    public partial class CustomDatePickerPage : PhoneApplicationPage, IDateTimePickerPage
    {
        /// <summary>
        /// Gets or sets the DateTime value for the IDateTimePickerPage interface.
        /// </summary>
        public DateTime? Value { get; set; }
    
        public CustomDatePickerPage()
        {
            InitializeComponent();
        }
    
        private void TomorrowButtonClick(object sender, RoutedEventArgs e)
        {
            ChooseDate(DateTime.Now.AddDays(1));
        }
    
        private void NextWeekButtonClick(object sender, RoutedEventArgs e)
        {
            ChooseDate(DateTime.Now.AddDays(7));
        }
    
        private void NextMonthButtonClick(object sender, RoutedEventArgs e)
        {
            ChooseDate(DateTime.Now.AddMonths(1));
        }
    
        private void ChooseDate(DateTime date)
        {
            // Commit date and close picker
            Value = date;
            NavigationService.GoBack();
        }
    
        private void CancelButtonClick(object sender, RoutedEventArgs e)
        {
            // Cancel selection and close picker
            Value = null;
            NavigationService.GoBack();
        }
    }

     

    As you can see, creating a custom picker page for DatePicker and TimePicker is really easy! As long as you play by a few basic rules, you have complete freedom to style the entire picker experience in a self-contained, interactive, designer-friendly way. It's kind of like re-Templating - but easier and more flexible - in a weird kind of way!

     

    [Click here to download the complete source code for the CustomDateTimePickerPage sample application.]

     

    Now get to it - I'm looking forward to seeing the kinds of custom picker experiences you all come up with! :)

  • Delay's Blog

    Continuing support for simple HTML display in Silverlight [HtmlTextBlock sample updated for Silverlight 2 Beta 1!]

    • 16 Comments

    A few months ago when Silverlight 1.1 Alpha was all the rage, I wrote a sample control that made a best-effort attempt to display simple HTML markup in Silverlight. The original HtmlTextBlock post described the control's purpose and the follow-up post detailed a number of improvements to HtmlTextBlock. (Please continue to refer to those posts for background and implementation details.) Now that Silverlight 2 Beta 1 is released, I've had a few internal and external requests to modify HtmlTextBlock to work with the latest Silverlight bits.

    HtmlTextBlock Demonstration

    I've updated the original HtmlTextBlock demonstration page and also updated the original source code download, so don't hesitate to try things out in your own browser and/or download the code to see how it works!

    Notes:

    • There were no fundamental changes to the features HtmlTextBlock uses, so the port from 1.1 Alpha to 2 Beta 1 was straightforward.
    • Thanks to Silverlight 2's flexible layout system, the code to override Width/Height/ActualWidth/ActualHeight and handle the Loaded event is all now unnecessary and has been removed.
    • The XAML passed to Control.InitializeFromXaml now specifies the default XML namespace.
    • Silverlight 2 Beta 1's TextBlock exposes the new properties LineHeight, LineStackingStrategy, and TextAlignment which HtmlTextBlock mirrors.
    • A handful of properties/classes changed names/types slightly.
    • The syntax for creating an instance of the Silverlight control in HTML has changed to use the <OBJECT> tag.

    Curiously, certain text/style/size combinations don't seem to render under Silverlight 2 Beta 1. For example, viewing the initial sample text at Verdana/8, Lucida Sans Unicode/10, or Times New Roman/10 shows an empty box. Because changing the font face/size doesn't actually change the Silverlight object tree that HtmlTextBlock creates, I'm inclined to believe this is a rendering issue with the Silverlight Beta rather than a bug in HtmlTextBlock. However, if anyone finds otherwise, please let me know!

    It's always great to hear that people have found value in the stuff I've posted to my blog. I hope that those of you who are using HtmlTextBlock continue using it successfully with Silverlight 2 Beta 1 - and that newcomers find something useful as well!

  • Delay's Blog

    One more platform difference more-or-less tamed [SetterValueBindingHelper makes Silverlight Setters better!]

    • 21 Comments

    Earlier this week I wrote about the "app building" exercise my team conducted and posted my sample application, a simple organizational hierarchy viewer using many of the controls in the Silverlight Toolkit. One of the surprises I had during the process of building this application was that Silverlight (version 2 as well as the Beta for version 3) doesn't support the scenario of providing a Binding in the Value of a Setter. I bumped into this when I was trying to follow one of the "best practices" for TreeView manipulation - but I soon realized the problem has much broader reach.

    First, a bit about why this is interesting at all. :) Because of the way TreeView works on WPF and Silverlight, it turns out that the most elegant way of manipulating the nodes (for example, to expand all the nodes in a tree) is to do so by manipulating your own classes to which the TreeViewItem's IsExpanded property is bound. Josh Smith does a great job explaining why in this article, so I won't spend more time on that here. However, as Bea Stollnitz explains near the bottom of this post (and as I mention above), the XAML-based Setter/Value/Binding approach doesn't work on Silverlight.

    In her post, Beatriz outlines a very reasonable workaround for this problem which is to subclass TreeView and TreeViewItem in order to override GetContainerForItemOverride and hook up the desired Bindings there. However, there are two drawbacks with this approach that I hoped to be able to improve upon: 1. It's limited to ItemsControl subclasses (because other controls don't have GetContainerForItemOverride) and 2. it moves design concerns into code (where designers can't see or change them).

    As part of my app building work, I came up with a one-off way of avoiding the ItemsControl coupling, but it wasn't broadly useful in its original form. Fortunately, in the process of extracting that code out in generalizing it for this post, I realized how I could avoid the second drawback as well and accomplish the goal without needing any code at all - it's all XAML, all the time! [Yes, designers, I [heart] you. :) ]

    The trick is to use a Setter to set a special attached DependencyProperty with a Value that specifies a special object which identifies the DependencyProperty and Binding to set. It's that easy! Well, okay, I have to do a bit of work behind the scenes to make this all hang together, but it does work - and what's more it even works for attached properties!

     

    Here's an example of SetterValueBindingHelper in action:

    SetterValueBindingHelperDemo sample

    First, the relevant XAML for the TreeViewItem:

    <Style TargetType="controls:TreeViewItem">
        <!-- WPF syntax:
        <Setter Property="IsExpanded" Value="{Binding IsExpanded}"/> -->
        <Setter Property="local:SetterValueBindingHelper.PropertyBinding">
            <Setter.Value>
                <local:SetterValueBindingHelper
                    Property="IsExpanded"
                    Binding="{Binding IsExpanded}"/>
            </Setter.Value>
        </Setter>
    </Style>
    

    Yes, things end up being a little bit more verbose than they are on WPF, but if you squint hard enough the syntax is quite similar. Even better, it's something that someone who hasn't read this post should be able to figure out on their own fairly easily.

    Here's the XAML for the top Button:

    <Style TargetType="Button">
        <!-- WPF syntax:
        <Setter Property="Content" Value="{Binding}"/> -->
        <Setter Property="local:SetterValueBindingHelper.PropertyBinding">
            <Setter.Value>
                <local:SetterValueBindingHelper
                    Property="Content"
                    Binding="{Binding}"/>
            </Setter.Value>
        </Setter>
    </Style>
    

    There's really nothing new here, but I did want to show off that SetterValueBindingHelper works for non-ItemsControls as well.

    Finally, here's the XAML for the right Button where two properties are being set by SetterValueBindingHelper:

    <Style TargetType="Button">
        <!-- WPF syntax:
        <Setter Property="Grid.Column" Value="{Binding}"/>
        <Setter Property="Grid.Row" Value="{Binding}"/> -->
        <Setter Property="local:SetterValueBindingHelper.PropertyBinding">
            <Setter.Value>
                <local:SetterValueBindingHelper
                    Type="System.Windows.Controls.Grid, System.Windows, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e"
                    Property="Column"
                    Binding="{Binding}"/>
            </Setter.Value>
        </Setter>
        <Setter Property="local:SetterValueBindingHelper.PropertyBinding">
            <Setter.Value>
                <local:SetterValueBindingHelper
                    Type="Grid"
                    Property="Row"
                    Binding="{Binding}"/>
            </Setter.Value>
        </Setter>
    </Style>
    

    As you can see, SetterValueBindingHelper also supports setting attached DependencyPropertys, so if you find yourself in a situation where you need to style such a creature, SetterValueBindingHelper's got your back. It's also worth pointing out that the Setter for "Column" is using the official assembly-qualified naming for the Type parameter of the SetterValueBindingHelper object. This form is completely unambiguous - and it's also big, ugly, and pretty much impossible to type from memory... :) So I added code to SetterValueBindingHelper that allows users to also specify the full name of a type (ex: System.Windows.Controls.Grid) or just its short name (ex: Grid, used by the Setter for "Row"). I expect pretty much everyone will use the short name - but sleep soundly knowing you can fall back on the other forms "just in case".

     

    [Click here to download the complete source code for SetterValueBindingHelper and the sample application.]

     

    Lastly, here's the code for SetterValueBindingHelper in case that's all you care about:

    Update (2010-10-31): At the suggestion of a reader, I've removed the implementation of SetterValueBindingHelper from this post because a newer version of the code (that works well on Silverlight 4, too) can be found in this more recent post. (FYI: The download link is the same for both posts and therefore always up-to-date.)

    /// <summary>
    /// Class that implements a workaround for a Silverlight XAML parser
    /// limitation that prevents the following syntax from working:
    ///    &lt;Setter Property="IsSelected" Value="{Binding IsSelected}"/&gt;
    /// </summary>
    public class SetterValueBindingHelper
    {
        /// <summary>
        /// Optional type parameter used to specify the type of an attached
        /// DependencyProperty as an assembly-qualified name, full name, or
        /// short name.
        /// </summary>
        [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods",
            Justification = "Unambiguous in XAML.")]
        public string Type { get; set; }
    
        /// <summary>
        /// Property name for the normal/attached DependencyProperty on which
        /// to set the Binding.
        /// </summary>
        public string Property { get; set; }
    
        /// <summary>
        /// Binding to set on the specified property.
        /// </summary>
        public Binding Binding { get; set; }
    
        /// <summary>
        /// Gets the value of the PropertyBinding attached DependencyProperty.
        /// </summary>
        /// <param name="element">Element for which to get the property.</param>
        /// <returns>Value of PropertyBinding attached DependencyProperty.</returns>
        [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters",
            Justification = "SetBinding is only available on FrameworkElement.")]
        public static SetterValueBindingHelper GetPropertyBinding(FrameworkElement element)
        {
            if (null == element)
            {
                throw new ArgumentNullException("element");
            }
            return (SetterValueBindingHelper)element.GetValue(PropertyBindingProperty);
        }
    
        /// <summary>
        /// Sets the value of the PropertyBinding attached DependencyProperty.
        /// </summary>
        /// <param name="element">Element on which to set the property.</param>
        /// <param name="value">Value forPropertyBinding attached DependencyProperty.</param>
        [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters",
            Justification = "SetBinding is only available on FrameworkElement.")]
        public static void SetPropertyBinding(FrameworkElement element, SetterValueBindingHelper value)
        {
            if (null == element)
            {
                throw new ArgumentNullException("element");
            }
            element.SetValue(PropertyBindingProperty, value);
        }
    
        /// <summary>
        /// PropertyBinding attached DependencyProperty.
        /// </summary>
        public static readonly DependencyProperty PropertyBindingProperty =
            DependencyProperty.RegisterAttached(
                "PropertyBinding",
                typeof(SetterValueBindingHelper),
                typeof(SetterValueBindingHelper),
                new PropertyMetadata(null, OnPropertyBindingPropertyChanged));
    
        /// <summary>
        /// Change handler for the PropertyBinding attached DependencyProperty.
        /// </summary>
        /// <param name="d">Object on which the property was changed.</param>
        /// <param name="e">Property change arguments.</param>
        private static void OnPropertyBindingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            // Get/validate parameters
            var element = (FrameworkElement)d;
            var item = (SetterValueBindingHelper)(e.NewValue);
            if ((null == item.Property) || (null == item.Binding))
            {
                throw new ArgumentException(
                    "SetterValueBindingHelper's Property and Binding must both be set to non-null values.");
            }
    
            // Get the type on which to set the Binding
            Type type = null;
            if (null == item.Type)
            {
                // No type specified; setting for the specified element
                type = element.GetType();
            }
            else
            {
                // Try to get the type from the type system
                type = System.Type.GetType(item.Type);
                if (null == type)
                {
                    // Search for the type in the list of assemblies
                    foreach (var assembly in AssembliesToSearch)
                    {
                        // Match on short or full name
                        type = assembly.GetTypes()
                            .Where(t => (t.FullName == item.Type) || (t.Name == item.Type))
                            .FirstOrDefault();
                        if (null != type)
                        {
                            // Found; done searching
                            break;
                        }
                    }
                    if (null == type)
                    {
                        // Unable to find the requested type anywhere
                        throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
                            "Unable to access type \"{0}\". Try using an assembly qualified type name.",
                            item.Type));
                    }
                }
            }
    
            // Get the DependencyProperty for which to set the Binding
            DependencyProperty property = null;
            var field = type.GetField(item.Property + "Property",
                BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Static);
            if (null != field)
            {
                property = field.GetValue(null) as DependencyProperty;
            }
            if (null == property)
            {
                // Unable to find the requested property
                throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
                    "Unable to access DependencyProperty \"{0}\" on type \"{1}\".",
                    item.Property, type.Name));
            }
    
            // Set the specified Binding on the specified property
            element.SetBinding(property, item.Binding);
        }
    
        /// <summary>
        /// Returns a stream of assemblies to search for the provided type name.
        /// </summary>
        private static IEnumerable<Assembly> AssembliesToSearch
        {
            get
            {
                // Start with the System.Windows assembly (home of all core controls)
                yield return typeof(Control).Assembly;
    
                // Fall back by trying each of the assemblies in the Deployment's Parts list
                foreach (var part in Deployment.Current.Parts)
                {
                    var streamResourceInfo = Application.GetResourceStream(
                        new Uri(part.Source, UriKind.Relative));
                    using (var stream = streamResourceInfo.Stream)
                    {
                        yield return part.Load(stream);
                    }
                }
            }
        }
    }
    
  • Delay's Blog

    Silverlight (and WPF) Data Visualization classes unsealed [Silverlight Toolkit October 2009 release now available!]

    • 6 Comments

    We've just published the October 2009 release of the Silverlight Toolkit as part of today's .NET 4 and Visual Studio 2010's Beta 2 release! One of the big things we've done with this release of the Toolkit is to add rich support for Visual Studio 2010's vastly improved Silverlight design-time experience. In fact, the new VS 2010 design-time experience has gotten so good that some developers have stopped using Blend altogether! :) I encourage everyone to have a look at the live samples for the latest release of the Silverlight 3 Toolkit, download the Toolkit installer, and try for yourself!

    Other big news for this release is the introduction of comprehensive, WPF-compatible drag-and-drop support for Silverlight! Although this support doesn't extend outside the web browser (that would require changes to Silverlight itself), it enables full-fidelity drag-and-drop experiences within the browser using the same API that WPF users are already accustomed to. And if that wasn't enough, there are also a collection of drag-and-drop-friendly "wrapper controls" for common scenarios (ex: ListBox, TreeView, and DataGrid) that make it trivial to add support for drag-and-drop to an existing control. Dragging and dropping within a control (to re-order items) or between controls (to move items around) is now just a few lines of XAML away! (Note: No code changes necessary!) But wait, there's more: There's also a wrapper for Charting's DataPointSeries that enables drag-and-drop into and out of a live Chart control! This really needs to be seen to be believed, so please visit the "Drag and Drop" page of the public samples for a great example of this. Then go read Jafar's post about the new drag/drop support for all the juicy details!

    Note: The October 09 release of the Silverlight Toolkit includes binaries for Silverlight 3 only. Now that Silverlight 3 has been out for a few months and is fully backward-compatible with all Silverlight 2 applications, we expect that everyone has upgraded from Silverlight 2 and are therefore no longer actively developing the Toolkit for Silverlight 2. Of course, if some of you have a specific need for Silverlight 2 Toolkit bits, previous releases continue to be available to download from CodePlex!

     

    With the introductory stuff out of the way, let's move on to the details of changes to the Data Visualization assembly and the corresponding improvements to Silverlight and WPF Charting. My recent post on Data Visualization Development Release 1 has already discussed most of these changes at length, so I'm just going to include the change descriptions here. For more detail on the motivation behind these changes or their implications for current and future possibilities, please refer back to that post.

    Notable Changes

    Unsealed (i.e., removed the "sealed" modifier from) all core Data Visualization classes. Although we aren't yet completely settled on the public-facing API for Data Visualization and reserve the right to make breaking changes in the future, these classes are being unsealed now to help simplify a wide variety of user scenarios that are being actively developed and that are cumbersome without the ability to subclass (without needing to create a private build of the assembly solely for the purpose of unsealing these classes). Other changes were kept to a minimum, but a couple of methods have been changed to protected virtual for consistency and/or convenience as well as some tweaks that resulted due to new code analysis warnings due to explicit interface implementations in an unsealed class.

    Introduced ISeries interface to Charting as "base interface" for all Series. This allows users to write ItemsControl-based Series which will automatically leverage all of the ItemsControl infrastructure for creating points, tracking data changes, etc. and also gives us a safe root for a future 3D series hierarchy. As part of this change, some interfaces have been cleaned up a bit (IStyleDispenser, ISeriesHost) and others have been created (IStyleDispenser.StylesChanged event). Also, some public methods with little justification have been removed/made private/moved lower (Chart.Refresh, Chart.ResetStyles, StyleDispenser.ResetStyles) and some vestigial code has been removed (ISeriesHost.GlobalSeriesIndexesInvalidated).

    Various usability improvements. Updated Series to look for "LegendItemStyle" in their ResourceDictionary for increased customizability. Added Owner property to LegendItem pointing to owning Series instance to simplify LegendItem-based user scenarios. Added ActualDataPointStyle and ActualLegendItemStyle properties and used Bindings to automatically propagate changes to the right places. (Aside: This fixes a bug that was reported against the WPF Toolkit as I was making the change!) Moved code so that PieSeries now has the DataPointStyle property like the other Series. Updated LegendItem default Template to include standard TemplateBindings for Background/BorderBrush/BorderThickness for more friendly designer experience.

    Breaking Changes

    Renamed Charting's StylePalette to Palette (for clarity) AND changed its type to IEnumerable<ResourceDictionary> (from IEnumerable<Style>) for a significant flexibility boost. Performed related renamings (many internal/private): IStyleDispenser->IResourceDictionaryDispenser, StylePalette->ResourceDictionaryCollection, StyleDispensedEventArgs->ResourceDictionaryDispensedEventArgs, StyleDispenser->ResourceDictionaryDispenser, StyleEnumerator->ResourceDictionaryEnumerator.

    Most notably, this change makes it possible to associate MULTIPLE things with a palette entry and enables designers to easily and flexibly customize things like the LineSeries PolyLineStyle in the Palette. Additionally it enables the use of DynamicResource (currently only supported by the WPF platform) to let users customize their DataPointStyle without inadvertently losing the default/custom Palette colors. (Note: A very popular request!) Thanks to merged ResourceDictionaries, this also enables the addition of arbitrary resources at the Palette level (like Brushes) which can then be referenced by DataPoints, etc..

    Changed return value of Charting's IAxis.GetPlotAreaCoordinate from UnitValue? to UnitValue to better support custom Axis implementations. Specifically, some numeric axis types (logarithmic axis, for example) don't support all numeric values and need a way to indicate that certain values (ex: <= 0 for logarithmic) are "not supported" for plotting. This was previously done by returning a null value, but now the code should return a UnitValue with Value=double.NaN. Convenience method UnitValue.NaN has been added to create such values easily. Because the Series implementations already need to handle NaN values, this change collapses two different edge cases into one and simplifies the code accordingly. Added code to Series to handle this situation by hiding (via Visibility=Collapsed) DataPoints on coordinates that are not valid.

    One notable consequence of this change is that the Visibility of DataPoints is now controlled by the Series and will be set to Visible or Collapsed as necessary. Therefore, any customizations that directly set this property may no longer work, but there are other simple ways of achieving the same effect and this change is not expected to cause any difficulty. For example, the "Sparkline" demo of the samples project was affected by this change because it provided a custom DataPointStyle that set Visibility to Collapsed. The fix is not only trivial, but an improvement: change the Style to specify a null Template instead!

    Other Changes

    Remove unnecessary code. Moved duplicated DependencyProperties IRangeAxis DependentRangeAxis and IAxis IndependentAxis from ColumnSeries and BarSeries into common base class ColumnBarBaseSeries. Moved duplicated DependencyProperties IRangeAxis DependentRangeAxis and IAxis IndependentAxis from AreaSeries and LineSeries into common base class LineAreaBaseSeries. Made similar changes for methods OnApplyTemplate and UpdateDataPoint and half of UpdateShape.

    Simplified default Palette Brushes by removing ScaleTransform and TranslateTransform and replacing with RadialBrush. The on-screen visuals remain the same, but the XAML is considerably smaller and simpler - and should be a bit quicker to render as well!

    Various other small changes.

     

    Of the two breaking changes, only the rename to Palette is likely to affect most people. Fortunately, converting existing code/XAML is really quite simple - which you can see as I recycle the example I gave previously.

    The old way:

    <chartingToolkit:Chart Title="Statistics (Custom Palette)">
        <chartingToolkit:Chart.StylePalette>
            <visualizationToolkit:StylePalette>
                <Style TargetType="Control">
                    <Setter Property="Background" Value="Blue"/>
                </Style>
                <Style TargetType="Control">
                    <Setter Property="Background" Value="Green"/>
                </Style>
                <Style TargetType="Control">
                    <Setter Property="Background" Value="Red"/>
                </Style>
            </visualizationToolkit:StylePalette>
        </chartingToolkit:Chart.StylePalette>
        ...
    </chartingToolkit:Chart>
    

    And the new way (with changes highlighted):

    <chartingToolkit:Chart Title="Statistics (Custom Palette)">
        <chartingToolkit:Chart.Palette>
            <visualizationToolkit:ResourceDictionaryCollection>
                <ResourceDictionary>
                    <Style x:Key="DataPointStyle" TargetType="Control">
                        <Setter Property="Background" Value="Blue"/>
                    </Style>
                </ResourceDictionary>
                <ResourceDictionary>
                    <Style x:Key="DataPointStyle" TargetType="Control">
                        <Setter Property="Background" Value="Green"/>
                    </Style>
                </ResourceDictionary>
                <ResourceDictionary>
                    <Style x:Key="DataPointStyle" TargetType="Control">
                        <Setter Property="Background" Value="Red"/>
                    </Style>
                </ResourceDictionary>
            </visualizationToolkit:ResourceDictionaryCollection>
        </chartingToolkit:Chart.Palette>
        ...
    </chartingToolkit:Chart>
    

    It's pretty clear that once you've done this once, it'll be easy to do anywhere else your project requires. I explained the motivations for this change previously, so I won't repeat myself here - I just wanted to call out how straightforward the upgrade is expected to be.

     

    Clearly, the big news for Data Visualization is the unsealing of the primary charting classes! Because I went into great detail on this earlier, I won't spend a lot of time on that here. Instead, I'd like to call out a particularly timely and relevant use of the new subclassing ability: Cory Plotts' LogarithmicAxis implementation for WPF and Silverlight! What's great about what he's done is that logarithmic axis support is one of our most requested features, and something we haven't had a chance to implement yet. I've always hoped that somebody in the community would be able to step up and share something here, so I was really excited to see Cory's blog post. If you're one of the users who's been waiting for a logarithmic axis, please have a look at Cory's implementation and see if it does what you need!

    Aside: You might be wondering why we haven't gotten to logarithmic axis ourselves... Well, as you may be aware, we've been operating under severe resource constraints from the beginning, and that forces us to try to choose our investments carefully. When we're trying to decide between two features and one of them constitutes a change to the core of the Charting framework while the other is something that derives from an existing class to build on top of the framework, we'll tend to make the core framework change and hope that the community is able to help with the subclassing change. Honestly, this seems like the right balance to me and is a large part of why we're unsealing now even though the Charting APIs aren't completely set in stone.

    Along similar lines, I encourage people who have been wanting to annotate their PieSeries charts to have a look at the fantastic work Bea Stollnitz has done: Part 1, Part 2, Part 3. Bea built on top of the sealed Charting hierarchy using some pretty clever tricks and techniques. But now that we've unsealed, it's my hope that she'll be able to take advantage of that to spend more time working on the great features she's adding and less time trying to jump through artificial hoops. :)

     

    There are two other things I'd like to call out here:

    As always, if you have any questions or feedback, the right places to start are the Silverlight Discussion Forum or the WPF Discussion List. Bugs and feature requests can be logged with the Silverlight Issue Tracker or the WPF Issue Tracker. Please raise issues that are clearly unique to one platform or the other in the obvious place. But for general questions and things that are common to both platforms, the Silverlight forum/list is probably a better place because there's more context and history there.

     

    Thanks very much for your interest in Silverlight and WPF Data Visualization - I hope you like the improvements!

Page 5 of 28 (277 items) «34567»