Archives for JoshEinstein.com

Lost “Button” Simulator in Silverlight

To celebrate the upcoming finale of LOST, I threw this thing together in Silverlight 4 using Expression Blend 4 and Visual Studio 2010. I also used a trial of Goldwave 5 to work on the sound effects.

I’ve included the source code so you can take a look around. This is an example of a pretty barebones MVVM app without the “M”. Some of the techniques I used were XAML storyboards, a simple state machine, semi-transparent overlays to simulate lighting, MediaElement to play audio files, ViewBox to stretch a portion of the view, TransitioningContentControl, etc. just to name a few.

So go ahead, feel important at work like you’re John Locke saving the world in between staff meetings. Just remember to enter the right numbers (with spaces) and push the “EXECUTE” button every 108 minutes or else…

(Or if you just get bored of waiting, click the clock to advance the timer. You can also right click the application to install it to your start menu.)

Tip for adding Silverlight references in Blend

I find it very frustrating sometimes to get something to build in Blend. Like many developers, I have a "Me.dll" that contains a lot of commonly used classes, custom controls, etc. As you might expect, this DLL often takes dependencies on other DLL’s that must also then be referenced.

In Blend, this can be a pain in the butt because the Silverlight assemblies are scattered into 3 main locations:

  • %ProgramFiles%\Reference Assemblies\Microsoft\Framework\Silverlight\v4.0
  • %ProgramFiles%\Microsoft SDKs\Silverlight\v4.0\Libraries\Client
  • %ProgramFiles%\Microsoft SDKs\Silverlight\v4.0\Toolkit\Apr10\Bin

It gets to be pretty frustrating pointing blend to these various paths to find System.ServiceModel.dll or something.

You can make this a lot easier without copying all the DLL’s by using symbolic links in Windows Vista/7/2008.

To create the symbolic links, open a PowerShell prompt as an administrator. (Make sure you have PowerShell Community Extensions installed, but who doesn’t?) Then paste or type the following script.

Set-Location C:\Temp\Refs

$SearchArgs = @{
    ErrorAction = 'SilentlyContinue'
    Include = @("System.*.dll", "Microsoft.*.dll")
    Exclude = @("*.resources.dll", "*.design.dll")
    Path = @(
        "$($Env:ProgramFiles)\Reference Assemblies\Microsoft\Framework\Silverlight\v4.0\*"
        "$($Env:ProgramFiles)\Microsoft SDKs\Silverlight\v4.0\Libraries\Client\*"
        "$($Env:ProgramFiles)\Microsoft SDKs\Silverlight\v4.0\Toolkit\Apr10\Bin\*"
        "$($Env:ProgramFiles) (x86)\Reference Assemblies\Microsoft\Framework\Silverlight\v4.0\*"
        "$($Env:ProgramFiles) (x86)\Microsoft SDKs\Silverlight\v4.0\Libraries\Client\*"
        "$($Env:ProgramFiles) (x86)\Microsoft SDKs\Silverlight\v4.0\Toolkit\Apr10\Bin\*"
    )
}

Get-ChildItem @SearchArgs | %{

    $LinkPath = $_.Name
    $TargetPath = $_.FullName

    Write-Host "$LinkPath -> $TargetPath"
    New-Symlink -Path:$LinkPath -Target:$TargetPath | Out-Null

}

Ted Dziuba: Why Engineers Hop Jobs

Came across this while browsing Google Reader…

via Ted Dziuba: Why Engineers Hop Jobs

People in my generation have a very low tolerance for bullshit, and software engineering, in general, is a very high bullshit career. If you couple that with the standard load of bullshit you would get from a non-technical Harvard MBA type boss — like many CEOs that you find trying to get rich in Silicon Valley by hiring some engineers to “code up this idea real quick” — it’s no wonder that a good engineer will walk off the job after his one year cliff vesting.

Rob Bushway: A Little Bit About Me

I am not much of a religious person. I went to catholic school from Kindergarten through 10th grade (and plan on sending my son), all of my grandparents were religious, and my aunt is a Franciscan nun.

