5
\$\begingroup\$

I'm trying to create a Bindable FlowLayoutPanel that can take a DataSource and map it the specified generic parameter <T> 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 (it's 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.

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; }
}
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Feb 9, 2012 at 16:14
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

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.

answered Apr 4, 2012 at 15:14
\$\endgroup\$
3
  • 1
    \$\begingroup\$ 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. \$\endgroup\$ Commented Apr 4, 2012 at 17:02
  • \$\begingroup\$ @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. \$\endgroup\$ Commented Apr 4, 2012 at 17:35
  • 2
    \$\begingroup\$ I'd be interested... I know it was a long time ago, but it was an interesting idea. Id love to know how it was ultimately solved. \$\endgroup\$ Commented Jan 22, 2016 at 0:42

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.