One of the tricky parts of software development is when you get those defects/bugs that you simply cannot reproduce. These can be because they require particular data in a back-end or because they are intermittent, and sometimes it is because the user is not relating a complete story of what they did to cause the problem. Diagnostics and Debug logging, and only logging stuff that matters, becomes something of an Art form.
I wrote previously about “magically” turning your standard Debug.Print output into a useful debug log in a debug build. (You could also set the DEBUG compile constant for your release build, I suppose). This post follows that up a bit by attempting to “automagically” add logging of User-Interface related events without having to manually add logging to every event handler.
When I first approached this problem- I started with that- “Oh, that’s easy, I’ll just slap a bit of debug output into all the applicable events” I failed to realize the scope of that particular task. hundreds of buttons, some inside user controls, some which needed event handlers added, no less. I decided it would be better to make it a general solution. I decided to go for a WinForms approach, however I’m sure it would be straightforward to adapt to WPF. I toyed with the idea of making it “central”- Such that the same code could be used for Windows Forms or WPF, but I found that approach required a bit more extra code in the actual use, so opted for a more specific solution.
The basic idea is pretty straightforward- have a member variable of desired forms and usercontrols, which, in it’s constructor, takes a ControlCollection from that Form or UserControl and then will hook recognized events for controls. This could benefit from some sort of extensibility as well- so that custom controls could have particular event’s “understood”, but I went with a simple approach. In fact, to start it only understands Buttons and only the Click event.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace UIDebuggingAssistant { public class UIDebuggingHelper { public event EventHandler<DebugUIEvent> UIEvent; private Object HookedParent = null; private void FireUIEvent(DebugUIEvent pEventArgs) { var copied = UIEvent; copied?.Invoke(this, pEventArgs); } public UIDebuggingHelper(Control.ControlCollection c) { foreach (Control ctrl in c) { HookControl(ctrl); } } protected void HookControl(Control c) { if (HookedParent == null) { var form = c.Parent as Form; if (form != null) { HookedParent = c.Parent; form.ControlAdded += UIDebuggingHelper_ControlAdded; } } foreach (Control child in c.Controls) { HookControl(child); } //hook supported controls. c.Click += C_Click; } private void C_Click(object sender, EventArgs e) { FireUIEvent(new DebugUIEvent((Control)sender,"Click")); } public static String ControlDescriptor(Control src) { Stack<Control> Listing = new Stack<Control>(); Listing.Push(src); var contained = src.Parent; while (contained != null) { Listing.Push(contained); contained = contained.Parent; } StringBuilder sb = new StringBuilder(); while (Listing.Count > 0) { sb.Append(Listing.Pop().Name); if (Listing.Count > 0) { sb.Append("=>"); } } return sb.ToString(); } private void UIDebuggingHelper_ControlAdded(object sender, ControlEventArgs e) { HookControl(e.Control); } } public class DebugUIEvent : EventArgs { public Control UIControl { get; set; } public String EventName { get; set; } public DebugUIEvent(Control pSource,String pEventName) { UIControl = pSource; EventName = pEventName; } } } |
The class is relatively simple to use. You merely add a member-level variable and initialize it on the Form Load, and hook the UIEvent event to perform the actual logging:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public partial class TestForm : Form { private UIDebuggingHelper ud = null; public TestForm() { InitializeComponent(); } private void TestForm_Load(object sender, EventArgs e) { ud = new UIDebuggingHelper(this.Controls); ud.UIEvent += Ud_UIEvent; } private void Ud_UIEvent(object sender, DebugUIEvent e) { lstEvents.Items.Add(UIDebuggingHelper.ControlDescriptor(e.UIControl) + " " + e.EventName); } } |
And there you go- now that event logs a bit of useful information about which controls are clicked, which can be used to track some of the User-Interface and how the User is interacting with your application, which can be a valuable tool in attempting to reproduce problems, bugs, and other issues that are being reported by users of your software.
Have something to say about this post? Comment!