User Feedback and the tension between Features and Maintenance

I've found User Feedback to be extremely important not only for detecting bugs but also for the way it influences app design. The better you understand how your users use an app the better you can accommodate that in the design.

Sometimes I find myself wishing I could record users using an app during various stages of development; when I'm there their behavior is invariably changed. As the developer I can't help but want to assist even though my help will tend to obscure cases where they might have gotten stuck. Another person present, "standing over your shoulder" so-to-speak, creates an audience and having an audience impacts different people differently. Both of these can act to exaggerate or minimize behaviors that would yield insight into how to improve the application.

Alas, in the real world user interaction studies are expensive, time consuming and, consequently, relatively rare. I've only once ever had the privilege of observing one.

As important as User Feedback is in some cases it can work against the goal of maintainability. Accommodating the specific request of the user in front of you means more special cases. Sometimes a user may not feel that they're being helpful unless they ask for something while you're there doing the walkthrough.

Early on this is rarely a problem but once the project reaches a certain level of complexity accommodating special cases gets harder to do without inadvertently breaking something else. Which means doing it is more likely to result in bugs either immediately or later when failing to accommodate the growing list of special cases. While it's really important to make the app do what its users want it to do, and therefore change the app based on their feedback, it's also important for the app to not crash and to make accommodating future requests less likely to cause a crash.

This tension tends to be minimal to non-existent during the first few iterations then gradually increases as the ship/release date nears. Perhaps the ideal number of user reviews at any given moment is proportional (proportional-squared anyone?) to the time until the release date.

Let f(x) be a function where x is the number of weeks until a release and f(x) is the ideal number of reviews at x.

f(x) = k*x^2

where k is a fractional constant inversely proportional to the likelihood of the reviewer requesting changes. So

k = c / P(change)

where c is a fudge factor to make sure f(x) falls within a reasonable range. e.g., No point in a formula that recommends more user reviews than there are seconds in a day.

Notice how k gets larger as P(change) tends to zero? :)

Accommodating an ActiveX Control that's Interfering with Windows Forms Events

For reasons not entirely clear to me a particular ActiveX control appears to interfere with Windows Forms events. The vendor has long since offered a purely managed implementation so they have little interest in supporting use of the ActiveX control via Runtime Callable Wrapper. Unfortunately, times being what they are, funds aren't available to take advantage of the purely managed implementation.

By interfere I mean, while this ActiveX control is performing a potentially long running operation the UI owning thread "magically" resumes event processing. I suspect this is intentional; the vendors users probably complained that their UIs froze whenever they performed this potentially long running operation. Since this 3rd party control was consumed mostly by single threaded VB6 apps the vendor may have decided to respond to these complaints in a way that causes a problem for Windows Forms apps.

I have lots of theories (e.g., there may be some interplay between the ActiveX control being free threaded while the main thread of the WinForms app runs in a single threaded apartment) but little time to investigate them. So I'm avoiding the problem by running the ActiveX control on a separate thread.

The managed ThreadPool is a wonderful place to run this code; I don't have to manage the setup or teardown of ThreadPool threads and the operation isn't so long running that it will prevent other threads from accessing the ThreadPool.

Since I'm running the ActiveX control in a separate thread to avoid interference with the UI thread, not because I want to execute concurrently, I need to wait until it's done before proceeding (and I *want* the UI to be unresponsive during this time - even have the WaitCursor showing). So a ManualResetEvent is signaled once the operation is complete.

What does this have to do with Pre 3.0 C#? Well, C# 2.0 introduced anonymous methods. These provide a way to succinctly pass short behavior (no more than a few operations) without having to declare a separate method.

So something along the lines of the following:

