P3.NET

The Old (New) Common Controls

The common controls provided by Windows are shipped as part of the OS in the Common Controls library.  When .NET was first introduced we had the v5 version.  When XP was released a new version (v6) was introduced.  Applications that needed to run under W2K or XP had to target v5 while XP-only applications could use v6.  v6 added many new controls and enabled theming.  For example task dialogs, the replacement for message boxes, require v6.  Ironically it isn’t even part of the framework yet, you have to use an addon.

Fast forward to today.  .NET v4 only supports XP and above.  Therefore if you’re writing a .NET application you can safely use the v6 controls.  But guess what – .NET uses v5 by default.

Enabling the v6 Controls

Enabling the v6 controls is, unfortunately, not a simple check box.  One approach is to make a specific API call (Application.EnableVisualStyles) before any user interface code.  If you’re writing a WinForms application then the default template generates the necessary code but I’ve always felt like this was a hack.  The code has to run before any code that might do anything related to user interface work otherwise the default v5 controls are loaded. 

The better approach is to add an application manifest that specifically identifies the v6 common controls.  This ensures that the proper version is loaded before any code is run.  Here’s how it might look like:

<dependency>
    
<dependentAssembly>
      
<assemblyIdentity type=win32 name=Microsoft.Windows.Common-Controls version=6.0.0.0
                        
processorArchitecture=* publicKeyToken=6595b64144ccf1df 
                        
language=*
        
/>
    
</dependentAssembly>
  
</dependency>>

Adding a manifest is not hard.  In fact if you are using UAC settings then chances are you already have one.  But honestly this shouldn’t be necessary anymore.  .NET v4 only supports XP+ so a .NET v4 application should load the v6 controls automatically.  But alas this is yet to be the default. 

Problems In Paradise

Unfortunately using a manifest to load the controls introduces some annoying problems.  The first problem is that ClickOnce won’t work anymore.  ClickOnce can’t handle dependent assemblies.  I’ve already blogged about this issue.

The second, and probably more annoying, problem is that you’ll occassionally be debugging your application and find that the manifest seems to be ignored.  This is most obvious when you try to use a task dialog in your code (say for error reporting) and you get an error trying to display the task dialog.  The error will generally say something about needing the v6 controls.  Checking the manifest will confirm that you have set the appropriate options.  You’ll then probably have to run your app and use a tool like Process Explorer to see what DLLs are actually getting loaded (since VS doesn’t seem to display unmanaged DLLs in .NET apps anymore).  You’ll find that v5 is in fact being loaded despite your manifest.  What is going on?

Back in the VS2005 days (approximately) Microsoft bragged about how much faster the debugger was.  They gained this speed boost by using the VS host process (vshost) to load programs to be debugged.  In previous versions when you started the debugger your process would start and all the pre-debug processing would occur.  When debugging was finished all the information was thrown out.  When you started debugging again the information had to be generated again.  vshost actually handles this now.  vshost is loaded when the project loads and remains running until VS shuts down.  This speeds up debugging greatly, but at a cost.  Windows cannot tell the difference between your application and vshost so Microsoft had to write some code to cause vshost to emulate your process as closely as possible (even down to the configuration files).  But at the end of the day vshost is ultimately responsible for running. 

Back to the application manifest.  If vshost loads DLLs before it reads your manifest file (which happens at arbitrary times in my experience) then it is possible that vshost will load the wrong DLL.  In the case of the common controls vshost uses v5 by default.  As a result if you are debugging your application through vshost and your manifest requests v6 controls then it might or might not work.  I’ve had it work after 5 debug sessions and fail on the 6th.  So the takeaway is that if you want to use the v6 controls then you need to disable the vshost process (Project properties -> Debug -> Enable the Visual Studio hosting process).  Note that this only applies to Windows applications.

Summary

In summary if you want a XP+ style user interface then you’re going to have to use an application manifest to load the correct version of the controls.  But this will prevent the use of ClickOnce publishing.  It also means that you will likely have to disable the vshost process for debugging.  I hope that Microsoft will fix this issue in a future version of VS.  I’d like to see an option for Windows projects that allow us to select the version of the controls (and perhaps other libraries) that we want to use.  We shouldn’t have to edit manifest files and hack around tools to use what should be the default settings going forward.  If you agree with me then please cast your vote at the Connect site on this issue. 