Having said that, I came across this moving piece by my friend Rob Bushway and figured I would share it. The Bushways are a great example of a close knit family and his is a story of perseverance, no matter what your faith is.

RobBushway.com

via A Little Bit About Me.

Ummm OK?

Okay, I get the idea but come on… Talk about verbose errors. I guess it’s better than “Argument out of range.”

Custom tool warning: There was a validation error on a schema generated during export:
Validation Error: Wildcard ‘##any’ allows element ‘Account’, and causes the content model to become ambiguous. A content model must be formed such that during validation of an element information item sequence, the particle contained directly, indirectly or implicitly therein with which to attempt to validate each item in the sequence in turn can be uniquely determined without examining the content or attributes of that item, and without any information about the items in the remainder of the sequence.

PowerShell Team announces FxCop rules for cmdlets

PowerShell Team has announced FxCop rules for cmdlet authors.

List of rules

  • AcceptForceParameterWhenCallingShouldContinue
  • AllCmdletsShouldAcceptPipelineInput
  • CallShouldProcessOnlyIfDeclaringSupport
  • DefineCmdletInTheCorrectNamespace
  • DoNotAccessPipelineParametersOutsideProcessRecord
  • DoNotCallCertainHostMethods
  • DoNotUseConsoleApi
  • FollowCmdletClassNamingConvention
  • OverrideProcessRecordIfAcceptingPipelineInput
  • ParameterShouldHaveConsistentTypePerNoun
  • UseCredentialAttributeForPSCredentialParameter
  • UseOnlyApprovedCharactersInVerbsAndNouns
  • UseOnlyStandardVerbs
  • UsePascalCasingInVerbsAndNouns
  • UseRecommendedParameterTypes
  • UseSingularNouns
  • UseSingularParameterNames
  • UseSwitchParameterInsteadOfBoolean

Dock TweetDeck to the side of your screen with PowerShell

I wanted to post this last night but I did not have an internet connection.

The best part about being a developer is that when software drives you nuts, many times you can do something about it that mere mortals cannot. That’s the case with TweetDeck. I love it but I am sick of having to move windows around to keep it in view. I have a wide screen so I wanted to dedicate a band of space to it, much like how the Vista sidebar worked. When I maximize other windows, they should not obscure the taskbar or TweetDeck.

The title of this post is a little misleading for two reasons…

  1. PowerShell really has nothing to do with it. It’s just the method I chose to kick off this little hack. The actual implementation is all Win32 API via C#.
  2. TweetDeck is just the application that happened to drive me to write this. You could just as easily change one line to dock Windows Live Messenger instead.

So the code is shown below. It’s nearly all in C# but PowerShell will dynamically compile it when you run the script. No exe’s needed. Just run the script and…

  • TweetDeck’s caption and window border will be removed
  • It will be docked against the right hand side of your primary screen (hard coded, sorry)
  • Maximized windows will adjust to the new workspace size

A couple caveats though.

  • Resize TweetDeck to the size you want it before doing this
  • Once docked, there’s currently no way to undock. So just close it from the taskbar.
  • TweetDeck is buggy in "single column view" as many UI elements are cut off. It’s best to turn off "use narrow columns" first.

Code is below. Enjoy.

$DockProcessName = 'tweetdeck'

Add-Type -Language CSharpVersion3 @"
using System;
using System.Runtime.InteropServices;

public static class WindowDockUtil
{

    #region Public Methods