ThreadPool.QueueUserWorkItem(new WaitCallback(
delegate(object throwAway)
// do something here

followed by


does the trick!

How to track logon attempts onto a WinXP/Server 2003 system

While looking for a way to figure out how long since a server last rebooted, I ran across the wonderful:

net statistics server

command. Gleefully I sent this off to my boss since it showed an uptime far longer than we were accustomed to. My glee was especially pronounced because the last server reboot had occured after I made a configuration change done specifically to increase uptime.

Of course, my boss was much more concerned about some other information revealed by net statistics server; namely, the number next to the phrase "password violations". Now I'm pretty sure that it's mainly people forgetting their password, autologon attempts that use the wrong password, etc... Par for the course in a Windows network.

Turns out Windows can write event log messages tracking detailed information about these sorts of activities. For instance, you can have Windows write an event log entry everytime someone successfully logs onto the machine. To do this:
  • Start gpedit.msc (from the run prompt).
  • Browse to Computer Configuration -> Windows Settings -> Security Settings -> Local Policies -> Audit Policy.
  • Double click "Audit Logon Events" then make sure both success and failure are checked.
  • Click OK.

That's it! Easily tested with a logon attempt using the wrong password. Remember to make sure you event log is sized properly and configured to rollover so that it doesn't fill up your hard drive.

Debugging while using BackgroundWorker

In an earlier post I extolled the virtues of BackgroundWorker. One peculiarity of working with it is that exceptions thrown during DoWork can be difficult to track down since it's not executing on the UI creating thread.

Fortunately Visual Studio 2005 (and up I assume) has a way to catch pretty much any Managed Exception (along with many Unmanaged Exceptions) even if it's handled by user code (e.g., by the AppDomain unhandled exception event). Under Debug->Exceptions -> Common Language Runtime Exceptions you can choose which Exceptions you want to trap/enter the debugger by checking the checkbox in the "Thrown" column.

As soon as all such exceptions are thrown the debugger traps the exception.

This is a great way to catch those Exceptions that would otherwise go unnoticed. Those unnoticed Exceptions often linger for months then become full blooded Application Crashes so anything that helps find them early is imho a good thing :)

The Intersection of a List of Strings in O(N)

Given k lists of strings the intersection of the lists is the set of strings that appear in every list.

The intersection is useful for, among other things, displaying options or values common to multiple selections. An action can then be performed on the common item in each list. For example, to provide a way to delete keywords that are common to objects that each maintain their own list of keywords, the intersection of those keyword lists is needed.

The naive algorithm is quadratic (O(n^2)).

To do this in O(n) use a Dictionary where the string is used as the key and int is a count of the number of times the key has been encountered.

Go through each list of strings. If a string hasn't been added to the Dictionary then add it with count=1. If the string already exists in the Dictionary then increment its counter. Finally iterate over every Dictionary entry accumulating only those keys with counter values equal to k. The keys with counter values equal to k form the intersection of the string lists.

As long as the underlying hashing function executes in constant time then this algorithm will execute in O(n).

About TreeViews and ListViews in Windows Forms

Recently I've been working on an explorer like application made up of a TreeView of folders, SplitContainers, ListView of items of interest and various TabControls.

TreeView, by default, hides the selection when the control loses focus. This can make it hard for users to determine which folder they're currently browsing when they click on any other control. Fortunately TreeView.HideSelection handles this by changing the background to SystemColors.Control when the control loses focus.

Ditto for ListView.

Drag and Drop is, in general, a LOT easier to use in Windows Forms than it was under MFC or even VB. However, it looks like there isn't built in support for changing the background color of nodes while the mouse hovers over them during a Drag and Drop operation.

Also, expanding the node under the mouse after hovering over it for a while during a Drag and Drop operation isn't built-in. Considering how convoluted that description is, it's no wonder!

BackgroundWorker is an absolute godsend! I'm glad I forced myself to use it. Make sure to save updating the UI for the progress event. Although it doesn't do anything you couldn't do manually (with a separate thread or the threadpool) having it available freed up time to handle other application related tasks (like interruptible directory scanning).

Bitmap.FromFile() will maintain a lock on the file; in my case this prevented as-you-go temp directory cleanup so used a MemoryStream instead.

ImageLists - Resizing destroys the underlying handle which resulted in ListView failing to render the images at the new size. The workaround I adopted was to copy the Images into a new ImageList (one created with the correct size). Suprisingly this ends up being more than performant enough for this application. Use a double-buffered ListView to reduce flicker.

Speaking of ListView, for reasons I haven't had time to determine, small icon view can render incorrectly when non-standard icon sizes and long item names are used. Oddly enough, it appears that the widths set for ListView.Columns elements, which shouldn't impact small icon view at all, seemed to effect the size allocated by ListView for each item. Since details view also has the small icon along with SubItems, I decided to drop small icon view in favor of details view.

RichTextBox rocks!

Getting Podcasts on Windows Mobile with BeyondPod

BeyondPod is a wonderful feed reader written entirely in .net (compact framework). There are some real small-form-factor UI gems in the program. Of course, performance could always be improved but IMHO it's definitely one of the best free feed readers out there.

Although it's great for reading feeds I've found that getting feeds into its list of feeds is a bit of a pain. Some of this is inherent in the form-factor; it's a lot easier to browse the web on a laptop or PC and web browsing is the best way that I've found to discover podcasts/feeds.

One way to get a list of feeds discovered on the web into BeyondPod is to use OPML files. These are xml files structured according to the OPML standard that can be used to aggregate feeds. There are plenty of OPML generators on the web.

So I usually load one of the web based OPML generators out there into a separate tab then copy and paste feed rss/atom links as I encounter them. Then I:
  1. Generate the opml file.
  2. Open it in an editor (UltraEdit) to remove all whitespace (BeyondPod unfortunately chokes on OPML files with whitespace between xml elements).
  3. Connect my Windows Mobile device.
  4. Copy the opml file onto the storage card.
  5. Import the opml file into BeyondPod.

With so many podcasts out there the frustrations of a long commute have been replaced with "darn, I'm at work already?" Usually in the middle of some juicy tech podcast....

Parsing the current date from a command prompt

It's often handy to be able to rename a file so that its filename includes the current date. Fortunately, since at least Windows XP, cmd.exe's wonder for /f option provides a way to do this:

REM parse current date
for /f "tokens=2,3,4 delims=/ " %%i in ('echo %DATE%') DO (
set YEAR=%%i
set MONTH=%%j
set DAY=%%k

This can be used later on in the .bat file with:

move /Y somefile.txt somefile-%YEAR%-%MONTH%-%DAY%.txt

The above snippet assumes that it's embedded inside of a .bat file (replace double %s with single % if executed interactively). To prevent naming collisions I usually embed the snippet between SETLOCAL and ENDLOCAL commands.

Accessing a DLLs Configuration Settings from a separate assembly

If you separate a lot of the UI for a WinForms project into separate DLLs you can run into the problem of managing application settings. Several workarounds have been proposed. C#2.0 introduced the friend keyword which provides an elegant workaround for this problem.

If your main exe is in assembly A then you typically want to save application settings before the application exits. However, if the application settings for the UI are associated with a separate dll then, by default, assembly A can't access these application settings via the automatically generated Properties.Settings class (it's marked internal).

The workaround is to make assembly A a "friend" of the other assembly (e.g., your windows controls library). This can be done in AssemblyInfo.cs of the other assembly with the following line:

[assembly: InternalsVisibleTo("assemblyA.exe")]

Then inside of assembly A you can access the strongly-typed Settings class:


Using IpcClientChannel to communicate with a process that might go away

IpcChannel is the managed class that provides access to named pipes. It's another remoting channel (alongside TcpChannel and HttpChannel).

If the client is accessing a Singleton it typically registers that type with the remoting infrastructure (via RemotingServices.RegisterWellKnownClientType()) then uses new to construct thet object. Since the type has been registered, behind the scenes everything is taken care of to access this object remotely.

What happens if you're talking to a process that you may need to periodically restart? Unfortunately you can't UnregisterWellKnownClientType() and you get an exception if you try to register the same type again (e.g., at a different URL).

Fortunately you can manually access the remote object with Activator.GetObject().

BackgroundWorker - When to check CancellationPending

BackgroundWorker was introduced in .NET 2.0 to solve the problem of executing long-running tasks while maintaining an interactive user interface.

One of the key issues that arises when using BackgroundWorker is determining when to check if the user has cancelled the long running operation. Thanks to this higher level abstraction (BackgroundWorker) a few insights become clear:
  • Cancellation must occur at some stopping point in the long running algorithm. By "stopping point" I mean some step in the process where you can check if the user has requested cancellation.
  • Check for cancellation only as frequently as is necessary to maintain the sense of interactivity.

The key insight, however, regards choosing the point at which you check for cancellation.

  • Check for cancellation just before executing a unit of work.

If you are executing the last unit of work then, essentially, there is no way to cancel the long running operation; the moment has passed. Since the user won't have to wait much longer until the operation is complete this does not conflict with the goal of supporting interactivity.

An analogy for the unit of work is jumping across stones to cross a creek. You can decide to stop at any time before you make the jump. You can decide to stop on any stone along the way. But you can't stop a jump while you're in the air (no jetpacks!) and you can't back out once you've jumped the last stone!