WPF commands with nested focus scope

Updated on Sunday, September 30, 2018

Here's a frustrating WPF scenario — you use the ApplicationCommands class to add Cut, Copy and Paste commands to toolbar and then put a TextBox on another toolbar. Click in the TextBox and the commands remain disabled. WTF, WPF?

The problem is with focus scopes. Your window is a focus scope and so are any menus or toolbars. This has the desirable property of allowing commands to target the control you were in immediately before invoking the command. You want paste to target the text box you're editing, not the menu item or button you clicked to request the paste.

So far so good. The problem is that the commanding system isn't smart enough to target the control with keyboard focus if it's in a nested focus scope. Remember that the window itself is a focus scope so our TextBox in a ToolBar (also a focus scope) is nested and immune to commands from our menu or toolbar.

Here's a simple window that demonstrates the problem:

Ignore the PreviewCanExecute handler for now. If you run this window and click in the main TextBox the paste button and menu item are enabled. Click in the toolbar TextBox and pasting isn't an option. Well, Ctrl-V still works and there's a context menu but you know what I mean.

The problem can be fixed by adding a command binding for ApplicationCommands.Paste and handling the PreviewCanExecute event:

When the window loads we're making note of the focus scopes for the toolbar and menu. Then when PreviewCanExecute fires we check to see if the element with the keyboard focus is in a different focus scope (and also that the window doesn't have keyboard focus). We then set the CommandTarget for the menu item and button to the element that has keyboard focus.

A handler isn't required for CanExecute as the command will take care of this with respect to the new CommandTarget.

Run the window again and you'll see that the paste button is enabled for both of the TextBox controls. When you click the button (or menu item) our PreviewCanExecute handler ignores the new keyboard focus and the command is sent to the desired control. 

One drawback of this approach is that keyboard focus isn't returned to the TextBox after the command executes. The CommandTarget remains in place so you can keep pasting and the command remains enabled but you lose the visual cue that lets you know where the target is. I haven't figured out a clean approach to this yet. When I do, I'll update this post. Better yet, if you've figured it out leave a comment.

(Related: Monitor page index status with Google Sheets, Apps Script and the Google Search Console API; WiX Tricks for Screen Savers; Get an email when your security camera sees something new (Apps Script + Cloud Vision))

(You might also like: GGNRA Dog Management Round 3; Capture DropCam (Nest Cam) frames to Google Drive; San Francisco Shoreline Timelapse)

(All Code Posts)

Converting Blogger ATOM export to BlogML

Updated on Thursday, December 26, 2019

I'm slowly converting a number of blogs from Blogger to BlogEngine.NET. The least fun part is dealing with the Blogger export file. For this blog I used a Powershell script but had problems with comments not exporting correctly and it was quite painful to fix everything up. Blogger allows you to export a copy of your blog using ATOM, however BlogEngine.NET (and other tools) speak BlogML.

I've just released a command line tool that takes the ATOM format Blogger export and converts it to BlogML. You can download Blogger2BlogML from GitHub. The tool uses .NET 4.0 (client profile) so you'll need to install this if you don't already have it. If you give Blogger2BlogML a try let me know how you get on. 

(Related: Migrating from Blogger to BlogEngine.NET; Export Google Fit Daily Steps, Weight and Distance to a Google Sheet; Capture DropCam (Nest Cam) frames to Google Drive)

(You might also like: California and San Francisco June 2016 Propositions; Mount Davidson; Commentary)

(All Code Posts)

StackOverflow DevDays

Updated on Saturday, July 18, 2020

Fort Mason, San Francisco

Just got back from the StackOverflow DevDays event in San Francisco. I was a bit worried that this would be overly focused on marketing FogBugz and StackOverflow. There were brief pitches on each (and I learned that FogCreek is launching hosted source control and code reviews called Kiln, now in beta and looking pretty nifty with tight integration with FogBugz) but this wasn't the focus.

I was also a little concerned that I'd be the only one there without a StackOverflow profile t-shirt. Luckily I didn't see any reputation toting pod people at the conference.

Happily the day was very code oriented, and very diverse. Spell checking in Python, smartphone development for iPhone, Nokia (via Qt) and Android, ASP.NET MVC and jQuery. I spend most of my time at the moment in C#/.NET and it was really valuable to spend a day briefly diving into different stacks and platforms.