Updating the UI On an Arbitrary Thread

(Originally published: 5 March 2007)

It is very common to have to perform work on a secondary thread.  This allows us to provide a more interactive UI to our users while still allowing us to do lengthy work.  Unfortunately this is not as easy as it first appears.  We will discuss the problem, the standard v1.x solution and the v2 solution to the problem.

Why Do We Need It

Windows has always had the cardinal rule that a UI element can only be interacted with on the thread that created it.  This is because internally Windows still uses a message queue to communicate with UI elements.  The message queue is per-thread.  It is created on the thread that created the element.  This ensures that all messages are serialized to the UI and simplifies our work. 

However it is often necessary to do work on secondary threads and still update the UI.  In order for this to work we have to actually send the request to the UI’s thread and then have it pushed onto the message queue.  There are a couple of different ways to do this.  We’ll look at the most common method in v1.x and v2.  .NET v3.x introduces the concept of synchronization contexts which can be used to execute code on specific threads.  Synchronization contexts are a lengthy topic that will not be covered in this article.

v1.x Solution – Invoke and InvokeRequired

Every UI element (a control, here on out) has a InvokeRequired property.  This property can be called on any thread and indicates whether the calling thread is the thread that created the control.  If it is then the property is false otherwise it is true.  When working with controls in a multithreaded environment you must check this property.  If it is true then you must marshal the request to the UI thread. 

You marshal the request to the UI thread using the Invoke or BeginInvoke/EndInvoke methods.  These methods can be called on any thread.  In each case they accept a delegate, switch to the UI thread and then call the delegate.

if (myControl.InvokeRequired) 

   myControl.Invoke(…);
else 
   …

To make things easier we generally wrap the above code in a method and then create a delegate with the same signature.  This allows us to use one function for two different purposes.  The first “version” is called in all cases and determines whether an invoke is required.  The second “version” actually does the work on the UI thread.  This is a nice way to hide the details from your clients.

public delegate void UpdateProgressDelegate ( int progress );

public void UpdateProgress ( int progress )
{
   //If not on UI thread
   if (myProgress.InvokeRequired)
      myProgress.Invoke(new UpdateProgressDelegate(UpdateProgress), new object[] { progress });
   else
   {
      //Do work here – called on UI thread
      myProgress.Value = progress;
   };
}

As far as your users are concerned this is a clean interface.  They simply call UpdateProgress.  Internally if an invocation is required to get the call on the UI thread then it does so.  However it calls the same method again.  The second time it is called it will be done on the UI thread.  The biggest downside to this code is that: a) you have to write it, and b) you have to define a delegate.  Here is how we might call it.

public void Run ( )
{
   Thread t = new Thread(new ThreadStart(DoWork)); 
   t.IsBackground = true
   t.Start();
}

public void UpdateProgress ( int progress ) 
{ myProgress.Value = progress; } 

private void DoWork ( ) 

   for (int nIdx = 1; nIdx <= 100; ++nIdx) 
   { 
      Thread.Sleep(50); 
      UpdateProgress(nIdx);
   }; 

v2 Solution – BackgroundWorker Component

Since this is such a common situation in v2 Microsoft has added the BackgroundWorker component (BWC).  This component’s sole purpose in life is to do work on a secondary thread and then update the UI.  The component requires that you identify the function to run.  It will then run the function on a thread pool thread.  Once it is complete the component will raise the RunWorkerCompleted event.

public void Run ( )
{
   BackgroundWorker bwc = new BackgroundWorker(); 
   bwc.DoWork += DoWork; 
   bwc.RunWorkerCompleted += OnCompleted; 
   bwc.RunWorkerAsync();    //Run it
}

private void OnCompleted ( object sender, RunWorkerCompletedEventArgs e ) 
{ MessageBox.Show(“Done”); } 

private void DoWork ( object sender, DoWorkEventArgs e ) 

   for (int nIdx = 1; nIdx <= 100; ++nIdx) 
   { Thread.Sleep(50); }; 
}

