Chris Cavanagh’s Blog

Entries from March 2009

SilverGlulxe – Interactive Fiction in Silverlight

March 23, 2009 · 2 Comments

Update: I got the box open!  I’m not going to tell you how though…

My son recently started working on an interactive fiction project.  Obviously I jumped at the chance to see what we could achieve with Silverlight :)

Interactive Fiction has come a long way since the text adventures I played in the 80’s (H2G2 and Leather Goddessses of Phobos :) ).  The original Infocom games used their own virtual machine (developed in 1979! – see Z-machine) so could be ported to many platforms with minimal effort.  Over the years the bytecode format was enhanced, eventually supporting graphics.  Other virtual machines were developed too, including Glulx (by Andrew Plotkin) which aimed to overcome limitations of the Z-machine formats.

In 2007 Graham Nelson released Inform 7, a design tool that allows you to write interactive stories with natural language, then compile them as Z-Code (Z-machine) and Glulx programs.  You can find Z-machine and Glulx interpreters for most platforms, but typically they’re written in C and bring their own baggage to the party (such as the Glk API).

In 2008 TextFyre released a shared source version of their FyreVM engine.  It’s written in C# and exposes a simple “channels” mechanism in place of the Glk API, which allows you to do anything you like with the input and output.  Here’s what we ended up with (currently running a story called Lowell’s Paradise by Jesse McGrew, who also wrote FyreVM – click image or here to run):

image

When you’re done with that, here’s another fun story called “Chocolate Freak” by Zapman: :)

image

One last thing…  Source code is available!  You can get it right here on CodePlex :)

Categories: .NET

Excel Formulas and the DLR – Part 2

March 23, 2009 · 18 Comments

Update: Get source code for this project on CodePlex!  Note the DLR is still being developed and hence a “moving target”.  Some (or many) of the items discussed here may change :)

Part 1 of this post gave a quick overview of writing a custom “parser generator” for Excel formulas.  Here we’ll take a look at a way to bind that to the DLR.  You can try out a sample Silverlight 2.0 application here or by clicking the image below:

image

The goal here is to create a calculation engine that understands Excel-like formulas, allowing for cell dependencies, simple ranges and custom functions.  In the screenshot above, a few identifiers (D5 – D7) have been assigned static values, then included in an expression later on.

Global math functions

Excel provides many global math functions.  I implemented a small subset of these as static methods like so:

/// <summary>
/// Excel global function implementations
/// </summary>
/// <remarks>Include an optional CodeContext as the first parameter if required.</remarks>
public class ExcelHelpers
{
    public static double Exp( double d ) { return Math.Exp( d ); }
    public static decimal Abs( decimal d ) { return Math.Abs( d ); }
    public static double Pow( decimal x, decimal y ) { return Math.Pow( (double)x, (double)y ); }
    public static DateTime Today() { return DateTime.Today; }

    public static decimal Min( params decimal[] p ) { return p.Min(); }
    public static decimal Max( params decimal[] p ) { return p.Max(); }
    public static decimal Round( decimal x, int decimals ) { return Math.Round( x, decimals ); }

    public static double RoundDown( decimal number, int digits )
    {
        var x = Math.Pow( 10, digits ); // 1, 10, 100, 1000, 10000
        var y = (double)number * x;
        return Math.Round( Math.Floor( y ) / x, digits < 0 ? 0 : digits );
    }

    public static double RoundUp( decimal number, int digits )
    {
        var x = Math.Pow( 10, digits ); // 1, 10, 100, 1000, 10000
        var y = (double)number * x;
        bool isExact = Math.Floor( y ) == y;
        return Math.Round( Math.Floor( y + ( isExact ? 0 : 1 ) ) / x, digits < 0 ? 0 : digits );
    }

    public static bool And( params bool[] conditions ) { return conditions.All( i => i ); }
    public static bool Or( params bool[] conditions ) { return conditions.Any( i => i ); }

    public static string Left( string text, int count ) { return !string.IsNullOrEmpty( text ) ? text.Substring( 0, count ) : ""; }
    public static string Right( string text, int count ) { return !string.IsNullOrEmpty( text ) ? text.Substring( text.Length - count ) : ""; }

    public static string Mid( string text, int start, int characters )
    {
        if ( string.IsNullOrEmpty( text ) ) return "";

        var length = text.Length;
        var startPos = start < 0 ? 0 : start - 1; //Javascript is 0 based index, excel is 1 based.
        var returnLength = length - characters;

        return ( characters <= 0 || startPos > length || returnLength < 0 ) // Invalid bound, return blank string
            ? ""
            : text.Substring( startPos, characters );
    }

    public static object Sum( params decimal[] values )
    {
        return values.Sum();
    }

    public static object Search( string find, string withIn ) { return Search( find, withIn, 1 ); }

    public static object Search( string find, string withIn, int start )
    {
        if ( string.IsNullOrEmpty( find ) ) return null;

        string search = withIn.Replace( "~*", "*" ).Replace( "~?", "?" );

        var pos = search.IndexOf( find, start - 1, StringComparison.InvariantCultureIgnoreCase );

        return pos > -1 ? (int?)( pos + 1 ) : null;
    }

    public static string Dump( CodeContext context )
    {
        return Dump( context, false );
    }

