Creating a Custom Avalon Expression

At last, I've got time to write about the Expression sample I mentioned before. I'll actually walk you through this, so fear not, gentle reader!

 

Starting from the start

First, let's start out with a new Avalon project, and put this code in the Window. This just declares a couple of TextBox instances in a Canvas and fixes their widths, allowing them to grow vertically.

<Window x:Class="Playground.Window1"
xmlns="https://schemas.microsoft.com/2003/xaml" xmlns:x="Definition"
Text="Playground"
Loaded="WindowLoaded">
<Canvas>
<TextBox ID="MyTextBox" Wrap="True" Width='120' FontSize='14' />
<TextBox ID="MyOtherTextBox" Wrap="True" Canvas.Left='140' Width='120' FontSize='14' />
</Canvas>
</Window>

 

Still with me? Excellent, the fun is just getting started.

 

I'm going to write a type of expression to do the following: as the user types, the text he types is going to get transparent. Not very useful, but certainly fun to play with. For extra kicks, I'll change the color based on whether the TextBox has focus or not, graying it out so it doesn't stand out so much.

 

Declaring a new expression type is super-simple.

///

<summary>
/// MyExpression will be my custom expression, to try some whacky things.
/// </summary>
class MyExpression : Expression
{
... code goes here...
}

 

The first day, he created the instance

OK, so now it's time to define a constructor for our expression. I want this to be based on the text length and focus state of a given textbox, so this is a good place to tell my expression what TextBox instance it should base its values on.

///

<summary>
/// MyExpression will be my custom expression, to try some whacky things.
/// </summary>
class MyExpression : Expression
{

  private TextBox target;

  /// <summary>Initialize a new MyExpression instance.</summary>
  /// <param name="target">TextBox that we'll be evaluating.</param>
  public MyExpression(TextBox target): base()
{
    if (target == null)
{
      throw new ArgumentNullException("target");
}
    this.target = target;
}
...
}

 

The next step is to let the property engine at what times does our expression need to be evaluated. Obviously a property can be invalidated "manually", but the engine also allows us to declaratively state what things we depend on. The following method in MyExpression does the trick: we're saying that we base our value on the values of the Text property and the IsFocusWithin property of our target control.

 

public override DependencySource[] GetSources()
{
  // This is where we declare what things do we depend on. If any
  // of these get invalidated, so will we.
  return new DependencySource[] {
    new DependencySource(this.target, TextBox.TextProperty),
    new DependencySource(this.target, TextBox.IsFocusWithinProperty),
};
}

 

Romantic moment: look at the trailing comma in my array declaration, not producing a compilation error; no more problems when reordering things or generating these lists in code. Don't you just love C#?

 

Evaluating the expression

Everything so far has been pretty simple, but what about the actual evaluation? A walk in the park, really. Read this code and see for yourself how easy this is.

 

...

/// <summary>
/// Gets the value for a given property on the specified dependency object.
/// </summary>
/// <param name="d">DependencyObject to get value for.</param>
/// <param name="dp">Property to get value for.</param>
/// <returns>The value of the property on the given object.</returns>
public override object GetValue(DependencyObject d, DependencyProperty dp)
{
// Do some checks, but the property engine should treat us well anyway.
  if (dp == null)
{
    throw new ArgumentNullException("dp");
}
  if (!typeof(Brush).IsAssignableFrom(dp.PropertyType))
{
    throw new ArgumentException("MyExpression works only on properties of Brush type.", "dp");
}

  // Now, let's party :-)
  // We'll make the text more transparent as the user types text,
  // and make it gray rather than blue if the control is not focused.
  byte a, r, g, b;
a = (target.Text.Length > 255) ? (byte)0 : (byte) (255 - target.Text.Length);
  if (target.IsFocusWithin)
{
r = g = 0;
b = 200;
}
  else
  {
r = g = b = 30;
}
  return new SolidColorBrush(Color.FromArgb(a, r, g, b));
}
...

 

And, yes, that's everything that's required. So, how do we use our nifty class? If you've made it this far, pat yourself on the back - you're two statements away from seeing the magic.

 

Assigning Expressions

Here's how we can assign our new expression to a value. Just use the SetValue method with your expression instead of a real value, and you're done - the property system figures out what you want to do and starts using your expression.

 

private

void WindowLoaded(object sender, EventArgs e)
{
  // We can set our own expression using the .SetValue method,
  // passing an Expression instance rather than a regular object.
  this.MyTextBox.SetValue(TextBox.ForegroundProperty, new MyExpression(this.MyTextBox));
  this.MyOtherTextBox.SetValue(TextBox.BackgroundProperty, new MyExpression(this.MyOtherTextBox));
}

 

Note how I was also able to use the expression to set the background property of the second textbox. If you run this and start typing text and switching from one control to the other, you'll see all the magic happen. It's actually quite fun to come up with these interesting tricks.

 

Here's a screen shot of what the window looks like after playing with it for a while. 

 

So, what do you think you would use Expressions for? Comment on, kind reader!