Friday, 9 July 2010

XAML for non-UI data

I hadn't considered using XAML to describe non-UI data or using the XamlReader to load/parse a XML data file. This post is a simple step-by-step guide to defining a XAML model and the associated C# classes to load our StateMachine Maps.

Starting with the bare minimum XAML and data class:
<MapLoader
xmlns="clr-namespace:DrawStateMachineMap;assembly=DrawStateMachineMap"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
</MapLoader>

[Serializable]
public class MapLoader
{
public static MapLoader Load(string location)
{
return (MapLoader)XamlReader.Load(new XmlTextReader(location));
}
}


Adding a collection of States:

<MapLoader
xmlns="clr-namespace:DrawStateMachineMap;assembly=DrawStateMachineMap"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

<MapLoader.States>
<State Name="Start">
</State>
</MapLoader.States>

</MapLoader>

[Serializable]
public class MapLoader
{
public StateList States { get; set; }

public MapLoader()
{
States = new StateList();
}


public static MapLoader Load(string location)
{
return (MapLoader)XamlReader.Load(new XmlTextReader(location));
}
}

[Serializable]
public class StateList : List<State> { }

[Serializable]
public class State
{
public string Name { get; set; }
}



Each State has a collection of Events:


<MapLoader
xmlns="clr-namespace:DrawStateMachineMap;assembly=DrawStateMachineMap"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<MapLoader.States>
<State Name="Start">

<State.Events>
<Event Name="OnGoToFirstPage" />
</State.Events>

</State>
</MapLoader.States>
</MapLoader>

[Serializable]
public class MapLoader
{
public StateList States { get; set; }

public MapLoader()
{
States = new StateList();
}
public static MapLoader Load(string location)
{
return (MapLoader)XamlReader.Load(new XmlTextReader(location));
}
}

[Serializable]
public class StateList : List<State> { }

[Serializable]
public class State
{
public string Name { get; set; }

public State()
{
Events = new EventList();
}

}

[Serializable]
public class EventList : List<Event> { }
[Serializable]
public class Event
{
public string Name { get; set; }
}



By decorating the MapLoader and State classes with the [ContentProperty] attribute we avoid the need to explictly reference the collection classes:

<MapLoader
xmlns="clr-namespace:DrawStateMachineMap;assembly=DrawStateMachineMap"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

<State Name="Start">
<Event Name="OnGoToFirstPage"/>
</State>
<State Name="FirstPage">
<Event Name="OnGoToLastPage"/>
<Event Name="OnGoToMiddlePage"/>
</State>

</MapLoader>

[Serializable]
[ContentProperty("States")]

public class MapLoader
{
public StateList States { get; set; }

public MapLoader()
{
States = new StateList();
}
public static MapLoader Load(string location)
{
return (MapLoader)XamlReader.Load(new XmlTextReader(location));
}
}

[Serializable]
[ContentProperty("Events")]

public class State
{
public string Name { get; set; }

public State()
{
Events = new EventList();
}
}


Final version with all the attributes defined for States and Events:

<MapLoader
xmlns="clr-namespace:DrawStateMachineMap;assembly=DrawStateMachineMap"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<State Name="Start">
<Event
Name="OnGoToFirstPage"
GotoState="FirstPage"
RunAction="DisplayFirstPage"
LayoutX="440"
LayoutY="120" />
</State>
<State
Name="FirstPage"
LayoutX="320"
LayoutY="200">
<Event
Name="OnGoToLastPage"
GotoState="LastPage"
RunAction="DisplayLastPage"
LayoutX="660"
LayoutY="260" />
<Event
Name="OnGoToMiddlePage"
GotoState="MiddlePage"
RunAction="DisplayMiddlePage"
LayoutX="440"
LayoutY="300" />
</State>
</MapLoader>

[Serializable]
[ContentProperty("States")]
public class MapLoader
{
public StateList States { get; set; }

public MapLoader()
{
States = new StateList();
}
public static MapLoader Load(string location)
{
return (MapLoader)XamlReader.Load(new XmlTextReader(location));
}
}

[Serializable]
[ContentProperty("Events")]
public class State
{
public string Name { get; set; }
public int LayoutX { get; set; }
public int LayoutY { get; set; }
public EventList Events { get; set; }

public State()
{
Events = new EventList();
}
}

[Serializable]
public class Event
{
public string Name { get; set; }
public string GotoState { get; set; }
public string RunAction { get; set; }
public int LayoutX { get; set; }
public int LayoutY { get; set; }

}

[Serializable]
public class StateList : List<State> { }

[Serializable]
public class EventList : List<Event> { }

Saturday, 3 July 2010

Comparing equality of generic objects

T value1;
T value2;
bool result = EqualityComparer.Default.Equals(value1, value2)

Martin's DataGrid VirtualizingPanel Epiphany

I have a View which looks a bit like this :

<DataGrid ItemSource={BindingToSomethingWithManyHundredRows} />

Now, you probably know a little about Virtualizing Panels.. Basically.. Rather than try to draw all 800 rows immediately, it’ll create the UI Elements for the rows actually visible on the screen (and a few extra to smooth scrolling), and then only create others as they’re scrolled into the visible area.

By default, DataGrid uses a VirtualizingPanel so all is happy and rendering takes about 0.3 secs. If I explicitly use a non virtualizing layout panel to display the rows, it’ll take about 15 seconds, so we don’t like this much.

Now.. I wanted to put a ComboBox above the DataGrid on the View, so while I was messing about with code I simply dropped a around the pair and carried on. So now I have :

<StackPanel>
<ComboBox/>
<Grid/>
</StackPanel>

Now.. I’ve done this before and I know that doing this will switch off the scrollbar of the Grid because the stackpanel has told the grid that it has as much space as it needs (so the rows disappear off the bottom of the page with no way to scroll to them).. But I was only testing some code I was working on, so I didn’t care..

What did surprise the heck out of me (which in hindsight it shouldn’t) is that it now took 15 seconds to render... So what’s happening? Well we’re still using a Virtualizing Panel, but it thinks all rows are visible because of the stackpanel, so it goes ahead and draws them all, even though they’re not actually visible..

So.. I’ve replaced my StackPanel with a Grid and all is happy again... But, I figured it was a worthwhile point to mention for you all – you don’t want to put StackPanels around ItemsControls which would Virtualize the layout panel for you.. A) you’ll lose scrolling unless you jump through various hoops, but B) you’ll lose all the performance benefits of using a Virtualizing Panel.. Which can be very dramatic..

Good luck & may all your panels be virtualizing

Shell Skins

To allow different shells to have very different styles by default, (e.g. for Kiosks vs Management apps) the selection of the application skin file is made in Client\Sandstorm\Sandstorm\Views\app.xaml.cs. On startup it looks in the shell config file for a value called ShellSkin for its path to the skin xaml file.