    public static string Dump( CodeContext context, bool verbose )
    {
        var scope = context.Scope as WorksheetScope;
        var result = new System.Text.StringBuilder();

        foreach ( var cell in scope.Worksheet.Cells )
        {
            if ( result.Length > 0 ) result.AppendLine();

            var expression = verbose
                ? cell.Value.Formula.Expression.DebugView
                : cell.Value.Formula.ExpressionText;

            result.AppendFormat(
                "{0} = {1}{2}",
                cell.Key,
                cell.Value.Value,
                expression != null ? " [ = " + expression + " ]" : null );
        }

        return result.ToString();
    }

These are all pretty trivial, but notice the Sum method where we’re able to take an array of decimals and use LINQ to sum the values together (go LINQ! :) ). 

You can add any method overrides you like – later we’ll hand them to the DLR and let it figure out the best one to call.  If you try the calculator demo above, you should be able to try these out (Dump() and Dump(true) can be quite revealing :) ).

Building the expression tree

In Part 1 I described using TinyPG to create a parser.  Once you’ve defined your grammar (and fixed any errors) it generates some C# partial classes.  The only one you really need to think about is ParseTree.cs.  Here’s my customized part of the class (remember it’s a partial class, so no need to touch the TinyPG generated file):

public partial class ParseTree
{
    private Dictionary<Operators, ExcelOperationBinder> opBinders = new Dictionary<Operators, ExcelOperationBinder>();

    private ExcelContext context;
    private Dictionary<SymbolId, ParameterExpression> variables = new Dictionary<SymbolId, ParameterExpression>();

    public Expression GetExpression( ExcelContext context )
    {
        this.context = context;

        return GetNodeExpression( this );
    }

    private Expression GetNodeExpression( ParseNode node )
    {
        switch ( node.Token.Type )
        {
            case TokenType.Start:
                return GetStartExpression( node );

            case TokenType.Assignment:
                return GetAssignmentExpression( node );

            case TokenType.CompareExpr:
                return GetComparisonExpression( node );

            case TokenType.AddExpr:
                return GetAddExpression( node );

            case TokenType.MultExpr:
                return GetMultiplyExpression( node );

            case TokenType.Atom:
                return GetAtomExpression( node );

            case TokenType.Range:
                return GetRangeExpression( node );

            case TokenType.TRUE:
                return Expression.Constant( true );

            case TokenType.FALSE:
                return Expression.Constant( false );

            case TokenType.NULL:
                return Expression.Constant( null );

            case TokenType.STRING:
                return GetString( node );

            case TokenType.INTEGER:
                {
                    int iVal;
                    var value = node.Token.Text;

                    return int.TryParse( value, out iVal )
                        ? Expression.Constant( iVal )
                        : Expression.Constant( long.Parse( value ) );
                }

            case TokenType.NUMBER:
                return Expression.Constant( decimal.Parse( node.Token.Text ) );

            case TokenType.Identifier:
                return GetVariableExpression( node );

            case TokenType.Method:
                return GetMethodExpression( node );
        }

        return Expression.Constant( node.Token.Text );
    }

    private IEnumerable<Expression> GetNodeExpressions( ParseNode node )
    {
        var expression = GetNodeExpression( node );

        if ( expression.Type != typeof( Range ) ) return new[] { expression };

        var range = (Range)( (ConstantExpression)expression ).Value;

        return from s in range.GetSymbols() select GetVariableExpression( s );
    }

    private Expression GetStartExpression( ParseNode node )
    {
        return GetNodeExpression( node.Nodes[ 0 ] );
    }

    private Expression GetAssignmentExpression( ParseNode node )
    {
        return Expression.Assign(
            GetVariableExpression( node.Nodes[ 0 ].Token.Text ),
            Utils.Convert( GetNodeExpression( node.Nodes[ 2 ] ), typeof( object ) ) );
    }

    private Expression GetComparisonExpression( ParseNode node )
    {
        ExcelOperationBinder oper = null;
        Expression expr = null;

        foreach ( var n in node.Nodes )
        {
            if ( n.Token.Type == TokenType.COMPARE )
            {
                switch ( n.Token.Text )
                {
                    case "=":
                    case "==": oper = OpBinder( Operators.Equals ); break;
                    case ">": oper = OpBinder( Operators.GreaterThan ); break;
                    case ">=": oper = OpBinder( Operators.GreaterThanOrEqual ); break;
                    case "<": oper = OpBinder( Operators.LessThan ); break;
                    case "<=": oper = OpBinder( Operators.LessThanOrEqual ); break;
                    case "!=":
                    case "<>": oper = OpBinder( Operators.NotEquals ); break;
                }
            }
            else
            {
                var next = GetNodeExpression( n );

                expr = ( expr == null )
                    ? next
                    : Expression.Dynamic( oper, typeof( object ), expr, next );
            }
        }

        return expr;
    }

    private Expression GetAddExpression( ParseNode node )
    {
        ExcelOperationBinder oper = null;
        Expression expr = null;

        foreach ( var n in node.Nodes )
        {
            if ( n.Token.Type == TokenType.PLUSMINUS )
            {
                switch ( n.Token.Text )
                {
                    case "+": oper = OpBinder( Operators.Add ); break;
                    case "-": oper = OpBinder( Operators.Subtract ); break;
                    case "&": oper = OpBinder( Operators.Concatenate ); break;
                }
            }
            else
            {
                var next = GetNodeExpression( n );

                expr = ( expr == null || oper == null )
                    ? next
                    : Expression.Dynamic( oper, typeof( object ), expr, next );
            }
        }

        return expr;
    }