    /// <summary>
    /// Docks the window to the right side of the screen.
    /// </summary>
    /// <param name="hWnd">The window handle.</param>
    public static void Dock( IntPtr hWnd )
    {

        if ( hWnd == IntPtr.Zero ) {
            return;
        }   // if

        ulong windowLong = GetWindowLong( hWnd, GWL_STYLE );
        windowLong &= ~WS_BORDER;
        windowLong &= ~WS_CAPTION;
        windowLong &= ~WS_THICKFRAME;

        SetWindowLong( hWnd, GWL_STYLE, windowLong );

        var abd = new APPBARDATA( );
        abd.cbSize = Marshal.SizeOf( abd );
        abd.hWnd = hWnd;
        abd.uCallbackMessage = RegisterWindowMessage( "AppBarMessage" );

        // Create AppBar
        SHAppBarMessage( ABMsg.ABM_NEW, ref abd );

        // Get the Window's size
        RECT wc = new RECT( );
        if ( !GetWindowRect( hWnd, ref wc ) ) {
            return;
        }   // if

        int screenWidth = GetSystemMetrics( SM_CXSCREEN );
        int screenHeight = GetSystemMetrics( SM_CYSCREEN );
        int borderWidth = GetSystemMetrics( SM_CXSIZEFRAME );

        // Set the app bar to dock to the right
        abd.uEdge = ABEdge.ABE_RIGHT;
        abd.rc.top = 0;
        abd.rc.bottom = screenHeight;
        abd.rc.right = screenWidth;
        abd.rc.left = abd.rc.right - ( wc.right - wc.left ) + ( borderWidth * 2 );

        // Set AppBar Size
        SHAppBarMessage( ABMsg.ABM_SETPOS, ref abd );

        // Move Window to New Position
        MoveWindow( abd.hWnd, abd.rc.left, abd.rc.top, abd.rc.right - abd.rc.left, abd.rc.bottom - abd.rc.top, true );

    }

    ///// <summary>
    ///// Removes the window from the side of the screen.
    ///// </summary>
    ///// <param name="hWnd">The handle.</param>
    //public static void UndockWindow( IntPtr hWnd )
    //{

    //    if ( hWnd == IntPtr.Zero ) {
    //        return;
    //    }   // if

    //    var abd = new APPBARDATA( );
    //    abd.cbSize = Marshal.SizeOf( abd );
    //    abd.hWnd = hWnd;

    //    // Remove AppBar
    //    SHAppBarMessage( ABMsg.ABM_REMOVE, ref abd );

    //    // How should I store state between Dock and Undock?
    //    ulong windowLong = GetWindowLong( hWnd, GWL_STYLE );
    //    windowLong |= WS_BORDER;
    //    windowLong |= WS_CAPTION;

    //    SetWindowLong( hWnd, GWL_STYLE, windowLong );

    //}

    #endregion

    #region Interop

    #region AppBar

    [StructLayout( LayoutKind.Sequential )]
    private struct APPBARDATA
    {
        public int cbSize;
        public IntPtr hWnd;
        public int uCallbackMessage;
        public ABEdge uEdge;
        public RECT rc;
        public IntPtr lParam;
    }

    private enum ABMsg : int
    {
        ABM_NEW = 0,
        ABM_REMOVE = 1,
        ABM_QUERYPOS = 2,
        ABM_SETPOS = 3,
        ABM_GETSTATE = 4,
        ABM_GETTASKBARPOS = 5,
        ABM_ACTIVATE = 6,
        ABM_GETAUTOHIDEBAR = 7,
        ABM_SETAUTOHIDEBAR = 8,
        ABM_WINDOWPOSCHANGED = 9,
        ABM_SETSTATE = 10
    }

    private enum ABNotify : int
    {
        ABN_STATECHANGE = 0,
        ABN_POSCHANGED = 1,
        ABN_FULLSCREENAPP = 2,
        ABN_WINDOWARRANGE = 3
    }

    private enum ABEdge : int
    {
        ABE_LEFT = 0,
        ABE_TOP = 1,
        ABE_RIGHT = 2,
        ABE_BOTTOM = 3
    }

    [DllImport( "Shell32", CallingConvention = CallingConvention.StdCall )]
    private static extern uint SHAppBarMessage( ABMsg dwMessage, ref APPBARDATA pData );

    #endregion

    #region Window Management

    [DllImport( "User32" )]
    private static extern int RegisterWindowMessage( string msg );

    [DllImport( "User32" )]
    private static extern IntPtr FindWindow( string lpClassName, string lpWindowName );

