Delay's Blog

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

Posts
  • Delay's Blog

    Script combining made easy [Overview of the AJAX Control Toolkit's ToolkitScriptManager]

    • 22 Comments

    Note: Recent versions of ASP.NET AJAX include the ScriptManager.CompositeScript property which performs much the same functionality as discussed here. More information is available in the article Combining Client Scripts into a Composite Script.

     

    Introduction

    The 10606 release of the AJAX Control Toolkit includes ToolkitScriptManager, a new class that derives from the ASP.NET AJAX ScriptManager and performs automatic script combining.

    What is meant by "script combining" and why is it desirable?

    ASP.NET AJAX Behaviors are typically implemented by JavaScript (JS) files that are downloaded by the browser as part of the web page's content/resources (like CSS files, images, etc.). Each JS file typically contains a single Behavior, so if a web page uses lots of Behaviors, it's going to download lots of Behavior JS files. The cost to download a single JS file is fairly minimal, but when there are many of them on a page, the serialized nature of Behaviors (later ones may depend on earlier ones and can't be loaded until the earlier ones have finished) means that it may take a bit of time for all the JS a page needs to download to the user's browser. The time in question is typically on the order of milliseconds, but every little bit helps when you're looking to give users the best possible experience!

    Script combining is beneficial because fewer JS files means fewer request/response operations by the browser - which translates directly into quicker page load times for users and less load on the web server. Furthermore, there will be less network traffic because the HTTP headers associated with each unnecessary request/response operation don't need to be transmitted (saving around 750 bytes for each combined script).

    At the extreme, one could combine all (~40) the Behaviors in the Toolkit into a single, monolithic JS file (either manually or as part of the build process) and always send that file to the browser. While there are certain benefits to this, we chose not to do so for two main reasons: 1) the ASP.NET AJAX framework the Toolkit builds upon is an object-oriented framework and it's beneficial to maintain the mental/physical separation that comes from keeping each behavior isolated and 2) any page that used any part of the Toolkit would be forced to download the entire set of scripts in the Toolkit.

    ToolkitScriptManager gives us the best of both worlds by combining exactly the relevant JS files used by each page into one file so the browser downloads only what's necessary for each page. ToolkitScriptManager's script combining is more than a simple concatenation of all the script files in the Toolkit; it's a dynamic merge of only the scripts that are actually being used by a page each time it's loaded by the browser. If a page has an ASP.NET AJAX UpdatePanel on it and additional scripts need to be sent as part of an async postback, then ToolkitScriptManager will automatically generate a combined script file containing only those scripts that the browser hasn't already downloaded.

    ToolkitScriptManager automatically compresses the combined script file if the browser indicates it supports compression - achieving slightly better compression in the process because most adaptive dictionary-based compression techniques (like HTTP's GZIP and Deflate) tend to compress data better in one chunk than in multiple chunks (because the dictionary doesn't keep getting reset). The combined script file is cached and reused by the browser just like the corresponding script files would have been if ToolkitScriptManager weren't being used.

    Basically, script combining effortlessly creates faster loading web pages - and happier users by extension!

    How do I use it?

    Simple: Just replace <asp:ScriptManager ... /> with <ajaxToolkit:ToolkitScriptManager ... /> in your ASPX page and you're done! (Of course, if you're not using the default namespaces "asp" and "ajaxToolkit", you'll need to substitute your own namespaces.) The scripts in the AJAX Control Toolkit are already enabled for combining, so it's really that easy!

    ToolkitScriptManager derives from ScriptManager, so it can be substituted trivially. Configuration-wise, it exposes a single new property beyond what ScriptManager already has: bool CombineScripts. The default value of "true" means that scripts will be automatically combined - specifying the value "false" disables the combining. (Alternatively, just switch back to ScriptManager for the same result.)

    How do I enable combining for my custom Behavior's scripts?

    As a security precaution to prevent malicious users from taking advantage of ToolkitScriptManager to access embedded resources from unrelated DLLs, the new assembly-level ScriptCombine attribute must be used to indicate that a particular assembly/script is allowed to take part in the script combining process. By default, none of the scripts in an assembly without the ScriptCombine attribute will take part in script combining. Adding the ScriptCombine attribute to an assembly indicates that all of its scripts can take part in script combining and ToolkitScriptManager will automatically include the relevant ones when generating a combined script file. For finer control over individual scripts in an assembly, the ScriptCombine attribute exposes an ExcludeScripts property and an IncludeScripts property - both are comma-delimited lists of individual script files. As stated, when neither property is specified, the default behavior is that all scripts are combinable. Once the IncludeScripts property is set, only the scripts explicitly specified by it are combinable. The ExcludeScripts property excludes any listed scripts from combining (whether or not IncludeScripts is set). Of course, most people will simply add the ScriptCombine attribute to their assembly and the default behavior does what they want. (As mentioned above, Toolkit scripts are already enabled for script combining.)

    How do scripts actually get combined? [Technical]

    Script combining is a two-stage process.

    The first stage takes place during the normal ASP.NET page lifecycle when ToolkitScriptManager overrides ScriptManager's OnLoad method to initialize its state and its OnResolveScriptReference method to find out when script references are being resolved. The initialization code in OnLoad consists of adding a HiddenField to the page and using it to track which scripts have already been loaded by the browser. The handling of OnResolveScriptReferences is a little more involved: the script reference is checked for combinability (i.e., does the assembly's ScriptCombine attribute allow the script to take part in script combining) and the script reference is changed to point to the URL of a combined script file. In this manner, all scripts that are part of the same combined script file get the same URL and the ScriptManager class outputs that URL to the page exactly once. Notably, because scripts may have a strict ordering, the presence of an uncombinable script in the middle of combinable scripts will result in two combined script files being generated (the first consisting of the scripts coming before the uncombinable script and the second consisting of the scripts after it).

    The URL of the combined script file is currently of the form: .../Page.aspx?_scriptcombiner_=;Assembly1.dll Version=1:MVID1:Script.Name.1.js:Script.Name.2.js;Assembly2.dll Version=2:MVID1:Script.Name.3.js. What this means is that the ASPX page itself is referenced with the special request parameter "_scriptcombiner_" and a semicolon-delimited list of assemblies with a colon-delimited list of the required scripts from each of them. The strong name of the assembly is used to avoid potential confusion if multiple versions of an assembly are present and the ModuleVersionID (MVID) is used to ensure that any changes to the assembly itself automatically invalidate all combined script files that reference it. In this manner, recompiling one of the assemblies contributing to a combined script file will cause the new scripts to be downloaded by the browser next time the page is loaded.

    The second stage of script combining takes place when the page is referenced with the "_scriptcombiner_" request parameter. ToolkitScriptManager overrides OnInit (one of the first parts of the page lifecycle) and uses that opportunity to generate the combined script file based on the value of the request parameter, outputs the combined script file to the browser, and stops further processing of the page lifecycle. Of note, the cache settings of the combined script file are set to the same values that the individual script files would have had if ToolkitScriptManager weren't being used and the combined script file is automatically compressed according to the browser's wishes. Similarly, any localized script resources for a script file that is in the process of being combined are loaded and sent to the browser as part of the combined script file. After all combined scripts are output, ToolkitScriptManager appends a small bit of script to the end of the file to update the page's HiddenField with the scripts that have just been added. In this manner, any additional scripts added during an async postback are automatically tracked by the page and subsequent async postbacks will know exactly which scripts have already been loaded by the browser.

    Why piggyback a request parameter on the same page instead of using an IHttpHandler? [Technical]

    ScriptManager uses ScriptResourceHandler (an IHttpHandler) to serve (uncombined) scripts, so it's natural to wonder why ToolkitScriptManager wouldn't do the same. The reason is that the AjaxControlToolkit DLL is often run in partial trust scenarios where it couldn't add such a handler to the system itself - and because we don't want people to have to modify their web.config file just to enable script combining. By making use of the same page for serving combined script files, ToolkitScriptManager offers a seamless experience that's simple to configure, simple to manage, and that works even for folks who don't have control over the web server that's hosting their content.

    Are there any tradeoffs when switching to ToolkitScriptManager?

    There are no significant tradeoffs that we know of, but there are a couple of implications it's good to be aware of. For one, the current combined script URL format is currently pretty verbose and can lead to unusually long URLs. While this hasn't been a problem so far, it will be easy to change the format in the future (with no impact to users) and we're already considering ways of doing so. Another thing to be aware of is that reusing the page to serve the combined script file means that there is some additional server processing that happens before/during the OnInit stage of the page lifecycle when processing a combined script file. (Though the additional work here is offset by the savings of not having to serve multiple JS files.) Again, this hasn't been an issue, but it's something to keep in mind if things behave differently after adding ToolkitScriptManager to a page.

    So just how risky is it to switch to ToolkitScriptManager?

    That's a loaded question [ :) ], but it's informative to note that all AJAX Control Toolkit sample pages (including the sample web site, automated tests, manual tests, etc.) have been converted over to use ToolkitScriptManager with only one issue: The Slider's SliderBehavior.js script uses a fairly obscure feature enabled by the PerformSubstitution property of the WebResource attribute that allows <%= WebResource/ScriptResource %> tags to be embedded in JS files and get resolved before the script is sent to the browser. This behavior isn't currently supported by ToolkitScriptManager (it will throw an informative Exception if it detects the presence of this construct), so the ExcludeScripts property of the ScriptCombine attribute on the AjaxControlToolkit DLL has been used to exclude the SliderBehavior.js file from being combined.

    Summary

    ToolkitScriptManager works seamlessly in every page of the AJAX Control Toolkit, so we encourage folks to give it a try if they're interested in the benefits it offers! As always, if you encounter any problems, please let us know by posting a detailed description of the problem to the AJAX Control Toolkit support forum.

    Happy script combining!!

  • Delay's Blog

    Safely avoiding the "access denied" dialog [How to: Work around the access denied cross-domain IFRAME issue in the AJAX Control Toolkit]

    • 27 Comments

    Bertrand recently blogged "How to work around the access denied cross-domain frame issue in ASP.NET Ajax 1.0". Unfortunately, a similar issue exists in the AJAX Control Toolkit - with a similar workaround that's detailed in this post.

    Background: The AJAX Control Toolkit attempts to use as much of the ASP.NET AJAX infrastructure as possible in order to keep things simple and benefit from the established architecture of ASP.NET AJAX. However, the ASP.NET AJAX 1.0 implementation of Sys.UI.getLocation was not accurate for IFRAME content with certain page layouts in IE6 - one of which was used by the Toolkit test harness. We couldn't have our automated tests failing - and the ASP.NET AJAX team wasn't able to include a fix in their 1.0 release - so we needed to address this somehow. Since we had developed our own getLocation implementation that was accurate in IE6, we decided to route all Toolkit calls to getLocation through our own wrapper function - which used our improved implementation for IE6 and called through to the ASP.NET AJAX implementation for everything else. This works pretty well - except that our version suffers from the same cross-domain permissions issue that the ASP.NET AJAX version suffers from (please refer to Bertrand's post for more details).

    Fix: We've just checked in a fix to the Toolkit's Development branch to add the same try/catch wrapper that Bertrand suggests; this fix will be included with the next Toolkit release (in about a month). In the meantime, people who want the fix sooner are encouraged to apply the changes themselves. To do so, open AjaxControlToolkit.sln in Visual Studio, open the file AjaxControlToolkit\Common\Common.js, find the getLocation function, add the yellow, italic block below, and build the project to get a new AjaxControlToolkit.dll with the fix. The complete function definition with the fix applied is included here to make this as easy as possible:

    getLocation : function(element) {
        
    /// <summary>Gets the coordinates of a DOM element.</summary>
        /// <param name="element" domElement="true"/>
        /// <returns type="Sys.UI.Point">
        ///   A Point object with two fields, x and y, which contain the pixel coordinates of the element.
        /// </returns>

        // workaround for an issue in getLocation where it will compute the location of the document element.
        // this will return an offset if scrolled.
        //
        if (element === document.documentElement) {
            
    return new Sys.UI.Point(0,0);
        }

        
    // Workaround for IE6 bug in getLocation (also required patching getBounds - remove that fix when this is removed)
        if (Sys.Browser.agent == Sys.Browser.InternetExplorer && Sys.Browser.version < 7) {
            
    if (element.window === element || element.nodeType === 9 || !element.getClientRects || !element.getBoundingClientRect) return new Sys.UI.Point(0,0);

            
    // Get the first bounding rectangle in screen coordinates
            var screenRects = element.getClientRects();
            
    if (!screenRects || !screenRects.length) {
                
    return new Sys.UI.Point(0,0);
            }
            
    var first = screenRects[0];

            
    // Delta between client coords and screen coords
            var dLeft = 0;
            
    var dTop = 0;

            var inFrame = false;
            
    try {
                inFrame = element.ownerDocument.parentWindow.frameElement;
            }
    catch(ex) {
                
    // If accessing the frameElement fails, a frame is probably in a different
                // domain than its parent - and we still want to do the calculation below
                inFrame = true;
            }

            
    // If we're in a frame, get client coordinates too so we can compute the delta
            if (inFrame) {
                // Get the bounding rectangle in client coords
                var clientRect = element.getBoundingClientRect();
                
    if (!clientRect) {
                    
    return new Sys.UI.Point(0,0);
                }

                
    // Find the minima in screen coords
                var minLeft = first.left;
                
    var minTop = first.top;
                
    for (var i = 1; i < screenRects.length; i++) {
                    
    var r = screenRects[i];
                    
    if (r.left < minLeft) {
                        minLeft = r.left;
                    }
                    
    if (r.top < minTop) {
                        minTop = r.top;
                    }
                }

                
    // Compute the delta between screen and client coords
                dLeft = minLeft - clientRect.left;
                dTop = minTop - clientRect.top;
            }

            
    // Subtract 2px, the border of the viewport (It can be changed in IE6 by applying a border style to the HTML element,
            // but this is not supported by ASP.NET AJAX, and it cannot be changed in IE7.), and also subtract the delta between
            // screen coords and client coords
            var ownerDocument = element.document.documentElement;
            
    return new Sys.UI.Point(first.left - 2 - dLeft + ownerDocument.scrollLeft, first.top - 2 - dTop + ownerDocument.scrollTop);
        }

        
    return Sys.UI.DomElement.getLocation(element);
    },

    Sorry for the trouble - we hope this patch helps any people who might be running into this issue!

    Updated at 6pm: Modified the getLocation implementation to return the correct value in all cases.

  • Delay's Blog

    Silverlight Charting is faster and better than ever [Silverlight Toolkit March 09 release now available!]

    • 12 Comments

    We've just published the March 09 release of the Silverlight Toolkit and I bet there's something in there to make just about everyone happy! There are bug fixes for some of your favorite controls, a variety of cool new features sprinkled all around, and some completely new controls that will add a little more excitement to your next Silverlight application. The Toolkit continues to support everyone's ongoing Silverlight 2 development efforts and now also supports the newly released Silverlight 3 Beta. What's more, you get all of this goodness for the same low, low price of: free! :)

    I encourage everyone to have a look at the live samples for Silverlight 2 or Silverlight 3, download the Toolkit installer(s), and enjoy the new stuff!

    Aside: As a public service, I'll also direct your attention to the new LayoutTransformer control which didn't get its own sample page this time around, but remains near and dear to my heart. And if you look closely enough, you just might be able to spot LayoutTransformer being used by Accordion and Charting...

     

    But enough about the rest of the Toolkit; you're probably here because you want to learn more about Silverlight Charting. Well, you've come to the right place! If you haven't already done so, this might be a good time to read or review my previous two introductory posts for Charting: this one for the initial November release and this one for the follow-up release in December. The March 09 release of Charting builds on the foundation Jafar and I previously established to add some new features, improve some usability scenarios, fix some bugs, and address what was easily our most common request: better performance!

    Let's start with the full version of the Charting release notes (a simplified version of these notes is available on the "What's New" page for the Toolkit - but it omits all the juicy details):

    Notable Changes

    Significant performance improvements have been made to many parts of the Charting framework which yield noticeable speed-ups for scenarios dealing with large data sets. Because performance is one of those areas where it's easy to make improvements against benchmarks and yet achieve very little benefit that's relevant to the user, I won't cite the benchmark-based improvements our testing shows for this release. Instead, I'll share the anecdotal story of a Charting customer who got a chance to try an early version of the code and found that a scenario which was starting to bog down with around 500 points under the previous release could now manage over 5000 points with no difficulty! Your mileage may vary, of course, but we definitely think the new Charting core feels snappier!

    Added new Series type AreaSeries which works just like LineSeries except that it fills in the area underneath the line. With the addition of AreaSeries in this release, Silverlight Charting now supports all of the major chart types offered by the top level of Excel's ribbon! We've still got some work to do before we can think about trying to replace Excel (like supporting stacked series!), but it's nice to be moving in the right direction and to have achieved this "mini milestone". :)

    Any Series can use a CategoryAxis as its independent value axis. Specifically, it is now possible to create a LineSeries with values on the vertical axis and categories (ex: Dogs, Cats, Fish) on the horizontal axis. The purists among you might argue that it's inappropriate to use a LineSeries in this manner because the presence of a line connecting each value suggests a cross-value correlation that's typically not present with categorized data. So the purists are welcome to ignore this feature entirely - but those of you who have requested this feature should please go ahead and make use of it however you feel best fits your particular scenario. :)

    Added DependentValuePath and IndependentValuePath string-typed properties to the Series classes (and a corresponding SizeValuePath property to BubbleSeries) to significantly simplify the experience of binding to properties of the underlying data objects being visualized. Instead of having to type something like DependentValueBinding="{Binding Value}", it's now also possible to use the simpler, easier to read DependentValuePath="Value". In fact, we like the simplicity of the new form so much that we've made the new *Path properties the default user-visible properties at design time! The *Binding properties are still present, of course, and can be used for more advanced scenarios such as those where it's necessary to use the Converter parameter of the Binding. The continued support of the *Binding properties also means that any existing code using the Charting core will continue to work just fine as-is and won't need to be changed.

    Numerous design-time enhancements for Silverlight 3 and Blend 3. The most obvious improvement here is that dragging a new Chart onto the design surface automatically creates some simple sample data as well as a ColumnSeries that's properly hooked up to that data. This means that not only does the new chart look like a chart (instead of being mostly blank), but it also demonstrates how to correctly hook a Series up to a data source. It's like getting a mini Charting tutorial for free! :) Another big improvement in this area is better exception handling during design-time so the code is less likely to throw exceptions during the intermediate configuration steps that are common to interactive design. This makes it easier to build a chart interactively without having it complain about missing values along the way. Other design-time improvements include improved metadata and a slightly improved workflow.

    Added support for Silverlight 3's new easing feature for better control over how data change animations behave. Available only with Silverlight 3 Charting, the new TransitionEasingFunction property of Series makes it easy to select any of Silverlight 3's easing functions to be used when animating the value change of the data points in a Chart. The default value, QuadraticEase, provides a smoother, more pleasing animation that helps keep the viewer focused on the data itself. And while we haven't explicitly changed the DataPoint default fade in/fade out show/hide transition behavior to use an easing function, it's easy for anyone else to do so by providing a custom DataPoint Template in the usual manner.

    Added ExtendRangeToOrigin and Origin properties to LinearAxis to enable more scenarios when this axis is used for dependent values. The ExtendRangeToOrigin property is handy when you know you want to display the origin (0) on the dependent axis, but you don't want to have to set Minimum or Maximum because your data could have positive or negative values - or both! Setting ExtendRangeToOrigin is an easy way to ensure that a ColumnSeries or BarSeries always displays the full extent of its columns/bars which makes it easy for the viewer to draw conclusions based on the relative sizes of the elements. Similarly, the Origin property allows you to override the default origin for scenarios where 0 just isn't good enough.

    Changed the underlying types of the Chart's Series and Axis properties to improve the code- and design-time experience when using them. As a result, Blend now automatically filters its class list and offers only valid types when adding a new item to either of these collections.

    Fixed a memory leak that could occur when a Chart was removed from the visual tree and had one or more Series with their ItemsSource properties set to an ObservableCollection(T) owned by the application. The specifics of the leak scenario are a bit technical, so I'll refer interested readers to this post of mine which contains a fairly detailed description of the problem and its solution.

    Changed the default Template of ScatterDataPoint to improve performance and provide more consistent behavior. The previous ScatterDataPoint Template employed a RotateTransform to create its diamond shape - in this release we've switched to a Path which enables us to simplify the Template reduce the ScatterDataPoint's overhead just a bit.

    Various architectural changes to improve the current and future programmability story for Charting. Most of these changes aren't the kinds of things that you'll notice as a user, but they're part of our ongoing effort to open up the Charting platform and provide a solid foundation for others to build upon.

    Breaking Changes

    Assembly name changed to System.Windows.Controls.DataVisualization.Toolkit.dll as part of a Toolkit-wide assembly renaming task. Because this change wasn't specific to Charting, I won't discuss it here. Please refer to the Toolkit release notes for more information about the renaming and how to easily migrate your applications.

    Root namespace changed to System.Windows.Controls.DataVisualization as part of a Toolkit-wide namespace renaming task. As above, this change had wasn't specific to Charting and will not be discussed here.

    Switched AxisOrientation enumeration values from Horizontal/Vertical to X/Y for clarity, succinctness, and future-proofing. As part of the architectural work that went on, we realized that we'd eventually be switching to X/Y as we introduced certain other features and decided to make the change now to help ease the transition later.

    Some of the *Axis properties on the Series classes have been renamed to be more general as a consequence of adding more flexible axis support to the Series classes. For example, what used to be named IndependentRangeAxis is now simply IndependentAxis.

    Data values represented by strings are no longer automatically converted to numbers or dates. This is another consequence of the more flexible axis support: when a chart is taking advantage of automatic axis creation, we can't be sure what the desired behavior is if the values "2007", "2008", "2009" are provided as the independent values. Specifically, it's unclear whether the intent is to create a CategoryAxis with three string categories or to create a LinearAxis spanning those numeric values. So we've resolved the ambiguity by requiring stronger typing of dependent and independent values: numeric values must be a numeric type (int, double, etc.) and chronological values must be a DateTime. Otherwise, category values will be used where possible. It's unfortunate that this change may break some existing scenarios, but we believe the ambiguity it removes is worthwhile and the overall behavior is now more consistent and predictable. Charting has always encouraged you to be in charge of your data and this change simply reinforces that - if you want to visualize numbers, then you need to provide numbers. :)

    The default Template of Chart has been reworked slightly to make things a bit easier for designers to customize. The most obvious change is the introduction of a new EdgePanel primitive for sizing/positioning the plot area and axes. Using EdgePanel makes this axis placement process more transparent to the designer and should be a little more resilient than the previous Grid-based approach.

    Legend is now a TemplatePart of Chart; its LegendItems property has been removed. Certain architectural changes made it necessary for the Chart to be able to identify its Legend and that meant that we needed to formalize things a bit and make Legend a TemplatePart. There's a slight loss of generality here because it is no longer possible to put two or more Legends in the same Template, but as far as we know, nobody was doing this anyway. Note that it is still perfectly acceptable to omit the Legend entirely; TemplateParts are always optional in a Template.

    Removed the MarkerWidth/MarkerHeight properties for LineSeries and ScatterSeries. We use the term "convenience properties" for properties that exist to make things easier for the user but that aren't actually necessary because there are other ways of doing the same thing. During development we're always trying to balance the benefits of convenience properties against the API bloat and possible confusion they can cause. In this case, we decided the MarkerWidth and MarkerHeight properties added very little value over re-Templating LineDataPoint or ScatterDataPoint (which is the default approach for changing the appearance of something in WPF and Silverlight). So the default width and height for these DataPoints are now set by their default Templates - and can be customized by re-Templating in the usual manner.

    The DataPointSeries and DataPoint classes have been changed to internal visibility to indicate that we are not yet ready for people to be deriving from them when writing their own Series. That's not to say we don't love to see people opening up the hierarchy and writing their own DataPointSeries subclasses - just that we want to be sure people realize that doing so isn't supported quite yet. (But if you do this anyway, please share your feedback with us - we'd like to ensure that when we do open things up they'll work as smoothly as possible!)

    Other Changes

    Various UI improvements and bug fixes for issues reported by customers or found internally.

    There's lots of good stuff here - we hope you'll enjoy using it as much as we enjoyed developing it!

     

    Please click here to download the complete Silverlight 2 and Silverlight 3 source code for a project demonstrating all of the sample charts shown below (and by my two previous introductory blog posts on Charting).

     

    Let's begin by looking at an example of the new support for using a CategoryAxis somewhere that it previously wouldn't have been allowed. For this example, we'll look at a fictitious pet shop chain's sales numbers for four types of animals across two different stores. We could go the traditional route and use ColumnSeries for this, but in this case we're looking to visually draw attention to the fact that one store is outselling the other in every category. With the latest Charting bits, it's easy to do this with LineSeries:

    LineSeries on a CategoryAxis

    Here's the XAML for this chart:

    <!-- Chart with shared DataContext -->
    <chartingToolkit:Chart
        Title="Pet Shop Sales Numbers"
        DataContext="{StaticResource SalesDataCollection}">
        <!-- West Store data -->
        <chartingToolkit:LineSeries
            Title="West Store"
            ItemsSource="{Binding}"
            IndependentValuePath="Animal"
            DependentValuePath="WestStoreQuantity"/>
        <!-- East Store data -->
        <chartingToolkit:LineSeries
            Title="East Store"
            ItemsSource="{Binding}"
            IndependentValuePath="Animal"
            DependentValuePath="EastStoreQuantity"/>
    </chartingToolkit:Chart>
    

    Because the "Animal" property of our data objects is of type string, the LineSeries automatically does what we want and uses a CategoryAxis for the horizontal axis! Note that we're also using the new *Path properties to specify the independent/dependent values instead of the *Binding properties. The new syntax is short, sweet, and to the point - and allows us to focus on visualizing our data instead of creating Bindings.

    One other thing worth calling attention to here is the way the data source is associated with both Series. In this case (as in many real-world scenarios), a single data source provides data objects for multiple series. While we could certainly recreate the same Binding to that data source in the ItemsSource property of each Series, there's a slightly easier way! What we do instead is specify the data source as the DataContext of the Chart object and then simply point both ItemsSource properties to it via a simple "{Binding}". The savings in this particular example are relatively minor, but for scenarios with more series, the convenience of being able to perform the actual binding to the data source in just one place becomes quite nice.

     

    For the next example, let's revisit one of the charts from last time that used BubbleSeries to visualize the performance of a made-up stock ticker by displaying its price and volume by date. But this time around we'll use two instances of the new AreaSeries class to display the same data. Here's how it looks:

    AreaSeries

    The XAML is very similar to last time:

    <chartingToolkit:Chart
        Title="Stock Performance Revisited"
        LegendTitle="Data">
        <!-- Volume -->
        <chartingToolkit:AreaSeries
            Title="Volume (M)"
            ItemsSource="{StaticResource StockDataCollection}"
            IndependentValuePath="Date"
            DependentValuePath="Volume"/>
        <!-- Price -->
        <chartingToolkit:AreaSeries
            Title="Price ($)"
            ItemsSource="{StaticResource StockDataCollection}"
            IndependentValuePath="Date"
            DependentValuePath="Price"/>
        <chartingToolkit:Chart.Axes>
            <!-- Axis for custom range -->
            <chartingToolkit:LinearAxis
                Orientation="Y"
                Minimum="0"
                Maximum="100"
                ShowGridLines="True"/>
            <!-- Axis for custom labels -->
            <chartingToolkit:DateTimeAxis
                Orientation="X">
                <chartingToolkit:DateTimeAxis.AxisLabelStyle>
                    <Style TargetType="chartingToolkit:DateTimeAxisLabel">
                        <Setter Property="StringFormat" Value="{}{0:MMM d}"/>
                    </Style>
                </chartingToolkit:DateTimeAxis.AxisLabelStyle>
            </chartingToolkit:DateTimeAxis>
        </chartingToolkit:Chart.Axes>
    </chartingToolkit:Chart>
    

    Notice how the semi-transparency of the fill used by AreaSeries makes it easy to plot both sets of data on the same chart. We've also gone ahead and added a title for the Legend via the LegendTitle property on Chart and customized the names of each series via the Title property on Series. We're using the simplified *Path syntax again to keep things easy to understand and we've preserve the previous sample's axis customization to provide a nicely readable representation of the dates being displayed (instead of the longer MM/DD/YYYY form that's used by default).

     

    For the last example, we'll need to use the Silverlight 3 version of Charting because we're going to take advantage of the new support for easing functions that's available there. The effort needed to customize the easing function used by a Series is practically nothing - all you need to do is add the highlighted portion below to an existing chart and you're done! It really is this easy:

    <chartingToolkit:Chart
        x:Name="EasingDemonstration"
        Title="Easing Demonstration">
        <chartingToolkit:ColumnSeries
            Title="Gelatin Sales">
            <!-- Custom easing function -->
            <chartingToolkit:ColumnSeries.TransitionEasingFunction>
                <ElasticEase EasingMode="EaseOut"/>
            </chartingToolkit:ColumnSeries.TransitionEasingFunction>
            <!-- Specify an appropriate color -->
            <chartingToolkit:ColumnSeries.DataPointStyle>
                <Style TargetType="Control">
                    <Setter Property="Background" Value="#ff33ff33"/>
                </Style>
            </chartingToolkit:ColumnSeries.DataPointStyle>
        </chartingToolkit:ColumnSeries>
        <!-- Custom Y axis for range -->
        <chartingToolkit:Chart.Axes>
            <chartingToolkit:LinearAxis
                Orientation="Y"
                Minimum="0"
                Maximum="10"
                ShowGridLines="True"/>
        </chartingToolkit:Chart.Axes>
    </chartingToolkit:Chart>
    

    Here's what the corresponding chart looks like:

    EasingFunction

    Okay, I admit: this is just a static picture and the effect of the easing function can't be appreciated here... However, if you download and build the Silverlight 3 version of the sample code linked to above, you'll find that clicking the "Randomize Data" button changes the data values every time it's pressed. And every time it does, the ElasticEase function specified above causes each of the bars to zoom quickly to their new values and then bounce around for a bit - just as if the bars were made out of gelatin! I'll grant that ElasticEase is probably not the most practical easing function for data visualization purposes, but we found that the default QuadraticEase behavior makes a very noticeable - yet subtle - impact on the viewer. It obviously won't turn a bad chart into a good chart, but it adds just a bit of extra polish that can help a good chart really shine! :)

     

    Finally, let's have a look at one of the many Blend improvements we've made in this release. One of the things we wanted to do was help newcomers to Charting get up and running as quickly as possible without having to learn a lot of obscure concepts. To that end, when you drop a new Silverlight 3 Chart onto the design surface with the new Blend 3 Preview, you'll automatically get not just XAML for the chart, but also a bit of sample data and a properly configured ColumnSeries that uses it to display a very simple chart!

    Blend 3 Charting User Experience

    Of course, we don't expect this to be the same data you intended to visualize, but the automatic creation and configuration of the complete end-to-end scenario should help people understand just what it takes to create a chart. Furthermore, it's easy to begin experimenting with the objects and data that are automatically created in order to learn more and develop an understanding about how the different properties work together. To make that experimentation even more rewarding (translation: less frustrating) we've done some work internally to suppress many of the exceptions that result from the invalid configurations that are so easy to accidentally trigger when interactively manipulating a chart in the designer. There's still a lot more that can be done to improve the design-time experience for Charting - but I hope you'll agree that changes like this move us strongly in the right direction!

     

    We've had a great time working on the March 09 release of Charting and hope you're as excited by the new functionality as we are! You can always browse the live Charting sample page for Silverlight 2 or Silverlight 3 to find out more - then please download the March 09 Toolkit release - for Silverlight 2 or Silverlight 3 - and start playing around with the new stuff! And as always, if you have questions about Charting, please ask them in the Silverlight Controls forum where there are plenty of folks who are happy to help out (including us!). If you run into problems and think you've found a bug, please report it with the Issue Tracker on the Toolkit's CodePlex site so that we'll know about it and others can tell us if they're seeing the same behavior.

    Thank you very much for taking an interest in Charting - I hope you have fun with the new bits!! :)

     

    PS - I'll be posting an update to my ChartBuilder application/tutorial (Background reading: Introduction, Update, New Release) in the next day or so. ChartBuilder itself hasn't changed much since last time, but there's going to be something special about it this time that think you'll agree is pretty exciting! :)

  • Delay's Blog

    ListPicker? I hardly even know 'er! [A detailed overview of the Windows Phone Toolkit's ListPicker control]

    • 63 Comments

    In yesterday's post, I announced the second release of the Silverlight for Windows Phone Toolkit and gave an overview of the four new controls it includes. (For a discussion of the controls in the original Windows Phone Toolkit, please see my announcement for that release.) In today's post, I want to focus on one of the new controls, ListPicker, and discuss it in detail.

     

    The sample application associated with the official Windows Phone Toolkit download offers a great overview of the Windows Phone Toolkit controls, but (deliberately) doesn't get into specific detail on any of them. This post is all about details, so I've written a dedicated sample application which is the source of all the XAML snippets and screenshots below:

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

     

    Background

    From my previous post:

    ListPicker is the Windows Phone 7 equivalent of the ComboBox control: give it a list of items and it will show the selected one and also allowing the user to pick from a list if they want to change it. The core applications on Windows Phone 7 implement two kinds of list selection: an in-place expander for picking among five or fewer items, and a full-screen popup for picking among more. The Toolkit's ListPicker control combines both experiences into one handy package by automatically selecting the right UX based on the number of items in its list! It is a standard ItemsControl subclass with all the common elements of a Selector, so the API will be familiar to just about everyone. In fact, you can take most existing ListBoxes and convert them to ListPickers just by changing the control name in XAML!

    That's the gist: ListPicker is the control of choice for selecting values in Windows Phone 7 applications. To be more explicit, it is most appropriate in "Settings"-like scenarios where the user is offered a variety of different options and it makes sense to display only the current value (with an option to show everything once the user decides to make a change). Conversely, ListPicker is not appropriate for displaying long lists of data that the user is going to scan and scroll; scenarios like the "People" or "Marketplace" applications are better served by a ListBox or the Windows Phone Toolkit's new LongListSelector.

     

    Typical Use

    The most common scenario for ListPicker looks something like this:

    <StackPanel>
        <toolkit:ListPicker
            Header="Rating"
            ItemsSource="{Binding Ratings}"
            SelectedIndex="1"
            SelectionChanged="RatingSelectionChanged"/>
        <TextBlock
            x:Name="RatingSelection"
            CacheMode="BitmapCache"/>
        ...
    </StackPanel>

    Which gets displayed like this (in normal and expanded forms):

    Typical example (normal) Typical example (expanded)

    As you'd expect for an ItemsControl subclass, the ItemsSource property is used to provide the list of items (see also: the Items property). And as you'd expect for a Selector-like control, the SelectionChanged event is used to signal changes and the SelectedIndex property is used to get or set the selection (see also: SelectedItem). Everything so far looks just like ListBox - the only difference is the Header property which can optionally be used to provide a simple, platform-consistent label for the ListPicker that offers additional context about the control's purpose (see also: HeaderTemplate).

    Aside: The built-in ListBox control will throw an exception if you set SelectedIndex as in the example above because it tries to apply the selection before the Binding has provided the list of items. ListPicker specifically handles this common scenario so you don't have to jump through hoops to make it work. :)

     

    Custom Templates

    Displaying strings is all well and good, but sometimes it's nice to display richer content:

    Custom template (normal) Custom template (expanded)

    The first thing to do is set the ItemTemplate property as you would for ItemsControl or ListBox - that applies the specified DataTemplate to each item and formats it attractively in the usual manner. That works great, but what about ListPicker's Full mode that's used when the list has too many items? By default, the same ItemTemplate automatically applies there, too, so you may not need to do anything more! However, the Full mode UI uses the entire screen, so it's pretty common to want to specifically customize the appearance of the items for that mode. Therefore, the FullModeItemTemplate property lets you provide a different DataTemplate to be used in the Full mode scenario. Another relevant property for such cases is FullModeHeader which sets the content that's shown at the top of the full-screen "popup".

    <toolkit:ListPicker
        Header="Spectrum"
        FullModeHeader="CHOOSE COLOR"
        ItemsSource="{Binding Rainbow}"
        ItemTemplate="{StaticResource RainbowTemplate}"
        FullModeItemTemplate="{StaticResource LargeRainbowTemplate}"/>

     

    Threshold Overrides

    By default, lists with five or fewer items expand in-place while lists with more items switch to a full-screen selection interface. This behavior matches the platform guidelines, but sometimes it might make sense to nudge the threshold one way or another (for very large or very small items, perhaps). You might even want to force a ListPicker to always use Expanded or Full mode...

    Threshold (full) Threshold (expanded)

    For these scenarios, there's the ItemCountThreshold property: it specifies the maximum number of items that will be displayed in Expanded mode. In addition to nudging it up or down a bit for custom scenarios, it can also be set to 0 to "always use Full mode" or a very large number to "always use Expanded mode". Granted, an application that forces Expanded mode for a list of 1000 items probably won't be easy to use - but the freedom is there to allow developers and designers to dial-in exactly the kind of experience they want.

    <toolkit:ListPicker
        Header="Rating"
        FullModeHeader="CHOOSE RATING"
        ItemsSource="{Binding Ratings}"
        ItemCountThreshold="0"/>
    <toolkit:ListPicker
        Header="Spectrum"
        ItemsSource="{Binding Rainbow}"
        ItemTemplate="{StaticResource RainbowTemplate}"
        ItemCountThreshold="100"/>

     

    Two-Way Binding

    Two-way binding

    As you'd expect, ListPicker can be used with TwoWay Bindings as well. This is particularly convenient for the SelectedIndex/SelectedItem properties where it's common to want to set the initial value based on a data model (see also: MVVM) and/or when you want the model to update directly when selection changes. The corresponding XAML looks just how you'd expect:

    <toolkit:ListPicker
        Header="Network"
        ItemsSource="{Binding Networks}"
        SelectedItem="{Binding CurrentNetwork, Mode=TwoWay}"/>
    <StackPanel
        Orientation="Horizontal"
        Margin="{StaticResource PhoneMargin}"
        CacheMode="BitmapCache">
        <TextBlock Text="Current Network: "/>
        <TextBlock Text="{Binding CurrentNetwork}"/>
    </StackPanel>

     

    Tips and Tricks

    At this point, I hope everyone knows how ListPicker works and has a good feel for when/where/why/how to use it. That being the case, there are a few additional things I'd like to draw attention to:

    1. The ListPicker philosophy is that "there is always an active selection", so it makes sure to select an item when it initializes or when the list changes. This automatic selection (of the first item in most cases) causes the SelectionChanged event to fire - and that causes the application's associated event handler to run (assuming one has been registered). In practice, this "initialization-time" event catches some people by surprise - but it's the intended behavior (and folks tend to agree it's correct once they understand why it happens). Now that you know about it, maybe your development experience will be a bit easier. :)

      Aside: If you want to ignore this event in code, it should be easy to detect because its SelectionChangedEventArgs.RemovedItems collection will be empty (have 0 items). And the only time that happens is when ListPicker is transitioning from an empty list to a non-empty one (e.g., on startup).
    2. ListPicker's transitions between Normal and Expanded mode are effectively animations of the control's Height. Because Height changes cause a layout pass, they don't take place on the composition thread and therefore are more susceptible to performance issues. An easy way to mitigate this in the typical "list of items in a StackPanel" scenario is to add CacheMode=BitmapCache to the elements that appear below the ListPicker (i.e., those that are pushed down by the animation). Please refer back to the first XAML snippet for an example - this tweak allows the Silverlight layout system to animate such controls as bitmaps and that helps the animation run a bit more smoothly.

      Aside: If you don't want to apply BitmapCache to every control individually, an alternate approach is to wrap the affected controls in another StackPanel and set the CacheMode property on the StackPanel instead. Please see the last XAML snippet above for an example of this.
    3. If you have a long list of controls in a StackPanel inside a ScrollViewer and there's a ListPicker near the bottom using Expanded mode, that expansion does not automatically scroll the screen to keep the ListPicker completely in view. On WPF, the fix would be a simple matter of calling FrameworkElement.BringIntoView. However, Silverlight doesn't have that API and there doesn't seem to be a good general purpose way for ListPicker to find the right parent to scroll. (Although walking up the visual tree to find the first ScrollViewer is probably right in most cases, it's not a sure thing; ListPicker errs on the side of caution and doesn't try to make guesses.) In practice, the underlying issue doesn't come up very often - when it has, my suggestion has been to use the ItemCountThreshold property to force the relevant ListPicker to use Full mode (which doesn't expand, so it doesn't alter the parent's layout, so it doesn't have this problem).

     

    Summary

    ListPicker is a relatively straightforward control that should be familiar to anyone who's used the standard ListBox. But while its API may be unremarkable, its user experience is all Windows Phone 7 goodness! :)

    I hope you enjoy it!

  • Delay's Blog

    LB-SV-FAQ [Examples, Notes, Tips, and More for Silverlight 2 Beta 1's ListBox and ScrollViewer controls!]

    • 52 Comments

    Silverlight 2 Beta 1 is available today, yay!! I'm celebrating by publishing an FAQ of sorts for the controls I worked on. While not all of the topics below are "frequently asked questions" (it's a little too soon for that...), they're all intended to help developers get started by covering the basics of these controls.

    If you're already familiar with WPF, then Silverlight should come pretty easily to you - but I'd still recommend skimming the topics below because some of the details are unique to Silverlight. If you're new to WPF and Silverlight, then I hope the topics below help jump-start the development process. In either case, I welcome your feedback, so please leave a comment below or contact me with any questions/problems/ideas you have!

    Enjoy!!

     

    General Questions

    What does this FAQ cover? The Silverlight 2 Beta 1 controls I worked on: ListBox, ListBoxItem, ScrollViewer, and ScrollContentPresenter.

    Why do all the hyperlinks point to WPF documentation? For one, that's all that was publically available when I started writing this post. For another, the WPF documentation is a superset of the Silverlight documentation and the additional detail there can help clarify issues. And finally, because one of the big goals for the Silverlight controls was complete (subset) compatibility with their WPF counterparts, the WPF documentation has been MY primary reference as well! But sometimes there's no substitute for the real thing; the Silverlight 2 Beta 1 documentation is a great reference, too. :)

    What's the meaning of the WPF/WPFIMPLEMENTATION defines in the controls source code? The development of ListBox (+ListBoxItem) and ScrollViewer (+ScrollContentPresenter) was done in parallel with the development of their base types (e.g., ItemsControl, ContentControl) and also in parallel with the development of the Silverlight 2 platform itself. To minimize the risk/impact of developing on a changing foundation, I did much of my development and unit testing on WPF by deriving from the corresponding base classes, using only the subset of WPF that Silverlight exposes, and avoiding features specific to either platform as much as possible.

    When compiling and running on WPF, I would add "WPF" to the list of conditional compilation symbols for the project. Therefore, code inside an #if WPF block applies only when running on WPF. In many cases, the pattern is #if WPF ... #else ... #endif and this corresponds to instances where some bit of code needed to be different between the two platforms (typically because it used features that weren't identical across both). In some cases, the relevant code only applies to one platform and the #else is missing or #if !WPF is used instead.

    The meaning of "WPFIMPLEMENTATION" is similar and is used by the unit tests for code that applies only when testing the WPF implementations of ListBox or ScrollViewer. I used unit tests in three different scenarios to help ensure the code was as correct and compatible as possible: testing the Silverlight implementation on Silverlight, testing the Silverlight implementation on WPF (#if WPF), and testing the WPF implementation on WPF (#if WPF, #if WPFIMPLEMENTATION). Though it may seem silly at first, the point of testing the WPF implementation on WPF was to make sure the unit tests were validating the correct behavior.

    What's a good strategy for investigating possible bugs? If you're trying to do something new and running into behavior that seems wrong, it's often helpful to identify exactly what/where the problem is. My first step is often to try the same scenario on WPF - if the behavior is the same there, then it's probably not a bug (and at least Silverlight is consistent!). But let's say the scenario works fine on WPF - my next step for ListBox problems is to try the same scenario using ItemsControl. This isn't always possible because ItemsControl offers only a subset of ListBox's functionality, but if I'm able to reproduce the problem using just ItemsControl, then ListBox is off the hook and that's a big chunk of code that's no longer in question. If ItemsControl isn't responsible, the next step I'll take is to switch to a debug version of System.Windows.Controls.dll so I can set breakpoints, tweak the control internals, etc.. For bugs in ListBox or ScrollViewer, this should be enough to identify most problems; for bugs in Silverlight, this helps to narrow things down.

    And now that you've identified a bug, please let us know so we can fix it! :)

     

    Common ListBox Scenarios

    Here is the XAML code for a handful of the most common ListBox scenarios along with a picture of what it looks like on Silverlight.

    ListBoxItems specified in XAML:

    ListBoxItems specified in XAML
    <ListBox>
        <ListBoxItem Content="ListBoxItem 0"/>
        <ListBoxItem Content="ListBoxItem 1"/>
        <ListBoxItem Content="ListBoxItem 2"/>
    </ListBox>

    FrameworkElements specified in XAML:

    FrameworkElements specified in XAML
    <ListBox>
        <Ellipse Width="20" Height="20"
                 Fill="Red" Stroke="Black"/>
        <Rectangle Width="20" Height="20"
                   Fill="Blue" Stroke="White"/>
        <Rectangle Width="20" Height="20"
                   Fill="Yellow" Stroke="Black"
                   RadiusX="5" RadiusY="5"/>
    </ListBox>

    Objects specified with ItemsSource and StaticResource Binding:

    Objects specified with ItemsSource and StaticResource Binding
    <ListBox
        ItemsSource="{StaticResource Products}"/>

    Horizontal layout with ItemsPanel and ItemsPanelTemplate:

    Horizontal layout with ItemsPanel and ItemsPanelTemplate
    <ListBox>
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel Orientation="Horizontal"/>
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
        <ListBoxItem Content="Item 0"/>
        <ListBoxItem Content="Item 1"/>
        <ListBoxItem Content="Item 2"/>
    </ListBox>

    Simple data visualization with DisplayMemberPath:

    Simple data visualization with DisplayMemberPath
    <ListBox
        ItemsSource="{StaticResource Products}"
        DisplayMemberPath="Name"/>

    Complex data visualization with ItemTemplate and DataTemplate:

    Complex data visualization with ItemTemplate and DataTemplate
    <ListBox ItemsSource="{StaticResource Products}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <Image Source="{Binding Image}"/>
                    <TextBlock Text="{Binding Name}"
                               VerticalAlignment="Center"/>
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>

    Customized ListBoxItem appearance with ItemContainerStyle and Style:

    Customized ListBoxItem appearance with ItemContainerStyle and Style
    <ListBox ItemsSource="{StaticResource Products}"
             DisplayMemberPath="Name">
        <ListBox.ItemContainerStyle>
            <Style TargetType="ListBoxItem">
                <Setter Property="Background" Value="Gray"/>
                <Setter Property="Foreground" Value="White"/>
            </Style>
        </ListBox.ItemContainerStyle>
    </ListBox>

    Minimally templated ListBox and ListBoxItem with Template and ControlTemplate:

    Minimally templated ListBox and ListBoxItem with Template and ControlTemplate
    <ListBox ItemsSource="{StaticResource Products}"
             DisplayMemberPath="Name">
        <ListBox.Template>
            <ControlTemplate TargetType="ListBox">
                <Border BorderBrush="Blue"
                        BorderThickness="2"
                        CornerRadius="5">
                    <ItemsPresenter/>
                </Border>
            </ControlTemplate>
        </ListBox.Template>
        <ListBox.ItemContainerStyle>
            <Style TargetType="ListBoxItem">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="ListBoxItem">
                            <Border BorderBrush="HotPink"
                                    BorderThickness="2"
                                    CornerRadius="3">
                                <ContentPresenter
                                    Content="{TemplateBinding Content}"
                                    ContentTemplate="{TemplateBinding ContentTemplate}"
                                    FontWeight="Bold"/>
                            </Border>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </ListBox.ItemContainerStyle>
    </ListBox>

    Note: For the samples that refer to {StaticResource Products}, the following code/XAML is assumed to be present:

    namespace LB_SV_FAQ
    {
        public class Products : List<Product>
        {
            public Products()
            {
                Add(new Product { Name = "Calculator" });
                Add(new Product { Name = "Notepad" });
                Add(new Product { Name = "Paint" });
            }
        }
        public class Product
        {
            public string Name { get; set; }
            public string Image { get { return Name + ".png"; } }
            public override string ToString() { return "Product: " + Name; }
        }
    }
    <UserControl x:Class="LB_SV_FAQ.Page"
        xmlns="http://schemas.microsoft.com/client/2007"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:LB_SV_FAQ">
        <Grid>
            <Grid.Resources>
                <local:Products x:Key="Products"/>
            </Grid.Resources>
        ...
        </Grid>
    </UserControl>
    

     

    ListBox Implementation Notes

    Why is SelectionMode.Single the only supported SelectionMode? The Silverlight controls are subsets of their WPF counterparts in order to keep complexity and download size as low as possible. In cases where it seemed that specific functionality was not widely used, we opted to exclude it for Beta 1 and use customer feedback to identify which missing features are the most important. SelectionMode.Multiple and SelectionMode.Extended both fall into the category of "very nice to have, but seemingly not critical for most scenarios" - if you need multiple selection support, please let us know!

    Why doesn't ListBox derive from Selector? On WPF, ListBox derives from the Selector class which derives from ItemsControl; on Silverlight, ListBox derives directly from ItemsControl. To simplify matters and keep development/testing/download costs down, the Selector class was thought to be unnecessary for Silverlight 2 Beta 1. With ListBox being the only Selector-derived class in the Beta 1 controls offering, the Selector functionality was merged into ListBox for compactness. (If a Selector class is introduced to the Silverlight controls in the future, it should have little/no effect on existing ListBox code.)

    Why does the ListBoxItem.ChangeVisualState method overlap playing the old/new Storyboards? The pattern of changing visual states by calling Begin for the new Storyboard and then calling Stop for the old one is a key part of the Silverlight state model. Starting the new Storyboard begins applying the values of whatever animations it represents (as you'd expect). Stopping the old one then automatically resets any animations it represents that aren't also being changed by the new Storyboard. Manipulating the two Storyboards in this particular manner is necessary for the correct visual behavior under Silverlight.

     

    ListBox Curiosities

    Why is the scrolling behavior slightly different than with WPF? If you have a ListBox with enough items that a vertical ScrollBar is displayed, using the Page Up/Down or arrow keys to scroll through the list behaves just a little bit differently on Silverlight than on WPF. In particular, WPF's default behavior is to keep the top-most item fully visible and flush against the top of the ListBox at all times. Therefore, scrolling doesn't affect the items' vertical placement until the very beginning/end of the list. Silverlight does not have the same behavior with respect to the top-most item, so the item placement tends to change slightly while scrolling through the list. This behavior difference is due to the fact that the WPF ListBox's default Style overrides the default ScrollViewer.CanContentScroll value of False and sets it to True, changing the ScrollViewer's scrolling mode from physical scrolling (pixel-based) to logical scrolling (item-based). Silverlight's ScrollViewer supports only physical scrolling, so this override is not present on Silverlight. To see the same Silverlight scrolling behavior on WPF, set ScrollViewer.CanContentScroll="False" on any ListBox with ScrollBars.

    Why is selection behavior incorrect when I have the same object in a ListBox multiple times? For compatibility with WPF, of course! :) Due to the way ItemsControl is implemented, it is necessary for it to maintain a mapping from the objects it contains to the corresponding wrappers (i.e., ListBoxItems) that get created for them. This is handled by the ItemContainerGenerator.ContainerFromItem method on WPF and by ListBox.GetListBoxItemForObject on Silverlight - but the idea is the same. When the same object reference is added to the ListBox multiple times, this mapping breaks down because it isn't prepared for a single object to map to multiple containers. As it happens, the WPF and Silverlight behaviors differ slightly here. Both seem wrong, but I'll suggest that the Silverlight behavior is slightly less wrong. The following XAML/code demonstrates the problem on WPF and Silverlight (click on the third item, then the second item to see the behavior difference):

    <ListBox x:Name="lb"/>
    object obj = new object();
    lb.Items.Add(obj);
    lb.Items.Add(obj);
    lb.Items.Add(obj);

    Note: The problematic object-to-ListBoxItem mapping is unnecessary when the items added to the ListBox are of type ListBoxItem. Therefore, if you need to store the same object multiple times, consider wrapping your items in unique ListBoxItem instances (setting the Content property to the wrapped item) before adding them to the ListBox.

     

    ListBox Bugs

    Why doesn't setting ListBox's SelectedIndex in XAML or the Page constructor seem to work? The internal ListBox state is actually correct in the code scenario (and unit tests verify this), but the visual representation is wrong. The following XAML/code demonstrates the problem:

    <ListBox x:Name="lb">
        <TextBlock Text="one"/>
        <TextBlock Text="two"/>
        <TextBlock Text="three"/>
    </ListBox>
    public Page()
    {
        InitializeComponent();
        lb.SelectedIndex = 1;
    }
    

    The problem is that when the SelectedIndex property gets set in the Page constructor, ItemsControl has not yet called PrepareContainerForItemOverride and so ListBox has not yet set up its mapping from object to ListBoxItem (see the "ListBox Curiosities" section above for more about this mapping). So when ListBox tries to toggle IsSelected on the corresponding ListBoxItem, no such ListBoxItem exists and ListBox gives up instead of making a note to come back and finish this task after the mapping has been established. (Unfortunately, the situation is even worse if SelectedIndex is set in XAML (vs. code) because at that time it is typically the case that ItemsControl's Items collection is still empty.)

    The workaround is to call something like the SelectedIndexWorkaround extension method (defined below) after setting SelectedIndex. SelectedIndexWorkaround defers setting SelectedIndex until it will have the desired effect - and thereby works around the bug. The change to the above code is:

    lb.SelectedIndex = 1;
    lb.SelectedIndexWorkaround();  // Added workaround
    

    Why doesn't setting IsSelected True before adding a ListBoxItem to the ListBox look right? Again the internal ListBox state is correct, but the visual representation is wrong. In this case, the visuals fix themselves if you move the mouse over the selected item and then off it because the ListBoxItem transitions from its hover appearance to its selected appearance and the visuals update correctly. The problem here is that when IsSelected is toggled, ListBoxItem plays its state transition Storyboard immediately - but the ListBoxItem is not in the visual tree yet so Silverlight doesn't actually apply the associated state changes. (To behave correctly in this scenario, ListBoxItem should be calling its ChangeVisualState method at the end of OnApplyTemplate and also in a handler for the Loaded event.) The following code demonstrates the problem:

    ListBoxItem lbi;
    lbi = new ListBoxItem();
    lbi.Content = "Unselected";
    lb.Items.Add(lbi);
    lbi = new ListBoxItem();
    lbi.Content = "Selected";
    lbi.IsSelected = true;
    lb.Items.Add(lbi);
    

    The workaround is to call something like the IsSelectedWorkaround extension method (defined below) after setting IsSelected. This method causes the appropriate Storyboard to play again once it will have the desired effect - and thereby works around the bug. The change to the above code:

    lbi.IsSelected = true;
    lbi.IsSelectedWorkaround();  // Added workaround
    

    Why can't I arrow up/down past a ListBox item with Visibility Collapsed? Because ListBox doesn't expect there to be "invisible" ListBoxItems. If you try the obvious workaround of leaving the ListBoxItem Visible and marking its Content Collapsed, arrow navigation no longer gets "stuck" - but the scenario is still confusing to the user because the ListBox lets focus get set to the "invisible" item. Rather than playing with Visibility, consider the more direct approach of removing items from the collection if you don't want them to be visible. An elegant approach is to use an ObservableCollection for storing items and then assign that collection to the ItemsSource property of the ListBox. Because ObservableCollection implements INotifyCollectionChanged, changes to it (e.g., via Add/Remove) are automatically communicated to ListBox's ItemsControl base class which automatically updates the ListBox display. All you need to do is keep the collection up to date - everything else is handled for you.

     

    ListBox Workaround Code

    The following C# static class implementation can be added to a project to introduce extension methods to help work around the bugs described above. Simply call the corresponding extension method after setting one of the associated properties and the proper behavior should occur. Please note that calling these methods is not typically necessary and should be done only if a scenario is affected by one of the above bugs.

    Sorry for the trouble!

    /// <summary>
    /// This class contains extensions to help work around two ListBox bugs in
    /// the Silverlight 2 Beta 1 controls release. Simply add it to your project
    /// and call the extension methods just after the properties they match.
    /// </summary>
    public static class ListBoxExtensions
    {
        /// <summary>
        /// Augments ListBox.SelectedIndex to work around a bug where setting
        /// SelectedIndex before the ListBox is visible does not update the UI.
        /// </summary>
        /// <remarks>
        /// Instead of:
        ///     listBox.SelectedIndex = 1;
        /// Use:
        ///     listBox.SelectedIndex = 1;
        ///     listBox.SelectedIndexWorkaround();
        /// </remarks>
        /// <param name="listBox">Extension method class.</param>
        public static void SelectedIndexWorkaround(this ListBox listBox)
        {
            int selectedIndex = listBox.SelectedIndex;
            bool set = false;
            listBox.LayoutUpdated += delegate
            {
                if (!set)
                {
                    // Toggle value to force the change
                    listBox.SelectedIndex = -1;
                    listBox.SelectedIndex = selectedIndex;
                    set = true;
                }
            };
        }
        /// <summary>
        /// Augments ListBoxItem.IsSelected to work around a bug where setting
        /// IsSelected before the ListBoxItem is visible does not update the UI.
        /// </summary>
        /// <remarks>
        /// Instead of:
        ///     listBoxItem.IsSelected = true;
        /// Use:
        ///     listBoxItem.IsSelected = true;
        ///     listBoxItem.IsSelectedWorkaround();
        /// </remarks>
        /// <param name="listBoxItem">Extension method class.</param>
        public static void IsSelectedWorkaround(this ListBoxItem listBoxItem)
        {
            bool isSelected = listBoxItem.IsSelected;
            bool set = false;
            listBoxItem.LayoutUpdated += delegate
            {
                if (!set)
                {
                    // Toggle value to force the change
                    listBoxItem.IsSelected = !isSelected;
                    listBoxItem.IsSelected = isSelected;
                    set = true;
                }
            };
        }
    }
    

     

    Common ScrollViewer Scenarios

    Using ScrollViewer to scroll content that is too large:

    Using ScrollViewer to scroll content that is too large
    <!-- Grid used to limit ScrollViewer size -->
    <Grid Width="150" Height="70">
        <ScrollViewer HorizontalScrollBarVisibility="Auto"
                      VerticalScrollBarVisibility="Auto">
            <TextBlock Text="ScrollViewer"
                       FontFamily="Arial Black"
                       FontSize="50"/>
        </ScrollViewer>
    </Grid>

    Using ScrollViewer to add ScrollBars to an application when the browser is resized:

    <UserControl x:Class="ResizableApplication.Page"
        xmlns="http://schemas.microsoft.com/client/2007"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
        <Grid>
            <ScrollViewer HorizontalScrollBarVisibility="Auto"
                          VerticalScrollBarVisibility="Auto">
                <Grid>
                    <!-- Page content goes here instead of the outer Grid -->
                </Grid>
            </ScrollViewer>
        </Grid>
    </UserControl>
    

     

    ScrollViewer Implementation Notes

    Why am I having a hard time specifying ScrollViewer's attached properties in XAML? Silverlight 2 Beta 1's XAML parser throws a XamlParseException (ex: "Unknown attribute ScrollViewer.VerticalScrollBarVisibility on element ListBox.") when trying to specify any control's attached properties on a different control in XAML. This problem should be fixed in a future release; for now there's an easy workaround: add an explicit xmlns definition for System.Windows.Controls and use that xmlns when setting the attached properties:

    <UserControl x:Class="LB_SV_FAQ.Page"
        xmlns="http://schemas.microsoft.com/client/2007"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls">
        <Grid>
            <ListBox controls:ScrollViewer.VerticalScrollBarVisibility="Hidden"/>
        </Grid>
    </UserControl>
    

    Why doesn't ScrollViewer support the ScrollViewer.CanContentScroll property? See the "ListBox Implementation Notes" section above for an explanation of how Silverlight controls try to be a minimal subset of core functionality and the "ListBox Curiosities" section for an explanation of how the lack of ScrollViewer.CanContentScroll affects ListBox.

    Why is there no ScrollChanged event or IScrollInfo interface? As with ScrollViewer.CanContentScroll, these were deliberate omissions intended to keep things simple and compact.

     

    ScrollViewer Curiosities

    Why does the ScrollViewer default to HorizontalScrollBarVisibility Disabled and VerticalScrollBarVisibility Visible? For compatibility with the WPF defaults, of course. :) Regarding the obvious next question about why those values are the WPF defaults, I don't know. Auto/Auto would seem more generally useful to me and, in fact, the WPF ListBox (and Silverlight ListBox) overrides the ScrollViewer defaults to set Auto/Auto! But maintaining compatibility wins, so the Silverlight ScrollViewer defaults to the same values used by WPF.

     

  • Delay's Blog

    Chart tweaking made easy [How to: Make four simple color/ToolTip changes with Silverlight/WPF Charting]

    • 37 Comments

    While answering a support forum question I'd seen a couple of times before, I figured it would be helpful to write a post showing how to make simple styling changes to charts created by the Charting controls that come with the Silverlight Toolkit. Note that I said simple changes - if you want to make more dramatic changes, you should go read some of the excellent tutorials Pete Brown has written on the topic. Links to Pete's posts (and other interesting posts) can be found on my latest Charting links post.

     

    The sample application we'll be working with here shows off four scenarios and looks like this:

    Styling Sample

     

    Simple Color Change

    In this scenario we have a basic Chart with a ColumnSeries and want to change the color to purple. As you'd expect, this is quite easy to do: we provide a custom Style that sets the Background color to purple and we're done!

    It's worth pointing out that we could add more Setters to this Style to customize things further - but I promised I'd keep this simple, so we won't do that right now. :)

    <charting:Chart
        Title="Simple Color Change">
        <charting:ColumnSeries
            ItemsSource="{Binding}"
            DependentValueBinding="{Binding Length}"
            IndependentValueBinding="{Binding}">
            <charting:ColumnSeries.DataPointStyle>
                <Style TargetType="charting:ColumnDataPoint">
                    <Setter Property="Background" Value="Purple"/>
                </Style>
            </charting:ColumnSeries.DataPointStyle>
        </charting:ColumnSeries>
    </charting:Chart>
    

     

    Custom ToolTip (Column)

    For this example, the setup is the same as last time except that now we want to change the default ToolTip that appears when the user hovers over any of the columns. As with just about every other visual default, the standard ToolTip is part of the ColumnDataPoint's default Template. So in order to customize it we start with a copy of that Template and modify it to suit our needs. Blend makes this easy, but I'm most comfortable in Visual Studio, so what we'll do here is go to the source code for ColumnDataPoint.xaml and copy the Style there to the Application.Resources section of our App.xaml.

    Aside: You can also use my handy-dandy SilverlightDefaultStyleBrowser for this. What's more, SilverlightDefaultStyleBrowser works even when you don't have access to source code for the control you're styling, so it's something to keep in mind for those occasions when Blend isn't readily available. :)

    Copying done, we can tweak the ToolTip to include a custom message as follows:

    <ToolTipService.ToolTip>
        <StackPanel>
            <ContentControl
                Content="Custom ToolTip"
                FontWeight="Bold"/>
            <ContentControl
                Content="{TemplateBinding FormattedDependentValue}"/>
        </StackPanel>
    </ToolTipService.ToolTip>
    

    After that, and it's simply a matter of assigning our customized Style/Template to the DataPointStyle property of the ColumnSeries:

    <charting:Chart
        Title="Custom ToolTip">
        <charting:ColumnSeries
            ItemsSource="{Binding}"
            DependentValueBinding="{Binding Length}"
            IndependentValueBinding="{Binding}"
            DataPointStyle="{StaticResource MyColumnDataPointStyle}"/>
    </charting:Chart>
    

     

    Simple Palette Change

    What we've done so far will work for all of the current series except for PieSeries which is special because each of its PieDataPoints gets a unique Style. In other words, there's no DataPointStyle property on PieSeries because one value just isn't enough! Therefore, PieSeries exposes a StylePalette property just like Chart does and we can use that to provide a collection of Styles for the pie slices. (Note that we can provide as many or as few as we want; PieSeries will start at the beginning and automatically loop through the collection as necessary.)

    In this case, we know our data has exactly four items, so we'll provide exactly four custom Styles to set the colors we want. Other than using a list this time around, it's just like the first example we saw:

    <charting:Chart
        Title="Simple Palette Change">
        <charting:PieSeries
            ItemsSource="{Binding}"
            DependentValueBinding="{Binding Length}"
            IndependentValueBinding="{Binding}">
            <charting:PieSeries.StylePalette>
                <datavis:StylePalette>
                    <Style TargetType="charting:PieDataPoint">
                        <Setter Property="Background" Value="Red"/>
                    </Style>
                    <Style TargetType="charting:PieDataPoint">
                        <Setter Property="Background" Value="Orange"/>
                    </Style>
                    <Style TargetType="charting:PieDataPoint">
                        <Setter Property="Background" Value="Green"/>
                    </Style>
                    <Style TargetType="charting:PieDataPoint">
                        <Setter Property="Background" Value="Blue"/>
                    </Style>
                </datavis:StylePalette>
            </charting:PieSeries.StylePalette>
        </charting:PieSeries>
    </charting:Chart>
    

     

    Custom ToolTip (Pie)

    Finally, let's customize the ToolTip for the slices of a PieSeries. Like before, we'll start by copying the default Style/Template from the source code for PieDataPoint.xaml and then customize the ToolTip found within:

    <ToolTipService.ToolTip>
        <StackPanel>
            <ContentControl
                Content="Custom ToolTip"
                FontWeight="Bold"/>
            <ContentControl
                Content="{TemplateBinding FormattedDependentValue}"/>
            <ContentControl
                Content="{TemplateBinding FormattedRatio}"/>
        </StackPanel>
    </ToolTipService.ToolTip>
    

    Because we want our PieSeries to use all the same colors as the default, the next step is to copy the default StylePalette from the code for Chart.xaml and add a single Setter for the Template property of each of the Styles within. All of which point to the one Template we just customized, so if we make any changes in the future there's exactly one place we need to touch and our changes automatically shows up everywhere they should:

    <datavis:StylePalette
        x:Key="MyStylePalette">
        <!--Blue-->
        <Style TargetType="Control">
            <Setter Property="Template"
                    Value="{StaticResource MyPieDataPointTemplate}"/>
            <Setter Property="Background">
                <Setter.Value>
                    ...
                </Setter.Value>
            </Setter>
        </Style>
        <!--Red-->
        <Style TargetType="Control">
            <Setter Property="Template"
                    Value="{StaticResource MyPieDataPointTemplate}"/>
            <Setter Property="Background">
                <Setter.Value>
                    ...
                </Setter.Value>
            </Setter>
        </Style>
        ...
    

    With that out of the way, all that remains is to use our customized StylePalette by assigning it to the StylePalette property of PieSeries:

    <charting:Chart
        Title="Custom ToolTip">
        <charting:PieSeries
            ItemsSource="{Binding}"
            DependentValueBinding="{Binding Length}"
            IndependentValueBinding="{Binding}"
            StylePalette="{StaticResource MyStylePalette}">
        </charting:PieSeries>
    </charting:Chart>
    

     

    Done!

    If you've gotten this far, I hope that you've gained at least a somewhat better understanding of how to perform some basic style changes to the Toolkit's Charting controls. We've really only scratched the surface, though, so I encourage interested readers to have a look at some of the other charting links for more details, ideas, and inspiration!

     

    [Please click here to download the source code for the sample application.]

  • Delay's Blog

    Keep a low profile [LowProfileImageLoader helps the Windows Phone 7 UI thread stay responsive by loading images in the background]

    • 38 Comments

    When writing applications for small, resource-constrained scenarios, it's not always easy to balance the demands of an attractive UI with the requirements of fast, functional design. Ideally, coding things the natural way will "just work" (the so-called pit of success) - and it usually does. But for those times when things need some extra "oomph", it's nice to have some options. That's what my latest project, PhonePerformance is all about: giving developers performance-focused alternatives for common Windows Phone 7 scenarios.

    Note: Everything in this post pertains to the latest (internal) Windows Phone 7 builds, not the public Beta bits. Although I'd expect most everything to work on the Beta build, I haven't tested it. While that might seem a bit premature, the team has committed to releasing the final development tools on September 16th, so I'm only leading the curve a little bit here. :)

     

    In this first post, I'll be demonstrating the use of the LowProfileImageLoader class. (A future post will explore the DeferredLoadListBox class which is also part of the sample download - but for now I don't want to get into what that's all about.) LowProfileImageLoader is meant to address a very specific scenario: loading lots of images from the web at the same time. This turns out to be more common than you might expect - just think about all the social media scenarios where every user has a little picture alongside their content. To help show what I'm talking about, I created the "Image Loading" sample application shown below - just run the sample, wait a little while for it to load the user list from the web (and enable the two buttons), then choose the "default" (left) or "performance" (right) scenario and watch the behavior of the little blue dots moving across the screen:

    PhonePerformance Image Loading sample
    Aside: My discussion assumes all testing is done on actual phone hardware. Although running the emulator on the desktop is a fantastic development experience, performance of applications in the emulator can vary significantly. The emulator is great for writing new code and fixing bugs, but performance work should be done on a real phone if at all possible.

    The ProgressBar element just below the title text has its IsIndeterminate property set to true, so it's always animating. And as others have explained, ProgressBar animates entirely on the UI thread - so while it might not be a good choice for real applications, it makes a great test tool! In this case, I'm using it to provide an indication of how overwhelmed the UI thread is - and therefore how responsive the application will be to user input or updates. If the dots are flowing smoothly, then life is good; if the dots get jumpy or stop animating completely, then the application is unresponsive.

    As you can see by trying the "default" side of the sample, populating an ItemsControl having a WrapPanel (actually, it's my BalancedWrapPanel!) ItemsPanel with a couple hundred small (48x48) images from the web (the followers of my Twitter account in case you're curious) hangs the application for a noticeable amount of time. The first part of the delay is simply the cost of applying the list to the container and the second part of the delay is the cost of downloading and processing those images (mostly on the UI thread).

    In an attempt to avoid this extended period of unresponsiveness, the "performance" side of the sample uses LowProfileImageLoader. Although the initial cost of binding the list is the same as before, using LowProfileImageLoader allows the UI thread to become responsive much sooner. The obvious side effect is that the images load more slowly - but the fact that those ProgressBar dots stay active the entire time demonstrates that the UI thread isn't hung like it is for the default scenario. Think about it like this: LowProfileImageLoader trades off image load speed in favor of application responsiveness.

     

    The way LowProfileImageLoader works is straightforward: it creates a worker Thread and performs as much work there as possible. As soon as the SourceUri attached property is set, that Uri is enqueued for the worker thread to process. Similarly, whenever an asynchronous response comes in, that's also queued for the worker thread to process. Meanwhile, the worker thread is looking for work on any of its (three) queues and processing it in batches for efficiency. At the same time, it's making regular calls to Thread.Sleep(1) which signals to the system that other threads with work to do should get priority. (True thread priorities would be ideal, but Thread.Priority doesn't exist in Silverlight.) Finally, when it has done as much as it can off the UI thread, the worker thread calls Dispatcher.BeginInvoke to perform the final BitmapImage operations on the UI thread where they need to take place (in batches, of course). The net effect of all of this is that all the image loading LowProfileImageLoader performs occurs on a single worker thread which does its best to be efficient and stay out of the UI thread's way as much as possible - which results in a happier UI thread and a less frustrated user. :)

     

    Of course, every application - and every scenario - is different, so there's no guarantee LowProfileImageLoader will help all the time (or even most of the time!). But what's nice is that it's really easy to hook up, so there's nothing to lose by trying it if your scenario seems relevant!

     

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

     

    To make things a tad more concrete, here's what a typical scenario looks like:

    <Image
        Source="{Binding ProfileImageUrl}"
        Width="24"
        Height="24"/>

    And here's all it takes to convert it to use LowProfileImageLoader:

    <Image
        delay:LowProfileImageLoader.UriSource="{Binding ProfileImageUrl}"
        Width="24"
        Height="24"/>

    (Remember to add the appropriate XMLNS to the top of the XAML file:)

    xmlns:delay="clr-namespace:Delay;assembly=PhonePerformance"

     

    PS - As an added bonus, the first 500 callers will receive a free (single-purpose, super bare-bones) Twitter API! (NIH disclaimer: I know there are Twitter libraries out there, but I generally avoid third party code because I don't want to deal with licensing issues. Yeah, it's a little paranoid, but it helps me sleep better at night - and besides, this was really simple to dash off.) Operators are standing by...

  • Delay's Blog

    Silverlight Charting remains just a click away - and runs on WPF, too!! [ChartBuilder sample and source code updated for Charting's March 09 release]

    • 17 Comments

    In yesterday's announcement of the March 09 release of the Silverlight Toolkit and Silverlight Charting, I gave an overview of some new Charting features. One of the things I always do right after we release a new version of Charting is update my ChartBuilder sample/application/learning tool. If you're not already familiar with ChartBuilder, here's some background reading: Introduction/user's guide for November 08 release, Fix for non-US cultures, Update for December 08 release.

    This time around is no different and I've just updated the live ChartBuilder application! It's a typical Silverlight 2 application, so you can run it with Silverlight 2 OR the newly released Silverlight 3 Beta.

     

    Please click this text or the image below to run the latest ChartBuilder in your browser.

    ChartBuilder on Silverlight

    You can click here to download the complete ChartBuilder source code.

     

    Release notes:

    • Added support for new series type AreaSeries.
    • Slightly better round-tripping behavior for doubles and DateTimes from the helper class Pair.
    • Various other minor improvements.
    • Updated the version to 2009-03-20.

     

    Although we have - literally - thousands of automated test cases for Charting, ChartBuilder remains our de facto testing tool and has helped us to prevent or debug countless issues during the months we've been developing the Charting framework. I use it regularly to investigate strange behavior or to provide the scaffolding for one-off test cases that would otherwise require quite a bit of time and effort to create.

    ChartBuilder won't win any awards for great user interface design, but it has been worth its weight in gold to us. I hope it helps you, too!

     

     

    Oh, and one more thing... :)

    It's always been our goal with Charting to support both Silverlight and WPF - and we've been compiling Charting for the WPF platform as part of our daily builds for the past few months. I thought it would be neat to show off a sneak preview of WPF Charting here, so I created a WPF build of ChartBuilder and hooked it up to the WPF build of Charting and - yup - it just worked. Well, mostly... :)

    Warning: We have done absolutely no testing of the WPF Charting bits and if you decide to pull them out of the source code download and play around with them, then you do so at your own risk!

    The great thing about what's going on here is that Silverlight Charting and WPF Charting are both compiled from exactly the same source code and XAML and allow you to use exactly the same source code and XAML in your projects. So you'll be able to create a chart on one platform and share it seamlessly with the other platform and you won't have to do any extra work! To help prove my point, ChartBuilder itself is built from exactly the same source code and XAML for both Silverlight and WPF!

     

    ChartBuilder on WPF

     

    The most obvious problem I've seen from playing around with WPF ChartBuilder for a little is that sometimes dynamically added/removed DataPoints don't play their reveal/show/hide transition properly. Some of the points coming in are obviously present in the chart because there's space reserved for them and their tooltips work properly - but they're not visible. And some of the points going out just don't go away like they're supposed to. My primary suspect here is VSM (the Visual State Manager; a Silverlight concept that's been ported to WPF); WPF Charting uses the WPF Toolkit's preview implementation of VSM and there may be some incompatibilities between that implementation and the one in Silverlight. Charting on WPF also has a few fit-and-finish issues; these are probably easy to fix, but like I said we haven't spent any time working on Charting for WPF.

    Important: If you do decide to try WPF Charting, please note that it's necessary to add references to both the DataVisualization assembly and the WPFToolkit assembly or else you'll get a weird error from WPF when we attempt to use VSM and its implementation isn't present.

     

    Okay, that's all I've got for now. :) I hope you have fun with Charting - on whatever platform you choose!

  • Delay's Blog

    Announcing a free, open source Charting solution for Silverlight [Silverlight Toolkit released today at PDC!]

    • 21 Comments

    Today at the Microsoft Professional Developers Conference, we announced the immediate availability of the Silverlight Toolkit. The Silverlight Toolkit is a collection of controls for Silverlight that add new functionality and enable new scenarios. There are some great controls in the Toolkit: WPF favorites (ex: DockPanel, WrapPanel, TreeView), cool new things (ex: AutoCompleteBox, NumericUpDown), and some that are a little of both (ex: ImplicitStyleManager). All of these controls are worth looking at and blogging about - but that's not what I'm going to do. :)

    Instead, I'd like to direct your attention to the Microsoft.Windows.Controls.DataVisualization.dll assembly which contains a set of completely new classes that enable Silverlight developers to easily create professional-looking column, bar, pie, line, and scatter charts that follow the same XAML developer/designer metaphor as the rest of the Silverlight platform. We've tried to make Silverlight Charting as easy as possible to use - while also enabling some very powerful scenarios like automatic support for dynamic changes to a bound data collection as well as a powerful extensibility model that allows people to write their own custom chart types with minimal difficulty.

    Here's all it takes to create a simple column chart in XAML (note that there's no code required):

    <charting:Chart Title="My First Chart">
        <charting:Chart.Series>
            <charting:ColumnSeries>
                <charting:ColumnSeries.ItemsSource>
                    <controls:ObjectCollection>
                        <sys:Double>1</sys:Double>
                        <sys:Double>2</sys:Double>
                        <sys:Double>3</sys:Double>
                    </controls:ObjectCollection>
                </charting:ColumnSeries.ItemsSource>
            </charting:ColumnSeries>
        </charting:Chart.Series>
    </charting:Chart>
    
    Sample Chart

    Looking at the structure of that XAML, we start with a Chart control and set its Title. Then we add an instance of the ColumnSeries class to the Chart's Series collection and provide a simple collection of doubles as the ItemsSource of the series (think ItemsControl.ItemsSource). Silverlight Charting does all the rest and renders the chart you see above.

    Being able to display static XAML-only data like this is great for learning and experimenting, but more advanced scenarios will usually be working with pre-existing business objects. So let's create a pie chart to display some fictitious statistics about a project's source code. Here's the business object we'll be working with:

    public class CodeElement : INotifyPropertyChanged
    {
        public string Name { get; set; }
    
        public int Lines
        {
            get { return _lines; }
            set
            {
                _lines = value;
                var handler = PropertyChanged;
                if (null != handler)
                {
                    handler.Invoke(this, new PropertyChangedEventArgs("Lines"));
                }
            }
        }
        private int _lines;
    
        public event PropertyChangedEventHandler PropertyChanged;
    }
    

    And here's a collection of them that we'll use as the source of the data for the chart:

    public class CodeElementCollection : ObservableCollection<CodeElement>
    {
        public CodeElementCollection()
        {
            Add(new CodeElement { Name = "Code", Lines = 400 });
            Add(new CodeElement { Name = "Comments", Lines = 200 });
            Add(new CodeElement { Name = "Whitespace", Lines = 100 });
        }
    }
    

    All we need to do is create a CodeElementCollection (I'll put it in the Resources section for ease of access) and use it with our chart:

    <charting:Chart Title="Source Code Statistics">
        <charting:Chart.Series>
            <charting:PieSeries
                ItemsSource="{StaticResource CodeElementCollection}"
                DependentValueBinding="{Binding Lines}"
                IndependentValueBinding="{Binding Name}"/>
        </charting:Chart.Series>
    </charting:Chart>
    
    Sample Chart

    This time the ItemsSource property is being set to a collection of objects. (Note: We could have set the ItemsSource property in code instead.) Because these business objects have multiple properties, the series needs to know which of those properties correspond to the independent and dependent values. So we set the DependentValueBinding and IndependentValueBinding properties of PieSeries to Bindings that identify the relevant business object properties. The chart automatically extracts the data values, creates the properly-named legend items, and renders a pie chart. It's as easy as, uh, pie! :)

    Now let's say someone adds a new XAML file to our imaginary project - we want our chart to automatically update itself to show the new code type. And because we've used an ObservableCollection for our collection, it will! In the sample project for this post (you can download it from the attachment link at the bottom of the post), there's a button to add a new CodeElement to the collection; the implementation looks like this:

    private void AddXamlStatistics_Click(object sender, RoutedEventArgs e)
    {
        _codeElementCollection.Add(new CodeElement { Name = "XAML", Lines = 100 });
    }
    

    Pressing that button gives us the following chart:

    Sample Chart

    You can't tell from the still images here, but the new pie slice displays with a very smooth animation that's typical of Charting's support for dynamic data.

    My code for the click handler doesn't stop the user from hitting that button a bunch of times; doing so just for fun gives us the following chart which demonstrates how Charting's Legend control automatically scrolls to accommodate large lists and also demonstrates the complete palette of colors that Charting supports by default:

    Sample Chart

    Naturally, folks might want to customize the default palette - and it's easy to do so with the StylePalette property on Chart. Here, I've switched the palette to simple, monochromatic red, green, and blue:

    <charting:Chart Title="Statistics (Custom Palette)">
        <charting:Chart.StylePalette>
            <datavis:StylePalette>
                <Style TargetType="Control">
                    <Setter Property="Background" Value="Blue"/>
                </Style>
                <Style TargetType="Control">
                    <Setter Property="Background" Value="Green"/>
                </Style>
                <Style TargetType="Control">
                    <Setter Property="Background" Value="Red"/>
                </Style>
            </datavis:StylePalette>
        </charting:Chart.StylePalette>
        <charting:Chart.Series>
            <charting:PieSeries
                ItemsSource="{StaticResource CodeElementCollection}"
                DependentValueBinding="{Binding Lines}"
                IndependentValueBinding="{Binding Name}"/>
        </charting:Chart.Series>
    </charting:Chart>
    
    Sample Chart

    A designer could, of course, make the custom Background brushes much nicer by adding a gradient, an overlay, etc.. The StylePalette is simply the place the Chart goes to get the next Style it needs when it's creating a new DataPoint instance (DataPoint = the visual representation of a column or pie slice) for a Series. The custom palette above uses TargetType="Control" to indicate that each Style is applicable to all the different data point types (ColumnDataPoint, PieDataPoint, etc.), but it's also possible to provide a specific TargetType to customize the data point types individually.

    Getting back to the topic of dynamic data, suppose that instead of a new CodeElement instance being added to the collection, the value of one of the existing CodeElements changes as a result of an edit to an existing file of our imaginary project's source code. Again, we want our chart to automatically update - and because our CodeElement business object implements the INotifyPropertyChanged interface, it will! The sample project has a button to increase the number of comment lines by 50 each time it's clicked:

    private void ChangeCommentStatistics_Click(object sender, RoutedEventArgs e)
    {
        CodeElement commentCodeElement = _codeElementCollection.Where(element => element.Name == "Comments").First();
        commentCodeElement.Lines += 50;
    }
    

    After clicking this button a few times, the chart has smoothly animated itself to the following state (there's no XAML entry because I reloaded the sample before doing this):

    Sample Chart

    Note that in both of the dynamic data scenarios so far, the code that updates the business data doesn't ever need to know that a chart is actively presenting that data. All the code needs to concern itself with is changing the business objects - and the chart automatically handles the rest. It doesn't get much easier than that! :)

    Of course, there's lots more to Charting than I've shown off here: drill-down support, reveal/hide animations, customizability, and more. And much of this can only be fully appreciated in a live demonstration - so please have a look at the live sample page for Charting for lots more examples. And remember that the complete source code to Charting, the samples page, and the automated tests is all freely available under the Microsoft Public License (Ms-PL). Believe me when I say we've tried to make it as easy as possible to get started with Charting!

    And to make things even easier, I'll be posting the following application (with full source code) to my blog in a day or so. It's called ChartBuilder and is an interactive Charting sample/learning tool/test bed all rolled up in one happy bundle of joy. I hope you'll find it useful:

    ChartBuilder

    And that's Charting in a nutshell! I expect there will be lots more written about Charting in the coming weeks. Those of us on the Charting team have done our best to come up with an API and an implementation that everyone will love, but a big part of that process is YOU. We're eager to hear your feedback as you start playing around with Charting in your own projects. This release of Silverlight Charting is what's known as a "preview release", so nothing is set in stone and we're completely open to improvements anywhere. We'll do our best to minimize breaking changes, but if there are places where things are just plain broken, we'll do what we can to fix them! If you have any questions about Charting, you can ask them in the Silverlight Controls forum. If you think you've found a bug, please report it with the Issue Tracker in the Toolkit's CodePlex site.

    Thank you for taking the time to learn about Silverlight Charting - it's my sincere hope that Charting helps Silverlight application developers deliver even more compelling applications with simplicity and ease!

     

    Aside: The development of Silverlight Charting has been a very interesting experience to be a part of. Throughout the entire process, we've been *extremely* resource-constrained (you wouldn't believe me if I told you), but when all is said and done, I'm really pleased with what we've accomplished!

    Further aside: I began leading the Charting effort a few months ago when my manager asked me to "think about developing a Charting story for Silverlight". I soon partnered with a few people on the SQL Data Visualization team and began working on the foundations of what you see today. We spent a lot of time trying to design a good, easy to use API for Charting that also made sense in the Silverlight world of templates, styling, etc.. (There are a lot of challenges here and I may blog more about these challenges in the future.) Near the end of August, Jafar Husain joined our team and the Charting effort. He's been responsible for large parts of the internal Charting infrastructure and there are examples of his influence throughout our final object model. He's going to be blogging about some of the internal details and decision-making process, so please subscribe to his blog if you want the gory details of how this stuff actually fits together.

  • Delay's Blog

    Columns of a different color [Customizing the appearance of Silverlight charts with re-templating and MVVM]

    • 19 Comments

    When we created Silverlight Charting (background reading here and here), we tried to make things as designer-friendly as possible. So friendly, in fact, that it would be possible for someone to take the default look-and-feel of what we'd released and significantly enhance it without changing the Charting framework at all. :) That said, it's worth noting that Charting controls are a little different than typical WPF/Silverlight controls: while it might make sense to completely change how a ListBox looks, there are certain aspects of a chart that can't be changed without rendering the visualization meaningless. And so there are certain assumptions behind our Charting implementation around things we didn't expect users to want to change. But that's the great thing about users: they want to change these things anyway! :)

    One of the fundamentals of column/bar charts is that the columns/bars of a single series are all drawn the same; that's what ties them together and makes it clear they represent a single series. If you create a column chart in Excel, the default color for the columns is blue. It's easy to change that color to orange or green or plaid, but by default all of the columns of the series change together because they're all part of the same series. (Incidentally, it is possible to change the colors of an individual column in Excel, but it's not entirely obvious how to do so and it's clearly not a mainline scenario.) With that in mind, it's no surprise that our charts behave similarly: you can provide whatever look you want for the columns and bars (via the ColumnSeries.DataPointStyle property, perhaps), but the columns and bars of a particular series always look the same.

    But what if your scenario is such that you want to do things a little differently and you want more control over the colors of individual columns and bars? Well, you take advantage of re-templating and Model-View-ViewModel (MVVM), that's what! :) You're reading this blog, so I'll assume you already know what re-templating is - if not, here's a good place to start. Model-View-ViewModel (MVVM) is probably less well known to date - it's an approach to application development commonly used with WPF and Silverlight where simple wrapper classes are used to expose aspects of the underlying data types in a manner that's easy for the UI layer to deal with. You can read lots more about MVVM on John Gossman's blog or this recent MSDN article by Josh Smith. But I'm not here to teach you what re-templating or MVVM are - I'm here to show you how to use them with Charting to implement the multi-colored column scenario!

     

    [Click here to download the complete Silverlight 2 source code for the sample application shown/discussed below.]

     

    Imagine that you're a teacher and you want to chart the grades of your students. You've already got a basic Student class that exposes some basic properties and you can create instances from a database or a file or something. The Student class probably looks like this:

    // Standard data object representing a Student
    public class Student : INotifyPropertyChanged
    {
        // Student's name
        public string Name { get; private set; }
    
        // Student's favorite color
        public Brush FavoriteColor { get; private set; }
    
        // Student's grade
        public double Grade
        {
            get { return _grade; }
            set
            {
                _grade = value;
                Helpers.InvokePropertyChanged(PropertyChanged, this, "Grade");
            }
        }
        private double _grade;
    
        // Student constructor
        public Student(string name, Brush favoriteColor)
        {
            Name = name;
            FavoriteColor = favoriteColor;
        }
    
        // INotifyPropertyChanged event
        public event PropertyChangedEventHandler PropertyChanged;
    }
    

    The class above exposes a name and a favorite color (which I've implemented here as a Brush for convenience). There's also a grade, but we'll come back to that shortly... The goal is for each column representing a student to be drawn using that student's favorite color. To accomplish this, all we need to do is re-template. Using a designer tool like Blend or something simple like my SilverlightDefaultStyleBrowser, we can copy the default Style for ColumnDataPoint and paste it into our project's resources. By removing stuff that's not relevant to the demonstration and making a single change (highlighted below), we arrive at something like the following:

    <Style
        x:Key="ColorByPreferenceColumn"
        TargetType="charting:ColumnDataPoint">
        <Setter Property="Background" Value="DarkGray"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate
                    TargetType="charting:ColumnDataPoint">
                    <Border
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                        <Grid Background="{Binding FavoriteColor}">
                            <Rectangle>
                                <Rectangle.Fill>
                                    <LinearGradientBrush>
                                        <GradientStop Color="#77ffffff" Offset="0"/>
                                        <GradientStop Color="#00ffffff" Offset="1"/>
                                    </LinearGradientBrush>
                                </Rectangle.Fill>
                            </Rectangle>
                            <Border BorderBrush="#ccffffff" BorderThickness="1">
                                <Border BorderBrush="#77ffffff" BorderThickness="1"/>
                            </Border>
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    

    This is just a tweak of the default template so that each column pulls its Background Brush from the FavoriteColor property of the underlying data object. Hook that up to a Chart/ColumnSeries in XAML, and that's all there is to it:

    Color from Student

    By the way, here's the XAML for that Chart:

    <charting:Chart
        x:Name="FavoriteColorColumnChart"
        Title="Grades - By Favorite Color"
        Grid.Column="0">
        <charting:ColumnSeries
            DependentValueBinding="{Binding Grade}"
            IndependentValueBinding="{Binding Name}"
            DataPointStyle="{StaticResource ColorByPreferenceColumn}">
            <charting:ColumnSeries.DependentRangeAxis>
                <charting:LinearAxis
                    Minimum="0"
                    Maximum="100"
                    Title="Grade"
                    ShowGridLines="True"/>
            </charting:ColumnSeries.DependentRangeAxis>
        </charting:ColumnSeries>
    </charting:Chart>
    
    Aside: This process is even easier on WPF! (Assuming I had access to something like daily builds of Charting for WPF, I might have even mocked this up quickly to prove it to myself...) Unfortunately, the necessary "Binding in a Setter" capability is not supported by Silverlight 2 in XAML or code:
    <Style
        x:Key="ColorByPreferenceColumn"
        TargetType="charting:ColumnDataPoint">
        <Setter Property="Background" Value="{Binding FavoriteColor}"/>
    </Style>
    

    So that's how easy it is to get custom column and bar colors if your data objects already expose the information you need!

    But what if you want to base the custom colors on something that's not directly available on the data objects and you also don't have the freedom to change the data objects themselves? In other words - continuing the example above - let's say we decided to change things so the columns are colored according to each student's current grade: great grades get green columns, satisfactory grades get yellow columns, and unsatisfactory grades get red columns.

    The first thing to consider when faced with a problem like this is whether an IValueConverter will work. I've written about the usefulness of IValueConverter before, so I won't spend more time on that here. IValueConverter is great if you want to take a single property and mutate it as part of a Binding. But what if you want to do something more complicated than that? Well, on WPF there's IMultiValueConverter which might do the trick, but that's not available on Silverlight and it's not always the answer anyway. So let's take advantage of MVVM to wrap our existing Student data objects with an object that's more view-friendly: StudentViewModel. Here's a trivial StudentViewModel class that exposes a Student and a Brush that's colored according to the Student's Grade property. Because Student implements INotifyPropertyChanged (like a well behaved class should), StudentViewModel can listen for changes to the Grade property and update its Brush automatically. StudentViewModel also implements INotifyPropertyChanged - so that anything referencing it will be notified about changes to the GradeColor property it exposes. Here's how it looks in code:

    // Custom data object to wrap a Student object for the view model
    public class StudentViewModel : INotifyPropertyChanged
    {
        // Student object
        public Student Student { get; private set; }
    
        // Color representing Student's Grade
        public Brush GradeColor { get; private set; }
    
        // StudentViewModel constructor
        public StudentViewModel(Student student)
        {
            Student = student;
            student.PropertyChanged += new PropertyChangedEventHandler(HandleStudentPropertyChanged);
        }
    
        // Detect changes to the Student's grade and update GradeColor
        void HandleStudentPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if ("Grade" == e.PropertyName)
            {
                if (Student.Grade < 50)
                {
                    GradeColor = new SolidColorBrush { Color = Colors.Red };
                }
                else if (Student.Grade < 80)
                {
                    GradeColor = new SolidColorBrush { Color = Colors.Yellow };
                }
                else
                {
                    GradeColor = new SolidColorBrush { Color = Colors.Green };
                }
                Helpers.InvokePropertyChanged(PropertyChanged, this, "GradeColor");
            }
        }
    
        // INotifyPropertyChanged event
        public event PropertyChangedEventHandler PropertyChanged;
    }
    
    Aside: I've typically seen view model classes implemented by re-exposing each of the interesting data object properties - so for each property Foo on the data object, there will be a property Foo' on the view model object (which is either identical to the original property or some derivative of it). While I can see the value of this approach in some cases, the duplication of properties always bothers me and so I've instead exposed the entire Student object from the StudentViewModel object as a property (along with the new GradeColor property). This saves me from duplicating any existing properties, exposes the entire Student object to users of the StudentViewModel object, and is completely future-proof because any updates to the Student implementation will automatically show up for users of StudentViewModel.

    Now that we've got a view model class that exposes a view-friendly property that is exactly what we need, our job is easy: change the chart to use StudentViewModels and change the custom template to reference the GradeColor property. Here's the new template (with the same kind of change as before):

    <Style
        x:Key="ColorByGradeColumn"
        TargetType="charting:ColumnDataPoint">
        <Setter Property="Background" Value="DarkGray"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate
                    TargetType="charting:ColumnDataPoint">
                    <Border
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                        <Grid Background="{Binding GradeColor}">
                            <Rectangle>
                                <Rectangle.Fill>
                                    <LinearGradientBrush>
                                        <GradientStop Color="#77ffffff" Offset="0"/>
                                        <GradientStop Color="#00ffffff" Offset="1"/>
                                    </LinearGradientBrush>
                                </Rectangle.Fill>
                            </Rectangle>
                            <Border BorderBrush="#ccffffff" BorderThickness="1">
                                <Border BorderBrush="#77ffffff" BorderThickness="1"/>
                            </Border>
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    

    The XAML for this chart is nearly identical and the end result looks just how we wanted it to:

    Color from Grade

    Having shown off how re-templating and MVVM enable more advanced Charting customization scenarios, I've accomplished what I set out to do and could have stopped here... But there was still one customer scenario I wanted to address: synchronizing the colors of pie slices in a pie chart with the colors of columns in a column chart. Given what we've just discussed, the solution is easy: just repeat the re-templating process for a second chart with a PieSeries and PieDataPoints. Because the column/slice colors come from the data objects and because both charts are sharing the same data objects, the color for every data object (student) will naturally be the same across both charts. The re-templated XAML is the same as before and the final result is exactly what we want:

    Color from Student as Pie

    Well, actually, that's not entirely true; Getting the pie slices right was trivial - but there was a bit of additional effort required to synchronize the colors of the pie chart's legend items with the pie slices...

    The way things work is that the Series creates whatever LegendItems it needs. As part of that creation, it also creates a "fake" DataPoint that's styled just like the "real" ones displayed in the chart. This fake data point exists so that the LegendItem's default Template can create Bindings for things like the Background and BorderBrush properties. (Recall that users can completely change the look of a DataPoint, so the only way we have to know how something will look is to create it and see.) This approach works out pretty well, but there was an oversight that caused problems for me when I tried to provide my own PieSeries.LegendItemStyle: the DataContext of the fake PieDataPoint wasn't set to the corresponding slice's data object. Normally, that's no big deal because it's unused - however in this case it's a problem because the custom Template we created above gets its color from the data object. Without a bound data object to provide context, the legend items weren't using the right colors. :(

    I thought about a few ways to work around this, but eventually decided the fix (the setting of the DataContext property for the fake PieDataPoint) belonged in the Charting code itself. Fortunately, Charting is open source, so it's easy for anybody to make such changes if/when the need arises! I've included a copy of the relevant source file with the one-line change I made (Changes\PieSeries.cs, line 317) and changed the sample project to use a custom build of Charting's Microsoft.Windows.Controls.DataVisualization.dll assembly that includes this change.

    And because I'm a nice guy, I also made the same change to the actual charting source code that's under development, got it reviewed, and submitted it (along with an associated unit test) for inclusion in the next official release of the Silverlight Toolkit! After all, if I needed this to work for my sample, chances are good that someone else might need it to work for their application as well. :)

    With that fix in place, here is the Style that applies the proper color to the LegendItems:

    <Style
        x:Key="ColorByPreferenceLegendItem"
        TargetType="charting:LegendItem">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="charting:LegendItem">
                    <StackPanel Orientation="Horizontal">
                        <Rectangle
                            Width="8" Height="8"
                            Fill="{Binding DataContext.FavoriteColor}"
                            Stroke="{Binding BorderBrush}"
                            StrokeThickness="1" Margin="0,0,3,0"/>
                        <datavis:Title Content="{TemplateBinding Content}"/>
                    </StackPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    

     

    And there you have it: a few simple ways to take Charting and extend it to do exactly what you want! The examples here are fairly simple, but re-templating and MVVM are very powerful concepts which enable a high degree of customization for Silverlight and WPF applications that's pretty hard to come by in other platforms. If you're trying to do something unique and you're not having any luck the "normal" way, please take a few moments to consider the techniques discussed here - you may find that your problem has an easy solution after all!

Page 2 of 28 (277 items) 12345»