Notice a few things about the code.  Firstly it is smaller as we don’t have to worry about the thread management.  Secondly notice we do not have to worry about the UI thread.  All events exposed by BWC are raised on the UI thread automatically.  Note that you can drop BWC onto your form or control because it shows up in the designer.  Personally I prefer to create it on the fly.

There is a problem with the above code.  It does not actually update the progress while it is running.  In order to raise progress events we need to tell BWC we will be raising progress notifications.  We also need to hook up another function to handle the event.

public void Run ( )
{
   BackgroundWorker bwc = new BackgroundWorker(); 
   bwc.DoWork += DoWork; 
   bwc.RunWorkerCompleted += OnCompleted; 
   bwc.WorkerReportsProgress = true
   bwc.ProgressChanged += OnProgressChanged;

   bwc.RunWorkerAsync();    //Run it
}

private void OnProgressChanged ( object sender, ProgressChangedEventArgs e ) 
{ myProgress.Value = e.ProgressPercentage; }

private void OnCompleted ( object sender, RunWorkerCompletedEventArgs e ) 
{ MessageBox.Show(“Done”); } 

private void DoWork ( object sender, DoWorkEventArgs e ) 

   BackgroundWorker bwc = sender as BackgroundWorker; 
   for (int nIdx = 1; nIdx <= 100; ++nIdx) 
   { 
      Thread.Sleep(50); 
      bwc.ReportProgress(nIdx); 
   };
}

The changes are in bold.  Basically the UpdateProgress of earlier changes to OnProgressChanged.  Again, it is called on the UI thread.  We have to report progress using BWC.ReportProgress.

One final feature of BWC that was not in the original code we wrote is cancellation support.  It is often nice to allow users to cancel lengthy tasks.  BWC supports this automatically.  All you have to do is tell it that you will allow cancellation and then periodically check for a cancellation request.  For algorithms that loop you generally check at the beginning of each loop.  The more frequently you check the cancellation flag (CancellationPending) the faster your user will see the process be cancelled.

public void Run ( )
{
   BackgroundWorker bwc = new BackgroundWorker(); 
   bwc.DoWork += DoWork; 
   bwc.RunWorkerCompleted += OnCompleted; 
   bwc.WorkerReportsProgress = true
   bwc.ProgressChanged += OnProgressChanged;
   bwc.WorkerSupportsCancellation = true
   bwc.RunWorkerAsync();    //Run it
}

private void btnCancel_Click ( object sender, EventArgs e ) 
{ bwc.CancelAsync(); }

private void OnProgressChanged ( object sender, ProgressChangedEventArgs e ) 
{ myProgress.Value = e.ProgressPercentage; }

private void OnCompleted ( object sender, RunWorkerCompletedEventArgs e ) 
{
   if (e.Cancelled) 
      MessageBox.Show(
“Cancelled”); 
   else 
      MessageBox.Show(“Done”);

private void DoWork ( object sender, DoWorkEventArgs e ) 

   BackgroundWorker bwc = sender as BackgroundWorker; 
   for (int nIdx = 1; nIdx <= 100; ++nIdx) 
   { 
      if (bwc.CancellationPending)
      {
         e.Cancel = 
true;
         
return
      };

      Thread.Sleep(50); 
      bwc.ReportProgress(nIdx); 
   };
}

Here we’ve set the property notifying BWC that we support cancellation and we check for cancellation during each iteration of the loop.  We also modified the completion handler to detect whether the operation completed or was cancelled.  The only hard part here is actually hooking a control (such as a button) up to cancel the work.  If you use the designer to drop the BWC on your form then you can use the field that is created to access the BWC.  In the above code it was a local variable so you would have to somehow store the local variable somewhere where the cancel button’s click handler could get to it.  I leave that for you to figure out.

Why Not Do It Automatically

Some people ask why, if it is so common, Windows does not do this automatically or why the framework developers didn’t do it, or why they shouldn’t do it themeselves.  The answer is performance.  As common as it might be most UI interaction still occurs on the appropriate thread.  It is not really worth the performance hit to always handle the situation.  This is a common theme throughout the framework.  Another problem with threading messages is the order.  In general if you send two messages (one right after the other) then you expect the first message to be processed first and then the second message.  With asynchronous calls this might not be true.  Therefore more effort must be put into ensuring that the ordering of messages is not important. 

