Delay's Blog

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

Posts
  • 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!

  • Delay's Blog

    Powerful log file analysis for everyone [Releasing TextAnalysisTool.NET!]

    • 36 Comments

    A number of years ago, the product team I was on spent a lot of team analyzing large log files. These log files contained thousands of lines of output tracing what the code was doing, what its current state was, and gobs of other diagnostic information. Typically, we were only interested in a handful of lines - but had no idea which ones at first. Often one would start by searching for a generic error message, get some information from that, search for some more specific information, obtain more context, and continue on in that manner until the problem was identified. It was usually the case that interesting lines were spread across the entire file and could only really be understood when viewed together - but gathering them all could be inconvenient. Different people had different tricks and tools to make different aspects of the search more efficient, but nothing really addressed the end-to-end scenario and I decided I'd try to come up with something better.

    TextAnalysisTool was first released to coworkers in July of 2000 as a native C++ application written from scratch. It went through a few revisions over the next year and a half and served myself and others well during that time. Later, as the .NET Framework became popular, I decided it would be a good learning exercise to rewrite TextAnalysisTool to run on .NET as a way to learn the Framework and make some architectural improvements to the application. TextAnalysisTool.NET was released in February of 2003 as a fully managed .NET 1.0 C# application with the same functionality of the C++ application it replaced. TextAnalysisTool.NET has gone through a few revisions since then and has slowly made its way across parts of the company. (It's always neat to get an email from someone in a group I had no idea was using TextAnalysisTool.NET!) TextAnalysisTool.NET is popular enough among its users that I started getting requests to make it available outside the company so that customers could use it to help with investigations.

    The effort of getting something posted to Microsoft.com seemed overwhelming at the time, so TextAnalysisTool.NET stayed internal until now. With the latest request, I realized my blog would be a great way to help internal groups and customers by making TextAnalysisTool.NET available to the public!

    TextAnalysisTool.NET Demonstration

    You can download the latest version of TextAnalysisTool.NET by clicking here (or on the image above).

    In the above demonstration of identifying the errors and warnings from sample build output, note how the use of regular expression text filters and selective hiding of surrounding content make it easy to zoom in on the interesting parts of the file - and then zoom out to get context.

    Additional information can be found in the TextAnalysisTool.NET.txt file that's included in the ZIP download (or from within the application via Help | Documentation). The first section of that file is a tutorial and the second section gives a more detailed overview of TextAnalysisTool.NET (excerpted below). The download also includes a ReadMe.txt with release notes and a few other things worth reading.

    The Problem: For those times when you have to analyze a large amount of textual data, picking out the relevant line(s) of interest can be quite difficult. Standard text editors usually provide a generic "find" function, but the limitations of that simple approach quickly become apparent (e.g., when it is necessary to compare two or more widely separated lines). Some more sophisticated editors do better by allowing you to "bookmark" lines of interest; this can be a big help, but is often not enough.

    The Solution: TextAnalysisTool.NET - a program designed from the start to excel at viewing, searching, and navigating large files quickly and efficiently. TextAnalysisTool.NET provides a view of the file that you can easily manipulate (through the use of various filters) to display exactly the information you need - as you need it.

    Filters: Before displaying the lines of a file, TextAnalysisTool.NET passes the lines of that file through a set of user-defined filters, dimming or hiding all lines that do not satisfy any of the filters. Filters can select only the lines that contain a sub-string, those that have been marked with a particular marker type, or those that match a regular expression. A color can be associated with each filter so lines matching a particular filter stand out and so lines matching different filters can be easily distinguished. In addition to the normal "including" filters that isolate lines of text you DO want to see, there are also "excluding" filters that can be used to suppress lines you do NOT want to see. Excluding filters are configured just like including filters but are processed afterward and remove all matching lines from the set. Excluding filters allow you to easily refine your search even further.

    Markers: Markers are another way that TextAnalysisTool.NET makes it easy to navigate a file; you can mark any line with one or more of eight different marker types. Once lines have been marked, you can quickly navigate between similarly marked lines - or add a "marked by" filter to view only those lines.

    Find: TextAnalysisTool.NET also provides a flexible "find" function that allows you to search for text anywhere within a file. This text can be a literal string or a regular expression, so it's easy to find a specific line. If you decide to turn a find string into a filter, the history feature of both dialogs makes it easy.

    Summary: TextAnalysisTool.NET was written with speed and ease of use in mind throughout. It saves you time by allowing you to save and load filter sets; it lets you import text by opening a file, dragging-and-dropping a file or text from another application, or by pasting text from the clipboard; and it allows you to share the results of your filters by copying lines to the clipboard or by saving the current lines to a file. TextAnalysisTool.NET supports files encoded with ANSI, UTF-8, Unicode, and big-endian Unicode and is designed to handle large files efficiently.

    I maintain a TODO list with a variety of user requests, but I thought I'd see what kind of feedback I got from releasing TextAnalysisTool.NET to the public before I decide where to go with the next release. I welcome suggestions - and problem reports - so please share them with me if you've got any!

    I hope you find TextAnalysisTool.NET useful as I have!

  • Delay's Blog

    You voted lots, we fixed lots [AJAX Control Toolkit release!]

    • 16 Comments

    Last night we published the 10920 release of the AJAX Control Toolkit. This release continued our trend of focusing on the most popular bugs and work items identified by the user community in the support forum and online issue tracker. A number of popular issues got fixed in this release, addressing nearly 1000 user votes!

    The release notes from the sample web site detail the improvements:

    General fixes:

    • Controls with Embedded styles (Calendar, Tabs and Slider): Toolkit controls no longer need explicit style references when loaded asynchronously. For example, if a Calendar control is placed inside an UpdatePanel and made visible on an UpdatePanel postback, the embedded styles are now loaded properly.
    • PopupBehavior positioning (AutoComplete, Calendar, DropDown, HoverMenu, ListSearch, PopupControl and ValidatorCallout): PopupBehavior now respects the position of its parent element even when the browser window is very narrow or the parent element is close the window edge.
    • Focusing extended controls (Accordion, CollapsiblePanel, DropShadow, Tabs): Pages that use Toolkit controls which re-parent DOM elements can use a workaround to focus a specific element on page load. The new method Utility.SetFocusOnLoad ensures that the desired control receives focus.

    Control specific fixes:

    • Calendar: Property to specify the position of Calendar, a default date feature that allows the calendar to start out with a selected date, and a consistent show, hide and focus story that makes the Calendar user experience more intuitive.
    • ModalPopup: Ability to disable repositioning of the ModalPopup in response to window resize and scroll.
    • ConfirmButton: ModalPopup functionality now supported in addition to the regular windows alert dialog.
    • MaskedEdit: Extended Textbox no longer uses Invariant culture if no CultureName is specified and falls back to the Page Culture.
    • AutoComplete: Allow users to associate additional data with the AutoComplete suggestions.
    • Slider: Slider can be easily customized using its various CSS properties.

    As with the previous release, we have published "source" and "no-source" versions for .NET 2.0/Visual Studio 2005 as well as for .NET 3.5/Visual Studio 2008 (still in Beta). Unique to the 3.5/2008 versions are the following:

    Features:

    • JavaScript IntelliSense support: We have added reference tags to all Toolkit JavaScript files that enables you to take advantage of new features in Visual Studio 2008 Beta 2. With the multi-targeting support in this Visual Studio Beta, IntelliSense will be available for the ASP.NET AJAX 1.0 flavor of the Toolkit as well. This article discusses the reference tag feature in detail.
    • Extender designer support: Enhanced designer support for Toolkit controls using the new "Add Extender" user interface.

    One thing we'd hoped to include with this release didn't quite make it in: our new automated testing framework. This framework is based on a different approach than our current framework - one that makes it easy to add additional test cases and leverage existing ones across new scenarios. The new testing framework has already dramatically improved our test coverage, helped identify new issues, and made fixing existing issues less risky!

    But we've been iterating on the new framework for the past couple of weeks and faced the usual decision when it came time to finalize this release: slip or ship. We slipped our release date a little in the hopes that we'd be able to include the new framework with this release, but eventually decided not to delay all the great new Toolkit code any longer. We wanted our users to take advantage of the new bits ASAP - so stay tuned for more on the new testing framework in a future release!

    As always, it's easy to sample any of the controls (no install required). You can also browse the project web site, download the latest Toolkit, and start creating your own controls and/or contributing to the project!

    If you have any feedback, please share it with us on the support forum!

  • Delay's Blog

    My new home page, extended [Updated collection of great Silverlight and WPF Charting resources!]

    • 25 Comments

    It's been a while since the March 09 release of the Silverlight Toolkit - and even longer since I last posted a collection of Charting links. It's clearly time for an update, so I've added a bunch of new links to the collection below (FYI: previously published links are gray):

    Overviews (100 level)

    Scenarios (200 level)

    Internals (300 level)

    Jafar Husain's posts (Partner level)

    My posts (Ego level)

    Many, many thanks to everyone who has spent time helping others learn how to use Silverlight/WPF Charting!

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

  • Delay's Blog

    Scrolling so smooth like the butter on a muffin [How to: Animate the Horizontal/VerticalOffset properties of a ScrollViewer]

    • 10 Comments

    Recently, I got email from two different people with the same question: How do I animate the HorizontalOffset/VerticalOffset properties of a Silverlight/WPF ScrollViewer control? Just in case you've never tried this yourself, I'll tell you that it's not quite as easy as you'd think; those two properties are both read-only and therefore can't be animated by a DoubleAnimation. The official way to accomplish this task is to call the ScrollToHorizontalOffset or ScrollToVerticalOffset method - but of course that can't be done by a DoubleAnimation either...

    Aside: Why are HorizontalOffset and VerticalOffset read-only in the first place? Good question; I don't know! I don't see why they couldn't be writable in today's world, but I bet there was a good reason - once upon a time. :)

    While I didn't have the time to implement a solution myself, I did have an idea for what to do. It was inspired by a rather quirky business plan and went like this:

    1. Create an attached DP for ScrollViewer of type double called SettableHorizontalOffset
    2. In its change handler, call scrollViewerInstance.ScrollToHorizontalOffset(newValue)
    3. Animate the attached DP on the ScrollViewer of your choice
    4. ???
    5. Profit

    I cautioned at the time that my idea might not work - and sure enough, when I tried it myself, it didn't! It turns out that neither Silverlight nor WPF much like the idea of pointing Storyboard.TargetProperty at an attached DependencyProperty. So much for my clever idea; I needed a different way to solve the problem... And before long I realized that I could use a Mediator just like I did for a similar situation with LayoutTransformer (see previous posts).

     

    So I created a simple class, ScrollViewerOffsetMediator, which exposes a ScrollViewer property that you point at a ScrollViewer instance (hint: use a Binding with ElementName to make this easy) and a VerticalOffset property which is conveniently writable. :) Any changes to the VerticalOffset property are automatically applied to the relevant ScrollViewer via a call to its ScrollToVerticalOffset method - so you can animate the mediator and it'll be just like you were animating the ScrollViewer itself!

    That solves the original problem and it's all well and good - but it doesn't quite address a pretty common scenario: wanting to animate a ScrollViewer from top to bottom. You see, VerticalOffset is expressed in pixels and it's not easy to pass the (unknown) height of an arbitrary ScrollViewer to an animation in static XAML. So I added another property called ScrollableHeightMultiplier which takes a value between 0.0 and 1.0, multiplies it by the ScrollViewer's current ScrollableHeight and sets the VerticalOffset to that value. In this manner, animations can be written in general, pixel-independent terms, and it's easy to get the animated scrolling effect everybody wants!

    Aside: I've implemented support for only the vertical direction - doing the same thing for the horizontal direction is left as an exercise to the reader.

     

    The sample application uses ScrollableHeightMultiplier and an EasingFunction (just to show off!) and looks like this:

    AnimatingScrollViewerOffsets sample

     

    [Click here to download the complete AnimatingScrollViewerOffsets sample.]

     

    The XAML for the sample is quite straightforward:

    <Grid Height="200" Width="150">
    
        <!-- Trigger to start the animation when loaded -->
        <Grid.Triggers>
            <EventTrigger RoutedEvent="Grid.Loaded">
                <BeginStoryboard>
                    <BeginStoryboard.Storyboard>
                        <!-- Animate back and forth forever -->
                        <Storyboard
                            AutoReverse="True"
                            RepeatBehavior="Forever">
                            <!-- Animate from top to bottom -->
                            <DoubleAnimation
                                Storyboard.TargetName="Mediator"
                                Storyboard.TargetProperty="ScrollableHeightMultiplier"
                                From="0"
                                To="1"
                                Duration="0:0:1">
                                <DoubleAnimation.EasingFunction>
                                    <!-- Ease in and out -->
                                    <ExponentialEase EasingMode="EaseInOut"/>
                                </DoubleAnimation.EasingFunction>
                            </DoubleAnimation>
                        </Storyboard>
                    </BeginStoryboard.Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </Grid.Triggers>
    
        <!-- ScrollViewer that will be animated -->
        <ScrollViewer
            x:Name="Scroller">
            <!-- Arbitrary content... -->
            <StackPanel>
                <ItemsControl
                    ItemsSource="{Binding}"
                    FontSize="32"/>
            </StackPanel>
        </ScrollViewer>
    
        <!-- Mediator that forwards the property changes -->
        <local:ScrollViewerOffsetMediator
            x:Name="Mediator"
            ScrollViewer="{Binding ElementName=Scroller}"/>
    
    </Grid>
    

     

    Here's the complete implementation of ScrollViewerOffsetMediator which naturally works perfectly well on WPF:

    /// <summary>
    /// Mediator that forwards Offset property changes on to a ScrollViewer
    /// instance to enable the animation of Horizontal/VerticalOffset.
    /// </summary>
    public class ScrollViewerOffsetMediator : FrameworkElement
    {
        /// <summary>
        /// ScrollViewer instance to forward Offset changes on to.
        /// </summary>
        public ScrollViewer ScrollViewer
        {
            get { return (ScrollViewer)GetValue(ScrollViewerProperty); }
            set { SetValue(ScrollViewerProperty, value); }
        }
        public static readonly DependencyProperty ScrollViewerProperty =
            DependencyProperty.Register(
                "ScrollViewer",
                typeof(ScrollViewer),
                typeof(ScrollViewerOffsetMediator),
                new PropertyMetadata(OnScrollViewerChanged));
        private static void OnScrollViewerChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            var mediator = (ScrollViewerOffsetMediator)o;
            var scrollViewer = (ScrollViewer)(e.NewValue);
            if (null != scrollViewer)
            {
                scrollViewer.ScrollToVerticalOffset(mediator.VerticalOffset);
            }
        }
    
        /// <summary>
        /// VerticalOffset property to forward to the ScrollViewer.
        /// </summary>
        public double VerticalOffset
        {
            get { return (double)GetValue(VerticalOffsetProperty); }
            set { SetValue(VerticalOffsetProperty, value); }
        }
        public static readonly DependencyProperty VerticalOffsetProperty =
            DependencyProperty.Register(
                "VerticalOffset",
                typeof(double),
                typeof(ScrollViewerOffsetMediator),
                new PropertyMetadata(OnVerticalOffsetChanged));
        public static void OnVerticalOffsetChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            var mediator = (ScrollViewerOffsetMediator)o;
            if (null != mediator.ScrollViewer)
            {
                mediator.ScrollViewer.ScrollToVerticalOffset((double)(e.NewValue));
            }
        }
    
        /// <summary>
        /// Multiplier for ScrollableHeight property to forward to the ScrollViewer.
        /// </summary>
        /// <remarks>
        /// 0.0 means "scrolled to top"; 1.0 means "scrolled to bottom".
        /// </remarks>
        public double ScrollableHeightMultiplier
        {
            get { return (double)GetValue(ScrollableHeightMultiplierProperty); }
            set { SetValue(ScrollableHeightMultiplierProperty, value); }
        }
        public static readonly DependencyProperty ScrollableHeightMultiplierProperty =
            DependencyProperty.Register(
                "ScrollableHeightMultiplier",
                typeof(double),
                typeof(ScrollViewerOffsetMediator),
                new PropertyMetadata(OnScrollableHeightMultiplierChanged));
        public static void OnScrollableHeightMultiplierChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            var mediator = (ScrollViewerOffsetMediator)o;
            var scrollViewer = mediator.ScrollViewer;
            if (null != scrollViewer)
            {
                scrollViewer.ScrollToVerticalOffset((double)(e.NewValue) * scrollViewer.ScrollableHeight);
            }
        }
    }
    

     

    PS - Thanks to Homestar Runner's Strong Bad for inspiring the title of this post.

  • Delay's Blog

    Never do today what you can put off till tomorrow [DeferredLoadListBox (and StackPanel) help Windows Phone 7 lists scroll smoothly and consistently]

    • 35 Comments

    In my previous post about how LowProfileImageLoader helps the Windows Phone 7 UI thread stay responsive by loading images in the background, I began with the following introduction:

    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 options. That's what my latest project, PhonePerformance is all about: giving developers performance-focused alternatives for common Windows Phone 7 scenarios.

     

    In this second post, I'll be demonstrating the use of my DeferredLoadListBox class in conjunction with the Silverlight StackPanel in order to get good performance from a scenario that's pretty common for social media applications: a scrolling list of entries with a picture and a brief bit of text. (Ex: Twitter posts, Facebook updates, blog comments, and the like.) As usual, I've written a sample application to show what I'm talking about - it displays a simple list of about 200 image+name pairs from the web (the followers of my Twitter account [you know who you are! :) ]). You can see a screen shot of the "List Scrolling" sample below:

    PhonePerformance List Scrolling sample
    Note: Everything in this post pertains to the latest (internal) Windows Phone 7 builds, not the public Beta bits. Although I'd expect things to work the same on the Beta build, I haven't verified that because the final bits will be released to the public on September 16th.
    Additional note: My discussion assumes all testing is done on actual phone hardware. Although running the emulator on a PC 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.

     

    Okay, go ahead and run the sample and wait a moment for it to load the user list from the web (and enable those two buttons). Then choose the "default" (left) or "performance" (right) scenario and watch the behavior of the little blue dots moving across the screen as the list content populates. (I discussed the motivation behind the blue dots in the previous post - for now just remember that when the dots are flowing smoothly, life is good - and when the dots get jumpy or stop animating completely, the application is unresponsive.)

    Right now, we're interested in the different load times of the two scenarios. The "performance" side uses a StackPanel to get good scroll performance, but making that switch without doing anything else is likely to increase load times versus the default VirtualizingStackPanel used by the Windows Phone ListBox (because of the loss of virtualization; more on this later). That's why the sample also uses DeferredLoadListBox - to offset the performance loss by re-introducing enough pseudo-virtualization to bring performance back up to where it was. And as the sample application shows, the load times of the two lists are very similar. (Granted, neither is instantaneous - but it's also not the case that one is consistently faster than the other.)

    Now that both lists are populated, the real experiment begins: go ahead and scroll the "default" (left) list up and down and watch the item content as you do so. When scrolling at slow or moderate speeds, the movement will be smooth and the experience will be great. But once you start scrolling quickly, things begin to break down - you may start to see brief glimpses of missing items, flickering, or even black-outs...

    So with that baseline experience in mind, it's time to scroll the "performance" (right) list to see how it compares. The first thing you'll probably notice is that it's slower to load the images - that's due to the use of LowProfileImageLoader and was the topic of my previous post. The next thing you'll probably notice is that the scrolling is smooth for just about any speed - and especially for content that's already been on the screen at least once! I won't claim scrolling is perfect with StackPanel and DeferredLoadListBox, but it has been my experience (and that of others) that it can be notably better than the default behavior.

     

    To understand why the DeferredLoadListBox+StackPanel combination is effective, it's necessary to understand a little about how VirtualizingStackPanel works - and why that ends up being a problem in this scenario...

    Pretty much every Panel implementation for Silverlight and WPF works by measuring and arranging all its elements according to various layout guidelines (ex: stack, grid, etc.). This makes it straightforward to implement a custom Panel, but it also means that all the Children must be created and live in the visual tree from the beginning. Because in the vast majority of cases there are only a handful of children (and they're all on screen anyway), this isn't a problem. However, in the "really long list" scenario, there are often only 5-10 items on the screen at a time - and a few hundred other items that aren't. VirtualizingStackPanel works in conjunction with ItemsControl and ItemContainerGenerator to create only those elements that are actually on the screen. As the user scrolls the list, VirtualizingStackPanel automatically creates containers for any items about to come into view and recycles the containers for items that just scrolled out of view. (In reality, there's usually a screen's worth of buffer on either side.) Consequently, there are two big wins with VirtualizingStackPanel: load time (by virtue of creating only a fraction of the total elements) and memory consumption (by virtue of keeping only a fraction of the total elements around at any time).

    But (as sometimes happens in life) VirtualizingStackPanel's strength is also its weakness. All that container recycling and item juggling takes time to execute - and that's precious time on the UI thread that can't be spent doing other things (like updating the UI). So what seems to happen when a list is scrolling quickly enough is that the VirtualizingStackPanel falls behind more and more - until some of those containers that are scrolling into view are blank because they haven't been created or populated yet! And that's when the user sees visual glitches and rendering issues...

    Therefore, the fundamental approach I've taken to avoiding problems in scenarios like this is to replace the ListBox's VirtualizingStackPanel ItemsPanel with a StackPanel. This is quite easy to do - and the StackPanel's non-virtualizing nature means that scrolling long lists will be smooth as silk. However, there are two downsides to making this switch: load time is considerably longer and memory use will be significantly higher. The increased memory consumption probably won't be an issue with small- or medium-sized lists, but it could start to be a problem for large lists containing thousands of items. But while there are options for mitigating this, that's not my objective and I won't be going into them here. (Besides, I'm not sure how practical it is for people to scroll super-long lists, anyway!)

    The way I avoid longer load times is by using the DeferredLoadListBox class I wrote for just this purpose - what it does is hold off on populating off-screen containers until they're about to show up on the screen. In this manner, it restores some of the benefits of VirtualizingStackPanel by making the relevant portion of the load time (roughly) independent of the total number of items in the list. But it's important to note that DeferredLoadListBox works in one direction only! Although there's no reason it couldn't "re-virtualize" items as they scroll out of view, it specifically doesn't do so because doing (and undoing) that would consume precious CPU cycles. DeferredLoadListBox is really only about softening the blow of switching from VirtualizingStackPanel to StackPanel - it's not about trying to re-implement VirtualizingStackPanel's fundamental behavior.

    So with scrolling glitches avoided and load time back to where it started, there's just one other thing slowing things down and detracting from the user experience: the jumpiness that takes place during the first full scroll of the list. What's going on there is that the first full scroll causes all those "pseudo-virtualized" containers to be created - which causes the corresponding images to be downloaded from the web. We've established that downloading images on the UI thread is bad for performance, and this is exactly the scenario I created LowProfileImageLoader for! So by throwing LowProfileImageLoader into the mix, the first full scroll stays responsive, too.

    And at this point, we've arrived at the smooth, pleasing, consistent scrolling experience you get from the "List Scrolling" sample's "performance" column! Yay us! :)

     

    Of course, every application - and every scenario - is different, so there's no guarantee StackPanel, DeferredLoadListBox, or LowProfileImageLoader will help all the time (or even most of the time!). But what's nice is that it's extremely easy to try them out (alone or together), so there's nothing to lose by trying them out if your scenario seems likely to benefit!

     

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

     

    To show what I mean, here's what the default scenario looks like:

    <ListBox ItemsSource="{Binding Followers}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <Grid Height="50">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="50"/>
                        <ColumnDefinition Width="10"/>
                        <ColumnDefinition/>
                    </Grid.ColumnDefinitions>
                    <Rectangle
                        Grid.Column="0"
                        Fill="{StaticResource PhoneChromeBrush}"/
                    <Image
                        Grid.Column="0"
                        Source="{Binding ProfileImageUrl}"
                        Width="48"
                        Height="48"/>
                    <TextBlock
                        Grid.Column="2"
                        Text="{Binding ScreenName}"
                        VerticalAlignment="Center"/>
                </Grid>
            </DataTemplate>
        </ListBox.ItemTemplate>
        <ListBox.ItemContainerStyle>
            <Style TargetType="ListBoxItem">
                <Setter Property="Height" Value="50"/>
            </Style>
        </ListBox.ItemContainerStyle>
    </ListBox>

    And here's all it takes to convert it to use StackPanel, DeferredLoadListBox, and LowProfileImageLoader:

    <delay:DeferredLoadListBox ItemsSource="{Binding Followers}">
        <delay:DeferredLoadListBox.ItemTemplate>
            <DataTemplate>
                <Grid Height="50">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="50"/>
                        <ColumnDefinition Width="10"/>
                        <ColumnDefinition/>
                    </Grid.ColumnDefinitions>
                    <Rectangle
                        Grid.Column="0"
                        Fill="{StaticResource PhoneChromeBrush}"/>
                    <Image
                        Grid.Column="0"
                        delay:LowProfileImageLoader.UriSource="{Binding ProfileImageUrl}"
                        Width="48"
                        Height="48"/>
                    <TextBlock
                        Grid.Column="2"
                        Text="{Binding ScreenName}"
                        VerticalAlignment="Center"/>
                </Grid>
            </DataTemplate>
        </delay:DeferredLoadListBox.ItemTemplate>
        <delay:DeferredLoadListBox.ItemContainerStyle>
            <Style TargetType="ListBoxItem">
                <Setter Property="Height" Value="50"/>
            </Style>
        </delay:DeferredLoadListBox.ItemContainerStyle>
        <delay:DeferredLoadListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel/>
            </ItemsPanelTemplate>
        </delay:DeferredLoadListBox.ItemsPanel>
    </delay:DeferredLoadListBox>

    (Don't forget to add the appropriate XMLNS to the top of the XAML file:)

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

     

    The only thing I haven't talked about yet is the requirement that each ListBoxItem container needs to have a fixed height (via the ItemContainerStyle property; see above). This is currently necessary because it enables some optimizations in DeferredLoadListBox - however, it's important to note there's no need for all the containers to have the same fixed height - just that they all need to have a fixed height.

     

    Aside: As it exists today, DeferredLoadListBox only works with vertical scrolling lists. Of course, the same concepts can be applied to horizontally scrolling lists, too, but because that's not consistent with the UI conventions of Windows Phone 7, I haven't tried to generalize the code to support both orientations. I optimized for the performance of the common scenario - but if you'd like to tweak things to support horizontal scrolling instead - or as well! - it should be fairly straightforward to do. :)
  • Delay's Blog

    Make it your own [WebMatrix's new extensibility support enables developers and applications to customize the web development experience to their liking!]

    • 5 Comments

    In conjunction with Microsoft's ongoing BUILD Windows conference, we have just released betas of the next version of the Microsoft Web Platform. Today's releases include a lot of new functionality and I encourage interested parties to have a look at what's new in ASP.NET and WebMatrix.

    In this post, I'm going to focus on one aspect of the WebMatrix 2 Beta: extensibility. By exposing a public API and opening up the application to others, WebMatrix enables individuals and community members to customize the experience to best fit their own unique processes and workflow. Many aspects of the development experience are configurable, so users can become more productive by streamlining common tasks, automating monotonous ones, and simplifying difficult ones!

     

    Task-based extensibility

    Extensibility for the WebMatrix 2 Beta takes three forms: task-based extensibility, help extensibility, and extensions.

    1. Task-based extensibility refers to the ability of a web application like Umbraco or WordPress to embed a file within the install package to customize the WebMatrix user interface for that particular application. By providing some simple XML, applications in the Web App Gallery can add custom links to the Ribbon or dashboard, protect core application files, provide enhanced Intellisense for PHP, and more.

    2. Help extensibility makes it possible to integrate custom content with WebMatrix's new, context-sensitive help pane. The new help system shows links to relevant content and videos based on where the user is in the application and what he or she is doing. Help content is drawn from a variety of sources; content providers can create custom feeds to cover new topics or provide more context on existing ones. This article explains how to create custom help content.

    3. For developers, the real power lies in the ability to write extensions that run inside WebMatrix because they're capable of far richer customization. WebMatrix extensions can be written in any .NET language, are loaded by MEF, the Managed Extensibility Framework, and installed/uninstalled (behind the scenes) as NuGet packages (with a slight twist I'll explain in a different post). Similar to Visual Studio, WebMatrix has an extension gallery that allows users to browse and install extensions from a central feed - or create and share custom feeds!

     

    ColorThemeManager

    Opening WebMatrix's extension gallery (by clicking the "Extensions/Gallery" Ribbon button from the Site workspace) shows some of the extensions that have been created to help give an idea what's possible. I'll call out three of them:

    • ColorThemeManager - This extension by Yishai Galatzer allows you to customize the colors used by the file editor in WebMatrix, export your settings, and import settings from other sources. So if you're one of those people who enjoys looking at green on black text, then you're in luck. :)

    • ImageOptimizer - This extension by Mads Kristensen makes it easy to "optimize" the PNG and JPEG images of a web site by removing unnecessary metadata and recompressing content to minimize file sizes - thereby keeping bandwidth down and site responsiveness up.

    • Snippets - This extension by me makes it easy to insert snippets of text into an open document in the Files workspace. It was written to make WebMatrix demos a little easier (watch for it in the WebMatrix: uber geek in designer clothes presentation today at BUILD!), but its simplicity makes it a good learning tool, too. I'll be blogging the complete source code for Snippets in a few days.

     

    WebMatrix Extension project template

    In addition to providing an overview of the Snippets sample, I plan to discuss other aspects of WebMatrix extensibility over the next few weeks. In the meantime, you can start exploring WebMatrix extensibility today:

    1. Download the Microsoft.WebMatrix.Extensibility CHM file, unblock it (important: click here for directions on "unblocking" a file), open it, and browse the contents. (If you see the message "Navigation to the webpage was canceled", then the file is still blocked.)

    2. Download the "WebMatrix Extension" Visual Studio project template, save the ZIP file in your "%USERPROFILE%\Documents\Visual Studio 2010\Templates\ProjectTemplates" directory, choose File, New, Project in Visual Studio 2010, select the "Visual C#" node, click the "WebMatrix Extension" item, type a project name (ex: "MyExtension" (no spaces, please)), and click OK.

    The project template sets up the right infrastructure, all the necessary references, includes pre- and post-build rules to make development a little easier, and helps get you started with a simple Ribbon-based extension that demonstrates some of the basic extensibility points. The template's ReadMe.txt explains how to configure Visual Studio so that pressing F5 will automatically load the extension inside WebMatrix for a simple, seamless debugging experience with complete breakpoint support, etc.. FYI that I'd like to improve this template by adding support for NuGet package generation (so it will be easier to deploy extensions to a gallery) and maybe also create a VISX wrapper for it (to enable more seamless install of the template itself).

    Aside: While the project template "helpfully" copies your extension to the right place for WebMatrix to load it on startup, the extension is not properly installed and so WebMatrix doesn't know how to uninstall it. For now, the easiest way to get rid of a custom extension is to close WebMatrix and delete the contents of the "%USERPROFILE%\AppData\Local\Microsoft\WebMatrix\Components" directory.

     

    The new extensibility APIs in the WebMatrix 2 Beta allow developers to get started with extensions today. And while there aren't yet extension points for everything, there are enough to enable some pretty interesting scenarios. Available extension points include:

    Microsoft.WebMatrix.Extensibility help file
    • Ribbon content
      • Buttons, groups, tabs, etc.
    • Application information
      • Name, local path, remote URI
    • Integrated dialog and notification UI
    • Context menu items for application files/directories
    • Editor manipulation
      • Text buffer, settings, theming, custom types
    • Dashboard content
    • Simple commanding
    • Active workspace
    • More...

    That said, people are going to have a lot of great extension ideas that are either difficult or impossible to achieve with the Beta APIs. Not being able to put good ideas into practice is certainly disappointing, but it's also a great opportunity to let us know what features are missing from the API and how we can improve it! To make that easy, there's a WebMatrix forum where you can ask questions and exchange ideas. Once you've tried things out, please go there and share your thoughts!

     

    Aside: It probably goes without saying (but I'll say it anyway!) that the APIs available in the WebMatrix 2 Beta are subject to change and it's likely that extensions written for Beta will need to be modified in order to run on later releases. That's not to discourage people from writing extensions, but rather an attempt to set expectations appropriately. :)
    Further aside: It's natural to wonder if existing plugins for Visual Studio will "just work" in WebMatrix. The answer is that they will not - but in most cases it turns out that trying to load a Visual Studio extension inside WebMatrix wouldn't be all that meaningful anyway... At this point, the functionality of these two products and the target audience (both users and developers) are different enough that things don't align in a way that makes this scenario work.
  • Delay's Blog

    Phone-y charts [Silverlight/WPF Data Visualization Development Release 4 and Windows Phone 7 Charting sample!]

    • 18 Comments

    The April '10 release of the Silverlight Toolkit brought stacked series support to the Data Visualization assembly on the Silverlight 4 platform! You can read an overview here and get a detailed description here. Almost simultaneously, the WPF team released WPF 4 as part of .NET 4 and Visual Studio 2010.

    So it's time for an updated version of my Silverlight/WPF Data Visualization Development Release. Like the previous version, this one continues to support the four platforms of interest to most .NET developers: Silverlight 3, Silverlight 4, WPF 3.5, and WPF 4. But what about Windows Phone 7, the new fifth platform for .NET developers? Yeah, it's supported, too! :)

     

    Silverlight/WPF Data Visualization Development Release 4

    As with previous Data Visualization Development Releases, I've updated the code and binaries to match the most recent Toolkit release. The Silverlight 4 Toolkit shipped most recently, so the code in this Development Release is identical to what just went out with that. Which means people using Data Visualization on Silverlight 3, Windows Phone 7, WPF 3.5, or WPF 4 can also take advantage of the latest round of improvements by updating their applications to use the binaries included with this Development Release (or by compiling the included source code themselves).

    Notes:

    • The directions for using this release are the same as last time, so please have a look at earlier posts if you're new to this or need a refresher.
    • It's easy to use Data Visualization on Windows Phone 7: simply add a reference to the pre-compiled System.Windows.Controls.DataVisualization.Toolkit.dll assembly from this Development Release and a reference to the System.Windows.Controls.dll assembly from the Silverlight 3 SDK (it contains the implementation of Legend's base class HeaderedItemsControl). Then write the same code/XAML you would for any of the other platforms - it's all interchangeable!
      Aside: The version of Silverlight used by Windows Phone 7 is conceptually equivalent to Silverlight 3 with a variety of Silverlight 4 enhancements throughout.
    • All the same cross-platform shared code/XAML goodness of previous Development Releases still applies.

    [Click here to download the SilverlightWpfDataVisualization solution including complete source code and pre-compiled binaries for all platforms.]

     

    Windows Phone 7 Data Visualization Sample

    Trying to use the November 2009 version of the Data Visualization assembly on Windows Phone 7 doesn't work because there's a bug in the Phone version of the .NET Framework that causes bogus exceptions. That bug will be fixed in a future drop of Windows Phone 7 developer bits, but for now it was simplest to work around it myself so folks could start playing around with Data Visualization on the phone today. I wrote the sample application below to show that the Silverlight 3 version of Data Visualization now works seamlessly on Windows Phone 7. And while I was at it, I went ahead and customized the default UI a bit (mostly just tweaking colors and backgrounds) so things really fit in with the overall look and feel of the platform:

    Sample in portrait orientation

    As part of this, I made some trivial Template customizations for the Chart element and added just a smidge of code to switch Templates when the device orientation changes. This allows the sample application to make better use of the available screen space (note how the Legend moves and changes shape):

    Sample in landscape orientation

    Notes:

    • Please be sure to open MainPage.xaml in the development environment before hitting F5 to run the sample - otherwise it seems that deployment of the application to the emulator fails with the message "Object reference not set to an instance of an object.".
    • For convenience, I've included the two required assemblies in the ZIP for the sample - all you need in order to build and run is the Windows Phone Developer Tools CTP which can be downloaded for free here.
    • Though this is hardly the most interesting chart ever, I hope some of what I show here inspires others to achieve greater things! :)

     

    [Click here to download the complete Windows Phone 7 Data Visualization sample application.]

  • Delay's Blog

    Silverlight Charting gets a host of improvements [Silverlight Toolkit December 08 release now available!]

    • 17 Comments

    The December 08 release of the Silverlight Toolkit was published a short while ago. Just about every control in the Toolkit got some love and attention for this release and I encourage you to have a look, download it, and enjoy!

    That said, readers of my blog know that I'm all about the Charting. :) So just like my original Charting announcement and overview post, here's an announcement of the December 08 Charting release and an overview of some of the high points. By now, I assume folks have had time to play around with Charting and are starting to get into some more advanced scenarios, so the samples are going to be a little more technical than before. If you want a refresher on basic Charting concepts, please have a look at my original overview - all of those concepts still apply.

    Though the Charting team was down to just two of us for this release, Jafar and I have done our best to deliver some pretty compelling new features. It's important to note that there are a few breaking changes from the November 08 release, but I hope you'll agree that the new functionality is worth the minor inconvenience of updating existing Charting code. As it happens, if you don't explicitly use the Axis class, you probably won't need to change anything at all!

     

    I wrote the following summary for the release notes, and it seems like a good way to begin:

    Notable Changes

    Support has been added for arbitrary numbers of Axes for a Chart. A key and significant consequence of this change is that it is now possible to mix previously incompatible Series in the same chart (example: Column and Bar). The former "automatically share axes when possible" behavior remains present and can be used to render two series with a shared independent axis and different dependent axes in order to display related-but-differently-scaled values (example: an engine's torque and RPM).

    The Column/Bar/Line/Scatter Series classes expose two new (optional) properties which specify a particular Axis instance to be used by the Series (rather than letting the Series choose from the Chart's Axes collection as it does by default). These properties allow very specific customization of each Series' axes in situations where maximum control is desired.

    A design-time assembly for Charting has been created which is automatically used by Blend and Visual Studio to enhance the design-time experience. Most class properties are now categorized into custom "Data Visualization" and "Data Visualization Styles" categories to make them easier to find - while particularly common properties are now found in the "Common" category. ToolTips identify the purpose of each control and property, and Toolbox icons are automatically associated with each class. (Note: Some of these features are only supported by one of the design tools.)

    The design-time behavior of DataPoints has been enhanced: DataPoints are now visible by default when dropped onto the design surface in Blend, so styling them is easier. PieDataPoint now creates a default Geometry (that can be customized interactively with the ActualRatio and ActualOffsetRatio properties) which makes it possible to see and understand relevant styling changes. DataPoints in a Chart show up in Visual Studio without a refresh of the design surface.

    The Chart class is now decorated with ContentPropertyAttribute("Series") which means that it is no longer necessary to explicitly wrap Series with <charting:Chart.Series> ... </charting:Chart.Series> in XAML. Additionally, this enables Blend to display a Chart's Series in the "Objects and Timeline" pane and makes accessing the properties of a Series considerably easier. Example of simplified XAML syntax:

        <charting:Chart>
            <charting:PieSeries ... />
        </charting:Chart>
    

    The behavior of the Axis classes during animations that expand or shrink the range of displayed data has been changed so that the animation of the size change is smooth (vs. jumping between axis intervals). This significantly increases the ease with which dynamic data changes can be observed and understood by the viewer.

    The DataPointSeries class (a subclass of Series) has been unsealed to make the task of writing a custom Series considerably easier. While this change doesn't expose the entire hierarchy on which the "in-box" Series are built, it is a significant benefit to developers because DataPointSeries implements many of the key Series notions: the ItemsSource property, DataPoint creation, dynamic data detection, change animation, show/hide transitions, DataPoint selection, and more.

    A new Series type, BubbleSeries, has been added (along with its associated BubbleDataPoint class). BubbleSeries is similar in nature to ScatterSeries, but conveys additional information by using the size of the data points to display an additional dependent value for each of the data points.

    In scenarios where multiple columns/bars (of ColumnSeries/BarSeries) share the same category, all such data points will now be automatically displayed overlapping so that smaller values will no longer be obscured from view by larger values.

    Breaking Changes

    The Axis.AxisType property has been replaced by a corresponding hierarchy of Axis classes. The concrete classes map directly to the AxisType values that were removed: LinearAxis, DateTimeAxis, CategoryAxis. In addition to improving overall API consistency, a consequence of this refactoring is that the LinearAxis.Minimum/Maximum properties are now strongly typed as double and the corresponding properties on DateTimeAxis are strongly typed as DateTime - both of which improve development-time and design-time usability.

    The Axis.ShouldIncludeZero property has been removed; this property is has no meaning for some axis types and is typically not needed because the same thing can be accomplished by setting Minimum or Maximum accordingly. A consequence of this is that Column/Bar charts will usually not include the 0 value by default (and therefore the "beginnings" of the columns/bars can be truncated). This behavior is consistent with Excel (demonstrated by creating a column chart of the values 10, 11, and 12 in Excel), though truncation is more likely because Charting's heuristics are more aggressive with regard to excluding 0.

    Other Changes

    Various UI improvements and bug fixes.

    Whew - there's a lot of good stuff in there! :)

     

    So now that we have an idea what's new, let's start by looking at the support for multiple axes with a common scenario: charting two quantities that are related, but measured in different units and/or on different scales. For the purposes of this demonstration, let's chart the performance characteristics of an imaginary engine:

    Chart with multiple axes

    [Note: Complete source code for all of the sample charts here can be found in the ChartingIntroduction.zip file attached to this post.]

    The XAML for this Chart is straightforward (and discussed below):

    <charting:Chart Title="Engine Performance">
        <!-- Power curve -->
        <charting:LineSeries
            Title="Power"
            ItemsSource="{StaticResource EngineMeasurementCollection}"
            IndependentValueBinding="{Binding Speed}"
            DependentValueBinding="{Binding Power}"
            MarkerWidth="5"
            MarkerHeight="5">
            <!-- Vertical axis for power curve -->
            <charting:LineSeries.DependentRangeAxis>
                <charting:LinearAxis
                    Orientation="Vertical"
                    Title="Power (hp)"
                    Minimum="0"
                    Maximum="250"
                    Interval="50"
                    ShowGridLines="True"/>
            </charting:LineSeries.DependentRangeAxis>
        </charting:LineSeries>
        <!-- Torque curve -->
        <charting:LineSeries
            Title="Torque"
            ItemsSource="{StaticResource EngineMeasurementCollection}"
            IndependentValueBinding="{Binding Speed}"
            DependentValueBinding="{Binding Torque}"
            MarkerWidth="5"
            MarkerHeight="5">
            <!-- Vertical axis for torque curve -->
            <charting:LineSeries.DependentRangeAxis>
                <charting:LinearAxis
                    Orientation="Vertical"
                    Title="Torque (lb-ft)"
                    Minimum="50"
                    Maximum="300"
                    Interval="50"/>
            </charting:LineSeries.DependentRangeAxis>
        </charting:LineSeries>
        <charting:Chart.Axes>
            <!-- Shared horizontal axis -->
            <charting:LinearAxis
                Orientation="Horizontal"
                Title="Speed (rpm)"
                Interval="1000"
                ShowGridLines="True"/>
        </charting:Chart.Axes>
    </charting:Chart>
    

    We start with the usual Chart object to contain the series - but now we can put the series directly inside the Chart tags instead of inside nested Chart.Series tags (though that syntax still works and is fully supported). There are two LineSeries here (one for torque and one for power) and each starts out as you'd expect by hooking up to the data and doing a bit of customization. But then there's something new: the LineSeries.DependentRangeAxis property is used to identify a specific LinearAxis. Every series type (other than PieSeries which doesn't have axes) now exposes a Dependent???Axis property and an Independent???Axis property (where "???" is "Range" or "Category" depending on which series it is).

    Normally, a series will search the Chart.Axis collection to find an axis that it can use - but when these new properties are set, it always uses the specified axis. So in this example we're providing a specific LinearAxis for each LineSeries to use and we're customizing it slightly to get the chart looking just how we want. Having specified both LineSeries in this manner, a third LinearAxis is added to the Chart.Axis collection where it will be found - and used - by both LineSeries when they acquire an independent value axis.

     

    Moving on, here's a chart using the new BubbleSeries in a financial scenario (for a fictional stock ticker symbol). It's the usual "stock price by date" chart we've all seen before - but this chart is also showing the trading volume each day by varying the size the data points:

    Bubble chart

    The XAML for that Chart is what you'd expect:

    <charting:Chart Title="Stock Performance">
        <!-- Stock price and volume -->
        <charting:BubbleSeries
            Title="ABCD"
            ItemsSource="{StaticResource StockDataCollection}"
            IndependentValueBinding="{Binding Date}"
            DependentValueBinding="{Binding Price}"
            SizeValueBinding="{Binding Volume}"
            DataPointStyle="{StaticResource CustomBubbleDataPointStyle}"/>
        <charting:Chart.Axes>
            <!-- Axis for custom labels -->
            <charting:DateTimeAxis
                Orientation="Horizontal">
                <charting:DateTimeAxis.AxisLabelStyle>
                    <Style TargetType="charting:DateTimeAxisLabel">
                        <Setter Property="StringFormat" Value="{}{0:MMM d}"/>
                    </Style>
                </charting:DateTimeAxis.AxisLabelStyle>
            </charting:DateTimeAxis>
        </charting:Chart.Axes>
    </charting:Chart>
    

    BubbleSeries is used just like a ScatterSeries, but it exposes an additional property SizeValueBinding that works just like Independent/DependentValueBinding to identify the source of the size values. The DataPointStyle property specifies a custom style for BubbleDataPoint (not shown here) where we've added the day's volume to the ToolTip of each bubble. This custom style was created in the usual manner - by starting from the default style for BubbleDataPoint and making the desired changes. (I did so in Visual Studio's XAML editor, but it could just as easily be done in Blend.)

    Tweaking things just a little more, the independent value axis is customized by setting its AxisLabelStyle property and providing a style for the new DateTimeAxisLabel class. DateTime/NumericAxis/AxisLabel are the classes used to display an axis's labels. In this case we've provided a custom StringFormat so that the dates are all displayed in the friendly Month+Day pattern seen above. DateTimeAxisLabel offers specific StringFormat properties for each supported IntervalType - but in this case we've simply used the StringFormat property which conveniently overrides the other, more specific types.

     

    Next up is a fairly typical column chart showing made-up bowling scores for some of the people on the Toolkit team. But there's twist because there are two "Shawn"s on the team:

    Chart with overlapping columns

    By now, the XAML is probably kind of boring in its predictability:

    <charting:Chart Title="Bowling Scores">
        <!-- Scores -->
        <charting:ColumnSeries
            Title="Score"
            ItemsSource="{StaticResource ScoreDataCollection}"
            IndependentValueBinding="{Binding Player}"
            DependentValueBinding="{Binding Score}">
            <charting:ColumnSeries.IndependentCategoryAxis>
                <!-- Axis for automatic sorting -->
                <charting:CategoryAxis
                    Orientation="Horizontal"
                    SortOrder="Ascending"/>
            </charting:ColumnSeries.IndependentCategoryAxis>
        </charting:ColumnSeries>
    </charting:Chart>
    

    This is a standard ColumnSeries, which is specifying a IndependentCategoryAxis so the new SortOrder property of CategoryAxis can be used to automatically sort the category names. This data set contains two items with the independent value "Shawn". Previously, both would have been displayed in the same category slot with the same width which means the column in front could have obscured the one in back and led viewers to believe that only four scores were being displayed. But now ColumnSeries and BarSeries automatically detect this situation and overlap columns/bars in the same category slot (after sorting them by size). This behavior makes it clear that five values are being shown and makes it easy to hover over either of the "Shawn"s to get a ToolTip with the associated score.

     

    The previous chart is interesting for another reason, too: it's using the natural, simple way to display the data, but a purist might argue that it's also the "wrong" way to do so... [Jafar and I actually had this "argument" - I won't say who took which position. :) ] Categorizing by player name works well enough, but what if we also had an image for each player and we wanted to display that image instead of their name? Changing the ColumnSeries.IndependentValueBinding property might work, but it's become obvious that we're mixing display concerns and data concerns.

    When maintaining developer/designer separation is critical, what's probably more appropriate is to categorize by the actual data object - and then display it however we want. Here's a chart that does just that:

    Chart with unique categories

    And here's the XAML - which is a bit more complicated than before, but still quite manageable:

    <charting:Chart>
        <!-- Customized Title -->
        <charting:Chart.Title>
            <StackPanel>
                <TextBlock
                    Text="Bowling Scores"
                    HorizontalAlignment="Center"/>
                <TextBlock
                    Text="(Alternate Approach)"
                    FontSize="10"
                    HorizontalAlignment="Center"/>
            </StackPanel>
        </charting:Chart.Title>
        <!-- Scores -->
        <charting:ColumnSeries
            Title="Score"
            ItemsSource="{StaticResource ScoreDataCollection}"
            IndependentValueBinding="{Binding}"
            DependentValueBinding="{Binding Score}">
            <charting:ColumnSeries.IndependentCategoryAxis>
                <!-- Axis for automatic sorting and custom labels -->
                <charting:CategoryAxis
                    Orientation="Horizontal"
                    SortOrder="Ascending">
                    <charting:CategoryAxis.AxisLabelStyle>
                        <Style TargetType="charting:AxisLabel">
                            <Setter Property="Template">
                                <Setter.Value>
                                    <ControlTemplate TargetType="charting:AxisLabel">
                                        <TextBlock Text="{Binding Player}"/>
                                    </ControlTemplate>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </charting:CategoryAxis.AxisLabelStyle>
                </charting:CategoryAxis>
            </charting:ColumnSeries.IndependentCategoryAxis>
        </charting:ColumnSeries>
    </charting:Chart>
    

    Now that we've set IndependentValueBinding to the data objects themselves, there are automatically five categories (one for each of the five objects in the data set). (Aside: CategoryAxis will still sort them for us because the data objects implement the IComparable interface.) Once the data is set up "properly", all that's left to do is customize how the objects are displayed - and this is done by providing a custom template for the AxisLabel class (something that's already been discussed).

    One other thing to note above is how the Chart.Title property is set to a tree of UI elements instead of the usual string. Chart.Title (and other such properties in Charting) follows the ContentControl model because it makes simple things simple while also allowing more advanced scenarios. In this case, all we've done is provide a "subtitle" - but we could also have gone much further and created something very customized.

     

    The last thing I wanted to highlight was some of the design-time enhancements for Charting in this release. Data points were previously a little tricky to customize because their default state is not visible (which allows the reveal/show animation to fade them in without flicker). Data points now check if they're being used in design-time and will automatically play their reveal/show animation so that they're visible on the design surface by default. PieDataPoint used to be extra tricky because its Geometry property is only set when it's actually being used by PieSeries. But now it knows about design-time and its ActualRatio/ActualOffsetRatio properties can be changed to interactively evaluate different styling approaches. Here's a stand-alone PieDataPoint in Blend showing off both of these improvements (along with the new "Data Visualization" property category):

    PieDataPoint in Blend

     

    I hope you're excited by some of the new functionality we've just looked at from the December 08 release of Charting! And naturally, we've made a some other improvements, too! :) You can browse the live Charting sample page to find out more - then download the December 08 Toolkit release and start playing around with the new stuff! As always, if you have 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.

     

    PS - If you're looking for an update to my ChartBuilder application (Background reading: Introduction, Update) to show off the new Charting functionality, please stay tuned because it will be available very soon! :)

    PPS - I'm on vacation in December, so responses to blog comments and emails may be delayed. But please don't let that stop you from contacting me: I'll follow up on everything I get - I just might be a little slower than usual. :)

Page 3 of 28 (277 items) 12345»