Joe White’s Blog

Life, .NET, and Cats


AutoHotKey for quick Git access

December 24th, 2011

I really like Git’s GUI tools: Git Gui for reviewing diffs and committing, and Gitk for dealing with branches. They’re powerful tools — powerful enough that I’m skeptical of any IDE that claims to include all that functionality in a little tool window.

So that means I do a lot of task switching between my IDE, Git Gui, and Gitk. And since I hate repetitive Alt+Tabbing, I’ve written an AutoHotKey script to make it easier.

AutoHotKey is an open-source tool that lets you bind scripts to various keys or key combinations on your keyboard. I’ve chosen these keybindings:

  • Numpad Minus: bring up the Git Gui window (or start it if it’s not already running), send F5 to refresh it, and put focus in the “description” box.
  • Numpad Plus: bring up the Gitk window (or start it if it’s not already running) and send F5 to refresh it. (Mnemonic: Gitk shows a tree, hence my choice of “+”)

I wrote a little AutoHotKey script and put it in version control at work, and several of the other devs started using it too. It’s almost disorienting when I’m at someone else’s computer and they don’t have it running.

Here’s the script. I suggest saving it as a GitHotKeys.ahk file somewhere inside your Git working copy; that way, if it needs to launch Git Gui or Gitk, they’ll automatically come up in the right repository.

; Copyright (c) 2011 Joe White
;
; Permission is hereby granted, free of charge, to any person obtaining
; a copy of this software and associated documentation files (the
; "Software"), to deal in the Software without restriction, including
; without limitation the rights to use, copy, modify, merge, publish,
; distribute, sublicense, and/or sell copies of the Software, and to
; permit persons to whom the Software is furnished to do so, subject to
; the following conditions:
;
; The above copyright notice and this permission notice shall be
; included in all copies or substantial portions of the Software.
;
; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
; OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
; SOFTWARE.

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.
SetTitleMatchMode, 1

NumpadSub::
IfWinNotExist, Git Gui
  Run, git gui,, Hide
WinActivate, Git Gui
IfWinActive, Git Gui
{
  ControlGetPos, control_x, control_y,,, TkChild18, Git Gui
  CoordMode, Mouse, Screen
  MouseGetPos, mouse_x, mouse_y
  click_x := control_x + 5
  click_y := control_y + 5
  CoordMode, Mouse, Relative
  Click %click_x%, %click_y%
  CoordMode, Mouse, Screen
  MouseMove, %mouse_x%, %mouse_y%, 0

  Send, {F5}
}
return

NumpadAdd::
IfWinNotExist, gitk
  Run, gitk
WinActivate, gitk
IfWinActive, gitk
  Send, {F5}
return

CoffeeScript’s for loops don’t work on strings (despite appearances to the contrary)

October 29th, 2011

I’ve been playing around with CoffeeScript, and found myself needing to loop over the characters (and indexes) in a string. So I tried a simple CoffeeScript for loop:

for char, index in line
    # ...

I ran it, and it worked, even in IE, so I assumed that CoffeeScript was doing whatever magical syntax was required to make it work everywhere.

Later I found out I was wrong. CoffeeScript was generating the syntax that works with arrays — list[index] — and that syntax only happens to sometimes work with strings. It works consistently in Chrome, and FireFox, and Opera. It even works in IE… but only if you have an HTML5 doctype.

<!DOCTYPE html>
<script>
document.write('abc'[1]);
</script>

The above HTML page works fine in IE9, outputting b as expected. But if you remove the <!DOCTYPE html> line, IE outputs undefined instead. That’s right, it loads a completely different version of JavaScript depending on what version of HTML you’re using! And if you load the above page into a WPF Frame, even the doctype won’t save you — it always outputs undefined.

When I went back and looked through the CoffeeScript documentation, sure enough, nowhere do they actually say that for loops can be used to iterate through the characters in a string. The fact that it ever works is apparently an accident, so don’t rely on it.

If you need to loop over the characters in a string, the right thing is to loop over the indexes and use charAt: See update below

for index in [0...line.length]
    char = line.charAt index
    # ...

Edit: Here’s a better way, suggested by the kind CoffeeScript folks:

for char, index in str.split ''

Contributing to open source: uhttpsharp

October 26th, 2011

I’ve been using open-source since at least my senior honors project in college, 15 years ago. I’m well overdue to give something back.*

Last night I made my very first commit to an open-source project: uhttpsharp, “a very lightweight & simple embedded http server for c#”. And I’ve added quite a bit of stuff in the day since that first commit — file serving, customizable index and error pages, keep-alive support. I’m thinking about adding NuGet support at some point.

