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. :)

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 3disables 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:

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):

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. :)

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

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!

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!

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.

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

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:

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!