    [DllImport( "User32" )]
    private static extern bool MoveWindow( IntPtr hWnd, int x, int y, int cx, int cy, bool repaint );

    [DllImport( "User32" )]
    private static extern bool GetWindowRect( IntPtr hWnd, ref RECT rect );

    [StructLayout( LayoutKind.Sequential )]
    private struct RECT
    {
        public int left;
        public int top;
        public int right;
        public int bottom;
    }

    [DllImport( "User32" )]
    private static extern int GetSystemMetrics( int Index );

    private const int SM_CXSCREEN = 0;
    private const int SM_CYSCREEN = 1;
    private const int SM_CXSIZEFRAME = 32;

    #endregion

    #region GetWindowLong/SetWindowLong

    [DllImport( "User32", EntryPoint = "SetWindowLongPtr" )]
    private static extern ulong SetWindowLong64( IntPtr hWnd, int nIndex, ulong dwNewLong );

    [DllImport( "User32", EntryPoint = "GetWindowLongPtr" )]
    private static extern ulong GetWindowLong64( IntPtr hWnd, int nIndex );

    [DllImport( "User32", EntryPoint = "SetWindowLong" )]
    private static extern uint SetWindowLong32( IntPtr hWnd, int nIndex, uint dwNewLong );

    [DllImport( "User32", EntryPoint = "SetWindowLong" )]
    private static extern uint GetWindowLong32( IntPtr hWnd, int nIndex );

    private const int GWL_WNDPROC =    ( -4 );
    private const int GWL_HINSTANCE =  ( -6 );
    private const int GWL_HWNDPARENT = ( -8 );
    private const int GWL_STYLE =      ( -16 );
    private const int GWL_EXSTYLE =    ( -20 );
    private const int GWL_USERDATA =   ( -21 );
    private const int GWL_ID =         ( -12 );

    private static ulong GetWindowLong( IntPtr hWnd, int nIndex )
    {
        if ( IntPtr.Size == 4 ) {
            return GetWindowLong32( hWnd, nIndex );
        }   // if
        else if ( IntPtr.Size == 8 ) {
            return GetWindowLong64( hWnd, nIndex );
        }   // else if
        else {
            throw new NotSupportedException( "Unsupported platform." );
        }   // else
    }

    private static ulong SetWindowLong( IntPtr hWnd, int nIndex, ulong dwNewLong )
    {
        if ( IntPtr.Size == 4 ) {
            return SetWindowLong32( hWnd, nIndex, (uint)dwNewLong );
        }   // if
        else if ( IntPtr.Size == 8 ) {
            return SetWindowLong64( hWnd, nIndex, dwNewLong );
        }   // else if
        else {
            throw new NotSupportedException( "Unsupported platform." );
        }   // else
    }

    #endregion

    #region Window Styles

    private const ulong WS_OVERLAPPED = 0x0000;
    private const ulong WS_POPUP = 0x80000000;
    private const ulong WS_CHILD = 0x40000000;
    private const ulong WS_MINIMIZE = 0x20000000;
    private const ulong WS_VISIBLE = 0x10000000;
    private const ulong WS_DISABLED = 0x8000000;
    private const ulong WS_CLIPSIBLINGS = 0x4000000;
    private const ulong WS_CLIPCHILDREN = 0x2000000;
    private const ulong WS_MAXIMIZE = 0x1000000;
    private const ulong WS_BORDER = 0x800000;
    private const ulong WS_DLGFRAME = 0x400000;
    private const ulong WS_VSCROLL = 0x200000;
    private const ulong WS_HSCROLL = 0x100000;
    private const ulong WS_SYSMENU = 0x80000;
    private const ulong WS_THICKFRAME = 0x40000;
    private const ulong WS_GROUP = 0x20000;
    private const ulong WS_TABSTOP = 0x10000;
    private const ulong WS_MINIMIZEBOX = 0x20000;
    private const ulong WS_MAXIMIZEBOX = 0x10000;
    private const ulong WS_CAPTION = WS_BORDER | WS_DLGFRAME;
    private const ulong WS_TILED = WS_OVERLAPPED;
    private const ulong WS_ICONIC = WS_MINIMIZE;
    private const ulong WS_SIZEBOX = WS_THICKFRAME;
    private const ulong WS_TILEDWINDOW = WS_OVERLAPPEDWINDOW;
    private const ulong WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
    private const ulong WS_POPUPWINDOW = WS_POPUP | WS_BORDER | WS_SYSMENU;
    private const ulong WS_CHILDWINDOW = WS_CHILD;