Most classes in the framework are not thread safe.  The reasoning is that it adds complexity and degrades performance.  A good multithreaded appliation does not allow just any object to be accessed across threads.  This would be a bad design.  Instead only a select few objects are shared and it is these objects that need to be thread safe, not the entire application.

Until Windows sheds its message queue behavior I’m afraid we are stuck with this limitation.  Although I believe WPF has somewhat remedied the situation…

Persisting Form Settings

(Originally published: 15 September 2007)

This article was originally written when .NET v2 was released.  Some of the information in here may be outdated with newer .NET versions but there is still some good information on persisting form settings and avoiding some pitfalls in the process.  Therefore this article is being posed unchanged from its initial form.

When working with Windows applications users expect that the application will remember its state when it is closed and restore that state when it is opened.  This allows the user to customize the layout of the application to fit their needs.  If Bill only works with a single application at a time he might like to have the application take up the whole screen so he can maximize his view.  Julie, on the other hand, runs several applications at once.  She wants to keep windows laid out in a certain order so she can efficiently copy and paste data between applications.  It doesn’t make sense to require Julie and Bill to share the same window layouts when their individual settings can be stored easily.

In the early betas of .NET v2 this functionality was available.  It seems to have been removed before the final release.  This article will discuss how to add in form state persistence to your WinForms applications.  This article will only deal with a single form and the basic form attributes but it can be extended to multiple forms with many different properties including tool windows and docking state.

Where to Store the Data

For persisting data on a per-user basis you basically have three common choices: registry, isolated storage and settings file.  The registry is commonly used for old-school programming.  The HKEY_CURRENT_USER key is designed for storing per-user settings.  It works a lot like a file system.  The registry is generally not recommended anymore.  It is designed for small pieces of data and has limited support for data formats.  It is, however, secure and requires more than a passing knowledge of Windows to use properly.  Therefore it is a good choice for settings that should generally be protected but not to big in size.  A big limitation of the registry is that it can’t be used in applications that don’t have registry access (like network applications or smart clients).

Isolated storage is a step up the file system hierarchy.  It looks like a file system (and actually resides in the system somewhere) but its actual location is hidden from applications.  Isolated storage allows any application to store data per-user.  The downside to isolated storage is that it can be a little confusing to use.  Additionally, since the actual path and file information is hidden, it can be hard to clean up corrupt data if something were to go wrong. 

Finally there is the settings file.  We are talking about the user settings file here, not the application settings file.  Each user can have their own settings file.  This file works similar to the application property settings file that you can use for application-wise settings.  The difference is that each user has their own copy and it is store in the user’s profile directory.

Before moving on it is important to consider versioning of the settings.  If you want a user running v1 of your application to be able to upgrade to v2 and not lose any of their settings then you must be sure to chose a persistence location that is independent of the version of the application.  The registry is a good choice here as isolated storage and user settings are generally done by application version.  Still it doesn’t make sense in all cases to be backward compatible with the settings file.  You will have to decide on a case by case basis.

FormSettings

Let’s start with the basic class we’ll use.  Ultimately, since we might have quite a few forms to persist we want to create a base class (FormSettings) that will take care of the details.  We can derive from this class for custom form settings as needed.  In this article we will use the user’s setting file so we derive from ApplicationSettingsBase.  If you want to use a different storage mechanism then you’ll need to make the appropriate changes. 

Since we want our class to work with multiple forms we need to make each form unique.  We will use the SettingsKey to make each form unique.  Each form must specify the key it will use.  Here is the start of our class.

public class FormSettings : ApplicationSettingsBase 

   public FormSettings ( string prefix ) 
   { 
      SettingsKey = prefix; 
   } 

   public void Load ( Form target )  />   {
      //Load
   }

   public void Save ( Form target ) />   {
      //Save
   }
}

When the form is loaded it will call Load to load its settings.  When the form is closed it will call Save.  Here is sample code for our form.

protected override void OnLoad(EventArgs e) 

    base.OnLoad(e); 

    m_Settings.Load(this); 

protected override void OnFormClosing ( FormClosingEventArgs e ) 

    base.OnFormClosing(e); 

    if (!e.Cancel) 
        m_Settings.Save(this);
}

private FormSettings m_Settings = new FormSettings(“MainForm”);

 