If you need a simple HTTP server for .NET, and you don’t want to use HttpListener (e.g. because you don’t want to require admin permissions to register the URI prefix), go check it out.

* Actually, I’ve done plenty of open-source work before, if you count stuff that I wrote myself. And some of that stuff does count; I think a few people have found DGrok helpful. But I have a strong tendency toward not-invented-here syndrome… and this time I offered to contribute to an existing open-source project, that I heard about through posts on StackOverflow. So it’s not just me writing something by myself, which is cool.

Well, okay, I’m contributing to this project so I can use it in another project that I am writing by myself…

.NET and CoffeeScript: comparing Jurassic, Jint, and IronJS

October 16th, 2011

Recently I went looking for ways to write a .NET desktop app that could compile CoffeeScript to JavaScript. There are already several NuGet packages for exactly this, but most of them look like they’re tightly bound to ASP.NET. So I struck out on my own, following the general steps in “CoffeeDemo – A Simple Demo of IronJS, using CoffeeScript”. The main CoffeeScript compiler is written in CoffeeScript, but they also provide one written in JavaScript, so my basic outline was:

  1. Instantiate a JavaScript engine.
  2. Tell the JavaScript engine to run coffee-script.js. This creates the CoffeeScript object and its compile method.
  3. Tell the JavaScript engine to call CoffeeScript.compile, and pass a string containing the CoffeeScript code to compile.

But my first attempt ran slower than I’d hoped for. (Well, coffee-script.js is 163 KB, and that’s the minified version! So I guess it does have a lot to do.)

I decided to find out whether I could do better: I tried several different JavaScript-in-.NET implementations, to see which one would perform the best. I tested Jurassic, Jint, and IronJS. My results are below, along with the C# code in case anyone is interested in seeing the minor differences between the APIs.

In all three cases, the coffeeCompiler parameter contains the 1.1.2 version of coffee-script.js, as downloaded from GitHub; and the input parameter contains a one-line CoffeeScript script:

alert "Hello world!"

Jurassic

Jurassic dynamically compiles JavaScript to CLR code at runtime, so you take a performance hit the first time you run some JS, but it should be pretty fast after that. Jurassic is available via NuGet.

private void CompileCoffeeScriptUsingJurassic(
    string coffeeCompiler, string input)
{
    Console.WriteLine("Jurassic");
    var stopwatch = Stopwatch.StartNew();
    Console.WriteLine(stopwatch.Elapsed + ": Creating engine");
    var engine = new ScriptEngine();
    Console.WriteLine(stopwatch.Elapsed + ": Parsing coffee-script.js");
    engine.Execute(coffeeCompiler);
    Console.WriteLine(stopwatch.Elapsed + ": Adding compile wrapper");
    engine.Execute("var compile = function (src) " +
        "{ return CoffeeScript.compile(src, { bare: true }); };");
    Console.WriteLine(stopwatch.Elapsed + ": Compiling CoffeeScript input");
    var output = engine.CallGlobalFunction("compile", input);
    Console.WriteLine(stopwatch.Elapsed + ": Done");
    Console.WriteLine("Output:");
    Console.WriteLine(output);
    Console.WriteLine();
}

Jint

Jint is a JavaScript interpreter. It’s not available through NuGet yet, but it’s a single DLL.

private void CompileCoffeeScriptUsingJint(
    string coffeeCompiler, string input)
{
    Console.WriteLine("Jint");
    var stopwatch = Stopwatch.StartNew();
    Console.WriteLine(stopwatch.Elapsed + ": Creating engine");
    var engine = new JintEngine();
    Console.WriteLine(stopwatch.Elapsed + ": Parsing coffee-script.js");
    engine.Run(coffeeCompiler);
    Console.WriteLine(stopwatch.Elapsed + ": Adding compile wrapper");
    engine.Run("var compile = function (src) " +
        "{ return CoffeeScript.compile(src, { bare: true }); };");
    Console.WriteLine(stopwatch.Elapsed + ": Compiling CoffeeScript input");
    object output = null;
    try
    {
        output = engine.CallFunction("compile", input);
    }
    catch (JsException ex)
    {
        Console.WriteLine("ERROR: " + ex.Value);
    }
    Console.WriteLine(stopwatch.Elapsed + ": Done");
    Console.WriteLine("Output:");
    Console.WriteLine(output);
    Console.WriteLine();
}