    #endregion

    #endregion

}   // class
"@

if ($DockProcess = Get-Process $DockProcessName -ea 0) {
   [WindowDockUtil]::Dock($DockProcess.MainWindowHandle)
}
else {
    Write-Warning "Cannot find the $DockProcessName process."
}

image

WPF : Using Markup Extensions to generate content with icons

It’s way too hard to get an image next to the text content of a Button, TextBlock, ListBoxItem, TreeViewItem, etc. Coming from a Windows Forms background where many controls had an Image property for displaying a small glyph next to text content, I am constantly frustrated by the verbose XAML required to achieve the same in WPF and Silverlight.

<StackPanel>
    <Button>
        <DockPanel>
            <Image Source="Images/Alert.png" />
            <TextBlock Text="Alerts" />
        </DockPanel>
    </Button>
    <Button>
        <DockPanel>
            <Image Source="Images/Color-Blue.png" />
            <TextBlock Text="Blue Category" />
        </DockPanel>
    </Button>
    <Button>
        <DockPanel>
            <Image Source="Images/Color-Green.png" />
            <TextBlock Text="Green Category" />
        </DockPanel>
    </Button>
    <Button>
        <DockPanel>
            <Image Source="Images/Color-Red.png" />
            <TextBlock Text="Red Category" />
        </DockPanel>
    </Button>
</StackPanel>

Well using a WPF markup extension in XAML we can make the code look a little less clunky. Markup extensions (which derive from System.Windows.MarkupExtension) are instantiated similarly to the way attributes are declared in C#. That is to say, there can be positional and/or named parameters. By overriding the ProvideValue you can use those parameters to construct whatever object structure you want and return it. The XAML parser will use this in place of your markup extension when it finds it.

<StackPanel>
    <Button Content="{e:Content Alerts, Image=Images/Alert.png}" />
    <Button Content="{e:Content Blue Category, Image=Images/Color-Blue.png}" />
    <Button Content="{e:Content Green Category, Image=Images/Color-Green.png}" />
    <Button Content="{e:Content Red Category, Image=Images/Color-Red.png}" />
</StackPanel>

Unfortunately Silverlight does not allow you to create custom markup extensions yet so this same technique cannot be applied to Silverlight where the situation is arguably worse with all the toolkit namespaces and such.

The code for the ContentExtension is very straightforward so I’m just going to add it to the end of the post instead of going through the hassle of uploading a project. Enjoy.

/// <summary>
/// A XAML Markup Extension that allows you to combine simple text content with an image
/// alongside instead of having to manually nest the image and text in a panel.
/// </summary>
public class ContentExtension : MarkupExtension
{

    // constructor with positional parameter
    public ContentExtension( string text )
    {
        Text = text;
    }

    // image must be specified as a named parameter
    public ImageSource Image
    {
        get;
        set;
    }

    public string Text
    {
        get;
        set;
    }

    public override object ProvideValue( IServiceProvider serviceProvider )
    {
        return new DockPanel {
            Children = {
                new Image {
                    Source = Image,
                    Stretch = Stretch.None,
                    VerticalAlignment = VerticalAlignment.Center,
                    HorizontalAlignment = HorizontalAlignment.Center,
                    Margin = new Thickness( 5 )
                },
                new TextBlock {
                    VerticalAlignment = VerticalAlignment.Center,
                    Text = Text
                }
            }
        };
    }

}   // class

Content Markup Extension

