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

    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

    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

    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

    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

    There's no substitute for customer feedback! [Improving Windows Phone 7 application performance now a bit easier with LowProfileImageLoader and DeferredLoadListBox updates]

    • 26 Comments

    PhonePerformance List Scrolling sample Windows Phone 7 applications run on hardware that's considerably less powerful than what drives typical desktop and laptop machines. Therefore, tuning phone applications for optimum performance is an important task - and a challenging one! To help other developers, I previously coded and blogged about two classes: LowProfileImageLoader (pushes much of the cost of loading images off the UI thread) and DeferredLoadListBox (improves the scrolling experience for long lists). These two classes can be used individually or together and have become a regular part of the recommendations for developers experiencing performance issues.

     

    I've heard from lots of people who've benefitted from LowProfileImageLoader and DeferredLoadListBox - thank you! I've also received some great suggestions; I recently implemented some of them and wanted to share the new code/bits:

    • The first issue was reported to me by Corrado Cavalli via email. Whereas my sample applications loaded data after they'd initialized, Corrado's DeferredLoadListBox scenario populated its data during initialization. This led to code running in OnApplyTemplate which made the (wrong) assumption that the ListBox's ListBoxItem containers would already have been created. That's not the case during OnApplyTemplate, so DeferredLoadListBox got confused and threw an exception. The simple fix for this problem is to allow for missing containers during OnApplyTemplate - which I've done. Fortunately, subsequent activity (container creation during PrepareContainerForItemOverride) already ensures the right things happen after the containers are created.

    • The second issue was reported to me by Pat Long in the comments to my first blog post. The problem he encountered was that Blend would crash when working with an application using LowProfileImageLoader in certain cases (presumably because of the background worker thread it creates). There's an easy fix here as well - simply short-circuit the worker thread at design-time because the performance implications of LowProfileImageLoader aren't relevant then anyway. I reproduced the problem myself with Visual Studio and fixed it there - then I checked Pat's blog and found he'd done almost exactly the same thing for his Blend-based scenario! [It's nice when separate efforts arrive at the same place. :) ]

    • The third suggestion also came from comments to that blog post - David Burela wanted to use LowProfileImageLoader for images that were part of his application. I'd only been considering the cost of downloading images when I wrote this class, but David pointed out that much of the same "UI thread-friendly" logic should apply to local images as well. Fortunately, it was a simple matter to add support for relative Uris (ex: "/Images/Picture.jpg") - and now LowProfileImageLoader works well with both kinds of images!

      Note: For this new functionality to work, the project's images must have their "Build Action" set to "Content" (not "Resource"). (Here's what that setting looks like in Visual Studio.)
    • The fourth suggestion came from the comments to my second blog post where Johnny Westlake pointed out that LowProfileImageLoader could be counterproductive once it had already worked its magic. In other words, though it's great for lowering the cost of initial image load, the throttling behavior might be undesirable during subsequent loads (ex: during Back button navigation) because by that time the image has been cached by the web stack and the download cost is much lower. At first, I wasn't sure how LowProfileImageLoader could tell which situation a particular image was in - then I realized I didn't have to! :)

      What I've done instead is add a static LowProfileImageLoader.IsEnabled property (true by default) that the developer can toggle whenever he/she wants to (ex: on Back navigation to a page that has already used LowProfileImageLoader). When this property is set to false, LowProfileImageLoader doesn't perform its UI thread sleight-of-hand so the image load behavior is the same as it would have been if LowProfileImageLoader weren't present at all. While this might seem like a bit of a pass the buck move on my part, the practical truth is that the application's author has way more context than LowProfileImageLoader does. So in lieu of a reliable way to detect the cache state of individual images (I'm open to suggestions, by the way!), the new IsEnabled property seems like a reasonable "next best thing".

     

    [Click here to download the compiled PhonePerformance assembly, sample applications, and full source code for everything.]

     

    My thanks go out to the people who have shared their experience and suggestions with/for LowProfileImageLoader and DeferredLoadListBox! While these two classes are not appropriate in every situation, they seem to be useful in enough situations that it's worth giving them a quick try if you're experiencing relevant performance problems. Both classes are easy to hook up, so trying them out takes no more than a couple of minutes - which will be time well spent if the problem goes away!

    I wish everyone good luck with their Windows Phone 7 applications - and please don't forget the value of great performance! :)

Page 5 of 28 (277 items) «34567»