    private Expression GetMultiplyExpression( ParseNode node )
    {
        ExcelOperationBinder oper = null;
        Expression expr = null;

        foreach ( var n in node.Nodes )
        {
            if ( n.Token.Type == TokenType.MULTDIV )
            {
                switch ( n.Token.Text )
                {
                    case "*": oper = OpBinder( Operators.Multiply ); break;
                    case "/": oper = OpBinder( Operators.Divide ); break;
                }
            }
            else
            {
                var next = GetNodeExpression( n );

                expr = ( expr == null || oper == null )
                    ? next
                    : Expression.Dynamic( oper, typeof( object ), expr, next );
            }
        }

        return expr;
    }

    private Expression GetAtomExpression( ParseNode node )
    {
        foreach ( var n in node.Nodes )
        {
            if ( n.Token.Type != TokenType.BROPEN && n.Token.Type != TokenType.BRCLOSE ) return GetNodeExpression( n );
        }

        return null;
    }

    private Expression GetVariableExpression( ParseNode node )
    {
        return GetVariableExpression( node.Nodes.First().Token.Text );
    }

    private Expression GetVariableExpression( string id )
    {
        return GetVariableExpression( SymbolTable.StringToCaseInsensitiveId( id ) );
    }

    private Expression GetVariableExpression( SymbolId symbol )
    {
        return variables.ContainsKey( symbol )
            ? variables[ symbol ]
            : variables[ symbol ] = Expression.Variable( typeof( object ), symbol.ToString() );
    }

    private Expression GetRangeExpression( ParseNode node )
    {
        var cellIds = ( from n in node.Nodes
                        where n.Token.Type == TokenType.CELLID
                        select new CellId( n.Token.Text ) ).ToArray();

        return ( cellIds.Length > 1 )
            ? Expression.Constant( new Range( cellIds ) )
            : GetVariableExpression( cellIds.First().Symbol );
    }

    private Expression GetString( ParseNode node )
    {
        var text = node.Token.Text;

        return Expression.Constant( text.Substring( 1, text.Length - 2 ) );
    }

    private Expression GetMethodExpression( ParseNode node )
    {
        var parameters = ( from n in node.Nodes
                           where n.Token.Type == TokenType.Params
                           from p in n.Nodes
                           where p.Token.Type != TokenType.COMMA
                           from e in GetNodeExpressions( p )
                           select e ).ToArray();

        var name = node.Nodes.First().Token.Text;

        if ( name.ToLower() == "iif" ) return GetConditionExpression( parameters );

        return Expression.Dynamic(
            context.CreateCallBinder( name, true, new CallInfo( parameters.Length ) ),
            typeof( object ),
            new[] { Utils.CodeContext() }.Union( parameters ).ToArray() );
    }

    private Expression GetConditionExpression( Expression[] args )
    {
        return Expression.Condition(
            Utils.Convert( args[ 0 ], typeof( bool ) ),
            Utils.Convert( args[ 1 ], typeof( object ) ),
            Utils.Convert( args[ 2 ], typeof( object ) ) );
    }

    private ExcelOperationBinder OpBinder( Operators oper )
    {
        return opBinders.ContainsKey( oper )
            ? opBinders[ oper ]
            : opBinders[ oper ] = new ExcelOperationBinder(
                context.Binder as ExcelBinder,
                Utils.CodeContext(),
                oper );
    }
}

All it does is recursively walk the parse tree and emit a DLR expression tree (Abstract Syntax Tree).  It has some basic handling for binary operators (including Excel / VB’s ‘&’ concatenation operator).  For some operations (such as binary operators and method calls) it creates DLR DynamicExpression nodes, which hold “just enough” information to define the operation to be performed.

Binders

Before the DLR can make any sense of your DynamicExpressions you need to give it some guidance about how to perform your operations.  To do this you create custom “language binders”, whose job is to provide the DLR with rules defining how to perform operations on various data types.  For this project I created the following classes:

  • ExcelContext – Derives from LanguageContext and provides some compilation helper methods and overrides to create related binders.
  • ExcelBinder – Defines standard operations for various data types and which conversions are valid.
  • ExcelCallAction – Maps “method call” actions to static methods on a helper class (see ExcelHelpers).
  • ExcelConversionBinder – Allows conversions to be overridden; currently just a placeholder.
  • ExcelOperationBinder – Defines how binary operations (add, multiply, comparison operators etc) should behave, performing some basic type coercion.
Operations

You can control the implicit type coercions the DLR is allowed to perform by defining “extension types”.  For each data type, you can create a static class with helper methods to perform the low-level operations.  These methods are marked with the [SpecialName] and [ImplicitConversionMethod] attributes.  You can see some examples here (for this project I wanted to treat all non-integer numbers as decimals).

Other interesting bits

