Delay's Blog

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

March, 2010

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

    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

    My new home page, refreshed [Updated collection of great Silverlight/WPF Data Visualization resources!]

    • 11 Comments

    Some great content has been published since I posted my previous collection of Silverlight/WPF Charting links. What's more, the November 2009 release of the Silverlight Toolkit and the February 2010 release of the WPF Toolkit have both been released, so please have a look at them if you haven't already!

    Now, without further ado, here are all links that are fit to print (FYI: previously published links are gray):

    Overviews (100 level)

    Scenarios (200 level)

    Internals (300 level)

    Team Member posts (Partner level)

    My posts (Ego level)

    My many thanks go out to everyone who has spent time helping people learn how to use Silverlight/WPF Data Visualization!

    PS - If I've missed any good resources, please leave a comment with a link - I'm always happy to find more great content! :)

    PPS - The most recent version of this collection will always be pointed to by http://cesso.org/r/DVLinks. If you're going to create a favorite or link to this post, please use that URL so you'll always be up to date.

  • Delay's Blog

    Sometimes you just gotta do the best you can [Tip: Read-only custom DependencyProperties don't exist in Silverlight, but can be closely approximated]

    • 13 Comments

    Tip

    Read-only custom DependencyProperties don't exist in Silverlight, but can be closely approximated

    Explanation

    My last tip discussed a special case of creating a Silverlight/WPF DependencyProperty where it's necessary to create a read-only property. (Aside: Read-only DependencyProperties are read-only outside the owning class, but can be changed by the class itself at any time.) This task is quite simple on WPF where a single call to RegisterReadOnly does it all. However, Silverlight (as of version 4) does not support the RegisterReadOnly method, so if you want a read-only DependencyProperty on that platform, you'll need to do some extra work. Unfortunately, I don't think it's possible to do a perfect job - but you can get fairly close with something like the code below. The basic principle is to catch illegal attempts to change the property's value (i.e., those coming from outside the owning class) and undo those changes as quickly and silently as possible. For convenience, the CLR wrapper's setter hides the details from users of the class (in a notable exception to one of the earlier tips). The code looks a little more complicated than it really is because it tries to be resilient to exceptions and because it uses two state variables to avoid calling the class's virtual OnPropertyChanged method when recovering from a bogus change. For consistency, the exception that's thrown after an invalid attempt to change the property's value is similar to the corresponding exception on WPF. And while this approach can't prevent bound values from seeing the property "twitch" briefly, I also don't know of a way to avoid that (recall that the DependencyProperty itself must be public so other code can create Bindings to it). Like I said above, this isn't perfect - but it's pretty close. :)

    Good Example

    public int MyReadOnly
    {
        get { return (int)GetValue(MyReadOnlyProperty); }
        protected set
        {
            try
            {
                _changingMyReadOnly = true;
                SetValue(MyReadOnlyProperty, value);
            }
            finally
            {
                _changingMyReadOnly = false;
            }
        }
    }
    private bool _changingMyReadOnly;
    private bool _restoringMyReadOnly;
    public static readonly DependencyProperty MyReadOnlyProperty = DependencyProperty.Register(
        "MyReadOnly", typeof(int), typeof(MyControl), new PropertyMetadata(0, OnMyReadOnlyChanged));
    private static void OnMyReadOnlyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        MyControl myControl = (MyControl)d;
        if (myControl._changingMyReadOnly)
        {
            if (!myControl._restoringMyReadOnly)
            {
                myControl.OnMyReadOnlyChanged((int)e.OldValue, (int)e.NewValue);
            }
        }
        else
        {
            try
            {
                myControl._restoringMyReadOnly = true;
                myControl.MyReadOnly = (int)e.OldValue;
            }
            finally
            {
                myControl._restoringMyReadOnly = false;
            }
            throw new InvalidOperationException("'MyReadOnly' property is read-only and cannot be modified.");
        }
    }
    protected virtual void OnMyReadOnlyChanged(int oldValue, int newValue)
    {
        // TODO: Handle property change
    }

    More information

  • Delay's Blog

    That's why it's called the default *value* instead of default *values* [Tip: The default value of a DependencyProperty is shared by all instances of the class that registers it]

    • 9 Comments

    Tip

    The default value of a DependencyProperty is shared by all instances of the class that registers it

    Explanation

    The last two tips explained how to set the default value of a Silverlight/WPF DependencyProperty. But there's something you need to be aware of when you're using either technique: the default value of a DependencyProperty is shared by all instances of a class. This doesn't tend to matter for value types, immutable reference types, and sharable types (like brushes), but it affects mutable reference types and can lead to unexpected behavior. The most common scenario is creating a collection-type DependencyProperty (for something like Collection(T)) - the intent is for each instance to have its own unique collection, but because the default value is shared, all instances end up sharing the same list! In such cases, there are two things to change: make the DependencyProperty read-only (with RegisterReadOnly) and initialize the property in the class constructor. [Didn't a previous tip say that was bad? Yes, but this scenario is special. :) ] When a class exposes a collection-type DependencyProperty, the intent is typically to use the same collection instance for the life of the object. And that's what makes it okay to set the property in the constructor: it doesn't matter that nobody can override the default value with a Style because they're not supposed to anyway. Next time: Why this can't be done on Silverlight.

    Good Example

    public Collection<string> MyStringCollection
    {
        get { return (Collection<string>)GetValue(MyStringCollectionProperty); }
    }
    protected static readonly DependencyPropertyKey MyStringCollectionPropertyKey =
        DependencyProperty.RegisterReadOnly(
            "MyStringCollection",
            typeof(Collection<string>),
            typeof(MyControl),
            new PropertyMetadata(null));
    public static readonly DependencyProperty MyStringCollectionProperty =
        MyStringCollectionPropertyKey.DependencyProperty;
    
    public MyControl()
    {
        SetValue(MyStringCollectionPropertyKey, new Collection<string>());
    }

    More information

  • Delay's Blog

    Freedom isn't free [Tip: When creating a DependencyProperty, follow the handy convention of "wrapper+register+static+virtual"]

    • 5 Comments

    Tip

    When creating a DependencyProperty, follow the handy convention of "wrapper+register+static+virtual"

    Explanation

    The fundamental steps for defining a Silverlight/WPF DependencyProperty are fairly rigid and not open to a great deal of flexibility (as I discuss in this earlier tip about the CLR wrapper). However, there's a bit more freedom once you add a default value or a PropertyChangedCallback delegate to the mix - but don't let it go to your head! :) For convenience and flexibility, I recommend the pattern shown below; the same one used by most of the core Silverlight and WPF controls. Observe that while the PropertyMetadata constructor requires a static delegate for property change notifications, doing instance-specific work in a static method is inconvenient. Therefore, the static method below does the bare minimum before handing execution off to a more appropriate instance method. (Aside: Explicit casts are safe because the DependencyProperty infrastructure is responsible for honoring the contract of the Register call.) The extra level of indirection also provides an opportunity to pass more meaningful parameters to the change handler: the property's old value and its new value. And because the instance method is virtual, subclasses can override it to receive their own notification of property changes easily and efficiently. Working with DependencyProperty can be tricky enough; do yourself a favor and start with a solid foundation.

    Good Example

    public int MyProperty
    {
        get { return (int)GetValue(MyPropertyProperty); }
        set { SetValue(MyPropertyProperty, value); }
    }
    public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register(
        "MyProperty", typeof(int), typeof(MyControl), new PropertyMetadata(0, OnMyPropertyChanged));
    private static void OnMyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((MyControl)d).OnMyPropertyChanged((int)e.OldValue, (int)e.NewValue);
    }
    protected virtual void OnMyPropertyChanged(int oldValue, int newValue)
    {
        // TODO: Handle property change
    }

    More information

  • Delay's Blog

    When you have two good options, go with the easier one [Tip: Set DependencyProperty default values in a class's default style if it's more convenient]

    Tip

    Set DependencyProperty default values in a class's default style if it's more convenient

    Explanation

    In the previous tip, I explained why it's usually wrong to assign a value to a Silverlight/WPF DependencyProperty in the constructor for a class. The preferred way is to pass the default value in the call to Register, but there's another good option: set the property's starting value in the default Style for the control by putting it in generic.xaml. A control's default style is applied when it is first created and the corresponding changes to its DependencyProperty values have very low precedence (though not as low as the default value passed to Register). Therefore, this is a safe place to set default values without the risk of overriding application-level customizations. A nice benefit of this approach is that it allows the value to be specified in XAML - which offers a designer-friendly syntax and can sometimes be easier to understand. In the example below, a rather complicated Brush is constructed in XAML; the matching code to create that same brush would not be as clear. Next time: Something to watch out for when setting default values.

    Good Example

    <ResourceDictionary
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:local="clr-namespace:DevelopmentTips">
        <Style TargetType="local:MyControl">
            <Setter Property="MyBrush">
                <Setter.Value>
                    <LinearGradientBrush>
                        <GradientStop Color="Red" Offset="0"/>
                        <GradientStop Color="Green" Offset="0.5"/>
                        <GradientStop Color="Blue" Offset="1"/>
                    </LinearGradientBrush>
                </Setter.Value>
            </Setter>
        </Style>
    </ResourceDictionary>

    More information

  • Delay's Blog

    Do one thing, and do it well [Tip: The CLR wrapper for a DependencyProperty should do its job and nothing more]

    • 5 Comments

    Tip

    The CLR wrapper for a DependencyProperty should do its job and nothing more

    Explanation

    The CLR wrapper for a Silverlight/WPF DependencyProperty exists purely as a convenience to the developer. The "real" value of a DependencyProperty is stored by the system and accessed by the GetValue and SetValue methods. In fact, parts of the system will only access the value in that manner (possible examples: XAML parser, storyboard animations, etc.). And even if that weren't the case, the fact that the DependencyProperty field is public means that other parts of an application might do so as well (and there is no way of knowing when or stopping them). Therefore, it's not possible to ensure that any custom logic added to the CLR property's set or get wrapper implementation will run every time the DependencyProperty is accessed. Unless you're a fan of inconsistent state, hard to find bugs, or the like, it is typically unwise to violate this convention.

    Good Example

    public int MyProperty
    {
        get { return (int)GetValue(MyPropertyProperty); }
        set { SetValue(MyPropertyProperty, value); }
    }

    More information

  • Delay's Blog

    The platform giveth power, don't taketh it away [Tip: Do not assign DependencyProperty values in a constructor; it prevents users from overriding them]

    Tip

    Do not assign DependencyProperty values in a constructor; it prevents users from overriding them

    Explanation

    Initializing variables in an object's constructor is considered a Good Thing. Traditionally, initialization is done with a simple assignment that sets the variable to its initial value: MyProperty = 10;. However, doing that with a Silverlight/WPF DependencyProperty uses the CLR wrapper (more background here and here) and results in a call to SetValue that sets the local value of that property. The precedence order for DependencyProperty values is such that the local value overrides almost any other value the application may have provided with a Style (normal or implicit) Setter or Trigger. But if a property can't be styled, then much of the goodness of being a DependencyProperty goes out the window... Fortunately, there are two good alternatives; the most direct is to pass the default value in the call to Register. Setting the default value that way is nice because it's easy, it's obvious, and it "just works". And since DependencyProperty default values have the lowest precedence of anything, you don't need to worry about overriding any customizations users may have made. Next time: The other option.

    Good Example

    public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register(
        "MyProperty", typeof(int), typeof(MyControl), new PropertyMetadata(123, OnMyPropertyChanged));

    More information

  • Delay's Blog

    Q: How do you eat an elephant? A: One bite at a time... [Announcing a new "Development Tips" series on my blog!]

    With all that's going on lately, more and more people are moving their development efforts to Silverlight and WPF. What's nice is that there are already a lot of great resources available to help developers learn the basics of Silverlight and WPF programming. Whether you prefer books, videos, blogs, etc., there's no shortage of material out there to help you get started!

    But what about the next stage? What do you do to learn the finer points of the platform? The subtle nuances? The tricks? The traps??

    One of my goals for this blog is to help intermediate and advanced developers shed their inhibitions and get more intimately involved with the platform. Therefore, many of my posts push the boundaries or do things in ways that might not be completely obvious to a newcomer. But there's another facet to becoming a proficient developer - learning best practices and incorporating them into your daily routine. To that end, I'll be doing a new series of posts tagged "Development Tips"!

    The idea is that each tip will include a short, clear directive, a brief, easy to understand explanation, a simple example, and a few links to more information. Some of the tips are bound to be things just about everyone knows, while others will probably be new to some of you. Some can be found in the documentation for the platform, but others will be simple conventions that have been found to make life easier. And though there are exceptions to every rule, I won't be calling them out because I want to keep the recommendations clear and concise.

    I'm going to try to avoid controversial topics, but it would be silly not to expect some discontent every now and then. :) If you disagree with something I've written, please leave a comment explaining why you disagree what you recommend instead. I'll follow up on comments like that and if there are enough people who call me out on something, I'll revisit the topic in a new post highlighting the controversy. Of course, I'm not claiming that anything I recommend is definitively the best technique! Every situation is different and everyone has their own favorite ways of doing things. Rather, I'd like to share some tips that I've found to work well in my experience - and that seem likely to help others in similar situations.

    Okay, enough boring background already - the next post will be the first of the Development Tips!

     

    PS - That link in the previous sentence takes you to my blog's tag filter for "Development Tips". I'll tag every tip like that so it will be easy to see them all in one place.

Page 1 of 1 (10 items)