2008-07-19

Validating NumericUpDown on compact framework

A customer requested that instead of my NumericUpDown controls silently capping the input value within the Minimum..Maximum range it instead showed an error message telling the user their input is incorrect and that they need to alter it.

I was a bit annoyed to see that NumericUpDown.Validating is never called on the compact framework, in addition there was no way to get the input value and either accept or reject it before it is applied to its data bindings.

There's an article here which shows how to implement auto-select text when the NumericUpDown receives focus and I have been using it since Feb 2006. I decided to extend upon the techniques within it to implement the Validating event. My goal was to fire the Validating event before the value is applied to all data-bindings, but also to allow the programmer to read NumericUpDown.Value in order to determine the new value. To do this I had to replace the WndProc of the control so that I could handle the
WM_UPDOWN_NOTIFYVALUECHANGED message, parse the value, validate it, and then either accept it (call the original WndProc) or restore the value to the current value.

Rather than teach how this is done I thought I would just include the source code here. One point to note though is that I had to have a "bool IsInternalCall" wrapped around my handler otherwise I would have re-entrant problems and experience a stack overflow. Here is the source, it includes the auto-select code by Mark Arteaga.

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Threading;
using System.ComponentModel;

namespace Mycompany.Windows.Forms
{
  public class NumericUpDownWithSelect : NumericUpDown, ISupportInitialize
  {
    #region API
    private const int GWL_WNDPROC = -4;
    private const int WM_UPDOWN_NOTIFYVALUECHANGED = 13;
    public const int WM_GETTEXTLENGTH = 0x000E;
    public const int WM_GETTEXT = 0x000D;
    private const int WM_GETSELECTION = 0x00B0;
    private const int WM_SETSELECTION = 0x00B1;

    private WndProcHandler NewWndProc = null;
    private IntPtr OldWndProc = IntPtr.Zero;

