Fixing ComboBox bugs…

The standard ComboBox in NETCF has a couple of “issues”, however it’s possible to workaround them with a bit of tweaking. I rolled together a number of these fixes into a ComboBoxEx class. Heres the code (C#), I hope this (with a few more missing features) will be in the next SDF build with designer support like our other controls:-


namespace OpenNETCF.Windows.Forms


{


      /// <summary>


      /// Extended ComboBox control.


      /// </summary>


      public class ComboBoxEx : ComboBox, IWin32Window


      {


            //windows messages


            private const int WM_SETREDRAW = 0x0b;


            private const int CB_SETCURSEL = 0x014E;


            private const int CB_DELETESTRING = 0x0144;


            private const int CB_INSERTSTRING = 0x014A;


 


            //native window handle


            private IntPtr m_handle;


 


            //databound collection


            private IBindingList thebindinglist = null;


 


            //is display updatable?


            private bool m_updatable = true;


 


            /// <summary>


            /// Gets the window handle that the control is bound to.


            /// </summary>


            public IntPtr Handle


            {


                  get


                  {


                        if(m_handle == IntPtr.Zero)


                        {


                              this.Capture = true;


                              m_handle = Win32Window.GetCapture();


                              this.Capture = false;


                        }


 


                        return m_handle;


                  }


            }


 


            // Redraw code courtesy of Alex Feinman – http://blog.opennetcf.org/afeinman/PermaLink,guid,9305a1d9-e24e-4310-89e2-f80808076a37.aspx


 


            /// <summary>


            /// Maintains performance when items are added to the <see cref=”ComboBoxEx”/> one at a time.


            /// </summary>


            public void BeginUpdate()


            {


                  m_updatable = false;


 


                  Win32Window.SendMessage(this.Handle, WM_SETREDRAW, 0, 0);


            }


 


            /// <summary>


            /// Resumes painting the <see cref=”ComboBoxEx”/> control after painting is suspended by the <see cref=”BeginUpdate”/> method.


            /// </summary>


            public void EndUpdate()


            {


                  m_updatable = true;


                  Win32Window.SendMessage(this.Handle, WM_SETREDRAW, 1, 0);


            }


 


            //ComboBox doesn’t support ItemChanges in a datasource implementing IBindingList


            //The following workaround forces the list to update if an item is changed


 


            //data source has changed


            protected override void OnDataSourceChanged(EventArgs e)


            {


                  //remove event handler


                  if(thebindinglist != null)


                  {


                        thebindinglist.ListChanged-= new ListChangedEventHandler(ComboBoxEx_ListChanged);


                        //reset our handle to the bound data


                        thebindinglist = null;


                  }


 


                  //get the underlying ibindinglist (if there is one)


                  if(this.DataSource is IListSource)


                  {


                        IList thelist = ((IListSource)this.DataSource).GetList();


                        if(thelist is IBindingList)


                        {


                              thebindinglist = (IBindingList)thelist;


                        }


                  }


                  else if(this.DataSource is IBindingList)


                  {


                        thebindinglist = (IBindingList)this.DataSource;


                  }


                 


                  if(thebindinglist != null)


                  {


                        //hook up event for data changed


                        thebindinglist.ListChanged+=new ListChangedEventHandler(ComboBoxEx_ListChanged);


                  }


                 


                  base.OnDataSourceChanged (e);


            }


 


            //called when a change occurs in the bound collection


            private void ComboBoxEx_ListChanged(object sender, ListChangedEventArgs e)


            {


                  if(m_updatable)


                  {


                        if (e.ListChangedType == ListChangedType.ItemChanged)


                        {


                              //update the item


 


                              //delete old item


                              Win32Window.SendMessage(this.Handle, CB_DELETESTRING, e.NewIndex, 0);


                              //get display text for new item


                              string newval = this.GetItemText(this.Items[e.NewIndex]);


                              //marshal to native memory


                              IntPtr pString = MarshalEx.StringToHGlobalUni(newval);


                              //send message to native control


                              Win32Window.SendMessage(this.Handle, CB_INSERTSTRING, e.NewIndex, pString);


                              //free native memory


                              MarshalEx.FreeHGlobal(pString);


                        }


                  }


            }


 


            /// <summary>


            /// Get or Set the selected index in collection.


            /// </summary>


            /// <remarks>Overridden to overcome issue with setting value to -1 (http://support.microsoft.com/default.aspx?scid=kb;en-us;327244)</remarks>


            public override int SelectedIndex


            {


                  get


                  {


                        return base.SelectedIndex;


                  }


                  set


                  {


                        if(value == -1)


                        {


                              Win32Window.SendMessage(this.Handle, CB_SETCURSEL, -1, 0);


                        }


                        else


                        {


                              base.SelectedIndex = value;


                        }


                  }


            }


 


      }


}



 


So whats in the above code? Well it integrates Alex Feinman’s recent tip to implement Begin/EndUpdate methods to suspend redrawing when populating the control.


Secondly if works around a bug in the DataBinding support for ComboBox. If you make an edit to an existing item in the bound collection the combo wont normally update (It does correctly react to additions and deletions).


Thirdly it overcomes a known issue with resetting the selected index property (by assigning -1) which normally has to be done twice to take effect – details in this KB article – Thanks to Tim Wilson for posting this KB on the newsgroup.


As a Bonus I’ve added a Handle property so you can get at the native control handle – which in conjunction with Win32Window methods allows you to tweak other aspects of the control.


UPDATE: Changed the databinding code to update a single item via P/Invoke rather than refreshing the list. Changed the SelectedIndex to set via P/Invoke – avoids raising the SelectedIndexChanged event.

Advertisements