Joel said that they'd be back next year and I'm hoping that it offers a similar diverse range of topics.

(Related: Business of Software 2009; The Secret Diary of a Xamarin Android Developer, Aged 48 1/3; Twitter's API has got too painful for me)

(You might also like: WPF commands with nested focus scope; Path; Sand Ladder at Fort Funston)

(All Code Posts)

MessageInterceptor doesn't always...

Updated on Sunday, May 3, 2020

I've been writing a play by text message version of battleships for Windows Mobile 6 using the MessageInterceptor class to receive messages. This works great on my AT&T Tilt but completely fails on my wife's Pantech device. The MessageInterceptor hooks up fine but never fires.

The 2.0 compact framework SmsAccount lets you send messages but unaccountably doesn't let you read them.

I avoided some unpleasant interop by grabbing the MAPIdotnet library. This implements a NewMessage event for MAPI stores, however as with MessageInterceptor the event hooks up OK but never fires on the damned Pantech.

MAPIdotnet does allow you to read the SMS inbox though, so the final answer is to fall back to looking for, processing and then deleting game related text messages on a timer. This is far from ideal as the game messages arrive and vibrate the phone before getting processed but at least it's now possible to play without buying a new phone.

To add to the frustration during development the Visual Studio 2005 toolbox lost all icons except for the pointer and a custom control that was part of the project. Resetting the toolbox didn't help, nor did restarting VS and the computer. The fix was to exit VS and then delete all the .tbd files from Microsoft\VisualStudio\8.0 in local application data. Sigh.

(Related: Export Google Fit Daily Steps, Weight and Distance to a Google Sheet; Capture DropCam (Nest Cam) frames to Google Drive; Monitor page index status with Google Sheets, Apps Script and the Google Search Console API)

(You might also like: Skype for Android - Getting Closer; Fitbit on a bike; Fort Funston in the rain)

(All Code Posts)

WiX Tricks for Screen Savers

Updated on Wednesday, May 5, 2021

I've been migrating my installers over to WiX. My only complaint with the WiX toolkit is that there's no bootstrapper included. This is important for installing any pre-requisites before passing control over to Windows Installer. Hopefully this will come after WiX 3.0 is released. For now, I've rolled my own bootstrapper to install .NET 2.0 if needed.

A couple of tips for screen savers. You can bring up the Windows screen saver settings using the following custom action. This is the same command that is launched when you right-click a screen saver (.scr) file and pick Install:

<CustomAction Id='InstallSS' Directory='SystemFolder' ExeCommand='rundll32.exe desk.cpl,InstallScreenSaver the.scr' Return='asyncNoWait'/>

Sequence this in InstallExecuteSequence after InstallFinalize:

You can also add a shortcut to your Program Files folder to configure the screen saver. This is really helpful for people who aren't sure how to get to the screen saver dialog from Control Panel or right-clicking the Desktop:

Unlike a shortcut to a file that you install you need to specify the Target. The example above assumes that ProgramMenuDir is the Id of your Start Menu folder. The shortcut should be in a Directory but not as a child of a File node.

Updated 2020-05-26 20:22:

It looks like something changed in Windows 10, around build 1903, that stops this from working on 64-bit systems. The screensaver will preview OK but does not start as expected (system screensavers start OK, so it's not some sort of power management problem). I could reproduce this on one box running 1909 but not another so maybe there is something else going on. The snippets above will run rundll32.exe from the SysWOW64 folder and at some point this seems to have stopped working. Changing to [System64Folder]rundll32.exe launches the System32 version which then causes the screensaver to load normally. If your screensaver is 32-bit and installed to SysWOW64 then you need a command that looks like this on a 64-bit system: [System64Folder]rundll32.exe desk.cpl,InstallScreenSaver [SystemFolder]the.scr

(Related: Catfood Software Support; Capture DropCam (Nest Cam) frames to Google Drive; Export Google Fit Daily Steps, Weight and Distance to a Google Sheet)

(You might also like: Privacy Policy Update and Comment Notifications; Treasure Island; Teddybear Cholla)

(All Code Posts)

code, c#

I Thought He Came With You is Robert Ellison's blog.

Newsletter