IronJS

IronJS is based on the DLR, so it seemed like it might strike a great balance between upfront compile time and runtime — after all, that’s what the DLR is all about.

IronJS is available through NuGet — there’s both an IronJS.Core (standalone) and an IronJS (depends on IronJS.Core), with nothing to explain the difference between the two; but at least for this code, you only need IronJS.Core.

private void CompileCoffeeScriptUsingIronJs(
    string coffeeCompiler, string input)
{
    Console.WriteLine("IronJS");
    var stopwatch = Stopwatch.StartNew();
    Console.WriteLine(stopwatch.Elapsed + ": Creating engine");
    var engine = new CSharp.Context();
    Console.WriteLine(stopwatch.Elapsed + ": Parsing coffee-script.js");
    engine.Execute(coffeeCompiler);
    Console.WriteLine(stopwatch.Elapsed + ": Adding compile wrapper");
    engine.Execute("var compile = function (src) " +
        "{ return CoffeeScript.compile(src, { bare: true }); };");
    Console.WriteLine(stopwatch.Elapsed + ": Fetching compile wrapper");
    var compile = engine.GetGlobalAs<FunctionObject>("compile");
    Console.WriteLine(stopwatch.Elapsed + ": Compiling CoffeeScript input");
    var result = compile.Call(engine.Globals, input);
    var output = IronJS.TypeConverter.ToString(result);
    Console.WriteLine(stopwatch.Elapsed + ": Done");
    Console.WriteLine("Output:");
    Console.WriteLine(output);
    Console.WriteLine();
}

The results

Jurassic
00:00:00.0000063: Creating engine
00:00:00.1540545: Parsing coffee-script.js
00:00:03.4346969: Adding compile wrapper
00:00:03.4408860: Compiling CoffeeScript input
00:00:05.3466983: Done
Output:
alert("Hello world!");

Jint
00:00:00.0000019: Creating engine
00:00:00.2617049: Parsing coffee-script.js
00:00:02.5270733: Adding compile wrapper
00:00:02.5295317: Compiling CoffeeScript input
ERROR: Parse error on line 2: Unexpected 'STRING'
00:00:02.5832895: Done
Output:

IronJS
00:00:00.0000019: Creating engine
00:00:00.2282421: Parsing coffee-script.js
00:00:55.5590620: Adding compile wrapper
00:00:55.5629230: Fetching compile wrapper
00:00:55.5642908: Compiling CoffeeScript input
00:01:17.8580574: Done
Output:
alert("Hello world!");