Persisting Properties

At a minimum a user would expect to be able to move the window around and resize it to fit their needs.  Therefore we need to load and save the following properties: DesktopLocation, Size and WindowStateDesktopLocation specifies the position, relative to the top-left corner of the desktop, of the top-left corner of the form.  The Size property indicates the width and height of the form.  Finally the WindowState is used to track when a window is minimized or maximized.  We will discuss this property shortly.

To load and save properties using the settings file we need to define a property for each item we want to save.  We need to mark the property as user-scoped and we need to get the value from and set the value to the settings file.  This is pretty straightforward so we will not dwell on the details.  Here is the code for getting and setting the property values.  One point of interest is that we use special values when we can not find the property values.  This will come back later when we talk about loading the settings.

public class FormSettings : ApplicationSettingsBase  
{
   … 

   [UserScopedSetting] 
   public Point Position  BR />   { 
      get 
      { 
         object value = this[“Position”]; 
         return (value != null) ? (Point)value : Point.Empty; 
      } 
      set { this[“Position”] = value; } 
   } 

   [UserScopedSetting] 
   public Size Size  R />   { 
      get 
      { 
         object value = this[“Size”]; 
         return (value != null) ? (Size)value : Size.Empty; 
      } 
      set { this[“Size”] = value; } 
   } 

   [UserScopedSetting] 
   public FormWindowState State  R />   { 
      get 
      { 
         object value = this[“State”]; 
         return (value != null) ? (FormWindowState)value : FormWindowState.Normal; 
      } 
      set { this[“State”] = value; } 
   } 
}

 

Saving the Settings

Saving the settings is really easy.  All we have to do is set each of the user-scoped property values and then call Save on the base settings class.  This will flush the property values to the user’s settings file.

public void Save ( Form target ) 

   //Save the values 
   Position = target.DesktopLocation; 
   Size = target.Size; 
   State = target.WindowState; 

   //Save the settings
   Save(); 
}

 

Loading the Settings

Loading the settings requires a little more work.  On the surface it is similar to the save process: get each property value and assign it to the form.  The only issue that comes up is what to do when no settings have been persisted (or they are corrupt).  In this case I believe the best option is to not modify the form’s properties at all and, therefore, let it use whatever settings were defined in the designer.  Let’s do that now and see what happens.

public void Load ( Form target ) 

   //If the saved position isn’t empty we will use it 
   if (!Position.IsEmpty) 
        target.DesktopLocation = Position; 
   //If the saved size isn’t empty we will use it 
   if (!Size.IsEmpty) 
     target.Size = Size; 

   target.WindowState = State; 
}

It seems to work.  This is too easy, right?  Now try minimizing the form and then closing it.  Open the form again.  Can’t restore it can you? 

Minimizing and Maximizing Forms

The problem is that for a minimized/maximized form the DesktopLocation and Size properties are not reliable.  Instead we need to use the RestoreBounds property which tracks the position and size of the form in its normal state.  If we persist this property when saving then when we load we can restore the normal position and size and then set the state to cause the form to minimize or maximize properly.  But there is another problem.  RestoreBounds isn’t valid unless the form is minimized or maximized.  Therefore our save code has to look at the state of the form and use DesktopLocation when normal and RestoreBounds when minimized/maximized.  Note that RestoreBounds.Size is valid in both cases although whether this is by design or not is unknown.  The load code remains unchanged as we will set the values based upon the form’s normal state and then tell the form to minimize or maximize.  Here is the updated code.

public void Save ( Form target ) 

   //Save the values 
   if (target.WindowState == FormWindowState.Normal) 
      Position = target.DesktopLocation; 
   else 
      Position = target.RestoreBounds.Location; 

   Size = target.RestoreBounds.Size; 
   State = target.WindowState; 

   //Save the settings 
   Save(); 
}

 

Disappearing Windows

The final problem with our code is the problem of disappearing windows.  We’ve all seen it happen.  You start an application and the window shows up in the Task Bar but not on the screen.  Windows doesn’t realize that the application’s window is off the screen.  This often occurs when using multiple monitors and we switch the monitors around or when changing the screen resolution.  Fortunately we can work around it.

