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>

Wednesday 2 June 2010

Current Item Pointers

Views also support the notion of a current item. You can navigate through the objects in a collection view. As you navigate, you are moving an item pointer that allows you to retrieve the object that exists at that particular location in the collection.
 

Because WPF binds to a collection only by using a view (either a view you specify, or the collection's default view), all bindings to collections have a current item pointer. When binding to a view, the slash ("/") character in a Path value designates the current item of the view.

 

In the following example, the data context is a collection view. The first line binds to the collection. The second line binds to the current item in the collection. The third line binds to the Description property of the current item in the collection.

 
<Button Content="{Binding }" />
<Button Content="{Binding Path=/}" />
<Button Content="{Binding Path=/Description}" />

Debugging Bindings

I just want to keep track of this page to remind me how to get more information about a binding when debugging binding issues.

PresentationTraceSources.TraceLevel

Monday 10 May 2010

Drawing WPF Curves with Arrow heads

Having drawn the shapes, the curves and found their intersection point, I finally needed to draw an arrow head. In order to draw the arrow at the right angle we need to know the tangent of the Bezier curve at the intersection point. Since that is beyond my maths capability I chose to find the intersection point between the line and an imaginary, slightly larger, rectangle that surrounds the target shape. The difference between the two intersection points provides the required angle for the arrow head.
private static void DrawArrowHead(Canvas canvas, PathGeometry linePath, Rect shapeRect, Color color)
{
// Get the intersection point of the imaginary, slightly
// larger rectangle that surrounds the targer shape.
Rect outerRect = new Rect(shapeRect.Left - 10, shapeRect.Top - 10, shapeRect.Width + 20, shapeRect.Height + 20);

RectangleGeometry shapeGeometry = new RectangleGeometry(shapeRect);
Point[] intersectPoints = GetIntersectionPoints(linePath, shapeGeometry);


double innerLeft = intersectPoints[0].X;
double innerTop = intersectPoints[0].Y;


shapeGeometry = new RectangleGeometry(outerRect);
intersectPoints = GetIntersectionPoints(linePath, shapeGeometry);


double outerLeft = intersectPoints[0].X;
double outerTop = intersectPoints[0].Y;


Polygon arrowHead = new Polygon();
arrowHead.Points = new PointCollection();
arrowHead.Points.Add(new Point(innerLeft, innerTop));
arrowHead.Points.Add(new Point(innerLeft + 10, innerTop + 5));
arrowHead.Points.Add(new Point(innerLeft + 10, innerTop - 5));
arrowHead.Points.Add(new Point(innerLeft, innerTop));
arrowHead.Stroke = new SolidColorBrush(color);
arrowHead.Fill = new SolidColorBrush(color);


// The differences between the intersection points on
// the inner and outer shapes gives us the base and
// perpendicular of the right-angled triangle
double baseSize = innerLeft - outerLeft;
double perpSize = innerTop - outerTop;
// Calculate the angle in degrees using ATan
double angle = Math.Atan(perpSize / baseSize) * 180 / Math.PI;


// Rotate another 180 degrees for lines in the 3rd & 4th quadrants
if (baseSize >= 0) angle += 180;


// Apply the rotation to the arrow head
RotateTransform rt = new RotateTransform(angle, innerLeft, innerTop);
arrowHead.RenderTransform = rt;


// Arrow heads are drawn over the lines but
// under the shapes
Canvas.SetZIndex(arrowHead, (int)Layer.Arrow);


canvas.Children.Add(arrowHead);
}

Drawing WPF Bezier Curves programmatically

I needed to draw a curved line between the mid-points of two shapes, centered on X1,Y1 and X2, Y2.
This method also returns a PathGeometry which is then used to determine the Intersection between the curve and the shapes.
private static PathGeometry DrawLine(Canvas canvas, double X1, double Y1, double X2, double Y2, Color color)
{
QuadraticBezierSegment qbs = new QuadraticBezierSegment(new Point(X2, Y1), new Point(X2, Y2), true);

PathSegmentCollection pscollection = new PathSegmentCollection();
pscollection.Add(qbs);

PathFigure pf = new PathFigure();
pf.Segments = pscollection;
pf.StartPoint = new Point(X1, Y1);

PathFigureCollection pfcollection = new PathFigureCollection();
pfcollection.Add(pf);

PathGeometry pathGeometry = new PathGeometry();
pathGeometry.Figures = pfcollection;

Path path = new Path();
path.Data = pathGeometry;
path.Stroke = new SolidColorBrush(color);
path.StrokeThickness = 2;
Canvas.SetZIndex(path, (int)Layer.Line);
canvas.Children.Add(path);

return pathGeometry;
}

Intersection of two WPF geometries

I needed to determine the intersection point of a Rectangle and a Bezier curve. I found the following code here.

I've included the code below in case the link is lost in the future.
public static Point[] GetIntersectionPoints(Geometry g1, Geometry g2)
{
Geometry og1 = g1.GetWidenedPathGeometry(new Pen(Brushes.Black, 1.0));
Geometry og2 = g2.GetWidenedPathGeometry(new Pen(Brushes.Black, 1.0));

CombinedGeometry cg = new CombinedGeometry(GeometryCombineMode.Intersect, og1, og2);
PathGeometry pg = cg.GetFlattenedPathGeometry();
Point[] result = new Point[pg.Figures.Count];

for (int i = 0; i < pg.Figures.Count; i++)
{
Rect fig = new PathGeometry(new PathFigure[] { pg.Figures[i] }).Bounds;
result[i] = new Point(fig.Left + fig.Width / 2.0, fig.Top + fig.Height / 2.0);
}
return result;
}

Canvas

I think this is about the minimum you need to draw a shape on a Canvas programmatically.
<Window x:Class="DrawStateMachineMap.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window2" Height="300" Width="300">
<Grid>
<Canvas Name="MainCanvas"></Canvas>
</Grid>
</Window>

      public Window2()
{
InitializeComponent();

Rectangle aRectangle = new Rectangle();
aRectangle.Width = 100;
aRectangle.Height = 50;
aRectangle.Stroke = Brushes.Red;

Canvas.SetLeft(aRectangle, 10);
Canvas.SetTop(aRectangle, 15);

MainCanvas.Children.Add(aRectangle);
}

Saturday 8 May 2010

Loading XML documents stored in the Output directory

I wanted to load an XML document that is distributed with my project.

// Load the XML State Transition Map
XmlDocument xmlStateMap = new XmlDocument();
xmlStateMap.Load(stateMapName);

I set the Copy to Output Directory property of the XML file to "Copy always". However the file never appeared in bin\debug. I also had to change the Build Action property from the default value of "Resource" to "Content" .