Jint wasn’t up to the task — it got a weird error when trying to call CoffeeScript.compile. I played with this a bit, and found that it would work if I passed an empty string, but give errors with non-blank CoffeeScript to compile; sometimes a string error like above, sometimes a weird error about multiline comments. It’s too bad, because Jint shows a lot of promise, speed-wise. I don’t know what the problem is; the error didn’t give me much to go on, and I’m not terribly motivated to pursue the problem when the other libraries work. (I did write this up in their bugtracker, though — it’s issue #6928.)

I was surprised that IronJS was so much slower than the others — about 20x slower than Jint at running coffee-script.js, and about 10x slower than Jurassic. This is especially puzzling since the article I based my code on mentions a “compilation lag”. To me, 55 seconds is hardly “lag”!

The winner here (and coincidentally the first one I tried) is Jurassic — so the performance that disappointed me is also the best I’m likely to get. On my laptop, you take about a 3.5-second penalty to compile coffee-script.js, and then another two seconds to run CoffeeScript.compile on a one-line script.

I did find that subsequent calls to CoffeeScript.compile were nearly instantaneous with all three libraries. So Jurassic’s 2 seconds is probably due to the JIT compiler running for the first time on that runtime-generated code. Not sure what to make of the 20 seconds for IronJS; is the DLR just that big?

Aptana, GPL, and the definition of “intact”

July 1st, 2011

I’m looking at Python IDEs and decided to try Aptana, but their installer isn’t giving the best first impressions. First it did nothing but peg my CPU for half a minute or so, without so much as a splash screen. Then, when it finally showed some GUI, the very first screenful of text was downright incomprehensible:

Aptana License Agreement

No, there’s no horizontal scrollbar to explain why the beginnings of words are cut off.

What I love most is how that last line insists that this victim of random hack-and-slash copy/paste must be kept… “intact”.

If Reflector needed money so badly, why didn’t they ask?

February 3rd, 2011

Your copy of Reflector will self-destruct at the end of February… unless you pay the ransom.

This is depressing, not because of the money — I could easily pay $35 for a kick-ass tool like Reflector — but because of the betrayal of trust.

It’s not unlike the way Borland repeatedly betrayed their users’ trust with crap like “Inprise” and “Application Lifecycle Management”. (What’s left of Borland just got bought out. Good riddance.) Or the way Embarcadero priced me out of the Delphi market a couple of years ago. (They’ve since decided that was a bad move and started selling a starter edition. Some people learn from their mistakes, though sometimes too late.)

When RedGate bought Reflector, they said that they would continue to offer a free version. Now they admit that they lied. Well, actually, they don’t admit anything; they just repeatedly say that they never “promised” a free version. I guess that interview, and the “Red Gate will continue to offer the tool for free to the community” soundbite, were imaginary.

But wait! You can buy a version that will continue to work forever! Honest! They promise! Well, no, actually. If you search their open letter for the word “promise”, you’ll find it conspicuously absent.

I’ve been reading the reactions on StackOverflow, and even finally got a Twitter account so I could follow the news there. Some people say “suck it up, it’s worth it”. More people say “that’s not the point, RedGate has proven they can’t be trusted”. I lean toward the latter camp.

Then, across the Twitter feed comes a link to a YouTube interview with Simon Galbraith, one of the co-founders of RedGate, about the decision to charge for Reflector. Apart from again going on about the word “promise”, he explains something that should have been forefront of their announcement: keeping up with new frameworks and new platforms is a big deal. They want to make Reflector an even more awesome tool, and people haven’t been paying for Reflector Pro (our department actually did buy it, BTW) so they can’t bankroll what they want to do.

But instead of actually talking to the community about this, they kept it quiet. They “agonized” over it for about six months, and then decided that the right move was to break their word, antagonize the community, and try to extort the money, at the cost of their professional reputation.

They forgot two things that should have made this easy.

One, they forgot to ask. Wikipedia isn’t afraid to ask for donations, and they get them. NaNoWriMo isn’t afraid to ask for donations, and they get them. Granted, there are plenty of open-source projects with “Donate” buttons that probably never see a dime. But for a tool like Reflector, if they had said, “Hey, we want to do X and Y and Z to make this great tool even better, but we can’t do it without your help. We need to raise this many dollars to make it happen. Who’s with us?” I think people would have responded.

And two, empathy matters. Putting up a cold, faceless, impersonal warning icon that says “Screw you, we know we said we wouldn’t do this but we’re sticking it to you anyway” is not going to earn you many friends.

Compare that to: “We need your help. We want to keep up with new platforms and features, and we want to do more than keep up: we want to make an awesome tool even more awesome. But we can’t do it without you. We know you’ve always had Reflector for free — and we promise that won’t change — but if this tool is going to survive the changes Microsoft has in store and still get even better than it’s ever been, if this tool that’s satisfied your curiosity, and taught you loads, and, yes, saved your butt time and time again, is going to stay relevant, we need you. Think back over the questions you’ve answered with Reflector, the number of people you’ve recommended Reflector to, the times you’ve sworn you couldn’t do your job without Reflector, and then just tell us this: Can we count on your help?”

Sigh.

Okay, that’s enough of that. Off to check out Monoflector.

“Retail” comic gets its own site, full archive

October 13th, 2010

For a while, my hometown’s local newspaper, the Cedar Rapids Gazette, carried a comic strip called “Retail”. I enjoyed the strip’s wry humor, even though I’ve never worked in retail. (I spent a few months as a checker in a grocery store, but that doesn’t compare to what happens in the strip.)

Earlier this year, the Gazette dropped “Retail”, which was very sad. (I only read the Gazette when I went back to visit family, but still, “Retail” was one of the high points of the comics page.) And here in Omaha, the World-Herald has never carried the strip. So I was psyched at the beginning of this year when Norm Feuti, the author, started posting daily “Retail” re-runs from 2006 on his personal site. It’s been in my Google Reader all year.

Now he’s announced that the “Retail” comic now has its own Web site, with unlimited archives. And yes, it has an RSS feed, so I can still get “Retail” in Google Reader — but now it’s today’s strip. Sweet!

From what I can tell, “Retail” is still syndicated in newspapers — it hasn’t made the jump to 100% webcomics. And I don’t see any way to buy dead-tree collections of the comics. (Not that I’ve ever bought a paper compilation of a webcomic… thought about it, never done it.) So for now, the strip is in an interesting state. But it’s an interesting state with full archives, so I’m happy.

If you haven’t read the comic, and enjoy dry humor, go check it out.

Interesting times

August 15th, 2010

I keep thinking I should blog about events in my life, so I can look back later and say, “When did that happen?” Maybe nobody but me will care, but the past couple of weeks were interesting enough that I’m going to blog about them anyway.

First, there was the hailstorm a week ago Thursday. Jennie and I were watching TV when we heard a weird thump overhead. It sounded like somebody had thrown a baseball at our roof. Then another, and another. I looked outside and it looked like someone had been throwing snowballs at our driveway. I later heard that it had been “softball-sized” or “three-inch” hail. It only lasted about two minutes, but it was crazy.

My car had been parked in the driveway, and the next morning, I found that its back window was basically gone. Maybe a third of its area was still intact; the rest was just not there anymore. There were tiny shards of glass everywhere, including the front seat of the car. This is the old car; we didn’t have comprehensive insurance on it, so I had to pay to repair it. It also hasn’t been able to start since last November, so we had to get it towed to the shop (it was either that, or have it get rained in). On the plus side, they got it running again, and they fixed the air conditioner too.

Then we found out our house’s roof took a beating in the storm, and we may have to get the whole roof replaced.The adjuster hasn’t been out to look at it yet, so we don’t know for sure, but we’re saving up to pay the deductible if they do decide it needs replacing.

Tycho has barely been eating for a while now, even if we offer him wet food, so Jennie took him to the vet. He’s lost a full pound, which doesn’t seem like a good sign, and the vet took X-rays (these are our cats — we’ve learned to take X-rays and do bloodwork if anything seems even a little out of the ordinary) and she found that he had several teeth whose roots were splitting. Doesn’t that sound like fun? She said he didn’t need them extracted right away, and gave him some pain medication. When he got home, he snarfed as much food as we could put in front of him. Since then, he’s been back to eating a few mouthfuls at each meal, even though the pain medication was supposed to last three days and we’ve given him even more pain meds since then. We’ll probably need to do something more about this soon.

And yesterday, I got scratched in the face by another cat — one with all four sets of claws, and one who was in a stressful situation and obviously far, far more freaked out than I realized. Also in a position to use all four sets of claws on my face at once, because I was stupid. Pulled my glasses right off. Didn’t get my eyes, thank goodness, but my eyebrows, both cheekbones, a couple of deep gashes in my chin, even clipped one ear and the back of my head. I went to urgent care and got five stitches in my chin. I looked like Frankenstein’s monster for most of the weekend, with all the dried blood from all the scratches. The blue thread of the stitches makes the red cut look like this huge scary purple gash, but when you look closer it’s not as frightening as it looks from a distance. The doc gave me a tetanus shot and some antibiotics, just in case, so now I’ve got a sore arm and chills (aftereffects of the tetanus shot) and a sore face (Advil helps). But I’ll be fine. I’m not one of those lawyer-happy freaks who would sue or something stupid like that — sometimes shit just happens! The cat has been removed from the stressful situation and should also be fine; this was hugely out of character and there’s no reason to think it’ll happen again — just one of those freak things. And on the plus side, with this cut on my chin, I may well end up with a Harrison Ford scar.

Oh, and between the vet bill, the car-repair bill, and one or two other things, Jennie and I had to sit down and redo the budget last Friday. We were afraid we would have to empty our emergency fund. It turns out we didn’t have to touch it. We reallocated a bunch of stuff, put other things off for another month or two, and found over a thousand dollars in this month’s budget. We had to make a few tough calls, but man. This is why we budget.

So there’s definitely some good stuff to go with all the excitement. But still, I think I’ll go do something very uninteresting now.

Refactoring with MVVM is easy!

July 30th, 2010

I’ve built up a kind of intellectual appreciation for some of the things MVVM gives you, but today I had a real “wow!” moment.

I’ve been working on a WPF UserControl, and it was getting kind of big and cumbersome. I wanted to extract some pieces from it into smaller UserControls to make it more manageable (and to make it easier to make some changes to the high-level layout).

So I created a new UserControl, moved my XAML into it, and referenced it from the original spot.

And that was it. I ran it, and it worked. That simple. No worries about moving dozens of code-behind methods onto the new control. No messing with method and field visibilities, and figuring out which objects the new control needed to have references to so it could do its work. No re-hooking event handlers.

Okay, it wasn’t quite cut-and-paste — there was some fixup to be done. The new UserControl needed some xmlns: attributes added. And I wanted the attached layout properties (Grid.Row, Grid.Column) to stay in the original file, not move into the new one (they’re part of the parent layout, not intrinsic to the child UI). So it took maybe a minute or so.

But it was nothing like the splitting headache that is extracting a UserControl in WinForms.

And then I extracted another UserControl. And I ran. And it just worked.

Wow.

Just, wow.

But the downside is, now I’ve got this overwhelming temptation to rewrite our million-line codebase in WPF…

MVVM and DialogResult with no code-behind

July 25th, 2010

I like the Model-View-ViewModel pattern in WPF, and the way it helps get code out of the UI and into a place you can test it. But every now and then you run into a weird limitation — something you can’t do out of the box. One such example is closing a dialog box.

WPF’s Button doesn’t have a DialogResult property like buttons did in Delphi and WinForms. Instead, the codebehind for your OK button has to manually set the Window’s DialogResult property to true. This makes sense in principle — it lets you validate the user input before you close — but it makes it hard to use “pure” MVVM with no code-behind. I don’t actually give a hoot about blendability (I still write all my own XAML), but since I’m still learning WPF and MVVM, I take it as a challenge to find pure-MVVM solutions to problems, just as a learning exercise.

The obvious (wrong) solution

The obvious solution would be to just do this:

<Window ...
        DialogResult="{Binding DialogResult}">

Then make your ViewModel implement INotifyPropertyChanged in the usual way, and DialogResult gets pushed up to the view the same way as everything else. Right?

Unfortunately, DialogResult isn’t a dependency property (good grief, why not?), so the above code gives you a runtime error when you try to create the window:

A ‘Binding’ cannot be set on the ‘DialogResult’ property of type ‘TestWindow’. A ‘Binding’ can only be set on a DependencyProperty of a DependencyObject.

Back to the drawing board.

Others’ solutions

Some Googling found a StackOverflow post, “how should the ViewModel close the form?”, with an accepted answer (with 5 downvotes) of “give up; you can’t use MVVM for dialog boxes”. But I’m not quite ready to throw in the towel, so I keep reading.

Another answer on the same question — which had 0 upvotes at the time I read it, despite perfectly answering the question — pointed to a blog post by Adam Mills: “Window.Close() from XAML”. Adam’s solution uses an attached behavior. I’m learning to appreciate the attached-behavior pattern; you create an attached property, but then give it side-effects. It’s a good way to get code out of the codebehind, and it forces you to make it reusable at the same time.

But I’m not crazy about the details of Adam’s solution, because it requires you to create a style, hook up triggers, …a lot of mess. His post doesn’t actually have a complete code sample, so I’m not even sure how you hook the style into your window, though I’m sure I could puzzle it out eventually. And even his incomplete example is five lines of XAML. It’d probably be up to 7 or 9 by the time you actually got it fully wired up, and that’s 7 or 9 lines that you have to repeat for every dialog box you write.

Shouldn’t it be simpler? Shouldn’t it be almost as simple as the databinding syntax would have been, if the WPF team had gotten it right and made DialogResult a dependency property?

The one-line* attached behavior

* Okay, yes, it’s two lines if you count the XML namespace.

So I rolled my own attached behavior that does make it almost that simple. Here’s how you use it:

<Window ...
        xmlns:xc="clr-namespace:ExCastle.Wpf"
        xc:DialogCloser.DialogResult="{Binding DialogResult}">

Your ViewModel should expose a property of type bool? (Nullable<bool>), and should implement INotifyPropertyChanged so it can tell the view when its value has changed.

Here’s the code for DialogCloser:

using System.Windows;
 
namespace ExCastle.Wpf
{
    public static class DialogCloser
    {
        public static readonly DependencyProperty DialogResultProperty =
            DependencyProperty.RegisterAttached(
                "DialogResult",
                typeof(bool?),
                typeof(DialogCloser),
                new PropertyMetadata(DialogResultChanged));
 
        private static void DialogResultChanged(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var window = d as Window;
            if (window != null)
                window.DialogResult = e.NewValue as bool?;
        }
        public static void SetDialogResult(Window target, bool? value)
        {
            target.SetValue(DialogResultProperty, value);
        }
    }
}

I’ve posted this as an answer on the StackOverflow question, so if you think it’s a good solution, feel free to vote it up so that others can find it more easily.


Joe White's Blog copyright © 2004-2011. Portions of the site layout use Yahoo! YUI Reset, Fonts, and Grids.
Proudly powered by WordPress. Entries (RSS) and Comments (RSS). Privacy policy