During loading we need to verify that the window is going to be in the workspace of the screen (which includes all monitors).  If it isn’t then we need to adjust the window to appear (at least slightly) on the screen.  We can do this by doing a quick check to make sure the restore position is valid and update it if not. 

What makes this quite a bit harder is the fact that screen coordinates are based off the primary monitor.  Therefore it is possible to have negative screen coordinates.  We also don’t want the Task Bar or other system windows to overlap so we will use the working area of the screen which is possibly smaller than the screen size itself.  .NET has a method to get the working area given a control or point but it returns the closest match.  In this case we don’t want a match.  .NET also has SystemInformation.VirtualScreen which gives us the upper and lower bounds of the entire screen but it doesn’t take the working area into account.

For this article we’ll take the approach of calculating the working area manually by enumerating the monitors on the system and finding the smallest and largest working areas.  Once we have the work area we need to determine if the caption of the form fits inside this area.  The caption height is fixed by Windows but the width will match whatever size the form is.  We do a little math and viola.  If the caption is visible then we will set the position otherwise, in this case, we simply let the form reset to its initial position.  Here is the load code.

public void Load ( Form target ) 

   //If the saved position isn’t empty we will use it 
   if (!Position.IsEmpty) 
   { 
       //Verify the position is visible (at least partially) 
       Rectangle rcArea = GetWorkingArea(); 

       //We want to confirm that any portion of the caption is visible 
       //The caption is the same width as the window but the height is fixed 
       //from the top-left of the window 
       Size sz = (Size.IsEmpty) ? target.Size : this.Size; 
       Rectangle rcForm = new Rectangle(Position, new Size(sz.Width, SystemInformation.CaptionHeight)); 
       if (rcArea.IntersectsWith(rcForm)) 
          target.DesktopLocation = Position; 
   }; 

   //If the saved size isn’t empty we will use it 
   if (!Size.IsEmpty) 
       target.Size = Size; 

   target.WindowState = State; 

private Rectangle GetWorkingArea () 

   int minX, maxX, minY, maxY; 
   minX = minY = Int32.MaxValue; 
   maxX = maxY = Int32.MinValue; 

   foreach (Screen scr in Screen.AllScreens) 
   { 
      Rectangle area = scr.WorkingArea; 

      if (area.Bottom < minY) minY = area.Bottom; 
      if (area.Bottom > maxY) maxY = area.Bottom; 

      if (area.Top < minY) minY = area.Top; 
      if (area.Top > maxY) maxY = area.Top; 

      if (area.Left < minX) minX = area.Left; 
      if (area.Left > maxX) maxX = area.Left; 

      if (area.Right < minX) minX = area.Right; 
      if (area.Right > maxX) maxX = area.Right; 
   }; 

   return new Rectangle(minX, minY, (maxX – minX), (maxY – minY)); 
}

The Case of the Mysteriously Changing Font

A while back I wrote up a simple IP address control for WinForms. This control relied on the underlying IP Address common control from Windows.

The goal was to provide the ability, in WinForms, to enter and validate an IP address without having to write a bunch of code. 

But a mysterious problem occurred when the control was used more than once in the same process.  The font would mysteriously change to bold.  This problem will be discussed later. 

Here is the process I used to create the control and ultimately track down and fix the issue.

Window Classes

When a window is created it must be assigned a window class.  The class determines, amongst other things, some basic properties that all windows of the class inherit.  This includes the default window procedure used to process messages.  Each window class must be registered using RegisterClassEx before it can be used.  The common controls in Windows each use their own window class.  Before the common controls can be used InitCommonControlsEx must be called to registered the common window classes.  Once a window class has been registered an instance of the window class is created by passing the class to the CreateWindowEx function in Windows.  When a common control’s window class is used in the call an instance of one of the common controls is created.

In WinForms the common controls are initialized automatically so no additional effort is needed.  For most of the common controls an existing WinForm control exists to wrap it.  Under the hood each control will pass the appropriate window class to the create call.  The property CreateParamss is responsible for configuring the window before it is created.  To create an instance of a common control the property is overridden to pass the appropriate window class. 

Creating the Control

To create the IP control first we create a new class called IPAddressTextBox and derived it from TextBox.  Because this is a common control CreateParams has to be overridden to use the appropriate window class.  Now when the control is created it will appear and work like the standard IP Address control in Windows.  Here is the basic code.

