I can feel Avalon changing me…
I’m writing a tiny little application for my writing on ClickOnce in Avalon. In fact, to demonstrate ClickOnce, the app hardly matters, so I picked something really simple: an excuse generator. I didn’t even have to make up the excuses, as I stole them from an office gag gift two years ago and wrapped them in an excuse web service (which is a whole other story : ).
Binding to Data Defined in Code
The idea was to have an array of excuse strings like so:
public partial class Window1 : Window { static string[] excuses = { "Jury Duty", ... "It's Not My Job", }; public Window1() { InitializeComponent(); this.DataContext = Window1.excuses; } ... }
Once I had the collection of strings, I make them available for binding by setting them to the DataContext of the main window and the rest of the code would simply be a matter of binding a TextBlock to the current excuse like so:
<Window ...> ... <TextBlock TextContent="{Bind Path=/}" /> <Button x:ID="newExcuseButton">New Excuse</Button> ... </Window>
By binding the TextContent property of the TextBlock to a Path of “/”, I’m explicitly saying “don’t try to dig into each object in the collection looking for sub-properties, but binding to the item itself.” Initially, the TextBlock will show the first item in the list:
When the button is pressed, I change the output by selecting a random item from the collection and setting it as the “current” item in the collection:
Random rnd = new Random(); void newExcuseButton_Click(object sender, RoutedEventArgs e) { ListCollectionView view = (ListCollectionView)Binding.GetDefaultView((IEnumerable)Window1.excuses); view.MoveCurrentToPosition(rnd.Next(view.Count - 1)); }
By grabbing the view for the excuses collection, I can do a number of things with it, including move the current item in the view to some specific position. When I do that, the data bound TextContent property of the TextBlock will be updated to show whatever is current:
So here’s the first thing that I’m notice that I really approach differently in Avalon: the data binding hammer is almost always the first tool I reach for in the programming box. In the old days, to write this app, I’d set the TextContent property directly in code. Now, I don’t even think to do that. Instead, I’ve got data and a property to set, so that’s data binding.
Binding to Objects Defined in XAML
“But wait!” I hear you howl. “What about internationalization?!?” I know. Hard-coded data in the source code is a bad idea. So where does it go? Into the XAML itself, of course, so that anyone working on the UI, whether for an English-speaking country or not, can add new items, port them to another language, etc. How to do it? Well, since XAML is an XML dialect for describing object hierarchies, I could define a new type (roughly):
class Excuse { string value; public string Value { get { ... } set { ... } } } class ExcuseData : List<Excuse> { }
Then I could write my excuse data as a hydration of objects using the ObjectDataSource (roughly):
... <Window.Resources> <ObjectDataSource x:Key="ExcuseData"> <l:ExcuseData> <l:Excuse Value="Jury Duty" /> ... <l:Excuse Value="It's Not My Job" /> </l:ExcuseData> </ObjectDataSource> </Window.Resources> ...
The XAML inside the ObjectDataSource will create an instance of the ExcuseData type, adding an object of type Excuse for each
Binding to Data Defined in XAML
Still, why go to the trouble of defining my own custom data type, which doesn’t have any behavior to speak of, when I’ve got the universal behavior-less data type — XML? I can use the XmlDataSource without any custom type at all:
…
You’ll notice that the XML looks almost exactly like the object data source, except that instead of using an attribute to keep the juicy bits, I use the content area itself to store the data, very like the initial array example (I could have used XML attributes, but it seemed overkill in this case). One other interesting bit is that XPath of the XML data source itself. It defines the bit of the data island from which I’m pulling the data. If I had used /Excuses, the data collection would have had a single item with a bunch of children. But using /Excuses/Excuse, I’m exposing the children directly.
To use the XML data source, I need to set it as somebody’s data context in the hierarchy of controls and bind to it:
<Window ...> <Window.Resources> <XmlDataSource x:Key="ExcuseData" XPath="/Excuses/Excuse">...</XmlDataSource> </Window.Resources> <Grid ... DataContext="{Bind DataSource={StaticResource ExcuseData}}"> ... <TextBlock ... TextContent="{Bind XPath=.}" /> <Button ... x:ID="newExcuseButton">New Excuse</Button> </Grid> </Window>
I should note that in future bits, I’ll be able to set the grid’s data context to {StaticResource ExcuseData} directly, but in the March 2005 CTP Avalon bits, I have to actually bind the data source to the data context in a double bind that blows my mind.
More importantly, notice that instead of using a Path to get to the data I want, I use an XPath. This allows me to further refine the XPath statement provided as part of the XML data source itself. However, since I don’t want to refine it any further, I use the XPath statement that says “just use the content of each of the elements.” You’ll notice that I used ”.” in the XPath statement to provide this meaning instead of “/” as I did in the case where I was using an object Path. The two syntaxi look the same, but are very different and you’ll want to watch yourself switching between the two.
Anyway, with the data moved to the XAML along with the UI and the binding logic, my actual code boils down to only the following:
public partial class Window1 : Window { Random rnd = new Random(); public Window1() { InitializeComponent(); this.newExcuseButton.Click += newExcuseButton_Click; } void newExcuseButton_Click(object sender, RoutedEventArgs e) { IDataSource dataSource = (IDataSource)this.FindResource("ExcuseData"); ListCollectionView view = (ListCollectionView)Binding.GetDefaultView((IEnumerable)dataSource.Data); view.MoveCurrentToPosition(rnd.Next(view.Count - 1)); } }
So, this is really only a few lines of code, one to hook up the button event handler, one to find the excuse data from the window resources, one to get the view on the excuse data and one to move the currency pointer to some other random spot. The actual display of the data is left to the data binding code (which somebody else gets to maintain).
One other thing worth noting is that I had defining event handlers in the XAML. Even when it’s me writing both the XAML and the code, I don’t like the XAML making demands on the code. If the XAML handles an event, e.g. with an event trigger, than that’s fine, but don’t make requirements of the code form the XAML. Instead, provide hooks for the code to do its work, e.g. the x:ID on the ExcuseData and the TextBlock are both used in the code, but neither requires anything of the code. If you’re going to separate the presentation from the logic, this is the kind of thing you’ll want to think about.
Where Are We?
Two things I’m finding that Avalon has changed about my thinking. The first is that data binding makes itself into even trivial Avalon applications and its presence is appreciated. The second is that I want to push as much stuff into XAML as I can. Keeping the data separate from the code makes a bunch of sense and, for my trivial application, keeping the data inline with the rest of the UI was very useful. It allows for easy maintenance and localization while pushing as much of my application into declarations and out of imperative statements. The more I can tell the computer what I want instead of how I want to accomplish it, the better off I am.