I defined a few classes to represent formulas, engines, cells and worksheets.  The idea is you create an engine and populate it with formulas, then use that engine to create one or more worksheets.  Everything is created / compiled on demand and invalidated appropriately when dependencies change:

  • Cell – Represents an instance of a Formula in a Worksheet.
  • Worksheet – Contains all Cells and an Engine reference.
  • Formula – Contains an ID, expression text, default value and cached expression tree.
  • Engine – Contains all Formulas.
  • Invoker – Caches dynamic delegates (see related post here).
Try it!

You can find the source for this project on CodePlex and you can try the Silverlight calculator test here :)   Don’t forget to look at the retirement planner example I posted too:

image

It’s built on an extended version of this project, able to load an Excel-like worksheet (including lookup tables and some scary long expressions).  As you drag the “retirement age” slider it’s recalculating several hundred table values (pretty quickly thanks to the DLR’s call site caching) and kicking off a tween-like animation (sometimes it’ll slow down as the axis scales etc; that’s just an animation issue rather than a DLR / engine problem :) ).

Categories: .NET

WPF Rounded Corners – and holes!

March 16, 2009 · 2 Comments

UPDATE: Silverlight 3 joins in the fun!

After my enormously successful WPF – Easy rounded corners for anything post(really!), I’ve had some questions about cropping corners of an image with its own transparency.

Since we’re using an OpacityMask, you can do far more than define basic on/off geometry.  There’s nothing stopping you using an image as part of the mask too…

<Page
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
      <TextBlock FontSize="128" HorizontalAlignment="Center" VerticalAlignment="Center">hello world!</TextBlock>
      <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
          <Border Name="mask" CornerRadius="20" Background="White">
              <Border.OpacityMask>
                  <ImageBrush ImageSource="SampleAlpha.png" Stretch="UniformToFill"/>
              </Border.OpacityMask>
          </Border>
          <Image Source="e:\Scribble\temp\SampleAlpha.png">
              <Image.OpacityMask>
                  <VisualBrush Visual="{Binding ElementName=mask}"/>
              </Image.OpacityMask>
          </Image>
      </Grid>
  </Grid>
</Page>

Here’s the result:

image

Click here or the image above to try it out.  Notice as you resize the window the clipped corners maintain the same radius…

kick it

Categories: .NET

Silverlight Asynchronous Loader Helper

March 16, 2009 · 1 Comment

Here’s a potentially useful (or tedium-saving) Silverlight class to simplify asynchronous server requests:

public class AsyncLoader
{
    private Action<Stream> onLoaded;
    private Action<Exception> onError;

    /// <summary>
    /// Loads from the specified URL.
    /// </summary>
    /// <param name="url">The URL.</param>
    /// <param name="onLoaded">The Action to invoke when loaded.</param>
    public static void Load( string url, Action<Stream> onLoaded )
    {
        Load( url, onLoaded, null );
    }

    /// <summary>
    /// Loads from the specified URL.
    /// </summary>
    /// <param name="url">The URL.</param>
    /// <param name="onLoaded">The Action to invoke when loaded.</param>
    /// <param name="onError">The Action to invoke on error.</param>
    public static void Load( string url, Action<Stream> onLoaded, Action<Exception> onError )
    {
        new AsyncLoader( onLoaded, onError ).Load( url );
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="AsyncLoader"/> class.
    /// </summary>
    /// <param name="onLoaded">The on loaded.</param>
    /// <param name="onError">The on error.</param>
    protected AsyncLoader( Action<Stream> onLoaded, Action<Exception> onError )
    {
        this.onLoaded = onLoaded;
        this.onError = onError;
    }

    /// <summary>
    /// Loads from the specified URL.
    /// </summary>
    /// <param name="url">The URL.</param>
    public void Load( string url )
    {
        var wc = new WebClient();
        wc.OpenReadCompleted += OpenReadCompleted;
        wc.OpenReadAsync( new Uri( "../" + url, UriKind.Relative ) );
    }

    /// <summary>
    /// Opens the read completed.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="System.Net.OpenReadCompletedEventArgs"/> instance containing the event data.</param>
    private void OpenReadCompleted( object sender, OpenReadCompletedEventArgs e )
    {
        if ( e.Error != null )
        {
            if ( onError != null ) onError( e.Error );
        }
        else if ( onLoaded != null )
        {
            try
            {
                onLoaded( e.Result );
            }
            finally
            {
                if ( e.Result != null ) e.Result.Close();
            }
        }
    }
}

It wraps a call to WebClient.OpenReadAsync and simplifies the calling code a little:

private void OnSourceChanged( string source )
{
    AsyncLoader.Load( source, OnLoaded, OnError );
    OnPropertyChanged( "Source" );
}

private void OnLoaded( Stream stream )
{
    var config = XElement.Load( stream );
    var engine = XmlEngineParser.Parse( config );
    var worksheet = engine.CreateWorksheet();

    // Other stuff...
}

private void OnError( Exception ex )
{
    // Handle error
}

As usual, don’t be tempted to update any Silverlight controls in a handler without going through a Dispatcher (to ensure you’re calling on the UI thread), like so:

Dispatcher.BeginInvoke( delegate
{
    // Now you can safely update your UI...
} );

Hope this helps!

Categories: .NET

Silverlight DependencyProperty Relay

March 16, 2009 · 1 Comment

UPDATE: Silverlight 3 (beta 1 at time of this post) supports ElementName and RelativeSource binding :)   See here.

