Pages

Wednesday, 8 December 2010

EditorBrowsable

The EditorBrowsableAttribute class specifies whether a property or method is viewable via Intellisense.
It is used like this:

using System.ComponentModel;
...
[EditorBrowsableAttribute(EditorBrowsableState.Advanced)]
protected void AdvancedMethod()
{
//The property or method is a feature that only advanced users should see. An editor can either show or hide such properties
}
[EditorBrowsableAttribute(EditorBrowsableState.Always)]
protected void AlwaysMethod()
{
//The property or method is always browsable from within an editor
}
[EditorBrowsableAttribute(EditorBrowsableState.Never)]
protected void NeverMethod()
{
//The property or method is never browsable from within an editor
}
I wanted to use it with a class that provided a fluent interface to hide the common methods like Equals, GetHashCode, GetType, ToString etc. to make the fluent interface syntax cleaner.
It transpires that it is only used by Intellisense when the decorated item is in a referenced assembly - shame.
Here is the fuller explanation from Linda Lui, Microsoft Online Community Support back in June 2006:

Speaking for C#, the EditorBrowsableAttribute attribute only influences C#
IntelliSense filtering behavior if the decorated item is imported from
metadata.
That's to say, you should compile the EditorBrowsableAttribute-decorated
project(e.g project 1) into an assembly(.dll or .exe) and then in another
project(e.g project 2) add a reference to that assembly. In project 2, you
should see the attribute at work.
C# IntelliSense filtering behavior is not influenced if you are coding in
project 1. What's more, if you add a project-to-project reference to
project 1 in project 2, C# IntelliSense filtering behavior is not
influenced when you are coding in project 2 either.
The IDE is intended to filter items from consumers of your assembly and not
to filter items from yourself as you code the assembly.

Tuesday, 28 September 2010

UpdateSourceTrigger in DataGrid

Inside a DataGrid, any control you add into a TemplateColumn which is bound TwoWay, used to use its default value of UpdateSourceTrigger, but the .NET 4 version of DataGrid overrides this by default to be Explicit.

For example:
<DataGrid>
<DataGrid.Columns>
<DataGrid.TemplateColumn>
<DataTemplate>
<TextBox Text={Binding MyProperty} />
</.....>

This used to work just fine.. When the value in TextBox changed, it would update MyProperty when the textbox lost focus (this is the default value for a textbox of UpdateSourceTrigger).
This no longer happens.
We now need to explicitly set the UpdateSourceTrigger for all UI controls inside a DataGrid Template column which should be updating their underlying property store.

Now becomes:
    <TextBox   Text={Binding MyProperty, UpdateSourceTrigger=LostFocus} />

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.

Saturday, 5 June 2010

Linking TextBox Key Presses to a DelegateCommand

I needed to detect the <return> key being pressed in a TextBox. The original technique used a KeyPress event and a small event handler in the code-behind. Since we're using MVVM I wanted to replace the event handler with a DelegateCommand.
This .Net 4 only solution binds the DelegateCommand to the Command property of a KeyBinding.

...
KeyReturnCommand = new DelegateCommand(ReturnKeyPressed);
...
public DelegateCommand KeyReturnCommand { get; set; }
private void ReturnKeyPressed(string text)
{
MessageBox.Show(text);
}

<TextBox x:Name="SearchText" Width="100" Height="25">
<TextBox.InputBindings>
<KeyBinding
Command="{Binding KeyReturnCommand}"
CommandParameter="{Binding ElementName=SearchText, Path=Text}"
Key="Enter"/>
</TextBox.InputBindings>
</TextBox>