public class IPAddressTextBox : TextBox
{
   protected override CreateParams CreateParams
   {
      get
      {
         CreateParams parms = basee.CreateParams;

         parms.ClassName = “SysIPAddress32”;;
                                
         return parms;
      }
   }
}

 

Propertiess

The IP Address control does not have any custom styles so there is no need to expose any additional properties.  However the base TextBox class has a couple of properties that do not make sense for the IP control such as maximum text length and if the control supports multiline.  These properties are (fortunately) virtual so they are overridden to do nothing, hidden from the editor and hidden from the designer.  Here is the code used to hide the unneeded properties.

[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[EditorBrowsable(EditorBrowsableState.Never)]
public override int MaxLength
{
   get { returnn 15; }
   set { }
}

[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Multiline
{
   get { returnrn false; }
   set { }
}

[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool ShortcutsEnabled
{
   get { return true; }
   set { }
}

The Browsable attribute controls whether the property shows up in the Properties grid or not.  The DesignerSerializationVisibility attribute controls what and if the property is serialized into the designer file.  The EditorBrowsable attribute controls whether the property is always, sometimes or never shown in the editor (Intellisense).

To make the control easier to use the control’s value will be exposed as an IPAddress value.  This simply requires that the text value be parsed.  Here is the property to expose the IP address.

[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public IPAddress Value
{
   get 
   {
      IPAddress addr;
      IPAddress.TryParse(basee.Text, out addr);

      return addr ?? IPAddress.None;
   }
                
   set
   {                                
      string addr = ((value != nulll) ? value.ToString() : “”);
      if (basee.Text != addr)
      {
         base.Text = addr;

         OnValueChanged();
      };
   }
}

This basic implementation ignores invalid data.  The setter calls a protected virtual method to raise an event when the value changes. This provides parallel functionality to the TextChanged event that all controls inherit.

Methods

The IP address control has only one custom message that can be exposed.  The IP address supports setting the focus to the individual portions of the address.  A SetFocus method is exposed to allow developers to set the focus to each of the individual fields of the address.

public void Focus ( byte field )
{
   if (IsHandleCreated)
      SafeNativeMethods.SendMessage(Handle, IPM_SETFOCUS, field, 00);
}

The method simply sends the appropriate message to the window using P/Invoke.

A few additional methods are defined to handle some basic input processing and to update the value if the text changes.

Problem In Paradise

The basic control is done.  Compiling and running the control in a sample Winform app shows that it is working correctly.  But there is a problem.  Once the control is destroyed (such as by closing the owning form) then subsequent uses of the control cause the font to be in bold.  It does not matter whether the same instance was used or an entirely new one was created.  Debugging the problem and tweaking with the control did not resolve the problem.  The font was somehow getting corrupted.  A little INet searching revealed that the IP Address control has a “bug” (this came from a message posted by an MS engineer).  The underlying common control will delete the font it is using when the control is destroyed.  While this sounds like a good thing it does introduce a serious problem.  In Winforms controls will use the same standard font by default.  If the default font is passed to the IP control and the control then deletes it then the standard font is corrupted and subsequent use will cause bizarre behavior. 

Fixing the Problem

Fortunately fixing this problem is not that difficult.  What needs to happen is that the underlying IP control needs to get its own font when it is created.  When the control eventually gets destroyed it will then delete the copy rather than the original. 

A control gets its font from the Font property.  To ensure that the control always gets its own copy of the font it needs to be cloned.  This is easily handled by overriding the Font property and simply cloning the font in the setter.

public override Font Font
{
   get { return base.Font; }
   set
   {
      base.Font = (Font)value.Clone();
   }
}

One final change is needed.  When the control is first created it will get the default font so the constructor of the control needs to ensure that a copy of the default font is used instead.

public IPAddressTextBox ( )
{
   this.Font = base.Font;
}

With these changes in place the control now gets its own copy of the font.  When the underlying IP control is destroyed it will delete the copy and the default font remains unaffected. 

Caveats

The attached file contains the control and a simple test program.  The control, as provided, is used in production code but might still contain some issues.  Before using this code in any production code be sure to fully test it.