Similar to the Silverlight relay base class I posted, here’s an alternative built around DependencyProperties:

/// <summary>
/// IDependencyRelay interface
/// </summary>
public interface IDependencyRelay
{
    void OnDependencyPropertyChanged( string name, DependencyPropertyChangedEventArgs args );
}

/// <summary>
/// DependencyRelay class
/// </summary>
public class DependencyRelay : FrameworkElement, IDependencyRelay, INotifyPropertyChanged
{
    /// <summary>
    /// Registers a new DependencyProperty.
    /// </summary>
    /// <param name="name">The name.</param>
    /// <param name="propertyType">Type of the property.</param>
    /// <param name="ownerType">Type of the owner.</param>
    /// <returns></returns>
    public static DependencyProperty RegisterProperty( string name, Type propertyType, Type ownerType )
    {
        return DependencyProperty.Register(
            name,
            propertyType,
            ownerType,
            new PropertyMetadata( MakeHandler( name ) ) );
    }

    /// <summary>
    /// Registers a new DependencyProperty.
    /// </summary>
    /// <param name="name">The name.</param>
    /// <param name="propertyType">Type of the property.</param>
    /// <param name="ownerType">Type of the owner.</param>
    /// <param name="defaultValue">The default value.</param>
    /// <returns></returns>
    public static DependencyProperty RegisterProperty( string name, Type propertyType, Type ownerType, object defaultValue )
    {
        return DependencyProperty.Register(
            name,
            propertyType,
            ownerType,
            new PropertyMetadata( defaultValue, MakeHandler( name ) ) );
    }

    /// <summary>
    /// Makes a PropertyChangedCallback handler.
    /// </summary>
    /// <param name="name">The name.</param>
    /// <returns></returns>
    private static PropertyChangedCallback MakeHandler( string name )
    {
        return delegate( DependencyObject obj, DependencyPropertyChangedEventArgs args )
        {
            ( obj as IDependencyRelay ).OnDependencyPropertyChanged( name, args );
        };
    }

    /// <summary>
    /// Called when a DependencyProperty has changed.
    /// </summary>
    /// <param name="name">The name.</param>
    /// <param name="args">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
    protected virtual void OnDependencyPropertyChanged( string name, DependencyPropertyChangedEventArgs args )
    {
        OnPropertyChanged( name );
    }

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Raises the PropertyChanged event.
    /// </summary>
    /// <param name="propertyName">Name of the property.</param>
    protected void OnPropertyChanged( string propertyName )
    {
        if ( PropertyChanged != null ) PropertyChanged( this, new PropertyChangedEventArgs( propertyName ) );
    }

    #endregion

    #region IDependencyRelay Members

    /// <summary>
    /// Called when a DependencyProperty has changed.
    /// </summary>
    /// <param name="name">The name.</param>
    /// <param name="args">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
    void IDependencyRelay.OnDependencyPropertyChanged( string name, DependencyPropertyChangedEventArgs args )
    {
        OnDependencyPropertyChanged( name, args );
    }

    #endregion
}

The idea is you either derive your relay class from DependencyRelay or implement the IDependencyRelay interface on your own class, then use DependencyRelay’s RegisterProperty method [instead of DependencyProperty.Register].  The result is the same, but it also raises INotifyPropertyChanged.PropertyChanged events when a DependencyProperty changes.

Here’s an example:

public class PlannerBinder : DependencyRelay
{
    public static readonly DependencyProperty ChartProperty = DependencyRelay.RegisterProperty(
        "Chart",
        typeof( Chart ),
        typeof( PlannerBinder ) );

    public Chart Chart
    {
        get { return (Chart)GetValue( ChartProperty ); }
        set { SetValue( ChartProperty, value ); }
    }

    public static readonly DependencyProperty TransformProperty = DependencyRelay.RegisterProperty(
        "Transform",
        typeof( Transform ),
        typeof( PlannerBinder ) );

    public Transform Transform
    {
        get { return (Transform)GetValue( TransformProperty ); }
        set { SetValue( TransformProperty, value ); }
    }

    public static readonly DependencyProperty AgeRangeProperty = DependencyRelay.RegisterProperty(
        "AgeRange",
        typeof( ValueRange ),
        typeof( PlannerBinder ) );

    public ValueRange AgeRange
    {
        get { return (ValueRange)GetValue( AgeRangeProperty ); }
        set { SetValue( AgeRangeProperty, value ); }
    }

    public static readonly DependencyProperty RetirementAgeProperty = DependencyRelay.RegisterProperty(
        "RetirementAge",
        typeof( double ),
        typeof( PlannerBinder ) );

    public double RetirementAge
    {
        get { return (double)GetValue( RetirementAgeProperty ); }
        set { SetValue( RetirementAgeProperty, value ); }
    }

    public static readonly DependencyProperty SliderMarginProperty = DependencyRelay.RegisterProperty(
        "SliderMargin",
        typeof( Thickness ),
        typeof( PlannerBinder ) );

    public Thickness SliderMargin
    {
        get { return (Thickness)GetValue( SliderMarginProperty ); }
        set { SetValue( SliderMarginProperty, value ); }
    }

