Wednesday, December 26, 2007 - Posts
NavigationWindow, WinFormsHost and TextBoxes: backspace bug
WPF NavigationWindow is a very handy class that allows you to easily build Vista-like wizards, or any kind of window that resembles the browser-style navigation:
The NavigationWindow will automatically handle the Backspace, Back/Next keys and perform the appropriate navigation on the window pages.
If you’re combining this class with the WindowsFormsHost to integrate WinForms controls, you may hit a very annoying bug: if there are textboxes in your WinForm control, hitting Backspace while editing the input on it will cause the navigation window to navigate back, instead of deleting the text on the TextBox. Yikes!
Being WPF the new kid on the block, I was immediately tempted to blame it. Turns out it’s a bug in the WinForms TextBoxBase :S.
You see, before WPF gets a chance to handle an input message, WindowsFormsHost tries to determine whether any contained control is interested in handling the message first and gives it the option to do that right away. This is done by calling the WinForms Control.PreProcessControlMessage() which ultimately calls the virtual Control.IsInputKey which controls use to determine a particular key is part of their input processing. The problem is that TextBoxBase does not say it’s interested in handling the Backspace key (but it does so for Esc, Tab, Home, End, etc., and inherits the text keys from the base Control class implementation of IsInputKey).
Therefore, the WindowsFormsHost doesn’t do anything further with the message, and the message enters the WPF input system, where it induces events tunneled and bubbled across the element tree. Eventually, NavigationWindow handles the key thanks to the built-in CommandBinding for navigating back. Bummer :(.
Fortunately, there are a couple workarounds:
-
Create your own TextBox-derived control that fixes the IsInputKey implementation and use that in your controls:
public class TextBox2 : TextBox { protected override bool IsInputKey(Keys keyData) { return base.IsInputKey(keyData) || keyData == Keys.Back; } }; -
Cancel NavigationWindow’s CommandBinding for the backspace key by doing this in the constructor of your NavigationWindow-derived class:
InputBindings.Add(new KeyBinding(ApplicationCommands.NotACommand, new KeyGesture(Key.Back)));
I prefer the latter, as it doesn’t require you to modify your existing WinForms controls. On the other hand, it removes the Backspace gesture for navigating back, but that’s not too bad as I doubt users are actually going to miss it (how many people navigate back in the browser by pressing Backspace anyway?)
I created a small repro and reported this bug through Connect. Feel free to vote for it ;)
posted Wednesday, December 26, 2007 10:20 AM by kzu with 1 Comments
What’s wrong with the Record/Reply/Verify model for mocking frameworks
Most mocking frameworks, and especially the two most popular ones, Rhino Mocks and TypeMock, use a record/reply/verify model where the developer invokes members on the mock during the record phase, does a replay to prepare the mock for those expectations, and finally do a verify before the test ends.
From Rhino documentation (emphasis mine):
Record & Replay model - a model that allows for recording actions on a mock object and then replaying and verifying them. All mocking frameworks uses this model. Some (NMock, TypeMock.Net, NMock2) use it implicitly and some (EasyMock.Net, Rhino Mocks) use it explicitly.
Well, it’s not true anymore that *all” mocking frameworks use record/replay :).
Anyway, an example from Rhino is:
[Test]
public void SaveProjectAs_CanBeCanceled()
{
MockRepository mocks = new MockRepository();
IProjectView projectView = (IProjectView)mocks .CreateMock(typeof(IProjectView));
Project prj = new Project("Example Project");
IProjectPresenter presenter = new ProjectPresenter(prj,projectView);
Expect.Call(projectView.Title).Return(prj.Name);
Expect.Call(projectView.Ask(question,answer)).Return(null);
**mocks.ReplayAll();**
Assert.IsFalse(presenter.SaveProjectAs());
**mocks.VerifyAll();**}
I find this record/replay/verify model somewhat unnatural. When you follow the typical progression explained in Mocks, Stubs and Fakes: it’s a continuum, you end up thinking about your mocks as invokers of your delegates/lambdas. There’s no especial concept to understand. It’s “plain .NET” if you will. The above test in MoQ (which is the result of taking that very progression and turning it into a generic mock framework) looks like the following:
[Test]
public void SaveProjectAs_CanBeCanceled()
{
string question, answer;
var projectView = new Mock<IProjectView>();
var prj = new Project("Example Project");
var presenter = new ProjectPresenter(prj, projectView.Object);
projectView.Expect(x => x.Title).Returns(prj.Name);
projectView.Expect(x => x.Ask(question, answer)).Returns(null);
Assert.IsFalse(presenter.SaveProjectAs());
}
Note that the only assertion is the one on the presenter.SaveProjectAs. There are absolutely no “side effects” from using the mock framework. It’s just a helper for your traditional state/classic test. And MoQ is mostly syntactic sugar for what you could have done yourself.
Verifying the mock slowly but almost inevitably takes you down the path of focusing your tests in the interaction as opposed to the state of the object under test. I agree with mockists that sometimes an interaction test is the only way to ensure a given behavior (cache hits/misses is the most evident one), but it’s more of the exception than the rule IMO.
That’s why MoQ doesn’t provide a Verify method on its Mock
posted Wednesday, December 26, 2007 4:20 AM by kzu with 8 Comments
/kzu dev↻d