Windows 7 PowerShell Tip

If you’re a system administrator (or like many developers), chances are you use PowerShell a lot and have the PowerShell console or PowerShell ISE on your Windows 7 taskbar. On Windows Vista and on Windows Server 2008 prior to R2 I was annoyed by having both ISE and console on the quick launch bar or pinned to the start menu.

In Windows 7 here’s a neat tip. Pin the PowerShell console to the taskbar and not the PowerShell ISE. When you right click on the icon, you get a handy jump list which includes not only "Run As Administrator" but also "Open PowerShell ISE". All in the space of a single tile.

Windows PowerShell Jump List

Accept simple mathematical expressions in Silverlight text boxes

Download MathEvalConverter.zip

Background

With the name Einstein, people typically assume I’m good at math. I have the utmost respect for the physicists and mathemeticians of our time, prior, and beyond. But to be honest, math is not my strong suit. In fact I have great difficulty simply adding or subtracting numbers without the use of a calculator. This deficiency actually worked to my advantage last month when I was observing the users of an application I had recently dogfed.

The application had several numeric input fields that took in costs, prices, commissions, etc. Like any run of the mill business application, these inputs affected various calculations that updated the UI accordingly. But while observing the users I kept noticing a very peculiar behavior. When they would type into a box for Cost, Price, etc. they would whip out a small desk calculator and add up some numbers before typing them into the box.

As it turns out, the values I took for granted as a pre-calculated input often require various "in-your-head" steps before arriving at what I assumed was a known "input".

"The cost of the product is normally $15 but I knocked it down by $3."

Given my mathematical challenges, I was very sympathetic to this situation. I don’t own a desk calculator but I am very familiar with the Start -> Run -> calc.exe ceremony. If I happen to have a PowerShell window open I’ll use that instead.

Did I miss a key requirement? Should there have been more input fields? Not really because the calculation steps are not necessarily formal aspects of the system. They’re mostly things people might normally do in their head had they not happen to have a calculator in front of them. And the type of calculations they perform vary from case to case.

Then I remembered… I encounter this same situation all the time when working in Excel. But Excel’s primary feature is the fact that you can type a "formula" or expression anywhere you’d put a constant value. Wouldn’t it be great if the text boxes in my application offered the same functionality? Well by the end of this post, they will!

The Plan

The idea is that my input boxes would allow the user to enter a number as usual, but if they entered an expression such as "2+2", committing the value would enter "4" into the field. The expression doesn’t need to be preserved, so the value of the field can still be backed by a simple number. Also, I only need to implement very basic expressions. I’ll stick to addition, subtraction, multiplication, division, grouping, and any combination of the above.

I thought about where I might add this functionality. I could subclass TextBox, but then the cells in a DataGrid would need to be addressed separately. I could probably have done it with a behavior but that didn’t seem appropriate either. In the end I decided to implement an IValueConverter. I was already using one to provide currency formatting in the control.

The end result would look like the working example shown below.


Get Microsoft Silverlight

The Math Parser

Because this application was an in-browser Silverlight application, I could avoid having to write my own math parser by taking advantage of the HTML bridge and the Eval() method. In short, I would pass the expression to the JavaScript engine to evaluate as if it were a line of code. Of course, I would need to validate the input first to ensure a malicious user could not take advantage of this fact.

To isolate this shortcut into replacable component, I extracted an IMathEvaluator interface that would be defined as follows.

public interface IMathEvaluator {
    bool TryEvaluate(string expression, out decimal value);
}

This allows me to include my JavaScript hack for the sake of this post while admitting that a less lazy developer could substitute a better implementation. One nice thing about this implementation is that I don’t need to determine if the user entered a number or expression. Even if they entered a simple number like 1, I still treat it as an expression.

The implementation is pretty simple too. It simply checks the input against a Regex to ensure it’s not going to pass some malicious payload to the JavaScript engine then calls Eval() via the Silverlight HTML bridge.

