Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

So I'm trying to create a Bindable FlowLayoutPanel that can take a DataSource and map it the specified generic parameter controls. Only minimal requirements are to have a selectable index and to be able to change the background color of the selected and deselected controls. I've tried to leave out the implementation of the selected index except for obvious cases. This leaves the option to the developer as to how to move the selected index when a control is added/removed/etc. I have an example program that uses the below and it seems to work ok (Its not very large, but too big to post here). I was hoping someone else could take a look at this and see if there are any obvious issues. Thanks for checking it out.

using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Design;

namespace DataBindingListOfUserControls
{
   /// <summary>
   /// Create a Bindable FlowLayoutPanel
   /// </summary>
   /// <typeparam name="T">The control type that will be in the FlowLayoutPanel</typeparam>
   /// <remarks>Note!!! If you wish to use this in a designer, you must create a class
   /// that does not use or derive from this generic class.  For example, doing something
   /// like: BindableFlowLayoutPanel[MyCtrl] would give you designer errors.  To fix this,
   /// you need to create a class that does not derive from the generic class.  So you
   /// would do this: class MyCtrlBflp : MiddleBflp, 
   /// then class MiddleBflp : BindableFlowLayoutPanel[MyCtrl]  Then you would use
   /// MyCtrlBflp from the Toolbox (and you can create an instance of this through the
   /// designer)</remarks>
   public partial class BindableFlowLayoutPanel<T> : UserControl, INotifyPropertyChanged 
      where T : Control, IBindableObject, new()
   {
      #region Constants
      /// <summary>
      /// The constant value if no control is selected
      /// </summary>
      private const int NO_SELECTED_INDEX = -1;
      #endregion

      #region Member Variables
      /// <summary>
      /// The data source that will bind to the controls
      /// </summary>
      private BindingSource mDataSource = null;
      /// <summary>
      /// The deselected background color of the controls
      /// </summary>
      private Color mDeselectedControlColor = SystemColors.Control;
      /// <summary>
      /// The selected background color of the controls
      /// </summary>
      private Color mSelectetedControlColor = SystemColors.GradientInactiveCaption;
      /// <summary>
      /// The selected control within the FlowLayoutPanel
      /// Let the model control this so you can implement your own desired
      /// navigation when items are added, removed, inserted, etc
      /// </summary>
      private int mSelectedIndex = NO_SELECTED_INDEX;
      #endregion

      #region Constructor
      /// <summary>
      /// Creates an instance of the Bindable FlowLayoutPanel
      /// </summary>
      public BindableFlowLayoutPanel()
      {
         InitializeComponent();
      }
      #endregion

      #region Properties
      /// <summary>
      /// Set the data source that will bind to the controls in the FlowLayoutPanel
      /// </summary>
      /// <param name="bindingSource">The binding source</param>
      [TypeConverter("System.Windows.Forms.Design.DataSourceConverter, System.Design")]
      [Editor("System.Windows.Forms.Design.DataSourceListEditor, 
         System.Design", typeof(UITypeEditor))]
      [AttributeProvider(typeof(IListSource))]
      [DefaultValue(null)]
      public object DataSource
      {
         set
         {
            // Only change if its different
            if (mDataSource != value)
            {
               // Unregister the events if we were already bound to a source
               CleanUpDataSource();

               // Check to make sure the object is a data source
               BindingSource new_binding_source = value as BindingSource;
               if ((value != null) && (new_binding_source == null))
               {
                  throw new ArgumentException("DataSource must be a BindingSource");
               }

               // Setup the binding source to listen for list changes and Enter event
               // listeners for each control
               mDataSource = new_binding_source;
               if (mDataSource != null)
               {
                  mDataSource.ListChanged += 
                     new ListChangedEventHandler(DataSourceListChangedEventHandler);

                  for (int i = 0; i < mDataSource.Count; i++)
                  {
                     AddControl(i);
                  }
               }
            }
         }
      }

      /// <summary>
      /// Get/Set the deselected background color of the controls
      /// </summary>
      public Color DeselectedControlColor
      {
         get
         {
            return mDeselectedControlColor;
         }
         set
         {
            mDeselectedControlColor = value;
         }
      }

      /// <summary>
      /// Get/Set the selected background color of the controls
      /// </summary>
      public Color SelectetedControlColor
      {
         get
         {
            return mSelectetedControlColor;
         }
         set
         {
            mSelectetedControlColor = value;
         }
      }

      /// <summary>
      /// Get/Set the currently selected control index
      /// Make this bindable and let the outside world control
      /// how this is updated
      /// <remarks>The selected index will be internally set to
      /// -1 if the selected index is removed or if a control
      /// is attempted to be selected but fails</remarks>
      /// </summary>
      [System.ComponentModel.Bindable(true)] 
      public int SelectedIndex
      {
         get
         {
            return mSelectedIndex;
         }
         set
         {
            if (mSelectedIndex != value)
            {
               // Clear the currently selected control
               ClearSelectedControl();
               mSelectedIndex = value;
               // Set the selected control
               if (SetSelectedControl(value))
               {
                  // Notify this change since its custom
                  NotifyPropertyChanged("SelectedIndex");
               }
               else
               {
                  // Dont notify the reset and set internally as not set
                  mSelectedIndex = NO_SELECTED_INDEX;
               }
            }
         }
      }
      #endregion

      #region Functions
      /// <summary>
      /// Add a new control at the specified index
      /// </summary>
      /// <param name="indexOfControl">The index to add/insert the new
      /// control</param>
      private void AddControl(int indexOfControl)
      {
         // Create a new control
         T new_control = new T();
         // Setup the bindings from the UI to the data source
         new_control.BindingObject = mDataSource[indexOfControl];
         // Keep track of when the control is entered
         new_control.Enter += new EventHandler(ControlEnteredEventHandler);
         // Update the background color
         new_control.BackColor =
            (SelectedIndex == indexOfControl) ? 
               SelectetedControlColor : DeselectedControlColor;
         // Add the new control to the end (no insert option, so add to end
         // and then set the child index)
         mFlowLayoutPanel.Controls.Add(new_control);
         // Change the location of the control to the index desired
         mFlowLayoutPanel.Controls.SetChildIndex(new_control, indexOfControl);
      }

      /// <summary>
      /// Remove listeners from the data source and from the individual controls
      /// Also call on Dispose
      /// </summary>
      private void CleanUpDataSource()
      {
         if (mDataSource != null)
         {
            // Unregister for list change events and remove the Enter event
            // listeners for each control
            mDataSource.ListChanged -= 
               new ListChangedEventHandler(DataSourceListChangedEventHandler);
            while (mFlowLayoutPanel.Controls.Count > 0)
            {
               RemoveControl(0);
            }
            // No selected controls
            SelectedIndex = NO_SELECTED_INDEX;
         }
      }

      /// <summary>
      /// Clear the selected control (Change background color to deselected)
      /// </summary>
      private void ClearSelectedControl()
      {
         if (SelectedIndex != NO_SELECTED_INDEX)
         {
            T last_ui_control_selected = GetControl(SelectedIndex);
            if (last_ui_control_selected != null)
            {
               last_ui_control_selected.BackColor = DeselectedControlColor;
            }
         }
      }

      /// <summary>
      /// Get the control at the specified index from the FlowLayoutPanel
      /// </summary>
      /// <param name="indexOfControl">The index of the control to get</param>
      /// <returns>The control at the specified index.  null if the control does
      /// not exist</returns>
      private T GetControl(int indexOfControl)
      {
         // Make sure the index is valid, otherwise return null
         if (indexOfControl >= mFlowLayoutPanel.Controls.Count)
         {
            return null;
         }
         return mFlowLayoutPanel.Controls[indexOfControl] as T;
      }

      /// <summary>
      /// Fire the property changed event with the specified property name
      /// </summary>
      /// <param name="propName">The property name that was updated</param>
      private void NotifyPropertyChanged(String propName)
      {
         PropertyChangedEventHandler prop_changed = PropertyChanged;
         if (prop_changed != null)
         {
            prop_changed(this, new PropertyChangedEventArgs(propName));
         }
      }

      /// <summary>
      /// Remove the control at the specified index
      /// </summary>
      /// <param name="indexOfControl">The index of the control to remove
      /// from the FlowLayoutPanel</param>
      private void RemoveControl(int indexOfControl)
      {
         // Get the control to remove from the FlowLayoutPanel
         T control_to_remove = mFlowLayoutPanel.Controls[indexOfControl] as T;
         // Unregister the Enter event
         control_to_remove.Enter -= new EventHandler(ControlEnteredEventHandler);
         control_to_remove.BindingObject = null;
         // Remove the control from the FlowLayoutPanel
         mFlowLayoutPanel.Controls.Remove(control_to_remove);
         if (mSelectedIndex == indexOfControl)
         {
            // Internally clear the selected because the control was removed
            mSelectedIndex = NO_SELECTED_INDEX;
         }
      }

      /// <summary>
      /// Set the specified control as selected
      /// </summary>
      /// <param name="indexOfControl">The index of the control
      /// in the FlowLayoutPanel</param>
      /// <returns>True if the control was selected, False otherwise</returns>
      private bool SetSelectedControl(int indexOfControl)
      {
         if (indexOfControl >= 0)
         {
            T control_to_select = GetControl(indexOfControl);
            if (control_to_select != null)
            {
               control_to_select.Select();
               control_to_select.BackColor = SelectetedControlColor;
               mFlowLayoutPanel.ScrollControlIntoView(control_to_select);
               return true;
            }
         }
         return false;
      }
      #endregion

      #region Event Handlers
      /// <summary>
      /// Handle when a control in the FlowLayoutPanel is entered
      /// </summary>
      /// <param name="sender">The control that fired the event</param>
      /// <param name="e">The event arguments</param>
      private void ControlEnteredEventHandler(object sender, EventArgs e)
      {
         // Keep track of the currently selected index since this could be user fired
         SelectedIndex = mFlowLayoutPanel.Controls.IndexOf(sender as Control);
      }

      /// <summary>
      /// Handle when the data source has changed.  Update the UI accordingly
      /// </summary>
      /// <param name="sender">The control that fired the event</param>
      /// <param name="e">The event arguments</param>
      private void DataSourceListChangedEventHandler(object sender, ListChangedEventArgs e)
      {
         if (e.ListChangedType == ListChangedType.ItemAdded)
         {
            // Add the new control to the UI at the end that maps to the new model added
            AddControl(e.NewIndex);
         }
         else if (e.ListChangedType == ListChangedType.ItemDeleted)
         {
            // Make sure the new index is ok to be deleted
            if (mFlowLayoutPanel.Controls.Count > e.NewIndex)
            {
               // Remove the control from the UI at the specified index
               RemoveControl(e.NewIndex);
            }
         }
      }
      #endregion

      #region INotifyPropertyChanged Members
      /// <summary>
      /// The Property Changed event
      /// </summary>
      public event PropertyChangedEventHandler PropertyChanged;
      #endregion
   } // BindableFlowLayoutPanel
} // DataBindingListOfUserControls

Here is the IBindableObject definition:

public interface IBindableObject
{
   object BindingObject { set; }
}
share|improve this question
add comment

1 Answer 1

up vote 1 down vote accepted

Turns out I was going about this the wrong way. I updated this to use the CurrencyManager class instead to manage the bindings for me. This was a much better solution.

share|improve this answer
    
It would be useful to edit your question or this answer to include the change, so anyone searching for the same information can see what the solution was. –  Dan Lyons Apr 4 '12 at 17:02
    
@DanLyons - Fair enough, if anyone shows interest or ask questions I'll give more detail. Posted for over a month and not even a comment or answer, so I dont think anyones interested. Hopefully pointing out what I added will help anyone if they decide to do something similar. I had never heard of the CurrencyManager. –  SwDevMan81 Apr 4 '12 at 17:35
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.