    protected override void OnDependencyPropertyChanged( string name, DependencyPropertyChangedEventArgs args )
    {
        base.OnDependencyPropertyChanged( name, args );

        if ( args.Property == ChartProperty )
        {
            SetBinding( RetirementAgeProperty, new Binding( "RetirementAge" ) { Source = Chart } );
            SetBinding( AgeRangeProperty, new Binding( "ValueRange" ) { Source = Chart.Axes[ 0 ] } );
        }

        if ( args.Property != SliderMarginProperty && Transform != null && AgeRange.Range > 0 )
        {
            var pos = Transform.Transform( new Point( ( RetirementAge - AgeRange.Minimum ) / AgeRange.Range, 0 ) );

            SliderMargin = new Thickness( pos.X - 5, -5, -5, -5 );
        }
    }
}

You can override OnDependencyPropertyChanged if you need to add some extra handling (calculating dependent values etc).  It’s not a huge time- or code-saver, but maybe you’ll find a use for it somewhere :)

Categories: .NET

Silverlight relay base class

March 9, 2009 · 5 Comments

UPDATE: Silverlight 3 (beta 1 at time of this post) supports ElementName and RelativeSource binding :)   See here.

Silverlight 2 doesn’t support WPF’s ElementName or RelativeSource binding.  The simplest solution is to use a “relay” class and have multiple UI elements binding to it.  There are some great posts about it here, here and here.

If you want to write a very simple relay, here’s a base class that can handle the basic plumbing:

public abstract class MultiBindableBase<TKey> : INotifyPropertyChanged
{
    /// <summary>
    /// Dictionary of bindable properties.
    /// </summary>
    private Dictionary<TKey, object> bindables = new Dictionary<TKey,object>();

    /// <summary>
    /// Gets or sets a child value with the specified key.
    /// </summary>
    /// <value>The value to get or set.</value>
    /// <remarks>Listens for PropertyChanged notifications if the value implements <see cref="INotifyPropertyChanged"/>.</remarks>
    protected object this[ TKey key ]
    {
        get
        {
            return bindables.ContainsKey( key )
                ? bindables[ key ]
                : bindables[ key ] = null;
        }

        set
        {
            if ( bindables.ContainsKey( key ) )
            {
                var oldNotify = bindables[ key ] as INotifyPropertyChanged;
                if ( oldNotify != null ) oldNotify.PropertyChanged -= OnNotifyPropertyChanged;
            }

            bindables[ key ] = value;

            var notify = value as INotifyPropertyChanged;
            if ( notify != null ) notify.PropertyChanged += OnNotifyPropertyChanged;

            OnPropertyChanged( key );
        }
    }

    /// <summary>
    /// Called when a property of a child value changes.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> instance containing the event data.</param>
    private void OnNotifyPropertyChanged( object sender, PropertyChangedEventArgs e )
    {
        foreach( var bindable in bindables )
        {
            if ( bindable.Value == sender )
            {
                OnPropertyChanged( bindable.Key );
                break;
            }
        }
    }

    /// <summary>
    /// Raises the PropertyChanged event for the specified key.
    /// </summary>
    /// <param name="key">The key.</param>
    protected void OnPropertyChanged( TKey key )
    {
        OnPropertyChanged( GetPropertyName( key ) );
    }

    /// <summary>
    /// Gets the name of a child property.
    /// </summary>
    /// <param name="key">The key.</param>
    /// <returns>Returns the name of the specified child property.</returns>
    protected abstract string GetPropertyName( TKey key );

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Raises the PropertyChanged event for the specified property name.
    /// </summary>
    /// <param name="propertyName">Name of the property.</param>
    protected void OnPropertyChanged( string propertyName )
    {
        if ( PropertyChanged != null ) PropertyChanged( this, new PropertyChangedEventArgs( propertyName ) );
    }

    #endregion
}

It’ll raise its own PropertyChanged events for values you assign, or if those values raise their own PropertyChanged events (if they implement INotifyPropertyChanged).  Here’s a trivial single-property implementation:

public class DependencyValue : MultiBindableBase<int>
{
    public object Value
    {
        get { return this[ 0 ]; }
        set { this[ 0 ] = value; }
    }

    protected override string GetPropertyName( int key )
    {
        return "Value";
    }
}

You could use this as a StaticResource in your XAML, perhaps to make a list of items share a common width (in the snippet below, assume “sharedWidth.Value” is set in code-behind to a calculated value):

<Border x:Name="tooltip" BorderBrush="SteelBlue" CornerRadius="10" Background="#D0FFFFFF" Padding="10" Opacity="0">
    <Border.Resources>
        <local:DependencyValue x:Key="sharedWidth"/>
    </Border.Resources>
    <StackPanel>
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Age" Width="{Binding Value, Source={StaticResource sharedWidth}}" FontSize="9" TextAlignment="Right" Margin="0,0,5,0"/>
            <TextBlock x:Name="tooltipXValue" FontSize="9" FontWeight="Bold"/>
        </StackPanel>
        <ItemsControl x:Name="tooltipContent">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Title}" Width="{Binding Value, Source={StaticResource sharedWidth}}" FontSize="9" TextAlignment="Right" Margin="0,0,5,0"/>
                        <TextBlock Text="{Binding TooltipValue, Converter={StaticResource stringFormatConverter}, ConverterParameter=C0}" FontSize="9" FontWeight="Bold"/>
                    </StackPanel>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </StackPanel>
