// Most of the important code for this control was
// taken from a control written by Nishant Sivakumar.
// http://www.codeproject.com/cs/combobox/DotNetMultiColumnComboBox.asp
//
// Bugfixes or Suggestions can be sent to [email protected]
using System;
using System.Windows.Forms;
using System.Collections;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Drawing;
using System.Globalization;
using System.Data;
namespace MultiColumnComboBoxDemo
{
//โค้ดเดิม https://www.codeproject.com/Articles/20014/Searchable-MultiColumn-ComboBox-with-Linked-TextBo
public class ComboBoxMultiColumn : ComboBox
{
private bool _AutoComplete;
private bool _AutoDropdown;
private Color _BackColorEven = Color.White;
private Color _BackColorOdd = Color.White;
private string _ColumnNameString = "";
private int _ColumnWidthDefault = 75;
private string _ColumnWidthString = "";
private int _LinkedColumnIndex;
private TextBox _LinkedTextBox;
private int _TotalWidth = 0;
private int _ValueMemberColumnIndex = 0;
private Collection<string> _ColumnNames = new Collection<string>();
private Collection<int> _ColumnWidths = new Collection<int>();
public ComboBoxMultiColumn()
{
DrawMode = DrawMode.OwnerDrawVariable;
// If all of your boxes will be RightToLeft, uncomment
// the following line to make RTL the default.
//RightToLeft = RightToLeft.Yes;
// Remove the Context Menu to disable pasting
ContextMenu = new ContextMenu();
}
public event System.EventHandler OpenSearchForm;
public bool AutoComplete
{
get
{
return _AutoComplete;
}
set
{
_AutoComplete = value;
}
}
public bool AutoDropdown
{
get
{
return _AutoDropdown;
}
set
{
_AutoDropdown = value;
}
}
public Color BackColorEven
{
get
{
return _BackColorEven;
}
set
{
_BackColorEven = value;
}
}
public Color BackColorOdd
{
get
{
return _BackColorOdd;
}
set
{
_BackColorOdd = value;
}
}
public Collection<string> ColumnNameCollection
{
get
{
return _ColumnNames;
}
}
public string ColumnNames
{
get
{
return _ColumnNameString;
}
set
{
// If the column string is blank, leave it blank.
// The default width will be used for all columns.
if (! Convert.ToBoolean(value.Trim().Length))
{
_ColumnNameString = "";
}
else if (value != null)
{
char[] delimiterChars = { ',', ';', ':' };
string[] columnNames = value.Split(delimiterChars);
if (!DesignMode)
{
_ColumnNames.Clear();
}
// After splitting the string into an array, iterate
// through the strings and check that they're all valid.
foreach (string s in columnNames)
{
// Does it have length?
if (Convert.ToBoolean(s.Trim().Length))
{
if (!DesignMode)
{
_ColumnNames.Add(s.Trim());
}
}
else // The value is blank
{
throw new NotSupportedException("Column names can not be blank.");
}
}
_ColumnNameString = value;
}
}
}
public Collection<int> ColumnWidthCollection
{
get
{
return _ColumnWidths;
}
}
public int ColumnWidthDefault
{
get
{
return _ColumnWidthDefault;
}
set
{
_ColumnWidthDefault = value;
}
}
public string ColumnWidths
{
get
{
return _ColumnWidthString;
}
set
{
// If the column string is blank, leave it blank.
// The default width will be used for all columns.
if (! Convert.ToBoolean(value.Trim().Length))
{
_ColumnWidthString = "";
}
else if (value != null)
{
char[] delimiterChars = { ',', ';', ':' };
string[] columnWidths = value.Split(delimiterChars);
string invalidValue = "";
int invalidIndex = -1;
int idx = 1;
int intValue;
// After splitting the string into an array, iterate
// through the strings and check that they're all integers
// or blanks
foreach (string s in columnWidths)
{
// If it has length, test if it's an integer
if (Convert.ToBoolean(s.Trim().Length))
{
// It's not an integer. Flag the offending value.
if (!int.TryParse(s, out intValue))
{
invalidIndex = idx;
invalidValue = s;
}
else // The value was okay. Increment the item index.
{
idx++;
}
}
else // The value is a space. Use the default width.
{
idx++;
}
}
// If an invalid value was found, raise an exception.
if (invalidIndex > -1)
{
string errMsg;
errMsg = "Invalid column width '" + invalidValue + "' located at column " + invalidIndex.ToString();
throw new ArgumentOutOfRangeException(errMsg);
}
else // The string is fine
{
_ColumnWidthString = value;
// Only set the values of the collections at runtime.
// Setting them at design time doesn't accomplish
// anything and causes errors since the collections
// don't exist at design time.
if (!DesignMode)
{
_ColumnWidths.Clear();
foreach (string s in columnWidths)
{
// Initialize a column width to an integer
if (Convert.ToBoolean(s.Trim().Length))
{
_ColumnWidths.Add(Convert.ToInt32(s));
}
else // Initialize the column to the default
{
_ColumnWidths.Add(_ColumnWidthDefault);
}
}
// If the column is bound to data, set the column widths
// for any columns that aren't explicitly set by the
// string value entered by the programmer
if (DataManager != null)
{
InitializeColumns();
}
}
}
}
}
}
public new DrawMode DrawMode
{
get
{
return base.DrawMode;
}
set
{
if (value != DrawMode.OwnerDrawVariable)
{
throw new NotSupportedException("Needs to be DrawMode.OwnerDrawVariable");
}
base.DrawMode = value;
}
}
public new ComboBoxStyle DropDownStyle
{
get
{
return base.DropDownStyle;
}
set
{
if (value != ComboBoxStyle.DropDown)
{
throw new NotSupportedException("ComboBoxStyle.DropDown is the only supported style");
}
base.DropDownStyle = value;
}
}
public int LinkedColumnIndex
{
get
{
return _LinkedColumnIndex;
}
set
{
if (value < 0)
{
throw new ArgumentOutOfRangeException("A column index can not be negative");
}
_LinkedColumnIndex = value;
}
}
public TextBox LinkedTextBox
{
get
{
return _LinkedTextBox;
}
set
{
_LinkedTextBox = value;
if (_LinkedTextBox != null)
{
// Set any default properties of the Linked Textbox here
_LinkedTextBox.ReadOnly = true;
_LinkedTextBox.TabStop = false;
}
}
}
public int TotalWidth
{
get
{
return _TotalWidth;
}
}
protected override void OnDataSourceChanged(EventArgs e)
{
base.OnDataSourceChanged(e);
if (DataSource is BindingSource)
DataSource = ToDataTable((BindingSource)DataSource);
InitializeColumns();
}
DataTable ToDataTable( BindingSource bs)
{
var bsFirst = bs;
while (bsFirst.DataSource is BindingSource)
bsFirst = (BindingSource)bsFirst.DataSource;
DataTable dt;
if (bsFirst.DataSource is DataSet)
dt = ((DataSet)bsFirst.DataSource).Tables[bsFirst.DataMember];
else if (bsFirst.DataSource is DataTable)
dt = (DataTable)bsFirst.DataSource;
else
return null;
if (bsFirst != bs)
{
if (dt.DataSet == null) return null;
dt = dt.DataSet.Relations[bs.DataMember].ChildTable;
}
return dt;
}
protected override void OnDrawItem(DrawItemEventArgs e)
{
base.OnDrawItem(e);
if (DesignMode)
return;
e.DrawBackground();
Rectangle boundsRect = e.Bounds;
int lastRight = 0;
Color brushForeColor;
if ((e.State & DrawItemState.Selected) == 0)
{
// Item is not selected. Use BackColorOdd & BackColorEven
Color backColor;
backColor = Convert.ToBoolean(e.Index % 2) ? _BackColorOdd : _BackColorEven;
using (SolidBrush brushBackColor = new SolidBrush(backColor))
{
e.Graphics.FillRectangle(brushBackColor, e.Bounds);
}
brushForeColor = Color.Black;
}
else
{
// Item is selected. Use ForeColor = White
brushForeColor = Color.White;
}
using (Pen linePen = new Pen(SystemColors.GrayText))
{
using (SolidBrush brush = new SolidBrush(brushForeColor))
{
if (! Convert.ToBoolean(_ColumnNames.Count))
{
e.Graphics.DrawString(Convert.ToString(Items[e.Index]), Font, brush, boundsRect);
}
else
{
// If the ComboBox is displaying a RightToLeft language, draw it this way.
if (RightToLeft.Equals(RightToLeft.Yes))
{
// Define a StringFormat object to make the string display RTL.
StringFormat rtl = new StringFormat();
rtl.Alignment = StringAlignment.Near;
rtl.FormatFlags = StringFormatFlags.DirectionRightToLeft;
// Draw the strings in reverse order from high column index to zero column index.
for (int colIndex = _ColumnNames.Count - 1; colIndex >= 0; colIndex--)
{
if (Convert.ToBoolean(_ColumnWidths[colIndex]))
{
string item = Convert.ToString(FilterItemOnProperty(Items[e.Index], _ColumnNames[colIndex]));
boundsRect.X = lastRight;
boundsRect.Width = (int)_ColumnWidths[colIndex];
lastRight = boundsRect.Right;
// Draw the string with the RTL object.
e.Graphics.DrawString(item, Font, brush, boundsRect, rtl);
if (colIndex > 0)
{
e.Graphics.DrawLine(linePen, boundsRect.Right, boundsRect.Top, boundsRect.Right, boundsRect.Bottom);
}
}
}
}
// If the ComboBox is displaying a LeftToRight language, draw it this way.
else
{
// Display the strings in ascending order from zero to the highest column.
for (int colIndex = 0; colIndex < _ColumnNames.Count; colIndex++)
{
if (Convert.ToBoolean(_ColumnWidths[colIndex]))
{
string item = Convert.ToString(FilterItemOnProperty(Items[e.Index], _ColumnNames[colIndex]));
boundsRect.X = lastRight;
boundsRect.Width = (int)_ColumnWidths[colIndex];
lastRight = boundsRect.Right;
e.Graphics.DrawString(item, Font, brush, boundsRect);
if (colIndex < _ColumnNames.Count - 1)
{
e.Graphics.DrawLine(linePen, boundsRect.Right, boundsRect.Top, boundsRect.Right, boundsRect.Bottom);
}
}
}
}
}
}
}
e.DrawFocusRectangle();
}
protected override void OnDropDown(EventArgs e)
{
base.OnDropDown(e);
if (_TotalWidth > 0)
{
if (Items.Count > MaxDropDownItems)
{
// The vertical scrollbar is present. Add its width to the total.
// If you don't then RightToLeft languages will have a few characters obscured.
this.DropDownWidth = _TotalWidth + SystemInformation.VerticalScrollBarWidth;
}
else
{
this.DropDownWidth = _TotalWidth;
}
}
}
protected override void OnKeyDown(KeyEventArgs e)
{
// Use the Delete or Escape Key to blank out the ComboBox and
// allow the user to type in a new value
if ((e.KeyCode == Keys.Delete) ||
(e.KeyCode == Keys.Escape))
{
SelectedIndex = -1;
Text = "";
if (_LinkedTextBox != null)
{
_LinkedTextBox.Text = "";
}
}
else if (e.KeyCode == Keys.F3)
{
// Fire the OpenSearchForm Event
if (OpenSearchForm != null)
{
OpenSearchForm(this, System.EventArgs.Empty);
}
}
}
// Some of the code for OnKeyPress was derived from some VB.NET code
// posted by Laurent Muller as a suggested improvement for another control.
// http://www.codeproject.com/vb/net/autocomplete_combobox.asp?df=100&forumid=3716&select=579095#xx579095xx
protected override void OnKeyPress(KeyPressEventArgs e)
{
int idx = -1;
string toFind;
DroppedDown = _AutoDropdown;
if (!Char.IsControl(e.KeyChar))
{
if (_AutoComplete)
{
toFind = Text.Substring(0, SelectionStart) + e.KeyChar;
idx = FindStringExact(toFind);
if (idx == -1)
{
// An exact match for the whole string was not found
// Find a substring instead.
idx = FindString(toFind);
}
else
{
// An exact match was found. Close the dropdown.
DroppedDown = false;
}
if (idx != -1) // The substring was found.
{
SelectedIndex = idx;
SelectionStart = toFind.Length;
SelectionLength = Text.Length - SelectionStart;
}
else // The last keystroke did not create a valid substring.
{
// If the substring is not found, cancel the keypress
e.KeyChar = (char)0;
}
}
else // AutoComplete = false. Treat it like a DropDownList by finding the
// KeyChar that was struck starting from the current index
{
idx = FindString(e.KeyChar.ToString(), SelectedIndex);
if (idx != -1)
{
SelectedIndex = idx;
}
}
}
// Do no allow the user to backspace over characters. Treat it like
// a left arrow instead. The user must not be allowed to change the
// value in the ComboBox.
if ((e.KeyChar == (char)(Keys.Back)) && // A Backspace Key is hit
(_AutoComplete) && // AutoComplete = true
(Convert.ToBoolean(SelectionStart))) // And the SelectionStart is positive
{
// Find a substring that is one character less the the current selection.
// This mimicks moving back one space with an arrow key. This substring should
// always exist since we don't allow invalid selections to be typed. If you're
// on the 3rd character of a valid code, then the first two characters have to
// be valid. Moving back to them and finding the 1st occurrence should never fail.
toFind = Text.Substring(0, SelectionStart - 1);
idx = FindString(toFind);
if (idx != -1)
{
SelectedIndex = idx;
SelectionStart = toFind.Length;
SelectionLength = Text.Length - SelectionStart;
}
}
// e.Handled is always true. We handle every keystroke programatically.
e.Handled = true;
}
protected override void OnSelectedValueChanged(EventArgs e)
{
base.OnSelectedValueChanged(e); //Added after version 1.3 on 01/31/2008
if (_LinkedTextBox != null)
{
if (_LinkedColumnIndex < _ColumnNames.Count)
{
_LinkedTextBox.Text = Convert.ToString(FilterItemOnProperty(SelectedItem, _ColumnNames[_LinkedColumnIndex]));
}
}
}
protected override void OnValueMemberChanged(EventArgs e)
{
base.OnValueMemberChanged(e);
InitializeValueMemberColumn();
}
private void InitializeColumns()
{
if (!Convert.ToBoolean(_ColumnNameString.Length) && DataManager !=null)
{
// MessageBox.Show("" + DataManager.Count);
PropertyDescriptorCollection propertyDescriptorCollection = DataManager.GetItemProperties();
_TotalWidth = 0;
_ColumnNames.Clear();
for (int colIndex = 0; colIndex < propertyDescriptorCollection.Count; colIndex++)
{
_ColumnNames.Add(propertyDescriptorCollection[colIndex].Name);
if (colIndex >= _ColumnWidths.Count)
{
_ColumnWidths.Add(_ColumnWidthDefault);
}
_TotalWidth += _ColumnWidths[colIndex];
}
}
else
{
_TotalWidth = 0;
for (int colIndex = 0; colIndex < _ColumnNames.Count; colIndex++)
{
if (colIndex >= _ColumnWidths.Count)
{
_ColumnWidths.Add(_ColumnWidthDefault);
}
_TotalWidth += _ColumnWidths[colIndex];
}
}
if (_LinkedColumnIndex >= _ColumnNames.Count)
{
_LinkedColumnIndex = 0; // Or replace this with an OutOfBounds Exception
}
}
private void InitializeValueMemberColumn()
{
int colIndex = 0;
foreach (String columnName in _ColumnNames)
{
if (String.Compare(columnName, ValueMember, true, CultureInfo.CurrentUICulture) == 0)
{
_ValueMemberColumnIndex = colIndex;
break;
}
colIndex++;
}
}
}
}