public sealed class JScriptMathEvaluator : IMathEvaluator
{
    public bool TryEvaluate(string expression, out decimal value ) {

        // When there's no text, just return zero
        if ( expression == null || expression.Trim().Length == 0 ) {
            value = 0;
            return true;
        }

        // remove currency symbols and commas
        // these are added when we format the number but we want to remove them before
        // parsing the text value because it would invalidate the JavaScript syntax
        var numberFormat = CultureInfo.CurrentCulture.NumberFormat;
        expression = expression.Replace( numberFormat.CurrencySymbol, "" );
        expression = expression.Replace( numberFormat.CurrencyGroupSeparator, "" );
        expression = expression.Replace( numberFormat.NumberGroupSeparator, "" );
        expression = expression.Replace( numberFormat.PercentGroupSeparator, "" );

        // Ensure that a simple expression consisting of only digits,
        // parenthases, and four operators (+, -, *, /) are entered.
        // Never pass non-validated input to JavaScript!
        if (Regex.IsMatch(expression, @"^[0-9\.\+\-\*\/\(\)\s]+$" ) ) {

            try {
                object eval = System.Windows.Browser.HtmlPage.Window.Eval( expression );
                value = Convert.ToDecimal(eval);
                return true;
            }   // try
            catch ( Exception ex ) {
                // trace exception or whatever
                // but let the function return false
            }   // catch

        }   // if

        value = 0;
        return false;

    }
}

The Value Converter

In order to convert the user’s input into a number I’ll need to invoke a conversion process when the value of the TextBox changes. I was already doing this with a FormattingConverter that would apply currency formatting to the number and then parse the text value with NumberStyles.

So let’s just derive a class from FormattingConverter with support for the ConvertBack method. We’ll call it MathEvalConverter.

public sealed class MathEvalConverter : FormattingConverter
{

    public MathEvalConverter( )
    {
        MathEvaluator = new JScriptMathEvaluator( );
    }

    public IMathEvaluator MathEvaluator
    {
        get;
        set;
    }

    protected override object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture )
    {

        // View -> ViewModel

        string expression = System.Convert.ToString( value );

        decimal numberValue;
        if ( MathEvaluator != null && MathEvaluator.TryEvaluate( expression, out numberValue ) ) {
            return numberValue;
        }   // if

        // We couldn't parse the expression.
        // Let silverlight reject it because:
        //   - if we throw an exception, it Silverlight bypasses validation events
        //   - if we return DependencyProperty.UnsetValue, the input will just disappear with no error!
        // By returning the unconverted value back to Silverlight, at least they'll see a conversion error.

        return base.ConvertBack( value, targetType, parameter, culture );

    }

}   // class

The converter doesn’t specifically use the JavaScript implementation. It goes through the IMathEvaluator interface which happens to be implemented by JScriptMathEvaluator.

MathEvalConverter In Action

We can apply the MathEvalConverter to any two-way binding between a numeric property on the binding source and a string property on a control. Since it derives from FormattingConverter, we can supply a format string to pretty format the numbers.

<UserControl>
    <FrameworkElement.Resources>
        <Local:MathEvalConverter x:Key="Eval" Format="C2" />
        <Local:FormattingConverter x:Key="Date" Format="d" />
    </FrameworkElement.Resources>
    <Form:DataForm Header="Expense Report">
        <StackPanel>

            <Form:DataField Label="Reported By">
                <TextBox Text="{Binding Name, Mode=TwoWay}" />
            </Form:DataField>

            <Form:DataField Label="Expense Date">
                <TextBox Text="{Binding Date, Converter={StaticResource Date}, Mode=TwoWay}" />
            </Form:DataField>

            <Form:DataField Label="Meals">
                <TextBox Text="{Binding Meals, Converter={StaticResource Eval}, Mode=TwoWay}" />
            </Form:DataField>

        </StackPanel>
    </Form:DataForm>
</UserControl>

Summary

Enough talk. Download the code, and let me know what you think. I have no idea if anyone finds these posts useful if you don’t post comments!

Download MathEvalConverter.zip