    public delegate IntPtr WndProcHandler(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
    [DllImport("coredll.dll", SetLastError = true)]
    private static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);
    [DllImport("coredll.dll", SetLastError = true)]
    private static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, StringBuilder buffer);
    [DllImport("coredll.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, WndProcHandler wndproc);
    [DllImport("coredll.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
    [DllImport("coredll.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr GetWindowLong(IntPtr hWnd, int nIndex);
    [DllImport("coredll.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr CallWindowProc(IntPtr wndProc, IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
    #endregion

    private bool ControlDisposed = false;
    private bool IsValidating = false;
    private decimal ValueToValidate;

    public NumericUpDownWithSelect()
    {
    }

    public new event CancelEventHandler Validating;
    protected virtual void OnValidating(out bool cancel, decimal newValue)
    {
      cancel = false;
      CancelEventHandler validating = Validating;
      if (validating == null)
        return;

      cancel = false;
      CancelEventArgs args = new CancelEventArgs(false);
      IsValidating = true;
      try
      {
        ValueToValidate = newValue;
        Validating(this, args);
      }
      finally
      {
        IsValidating = false;
      }
      cancel = args.Cancel;
    }

    private decimal currentValue = 0;
    public new decimal Value
    {
      get
      {
        if (IsValidating)
          return ValueToValidate;
        return base.Value;
      }
      set
      {
        bool cancel;
        OnValidating(out cancel, value);
        if (!cancel)
        {
          base.Value = value;
          currentValue = value;
        }
      }
    }

    #region Validation
    protected override void OnHandleCreated(EventArgs e)
    {
      base.OnHandleCreated(e);
      if (this.Site == null)
      {
        NewWndProc = new WndProcHandler(ReplacementWndProcImpl);
        OldWndProc = SetWindowLong(this.Handle, GWL_WNDPROC, NewWndProc);
      }
    }

    private static bool IsInternalCall = false;
    private IntPtr ReplacementWndProcImpl(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam)
    {
      bool cancelled = false;
      if (msg == WM_UPDOWN_NOTIFYVALUECHANGED && !IsInternalCall)
      {
        IsInternalCall = true;
        try
        {
          int length = CallWindowProc(OldWndProc, this.Handle, WM_GETTEXTLENGTH, IntPtr.Zero, IntPtr.Zero).ToInt32();
          StringBuilder buffer = new StringBuilder(length + 1);
          SendMessage(this.Handle, WM_GETTEXT, length + 1, buffer);
          try
          {
            decimal newValue = decimal.Parse(buffer.ToString());
            OnValidating(out cancelled, newValue);
            if (cancelled)
            {
              Value = currentValue;
            }
          }
          catch (FormatException)
          {
            cancelled = true;
          }
        }
        finally
        {
          IsInternalCall = false;
          if (cancelled)
          {
            Focus();
            SelectAll();
          }
        }
      }
      return CallWindowProc(OldWndProc, hWnd, msg, wParam, lParam);
    }
    #endregion

    #region AutoSelect

    private delegate void SelectAllInvoke();

    private bool suppressOnGotFocus = false;

    protected override void OnGotFocus(EventArgs e)
    {
      base.OnGotFocus(e);
      if (!this.suppressOnGotFocus)
        SelectAll();
    }

    public void SelectAll()
    {
      this.SelectInternal(0, this.Value.ToString().Length);
    }

    public void Select(int start, int length)
    {
      this.SelectInternal(start, length);
    }

    private void SelectInternal(int start, int length)
    {
      if (!ControlDisposed)
      {
        this.suppressOnGotFocus = true;
        if (!this.Focused)
          this.Focus();
        IntPtr ret = SendMessage(this.Handle, WM_SETSELECTION, start, length);
        this.suppressOnGotFocus = false;
      }
    }
    #endregion

    protected override void Dispose(bool disposing)
    {
      ControlDisposed = true;
      base.Dispose(disposing);
    }

    #region ISupportInitialize Members
    //This region is here simply because the WinForm designer insists on casting this control
    //to ISupportInitialize
    void ISupportInitialize.BeginInit()
    {
    }

    void ISupportInitialize.EndInit()
    {
    }

    #endregion
  }

}


And how might you use it?

private void numericUpDownWithSelect1_Validating_1(object sender, CancelEventArgs e)
{
  if (numericUpDownWithSelect1.Value < numericUpDownWithSelect1.Minimum
    || numericUpDownWithSelect1.Value > numericUpDownWithSelect1.Maximum)
  {
    //No need to cancel, the new value will be rejected
    MessageBox.Show("Warning, value is about to be capped");
  }
  if (numericUpDownWithSelect1.Value > 5)
  {
    MessageBoxIcon icon = new MessageBoxIcon();
    e.Cancel =
      MessageBox.Show(
    "Is it really greater than 5?",
        "Are you sure?",
        MessageBoxButtons.YesNo,
        icon,
        MessageBoxDefaultButton.Button1) != DialogResult.Yes;
  }
}

2008-07-08

Single instance application

An app I am working on needs to be a single instance. It is associated with certain file extensions so that when I select a character or license file it will be imported automatically. When the user buys a character or license (etc) from the website it will be downloaded and opened, and then imported.

Obviously it is a pretty poor user experience if they have to close the app, download, close the app, download... So what I really needed was a way to have the 2nd instance of the application to invoke the first instance and pass the command line parameters. Here is a simple solution I implemented using remoting.

01: An interface

public interface ISingleInstance
{
void Execute(string[] args);
}


02: A class that implements the interface

public class SingleInstance : MarshalByRefObject, ISingleInstance
{
  private static object SyncRoot = new object();
  private static Form1 MainForm;

  static SingleInstance()
  {
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
  }

  public void Execute(string[] args)
  {
    bool isNew = false;
    lock (SyncRoot)
    {
      isNew = (MainForm == null);
      if (isNew)
        MainForm = new Form1();
      MainForm.AcceptCommandLineArguments(args);
    }
    if (isNew)
      Application.Run(MainForm);
  }
}


03: And finally the remoting code in the Program.cs file itself:

static class Program
{
  private static IpcChannel IpcChannel;

  [STAThread]
  static void Main(string[] args)
  {
    bool isNew;
    using (Mutex mutex = new Mutex(true, "TheCatSatOnTheMat", out isNew))
    {
      if (isNew)
        RegisterServer();
      else
      {
        IpcChannel = new IpcChannel("Client");
        ChannelServices.RegisterChannel(IpcChannel, false);
      }
      ISingleInstance app = (ISingleInstance)Activator.GetObject(typeof(ISingleInstance), "ipc://Server/RemotingServer");
      app.Execute(args);
    }
  }

  private static void RegisterServer()
  {
    IpcChannel = new IpcChannel("Server");
    ChannelServices.RegisterChannel(IpcChannel, false);
    RemotingConfiguration.RegisterWellKnownServiceType(typeof(SingleInstance), "RemotingServer", WellKnownObjectMode.Singleton);
  }
}


This is more of a note to myself in case I lose my small test app before I come around to implementing it into my main application :-)

More leak fixes

I have changed the DirtyObjectCatcher so that it initially only hooks

Form.Disposed - Automatically disposes the DirtyObjectCatcher
Form.Closed - Unhooks all additional form events (below)
Form.Shown - To hook additional form events (below)

==Additional form events==
Form.Activated
Form.MdiParent.Activated
Form.MdiChildActivate

The additional events are to ensure that the DirtyObjectCatcher's undo block is moved to the top. The reason that these events are now unhooked is so that there is no strong event references from the application's main form (Form.MdiParent) to this component, keeping it alive. Now we only have long-term event references from the owning form itself. Really though this is just an added precaution against something I may not have thought of :-)

The true memory saver comes from only holding a WeakReference to the owning form. Otherwise in an MDI application we have the following

MainForm.MdiChildActivate->DirtyObjectCatcher->Form

In such a case closing the MDI child form will not be collected it because it is referenced by the DirtyObjectCatcher, which cannot be collected because it is referenced by the application's main form. Unhooking these events and holding only a WeakReference prevents the leakage.

In addition I have hooked Form.Disposed so that this component is disposed along with its owner. I have also hooked DirtyObjectCatcher.Disposed from ObjectValidator so that it may also auto-dispose itself and knows not to place any new subscriptions.

Again the files are available here.

In addition I was able to track down and reproduce a couple of leaks in ECO 4 which are now fixed internally.

  1. Deactivating / Reactivating an EcoSpace would leak an object reference each time. This is not the case if you allow the EcoSpace to be collected.
  2. Using OclVariables with OclPsHandle and calling EcoSpace.Persistence.Refresh would leak a single object reference, also if OclPsHandle.Execute was executed.

I monitored my app with over 4,000 tasks today (normally there are about 200) for a few hours and the memory usage didn't budge!

2008-06-27

Memory leaks

As a follow up to yesterday's post here is a list of problems I found...

01: The following code for some reason causes the form holding the DirtyObjectCatcher to remain referenced.


if (Owner.MdiParent != null)
{
Owner.MdiParent.Activated += new EventHandler(MdiParent_Activated);
Owner.MdiParent.MdiChildActivate += new EventHandler(MdiParent_MdiChildActivate);
}
The odd thing about this is that it really is the events that matter! I subscribe Shown, Disposed, Activated on the Owner (which is a form) and that doesn't cause the same behaviour. The reason is that the Owner normally gets disposed and takes out the DirtyObjectCatcher with it, however, if I have Owner.MdiParent events referencing DirtyObjectCatcher then the form will never get disposed because DirtyObjectCatcher has a strong reference to Owner. Maybe I should change it, but for now the Shown and Activated events seem to be doing the trick.

02: This one was a real pain! DirtyObjectCatcher creates its own UndoBlock and then subscribes to it, so that whenever the user modifies an object it goes into the UndoBlock and the catcher is notified. At this point other components (such as ObjectValidator) can get a list of modified objects and do something (such as evaluate their OCL constraints).

Unfortunately this event fires at some point between the user making the change and the ECO cache being ready for the new value to be read. For this reason in the past I had to set

Application.Idle += CheckForModifiedObjects;

CheckForModifiedObjects would then trigger the relevant events in Application's Idle event so that we know the changes to the ECO cache are ready to be read. Unfortunately I made a silly mistake, I forgot to do this as the first line of CheckForModifiedObjects

Application.Idle -= CheckForModifiedObjects;

As a result the static Application class had a reference to my DirtyObjectCatcher via an event. DirtyObjectCatcher holds a reference to the form (Owner) and the EcoSpace (ServiceProvider). So the form and the EcoSpace would remain in memory.

03: In the past you could not subscribe to UndoBlock so I had to subscribe to DirtyListService. Unfortunately this would not get triggered if you activated a DirtyObjectCatcher on an object that was already dirty, or transient. To overcome this I added a CheckForUpdates method which would get every instance of DirtyObjectCatcher to check its UndoBlock for changes.

The idea was that you plug into the ECO cache and call the static DirtyObjectCatcher.CheckForUpdates whenever something changed. A pain in the ass. I requested UndoBlock.Subscribe and it was implemented within days (thanks Jonas!) but I left that code in just in case anyone was using it. Unfortunately this meant I had a static List sitting around holding references to my DirtyObjectCatchers. Again this referenced the EcoSpace and the Form, so the Form was never disposed in order to dispose of my DirtyObjectCatcher.

This wasn't noticed for some time because I use the AutoFormService to handle the lifetime of my forms, but my current app has quite a few forms where I manage the lifetime manually so the form wasn't automatically being disposed as I had expected. In addition the Form class does not call Dispose on any of its components when you call its Dispose method, which I find very strange!

Anyway, that's the lot. What a hellish week! I have uploaded the lastest version of EcoExtensions here:

www.peterlesliemorris.com/blogfiles/ecoextensions.zip

DirtyObjectCatcher

Oh boy, what a nightmare! After days of messing around I finally found where the memory leak is in my app, it was in DirtyObjectCatcher!

The DirtyObjectCatcher used to subscribe to the DirtyListService, so that it was notified whenever an object was made dirty. I experienced this problem...

01: User creates a "Call" to a customer site.
02: User edits a purchase order.
03: Save purchase order (merges the undo block to the Call undo block and closes the form)
04: Edit the purchase order again from the Call form

The PurchaseOrder is already dirty so it wont get triggered again, this used to result in no constraints being checked etc and the possibility of entering dodgy data. The solution at the time was to have a static list in DirtyObjectCatcher

private List Instances;

whenever a new instance was created it would be added, whenever Dispose was called it would be removed. I then hooked into the cache chain and whenever a value changed I would call a static method on DirtyObjectCatcher which iterated over Instances and told each to check if anything had changed. It worked fine enough, and I put in a request to add a Subscribe method to IUndoBlock.

My request for IUndoBlock.Subscribe was soon added so I changed the code. Now it worked lovely! Unfortunately there was a problem! I had left the static code in the component just in case anyone was using it, I should really have just removed it as it is now obsolete. The really big problem however was that I had never added a Finalizer to the DirtyObjectCatcher class to call Dispose(). I had assumed the following in the Windows.Form default code template would call Dispose.



private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}



However I was wrong. The components on the form are never added to this "components" member. As a result the Instances list grew and grew, and was never emptied. DirtyObjectCatcher holds a reference to the EcoSpace, so this is why the memory usage got bigger and bigger! I was going to add the Finalizer and remove the instance from Instances, but I have decided to remove Instances completely!

Update: There were some other odd issues too which I will blog about tomorrow after I have released an update!

2008-06-25

TeamCoherence - disaster!

That's it, my short evaluation of TC is over!

Problem 1: I emailed support with a question weeks ago, didn't get a response. Not impressed.

Problem 2: I reinstalled my O/S recently so had to restore my version control folder. When I try to check files out I now get an "Object not found" error, whatever that means? Some searching reveals it is a bug that has been fixed in the version I have, I beg to differ.

Problem 3: This one was the worst! I use TC client for a customer already which is why I decided to try out the server. I connected to the customer server, checked out loads of files, upgraded my VS2005 project to VS2008 and then checked everything back in. What a disaster! TC had replaced the source in the customer files with source from my private local server!

I couldn't believe it! As I looked through Assembly.cs files I could see WinForm code from the last project I worked on locally! This is obviously bad because I had exposed source code from one concerned party to another. Luckily it was my own source code and I trust the person I exposed it to, but that could have landed me in legal trouble if there had been NDA's involved!

Also, there didn't seem to be a way to undo all changes in check-in number X. I had to find each file with a change containing the text "VS2008" and delete the revision manually. This was made worse by the fact that the F3 search in TC doesn't take you to the next found item but instead takes you to the first found item after the current folder! So, I spent quite a bit of my day yesterday scanning through hundreds of files and manually deleting revisions from a single check-in.

I am actually shaking my head as I write this, I just can't believe it!

2008-06-16

TimeBasedSyncHandler

I recently had to tweak my remote persistence server settings. I noticed that I had leaft the SyncHandler.HistoryLength at the default value of 10,000 items. This was overkill because my clients sync every 3 seconds. I thought maybe I should drop this down to about 100, that should be okay? Each client only does about one update every few minutes so I it should, right?

Problem is not all of my users are people. One user is a messenger service which looks for unsent messages, sends them one at a time, marking each in turn as sent and updating the database. This gets run every five minutes so probably sends about fifty messages at the most, but what if someone decides to change it to ten minutes, or thirty? So maybe I should increase the HistoryLength to about 200?

Then there is the other client, this one syncs with an external database. This could perform hundreds of updates every minute. If the messenger is set to run every thirty minutes...I'm not sure what a good HistoryLength would be! If I set it too low I fill it up quickly and my clients will have to deactivate/reactivate their EcoSpace instances. If I set it too high I might use up too much memory, especially if the messenger runs infrequently and the history gets filled up with "human" client updates which are much larger.

So, I wrote my own SyncHandler. This one accepts a TimeSpan property, which by default is five minutes but on my server I dropped it to two minutes (forty times longer than my clients' sync intervals.) The idea is that I keep changes for the specified period of time no matter how irregularly the updates occur. This way the server uses more memory during heavy usage but

A: Usage drops during lower activity, in fact it goes right down if there are no updates for the specified TimeSpan.
B: It wont run out of history length too quickly for the client.

If you'd like to try it for yourself take a look here.