Delay's Blog

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

June, 2010

Posts
  • Delay's Blog

    Breaking up (lines) is (not) hard to do [Tip: Put XAML attributes and elements on multiple lines so the markup is easy to read and work with]

    • 12 Comments

    Tip

    Put XAML attributes and elements on multiple lines so the markup is easy to read and work with

    Explanation

    The last few tips have dealt with DependencyProperty issues. Now it's time for something completely different: XAML. While it's nice to pretend XAML editing can all be done in a design tool like Visual Studio or Expression Blend, I've always felt that XAML should be pleasant for people to work with, too. Therefore, it's worthwhile to establish a common approach to XAML formatting. What I find works well is to put attributes and elements on separate lines with a single indent for continuations and nesting. (Aside: I make an exception for elements with a single attribute to keep simple things compact.) I also tend to order properties according to importance, starting with the most significant ones and ending with the least. I consider the x:Name of an element to be the most important, so it typically comes first. Attached properties also rank high in my book and are usually next. After that, I tend to list common properties (ex: ContentControl.Content and TextBlock.Text) first and formatting properties (FrameworkElement.HorizontalAlignment and .Margin) closer to the end. In addition to keeping lines short and easy to read, this approach is very revision control-friendly - by which I mean that edits, adds, and removes all show up unambiguously. Sometimes people forget that markup is code, too - please treat it with the same respect and discipline. :)

    Good Example

    <Button
        x:Name="TheButton"
        Grid.Row="0"
        Content="Click me"
        Click="TheButton_Click"
        HorizontalAlignment="Center"
        Margin="10">
        <Button.Background>
            <SolidColorBrush Color="Pink"/>
        </Button.Background>
    </Button>

    More information

  • Delay's Blog

    Revisiting the Code Not Taken [Updated analysis of two ways to create a full-size Popup in Silverlight]

    Earlier this year I wrote about two approaches for creating a Popup to overlay the entire Silverlight plug-in. I began by showing the technique most people start with and then demonstrated a rather surprising side-effect of that approach. Here's the relevant screen shot:

    A quirk of handling the Resized event on Silverlight 3

    Unless you've read the original post, the problem may not be obvious - here's what I said at the time:

    But something is still wrong in the image above... If you look carefully, you can see that the browser zoom is set at 50% - yet somehow the image is sized correctly despite us not doing any work to handle the browser's zoom setting yet. How can that be? Hold on, Sherlock, there's another clue in the image: look at the size of the buttons. Yeah, those buttons are not the size they should be for the 50% zoom setting that's active (refer back to the previous image if you don't believe me). Those buttons are at the 100% size - wha??

    Aside: Hey, don't feel bad, it weirded me out, too. :)

    It turns out that when you attach an event handler to the Resized event, Silverlight 3 disables its support for browser zoom. The reason being that Silverlight 3 assumes the application has chosen to handle that event because it wants full control over the zoom experience (via ZoomFactor and Zoomed, perhaps). Now that's really kind of thoughtful of it and everything - but in this case it's not what we want. In fact, that behavior introduces a somewhat jarring experience because the graphics visibly snap between 50% and 100% as the Resized event handler is attached and detached.

     

    Well, that was then and this is now: I'm pleased to report that Silverlight 4 does not have the troublesome "hooking Resized disables browser zoom" behavior! Consequently, projects targeting Silverlight 4 are free to hook the Resized event without worrying that their browser zoom behavior will be compromised. Yay!

    Aside: Of course, projects targeting Silverlight 3 (or previously compiled for it) and running on Silverlight 4 will continue to see the same Silverlight 3 behavior. This is because Silverlight 4 maintains backward compatibility with previous versions to avoid "breaking the web" when a new version comes out. Specifically, applications written for version N of Silverlight are expected to run the same on version N+M - even when there have been changes to the relevant functionality after version N was released.

     

    Now that it's possible, I've updated the sample to demonstrate the desired behavior on Silverlight 4 by making the following tweak to the Application.Current.Host.Content portions:

    EventHandler rootResized = delegate
    {
        child.Width = root.ActualWidth / root.ZoomFactor;
        child.Height = root.ActualHeight / root.ZoomFactor;
    };

    Everything else remains the same, and now the Application.Current.Host.Content approach works almost as well as the Application.Current.RootVisual approach I recommended previously (though the image still flickers when changing the browser zoom):

    Correctly sizing the Popup to cover the plug-in

     

    With both approaches nearly equivalent on Silverlight 4, you might wonder if I'd like to revise my earlier recommendation to prefer the RootVisual-based approach... Well, I would not! I still feel the RootVisual approach is simpler and easier to understand, so I'll continue to use it myself and recommend it to others. However, there's no longer a compelling reason not to use the Content-based approach, so I'm cool if that's your personal preference. :)

     

    [Click here to download the complete source code for the original sample (a Visual Studio 2010 (Beta 2) project targeting Silverlight 3)]

    [Click here to download the complete source code for the new sample (a Visual Studio 2010 project targeting Silverlight 4)]

  • Delay's Blog

    SplitButtoning hairs [Two fixes for my Silverlight SplitButton/MenuButton implementation - and true WPF support]

    • 31 Comments

    One of my ContextMenu test cases for the April '10 release of the Silverlight Toolkit (click here for the full write-up) was to implement a quick "split button" control for Silverlight using Button and ContextMenu. In that post, I cautioned that my goal at the time was to do some scenario testing, not to build the best SplitButton control ever. However, what I came up with seemed to work pretty well in practice and I figured folks could probably use the code mostly as-is.

    And it seems like they did - because I got two bug reports in that post's comments section! :)

    SplitButton and MenuButton

     

    Let me address them in reverse order:

    The position of the ContextMenu was wrong when the browser is zoomed: True enough - though I'm going to ask for a bit of leniency here because the cause of the misalignment is actually a bug in Silverlight (which I've already reported). It seems the results of a call to element.TransformToVisual(Application.Current.RootVisual) or element.TransformToVisual(null) are not consistent for elements that are vs. are not inside a Popup control when the browser is zoomed (in or out). As a result, the SplitButton code to position the menu got inconsistent data and was unable to place the menu correctly. I've tweaked the code slightly to accommodate the underlying issue and now the menu is properly aligned at any zoom setting.

    While I was at it, I figured it might be nice if the menu moved around with the SplitButton as the user resized the browser or changed the zoom while the menu was displayed. This is admittedly an edge case, but it's easy enough to handle by hooking the LayoutUpdated event while the menu is displayed (and only while it's displayed!), so I did that and now the menu sticks to the button and refuses to be shaken off. :)

    The code didn't work on WPF: I had a footnote claiming my Silverlight implementation should work on WPF as well, though I hadn't tried it myself. It turns out that statement is mostly true - except for the positioning logic which bumps into an subtle API incompatibility with the TransformToVisual method. (Noticing a pattern here?) In the process of getting things working for WPF, I realized the code to position the menu could be simplified somewhat with the (WPF-only) TranslatePoint method. Therefore, the actual positioning logic is a tad different across the two platforms ("a tad" == 4 lines of code) while everything else stays the same. This time when I claim SplitButton and MenuButton work on WPF, it's because I've tried it. :)

    In fact, I've added a new, WPF-specific assembly (SplitButtonWpf) and demo (SplitButtonWpfSample) to the sample code associated with this post. Which means there is a dedicated assembly containing SplitButton and MenuButton for both platforms as well as a separate sample application for each!

     

    [Click here to download the complete Silverlight/WPF source code for SplitButton/MenuButton and the sample application shown above.]

     

    With those changes in place (and an unrelated key handling tweak for WPF), I feel even better about the prospects of using SplitButton and MenuButton in a real application. Naturally, if something else comes up, please let me know. Otherwise, I hope you find it useful!

  • Delay's Blog

    The source code IS the executable, RTM edition [Updated CSI, a C# interpreter (with source and tests) for .NET 4 RTM]

    • 1 Comments

    CSI is a simple C# interpreter and has been available for .NET 1.1, 2.0, 3.0, and 3.5 for a while now. Earlier this year, I updated CSI for .NET 4 Beta 2, and now I've (somewhat belatedly) updated it for the final, public .NET 4 Framework. Today's post is mainly about getting an official .NET 4 RTM-compiled build of CSI released, so there aren't any functional changes to the tool itself.

    FYI: I have a TODO list and there are some interesting things on it - it's just that none of them seemed particularly urgent.

    The links above explain what CSI is and how it works; the executive summary is that CSI offers an alternative to typical CMD-based batch files by enabling the use of the full .NET framework and stand-alone C# source code files for automating simple, repetitive tasks. It accomplishes that by compiling source code "on the fly" and executing the resulting assembly behind the scenes. The benefit is that it's easy to represent tasks with a simple, self-documenting code file that leaves no need to worry about compiling a binary, trying to keep it in sync with changes to the code, or tracking project files and remembering how to build everything.

     

    [Click here to download CSI for .NET 4.0, 3.5, 3.0, 2.0, and 1.1 - along with the complete source code and test suite.]

     

    Notes:

    • The copy of CSI.exe in the root of the download ZIP is now the .NET 4 version because that's the latest public .NET Framework. Previous versions of CSI can be found in the Previous Versions folder: CSI11.exe, CSI20.exe, CSI30.exe, and CSI35.exe.
    • As with the previous release, I have not re-compiled the .NET 1.1 version, CSI11.exe - largely because I don't have .NET 1.1 installed anywhere. :)

     

    Here's the "read me" file for a slightly better idea of how CSI works:

    ====================================================
    ==  CSI: C# Interpreter                           ==
    ==  David Anson (http://blogs.msdn.com/b/delay/)  ==
    ====================================================
    
    
    Summary
    =======
    CSI: C# Interpreter
         Version 2010-06-07 for .NET 4.0
         http://blogs.msdn.com/b/delay/
    
    Enables the use of C# as a scripting language by executing source code files
    directly. The source code IS the executable, so it is easy to make changes and
    there is no need to maintain a separate EXE file.
    
    CSI (CodeFile)+ (-d DEFINE)* (-r Reference)* (-R)? (-q)? (-c)? (-a Arguments)?
       (CodeFile)+      One or more C# source code files to execute (*.cs)
       (-d DEFINE)*     Zero or more symbols to #define
       (-r Reference)*  Zero or more assembly files to reference (*.dll)
       (-R)?            Optional 'references' switch to include common references
       (-q)?            Optional 'quiet' switch to suppress unnecessary output
       (-c)?            Optional 'colorless' switch to suppress output coloring
       (-a Arguments)?  Zero or more optional arguments for the executing program
    
    The list of common references included by the -R switch is:
       System.dll
       System.Data.dll
       System.Drawing.dll
       System.Windows.Forms.dll
       System.Xml.dll
       PresentationCore.dll
       PresentationFramework.dll
       WindowsBase.dll
       System.Core.dll
       System.Xml.Linq.dll
       Microsoft.CSharp.dll
       System.Xaml.dll
    
    CSI's return code is 2147483647 if it failed to execute the program or 0 (or
    whatever value the executed program returned) if it executed successfully.
    
    Examples:
       CSI Example.cs
       CSI Example.cs -r System.Xml.dll -a ArgA ArgB -Switch
       CSI ExampleA.cs ExampleB.cs -d DEBUG -d TESTING -R
    
    
    Notes
    =====
    CSI was inspired by net2bat, an internal .NET 1.1 tool whose author had left
    Microsoft. CSI initially added support for .NET 2.0 and has now been extended
    to support .NET 3.0, 3.5, and 4.0. Separate executables are provided to
    accommodate environments where the latest version of .NET is not available.
    
    
    Version History
    ===============
    
    Version 2010-06-07
    Update .NET 4 (RTM) version
    Make .NET 4 version primary
    
    Version 2010-01-04
    Add .NET 4 (Beta 2) version
    Minor updates
    
    Version 2009-01-06
    Initial public release
    
    Version 2005-12-15
    Initial internal release
  • Delay's Blog

    Please rate your dining experience [How to: Show text labels on a numeric axis with Silverlight/WPF Toolkit Charting]

    • 7 Comments

    A customer contacted me a few days ago asking how to display text labels on a NumericAxis. (Whereas CategoryAxis makes it easy to use text labels for the independent axis, this request was about text labels on the dependent axis.) It's a bit of an unusual request (I spent a minute just now and don't see how to accomplish this in Excel), but I knew it would be easy to do with the Charting controls in the Data Visualization assembly that's part of the Silverlight Toolkit and WPF Toolkit.

    The underlying scenario is to provide labels for the results of one of those "How are we doing?" surveys restaurants and hotels like to give out. The chart should look like this:

    Text labels on the dependent axis

    Though I originally suggested a different approach, the act of coding it up myself suggested a trusty old IValueConverter would be most appropriate. (Aside: See more IValueConverter tricks here and here.) The basic approach is to explicitly specify the dependent axis and then use it to configure exactly the set of tick marks we want. Once that's done, a simple bit of IValueConverter magic converts the numeric values into their corresponding text labels - and the problem is solved! :)

     

    I've added the sample shown here to my DataVisualizationDemos application which is collection of all the Data Visualization samples I've blogged. Like the core Data Visualization code itself, the demo app compiles for and runs on multiple platforms with the same code and XAML - it's an easy way to publish a sample and show it running on Silverlight 3, Silverlight 4, WPF 3.5, and WPF 4. Just for kicks, I've used the "Compatible" ColumnSeries for this example - but it works just as well with traditional ColumnSeries. Better yet, the basic idea can be generalized to solve a variety of similar problems as well!

     

    [Click here to download the complete source code for the cross-platform DataVisualizationDemos sample application. ]

     

    Here's the relevant XAML:

    <!-- Chart of customer feedback -->
    <charting:Chart Title="Customer Feedback">
        <compatible:ColumnSeries
            ItemsSource="{Binding}"
            IndependentValuePath="Topic"
            DependentValuePath="Rating">
    
            <!-- Custom Y axis for text labels -->
            <compatible:ColumnSeries.DependentRangeAxis>
                <charting:LinearAxis
                    Orientation="Y"
                    Minimum="0"
                    Maximum="4"
                    Interval="1"
                    ShowGridLines="True">
    
                    <!-- Custom style/template for text labels -->
                    <charting:LinearAxis.AxisLabelStyle>
                        <Style TargetType="charting:AxisLabel">
                            <Setter Property="Template">
                                <Setter.Value>
                                    <ControlTemplate TargetType="charting:AxisLabel">
                                        <TextBlock Text="{Binding Converter={StaticResource RatingToStringConverter}}"/>
                                    </ControlTemplate>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </charting:LinearAxis.AxisLabelStyle>
                </charting:LinearAxis>
            </compatible:ColumnSeries.DependentRangeAxis>
    
            <!-- Custom style for different background -->
            <compatible:ColumnSeries.DataPointStyle>
                <Style TargetType="charting:DataPoint">
                    <Setter Property="Background" Value="#ff00a0e0"/>
                </Style>
            </compatible:ColumnSeries.DataPointStyle>
        </compatible:ColumnSeries>
    </charting:Chart>

    And code:

    /// <summary>
    /// Implements IValueConverter to convert from double rating values to friendly string names.
    /// </summary>
    public class RatingToStringConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            // Validate parameters
            if (!(value is double))
            {
                throw new NotSupportedException("Unsupported value type in RatingToStringConverter.");
            }
            // Convert number to string
            double doubleValue = Math.Floor((double)value);
            if (0.0 == doubleValue)
            {
                return "Awful";
            }
            else if (1.0 == doubleValue)
            {
                return "Poor";
            }
            else if (2.0 == doubleValue)
            {
                return "Fair";
            }
            else if (3.0 == doubleValue)
            {
                return "Good";
            }
            else if (4.0 == doubleValue)
            {
                return "Great";
            }
            else
            {
                throw new ArgumentException("Unsupported value in RatingToStringConverter.");
            }
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
Page 1 of 1 (5 items)