</Border>

Let me know if it’s any use! :)

Categories: .NET

Parameterized DLR Invoker

March 6, 2009 · 2 Comments

Note: The DLR is still a moving target :) – Recent changes to the ScriptCode class may make this post irrelevant…

If you’ve spent much time with the Dynamic Language Runtime you’ve maybe encountered the ScriptCode class and the DlrMainCallTarget delegate.  ScriptCode is intended to be the main entry point for your code; it wires up everything the DLR needs (code context, language and scope parameters) and conveniently caches a delegate.

However if you only need the DLR to evaluate arbitrary parameterized expressions, ScriptCode can be a little restrictive.  The classes below are useful alternatives:

Invoker:

public class Invoker
{
    public delegate object CallTarget( Scope scope, LanguageContext language, CodeContext context );

    private static DlrParameterResolver resolver = new DlrParameterResolver();

    protected LanguageContext language;
    private Expression code;
    private Delegate target;

    /// <summary>
    /// Initializes a new instance of the <see cref="Invoker&lt;T&gt;"/> class.
    /// </summary>
    /// <param name="code">The code.</param>
    /// <param name="language">The language.</param>
    public Invoker( Expression code, LanguageContext language )
    {
        this.code = code;
        this.language = language;
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="Invoker"/> class.
    /// </summary>
    /// <param name="invoker">The invoker.</param>
    /// <param name="arguments">The arguments.</param>
    /// <remarks>This constructor "bakes" arguments into the invocation of another <see cref="Invoker"/></remarks>
    public Invoker( Invoker invoker, params object[] arguments )
        : this( invoker, (IEnumerable<object>)arguments )
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="Invoker"/> class.
    /// </summary>
    /// <param name="invoker">The invoker.</param>
    /// <param name="arguments">The arguments.</param>
    /// <remarks>This constructor "bakes" arguments into the invocation of another <see cref="Invoker"/></remarks>
    public Invoker( Invoker invoker, IEnumerable<object> arguments )
        : this( invoker.Target, invoker.language, arguments )
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="Invoker"/> class.
    /// </summary>
    /// <param name="target">The target delegate.</param>
    /// <param name="language">The language context.</param>
    /// <param name="arguments">The arguments.</param>
    /// <remarks>This constructor "bakes" arguments into the invocation of another <see cref="Invoker"/></remarks>
    public Invoker( Delegate target, LanguageContext language, IEnumerable<object> arguments )
    {
        if ( target != null )
        {
            var args = new Expression[]
                {
                    DlrParameterResolver.ScopeParameter,
                    DlrParameterResolver.LanguageParameter,
                    DlrParameterResolver.ContextParameter
                }.Union( arguments.Select(
                    a => ( a is Expression )
                        ? (Expression)a
                        : Expression.Constant( a, typeof( object ) ) ) ).ToArray();

            this.code = Expression.Invoke(
                Expression.Constant( target ),
                args );

            this.language = language;
        }
    }

    /// <summary>
    /// Gets or sets the code.
    /// </summary>
    /// <value>The code.</value>
    public Expression Code
    {
        get { return code; }
        set
        {
            Interlocked.Exchange<Expression>( ref code, value );
            Interlocked.Exchange<Delegate>( ref target, null );
        }
    }

    /// <summary>
    /// Gets the target.
    /// </summary>
    /// <value>The target.</value>
    public Delegate Target
    {
        get
        {
            if ( target == null && code != null )
            {
                Interlocked.CompareExchange<Delegate>( ref target, MakeDelegate(), null );
            }

            return target;
        }
    }

    /// <summary>
    /// Makes the delegate.
    /// </summary>
    /// <returns></returns>
    protected virtual Delegate MakeDelegate()
    {
        return DlrParameterResolver.MakeLambda<CallTarget>( code, "Invoker", null ).Compile();
    }

    /// <summary>
    /// Invokes the target with the specified scope.
    /// </summary>
    /// <param name="scope">The scope.</param>
    /// <param name="args">Optional arguments.</param>
    /// <returns>The result of the invocation.</returns>
    public virtual object Invoke( Scope scope )
    {
        return ( (CallTarget)Target )( scope, language, new CodeContext( scope, language ) );
    }

    /// <summary>
    /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
    /// </summary>
    /// <returns>
    /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
    /// </returns>
    public override string ToString()
    {
        return Code.ToString();
    }
}

ParameterizedInvoker:

public class ParameterizedInvoker : Invoker
{
    /// <summary>
    /// Initializes a new instance of the <see cref="Invoker&lt;T&gt;"/> class.
    /// </summary>
    /// <param name="code">The code.</param>
    /// <param name="language">The language.</param>
    public ParameterizedInvoker( Expression code, LanguageContext language, params ParameterExpression[] parameters )
        : base( code, language )
    {
        this.Parameters = parameters;
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="Invoker&lt;T&gt;"/> class.
    /// </summary>
    /// <param name="code">The code.</param>
    /// <param name="language">The language.</param>
    public ParameterizedInvoker( LambdaExpression code, LanguageContext language )
        : this( code.Body, language, code.Parameters.ToArray() )
    {
    }

    /// <summary>
    /// Gets or sets the parameters.
    /// </summary>
    /// <value>The parameters.</value>
    public IEnumerable<ParameterExpression> Parameters { get; set; }

    /// <summary>
    /// Makes the delegate.
    /// </summary>
    /// <returns></returns>
    protected override Delegate MakeDelegate()
    {
        var lambda = ( Code is LambdaExpression )
            ? (LambdaExpression)Code
            : DlrParameterResolver.MakeLambda( Code, "Invoker", Parameters.ToArray() );

        return lambda.Compile();
    }

    /// <summary>
    /// Invokes the target with the specified scope.
    /// </summary>
    /// <param name="scope">The scope.</param>
    /// <param name="args">Optional arguments.</param>
    /// <returns>The result of the invocation.</returns>
    public object Invoke( Scope scope, params object[] args )
    {
        return Target.DynamicInvoke( args );
    }
}

DlrParameterResolver (similar to GlobalRewriter in the DLR):

public class DlrParameterResolver : ExpressionVisitor
{
    private static DlrParameterResolver resolver = new DlrParameterResolver();

    public static readonly ParameterExpression ScopeParameter = Expression.Parameter( typeof( Scope ), "$scope" );
    public static readonly ParameterExpression LanguageParameter = Expression.Parameter( typeof( LanguageContext ), "$language" );
    public static readonly ParameterExpression ContextParameter = Expression.Parameter( typeof( CodeContext ), "$context" );

    /// <summary>
    /// Visits the children of the extension expression.
    /// </summary>
    /// <param name="node">The expression to visit.</param>
    /// <returns>
    /// The modified expression, if it or any subexpression was modified;
    /// otherwise, returns the original expression.
    /// </returns>
    /// <remarks>
    /// This can be overridden to visit or rewrite specific extension nodes.
    /// If it is not overridden, this method will call <see cref="M:Microsoft.Linq.Expressions.Expression.VisitChildren(Microsoft.Linq.Expressions.ExpressionVisitor)"/>,
    /// which gives the node a chance to walk its children. By default,
    /// <see cref="M:Microsoft.Linq.Expressions.Expression.VisitChildren(Microsoft.Linq.Expressions.ExpressionVisitor)"/> will try to reduce the node.
    /// </remarks>
    protected override Expression VisitExtension( Expression node )
    {
        var context = node as CodeContextExpression;

        if ( context != null ) return ContextParameter;

        return base.VisitExtension( node );
    }

    /// <summary>
    /// Visits the <see cref="T:Microsoft.Linq.Expressions.ParameterExpression"/>.
    /// </summary>
    /// <param name="node">The expression to visit.</param>
    /// <returns>
    /// The modified expression, if it or any subexpression was modified;
    /// otherwise, returns the original expression.
    /// </returns>
    protected override Expression VisitParameter( ParameterExpression node )
    {
        if ( node.Type == typeof( Scope ) ) return ScopeParameter;
        if ( node.Type == typeof( LanguageContext ) ) return LanguageParameter;

        return base.VisitParameter( node );
    }

    /// <summary>
    /// Makes the lambda builder.
    /// </summary>
    /// <param name="body">The body.</param>
    /// <param name="name">The name.</param>
    /// <param name="parameters">The parameters.</param>
    /// <returns></returns>
    private static LambdaBuilder MakeLambdaBuilder( Expression body, string name, params ParameterExpression[] parameters )
    {
        var builder = Utils.Lambda( typeof( object ), name );

        builder.AddParameters( ScopeParameter, LanguageParameter, ContextParameter );
        if ( parameters != null ) builder.AddParameters( parameters );

        var visited = resolver.Visit( body );

        builder.Body = ( visited.Type != typeof( object ) )
            ? Expression.Convert( visited, typeof( object ) )
            : visited;

        return builder;
    }

    /// <summary>
    /// Makes the lambda.
    /// </summary>
    /// <param name="body">The body.</param>
    /// <param name="parameters">The parameters.</param>
    /// <returns></returns>
    public static LambdaExpression MakeLambda( Expression body, string name, params ParameterExpression[] parameters )
    {
        return MakeLambdaBuilder( body, name, parameters ).MakeLambda();
    }

    /// <summary>
    /// Makes the lambda.
    /// </summary>
    /// <param name="body">The body.</param>
    /// <param name="parameters">The parameters.</param>
    /// <returns></returns>
    public static Expression<T> MakeLambda<T>( Expression body, string name, params ParameterExpression[] parameters )
    {
        return (Expression<T>)MakeLambdaBuilder( body, name, parameters ).MakeLambda( typeof( T ) );
    }
}

DlrParameterResolver is just an ExpressionVisitor the replaces any Scope, LanguageContext and CodeContext Expressions with known parameters (the static ParameterExpressions defined at the top).  The DLR’s GlobalRewriter does a similar thing, but defines a new top-level scope for the CodeContext.

Invoker compiles an Expression and caches the delegate (essentially the same thing as the DLR’s ScriptCode class).  ParameterizedInvoker just adds parameter support.  Invokers can be nested and used to create all kinds of interesting things…

I’ll include examples (and hopefully answer the giant “why would I want this?”) with the next part of my Excel Formulas and the DLR post, but let me know if you want more info before then :)

Categories: .NET