| using System; |
| using System.Collections; |
| using System.Collections.Generic; |
| using System.Collections.ObjectModel; |
| using System.ComponentModel; |
| using System.Drawing; |
| using System.Linq; |
| using System.Windows.Forms; |
| using Telerik.WinControls.UI; |
| using Telerik.WinControls; |
| |
| namespace Our.Win.Core.Controls |
| { |
| public partial class OurRadCombo : RadMultiColumnComboBox |
| { |
| private bool _autoComplete = true; |
| private bool _autoTab = true; |
| private string _displayMember = ""; |
| private DisplayColumnInfo[] _columns; |
| private bool _columnsSet; |
| |
| private readonly List<ComboSearchData> _searchData = new List<ComboSearchData>(); |
| |
| public override string ThemeClassName |
| { |
| |
| get |
| { |
| return "Telerik.WinControls.UI.RadMultiColumnComboBox"; |
| } |
| |
| set |
| { |
| |
| base.ThemeClassName = value; |
| |
| } |
| |
| } |
| |
| public OurRadCombo() |
| { |
| Init(); |
| } |
| |
| public OurRadCombo(IContainer container) |
| { |
| container.Add(this); |
| Init(); |
| } |
| |
| private void Init() |
| { |
| InitializeComponent(); |
| InitializeLayout(); |
| |
| MultiColumnComboBoxElement.TextBoxElement.KeyPress += OurRadComboKeyPress; |
| MultiColumnComboBoxElement.TextBoxElement.KeyDown += OurRadComboKeyDown; |
| DropDownClosed += OurRadComboDropDownClosed; |
| //DropDownStyle = RadDropDownStyle.DropDownList; |
| MultiColumnComboBoxElement.EditorControl.MasterGridViewTemplate.ShowRowHeaderColumn = false; |
| AutoFilter = true; |
| |
| EditorControl.CellClick += EditorControlCellClick; |
| ThemeName = "Vista"; |
| |
| //disable fade animation - takes forever on farm |
| //Had to comment this out, sometimes dropdown doesn't close, if list is longer than visible height |
| //MultiColumnPopupForm.FadeAnimationType = FadeAnimationType.None; |
| |
| //This has the same effect as setting the fade to none, but doesn't prevent the drop-down from closing. |
| MultiColumnPopupForm.FadeAnimationFrames = 1; |
| |
| } |
| |
| void EditorControlCellClick(object sender, GridViewCellEventArgs e) |
| { |
| SetNextControl(); |
| } |
| |
| protected override void OnLoad(Size desiredSize) |
| { |
| base.OnLoad(desiredSize); |
| |
| |
| ((GridTableElement)MultiColumnComboBoxElement.EditorControl.GridElement). |
| AlternatingRowColor = Colors.OurAlternateRow; |
| |
| } |
| |
| public new string DisplayMember |
| { |
| get { return _displayMember; } |
| set |
| { |
| base.DisplayMember = value; |
| if (value != _displayMember) |
| { |
| _displayMember = value; |
| SetSearchData(); |
| } |
| } |
| } |
| |
| void OurRadComboDropDownClosed(object sender, RadPopupClosedEventArgs args) |
| { |
| // works but looks funky |
| //SetNextControl(); |
| } |
|
| #region Properties |
| |
| /// <summary> |
| /// Sorry, we only allow DropDownList style |
| /// </summary> |
| [Browsable(true), |
| DefaultValue(true), |
| Description("Sorry, we only allow DropDownList style. The setter property is only for backwards compatibility")] |
| [Obsolete] |
| public new RadDropDownStyle DropDownStyle |
| { |
| get { return RadDropDownStyle.DropDownList; } |
| set { /*only provided for backward compatibility*/ ; } |
| } |
| |
| |
| /// <summary> |
| /// Use AutoComplete for this control |
| /// </summary> |
| [Browsable(true), |
| DefaultValue(true), |
| Description("Use AutoComplete for this control")] |
| public bool AutoComplete |
| { |
| get { return _autoComplete; } |
| set { _autoComplete = value; } |
| } |
| |
| |
| |
| /// <summary> |
| /// Use AutoTab (to next control in tab stop) for this control |
| /// </summary> |
| [Browsable(true), |
| DefaultValue(true), |
| Description("Use AutoTab for this control causing the focus to move on to the next Tab Stop when the DropDown is closed")] |
| public bool AutoTab |
| { |
| get { return _autoTab; } |
| set { _autoTab = value; } |
| } |
| |
| |
| /// <summary> |
| /// Gets or sets the dropdown's DataSource. Sets selected index to -1. |
| /// </summary> |
| public new object DataSource |
| { |
| get |
| { |
| return base.DataSource; |
| } |
| |
| set |
| { |
| base.DataSource = value; |
| SetDataSourceCommon(); |
| } |
| } |
| |
| private bool _rowColors = true; |
| /// <summary> |
| /// UseAlternatingRowColors |
| /// </summary> |
| [Browsable(true), |
| DefaultValue(true), |
| Description("Use alternating row colors in drop down grid")] |
| public bool UseAlternatingRowColors |
| { |
| get { return _rowColors; } |
| set |
| { |
| _rowColors = value; |
| MultiColumnComboBoxElement.EditorControl.EnableAlternatingRowColor = _rowColors; |
| } |
| } |
| |
| private bool _columnHeaders = true; |
| /// <summary> |
| /// Show column headers. |
| /// </summary> |
| /// |
| [Browsable(true), |
| DefaultValue(true), |
| Description("Shows or hides column header row."), |
| EditorBrowsable(EditorBrowsableState.Always), |
| Localizable(true)] |
| public bool ShowColumnHeaders |
| { |
| get |
| { |
| return _columnHeaders; |
| } |
| |
| set |
| { |
| _columnHeaders = value; |
| MultiColumnComboBoxElement.EditorControl.MasterGridViewTemplate.ShowColumnHeaders = _columnHeaders; |
| } |
| } |
| |
| private bool _rowSelectColumn; |
| /// <summary> |
| /// Show or hide row header column. |
| /// </summary> |
| [Browsable(true), |
| DefaultValue(false), |
| Description("Shows or hides column selection row."), |
| EditorBrowsable(EditorBrowsableState.Always), |
| Localizable(true)] |
| public bool ShowRowSelectColumn |
| { |
| get { return _rowSelectColumn; } |
| set |
| { |
| _rowSelectColumn = value; |
| MultiColumnComboBoxElement.EditorControl.MasterGridViewTemplate.ShowRowHeaderColumn = _rowSelectColumn; |
| } |
| } |
| |
| |
| /// <summary> |
| /// Overide the Value property so that it will get and set an empty string when a null is get or set. |
| /// This is used to work-around the inability to handle nulls in this version. |
| /// </summary> |
| public bool ReplaceNullValueWithEmptyString |
| { get; set; } |
|
| #endregion |
|
| #region Methods |
| |
| |
| /// <summary> |
| /// Sets basic properties for look and feel of control. |
| /// </summary> |
| private void InitializeLayout() |
| { |
| MultiColumnComboBoxElement.EditorControl.MasterGridViewTemplate.AutoSizeColumnsMode = |
| GridViewAutoSizeColumnsMode.Fill; |
| } |
| |
| /// <summary> |
| /// Autosizes columns in drop down. |
| /// </summary> |
| public void AutoSizeAllColumns() |
| { |
| MultiColumnComboBoxElement.EditorControl.MasterGridViewTemplate.BestFitColumns(); |
| } |
| |
| /// <summary> |
| /// Hides columns that are not present in the provided DisplayColumnInfo array. |
| /// </summary> |
| /// <param name="columns"></param> |
| public void ShowColumns(params DisplayColumnInfo[] columns) |
| { |
| try |
| { |
| // save in case datasource changes which sets all columns to isVisible |
| _columns = columns; |
| |
| foreach (GridViewDataColumn column in MultiColumnComboBoxElement.Columns) |
| { |
| column.IsVisible = false; |
| |
| foreach (DisplayColumnInfo colInfo in columns) |
| { |
| if (colInfo.ColumnName == column.UniqueName) |
| { |
| column.IsVisible = true; |
| column.HeaderText = colInfo.ColumnCaption; |
| column.SortOrder = colInfo.Order; |
| } |
| } |
| } |
| _columnsSet = true; |
| } |
| catch (Exception) |
| { |
| { } |
| } |
| } |
| |
| /// <summary> |
| /// Hides columns that are not present in the provided string array. |
| /// </summary> |
| /// <param name="cols"></param> |
| public void ShowColumns(string[] cols) |
| { |
| try |
| { |
| _columns = new DisplayColumnInfo[cols.Count()]; |
| var colidx = 0; |
| |
| foreach (GridViewDataColumn column in MultiColumnComboBoxElement.Columns) |
| { |
| column.IsVisible = false; |
| |
| foreach (string str in cols) |
| { |
| if (str == column.UniqueName) |
| { |
| column.IsVisible = true; |
| |
| if (string.IsNullOrEmpty(column.HeaderText)) |
| { |
| column.HeaderText = str; |
| } |
| } |
| _columns[colidx] = new DisplayColumnInfo(str, column.HeaderText); |
| } |
| } |
| _columnsSet = true; |
| } |
| catch (Exception) |
| { |
| { } |
| } |
| } |
| |
| /// <summary> |
| /// Provides a mechanism to bind an array of strings to the multi-column combo box. RadGrid will display |
| /// only a string's length unless you bind to a list of objects that exposes a string property. |
| /// </summary> |
| public void DataSourceAsBindingList(string[] str) |
| { |
| var bindList = new BindingList<ObjectList>(); |
| foreach (string s in str) |
| { |
| bindList.Add(new ObjectList(s)); |
| } |
| DataSource = bindList; |
| } |
| |
| /// <summary> |
| /// Bind a Ilist of type string to a BindingList of type that the combo grid can use. |
| /// </summary> |
| /// <param name="col"></param> |
| public void DataSourceAsBindingList(IList<string> col) |
| { |
| var bindList = new BindingList<ObjectList>(); |
| foreach (string s in col) |
| { |
| bindList.Add(new ObjectList(s)); |
| } |
| DataSource = bindList; |
| } |
| |
| /// <summary> |
| /// Bind a ReadOnlyCollection of type string to a BindingList of type that the combo grid can use. |
| /// </summary> |
| /// <param name="col"></param> |
| public void DataSourceAsBindingList(ReadOnlyCollection<string> col) |
| { |
| var bindList = new BindingList<ObjectList>(); |
| foreach (string s in col) |
| { |
| bindList.Add(new ObjectList(s)); |
| } |
| DataSource = bindList; |
| } |
| |
| /// <summary> |
| /// Binds a list of type T to the grid using a BindingList. Use this method when the Telerik |
| /// grid does not bind to something that a control should be able to bind to. |
| /// </summary> |
| /// <typeparam name="T"></typeparam> |
| /// <param name="list"></param> |
| public void DataSourceAsBindingList<T>(List<T> list) |
| { |
| var bindList = new BindingList<ObjectList>(); |
| |
| foreach (var item in list) |
| { |
| bindList.Add(new ObjectList(item)); |
| } |
| DataSource = bindList; |
| } |
| |
| /// <summary> |
| /// Binds an ArrayList to the grid using a BindingList. Use this method when the Telerik |
| /// grid does not bind to something that a control should be able to bind to. |
| /// </summary> |
| /// <param name="list"></param> |
| public void DataSourceAsBindingList(ArrayList list) |
| { |
| var bindList = new BindingList<ObjectList>(); |
| |
| foreach (var item in list) |
| { |
| bindList.Add(new ObjectList(item)); |
| } |
| DataSource = bindList; |
| } |
| |
| /// <summary> |
| /// Handles the common processes of setting the datasource |
| /// </summary> |
| private void SetDataSourceCommon() |
| { |
| SetSearchData(); |
| if (_columnsSet) |
| ShowColumns(_columns); |
| |
| SelectedIndex = -1; |
| } |
| |
| public int FindExact(string find) |
| { |
| return FindStringExact(find, _searchData); |
| } |
| |
| |
| public object FindItemExact(object find) |
| { |
| var idx = FindStringExact(find.ToString(), _searchData); |
| if (idx == -1) |
| return null; |
| return from o in _searchData where o.Idx == idx select o.Val; |
| } |
| |
| public object FindItemExactAndSetIndex(object find) |
| { |
| int idx = FindStringExact(find.ToString(), _searchData); |
| if (idx == -1) |
| return null; |
| SelectedIndex = idx; |
| return from o in _searchData where o.Idx == idx select o.Val; |
| } |
| |
| private static int FindString(string find, IEnumerable<ComboSearchData> searchData) |
| { |
| var found = (from o in searchData |
| where o.Val.Trim().ToUpper().StartsWith(find.Trim().ToUpper()) |
| select o).FirstOrDefault(); |
| |
| if (found != null) |
| return found.Idx; |
| return -1; |
| } |
| |
| private static int FindString(string find, IEnumerable<ComboSearchData> searchData, int selectedIndex) |
| { |
| var found = (from o in searchData |
| where o.Val.Trim().ToUpper().StartsWith(find.Trim().ToUpper()) && o.Idx > selectedIndex |
| select o).FirstOrDefault(); |
| |
| if (found != null) |
| return found.Idx; |
| return -1; |
| } |
| |
| private static int FindStringExact(string find, IEnumerable<ComboSearchData> searchData) |
| { |
| var found = (from o in searchData |
| where o.Val.Trim().ToUpper() == find.Trim().ToUpper() |
| select o).FirstOrDefault(); |
| |
| if (found != null) |
| return found.Idx; |
| return -1; |
| } |
| |
| |
| private void SetSearchData() |
| { |
| var idx = 0; |
| _searchData.Clear(); |
| // |
| // If the DisplayMember is not set then use cell index 0. |
| // So, it would be a good idea to always set DisplayMember. |
| // |
| var cellidx = 0; |
| if (_displayMember.Trim().Length > 0) |
| if (EditorControl.MasterGridViewTemplate.Columns[_displayMember] == null) |
| return; |
| else |
| cellidx = EditorControl.MasterGridViewTemplate.Columns[_displayMember].Index; |
| |
| |
| foreach (var row in EditorControl.Rows) |
| { |
| if (row.Cells[cellidx] == null) //check added by JustinG Mar 24, 2010 |
| continue; |
| if (row.Cells.Count == 1 && row.Cells[cellidx].ColumnInfo.UniqueName == "Length") |
| // The Binding Source is an array. |
| _searchData.Add(new ComboSearchData { Idx = idx++, Val = row.DataBoundItem.ToString() }); |
| else |
| _searchData.Add(new ComboSearchData { Idx = idx++, Val = row.Cells[cellidx].Value.ToString() }); |
| } |
| } |
| |
| private void OurRadComboKeyDown(object sender, KeyEventArgs e) |
| { |
| if (e.KeyCode == Keys.Enter) |
| { |
| SetNextControl(); |
| e.Handled = true; |
| return; |
| } |
| |
| |
| // 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 = ""; |
| } |
| } |
| |
| |
| private void OurRadComboKeyPress(object sender, KeyPressEventArgs e) |
| { |
| var idx = -1; |
| string toFind; |
| |
| try |
| { |
| if (!Char.IsControl(e.KeyChar)) |
| { |
| if (AutoComplete) |
| { |
| toFind = Text.Substring(0, ((RadTextBoxItem)FocusedElement).SelectionStart) + e.KeyChar; |
| idx = FindStringExact(toFind, _searchData); |
| |
| if (idx == -1) |
| { |
| // An exact match for the whole string was not found |
| // Find a substring instead. |
| idx = FindString(toFind, _searchData); |
| } |
| else |
| { |
| // An exact match was found. Close the dropdown. |
| EditorControl.CloseEditor();// DroppedDown = false; |
| } |
| |
| if (idx != -1) // The substring was found. |
| { |
| SelectedIndex = idx; |
| ((RadTextBoxItem)FocusedElement).SelectionStart = toFind.Length; |
| ((RadTextBoxItem)FocusedElement).SelectionLength = Text.Length - ((RadTextBoxItem)FocusedElement).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(), _searchData, SelectedIndex); |
| |
| if (idx != -1) |
| { |
| SelectedIndex = idx; |
| } |
| } |
| } |
| } |
| // If you find the AutoComplete not working, check here. |
| // You can always throw the exception but you will have to handle it. |
| catch (Exception) |
| { |
| e.KeyChar = (char)0; |
| } |
| |
| // 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(((RadTextBoxItem)FocusedElement).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, ((RadTextBoxItem)FocusedElement).SelectionStart - 1); |
| idx = FindString(toFind, _searchData); |
| |
| if (idx != -1) |
| { |
| SelectedIndex = idx; |
| ((RadTextBoxItem)FocusedElement).SelectionStart = toFind.Length; |
| ((RadTextBoxItem)FocusedElement).SelectionLength = Text.Length - ((RadTextBoxItem)FocusedElement).SelectionStart; |
| } |
| } |
| |
| // e.Handled is always true. We handle every keystroke programatically. |
| e.Handled = true; |
| } |
| |
| private void SetNextControl() |
| { |
| // this actually works better than the Parent.SelectNextControl(this, true, false, true, true); |
| if (_autoTab && SelectedIndex >= 0) |
| //Parent.SelectNextControl(this, true, false, true, true); |
| SendKeys.Send("{tab}"); |
| |
| // TODO: This needs some testing. Maybe only invoke if not _autoTab? |
| InvokeInputConfirmed(); |
| } |
|
| #endregion |
|
|
| #region InputConfirmed Event handling |
| |
| private event InputConfirmedEventHandler _inputConfirmed; |
| |
| [Browsable(true)] |
| public event InputConfirmedEventHandler InputConfirmed |
| { |
| add { _inputConfirmed += value; } |
| remove { _inputConfirmed -= value; } |
| } |
| |
| protected void InvokeInputConfirmed() |
| { |
| if (_inputConfirmed != null) |
| { |
| _inputConfirmed(this, new EventArgs()); |
| } |
| } |
| |
| public virtual void OnInputConfirmed(EventArgs e) |
| { |
| if (_inputConfirmed != null) |
| { |
| _inputConfirmed(this, e); |
| } |
| } |
|
| #endregion |
|
|
|
| #region Internal Helper Classes |
| |
| internal class ComboSearchData |
| { |
| internal int Idx { get; set; } |
| internal string Val { get; set; } |
| } |
|
| #endregion |
| |
| } |
|
| #region Public Helper Classes |
| public class DisplayColumnInfo |
| { |
| |
| public DisplayColumnInfo(string columnName, string columnCaption) |
| { |
| ColumnName = columnName; |
| ColumnCaption = columnCaption; |
| } |
| |
| public DisplayColumnInfo(string columnName, string columnCaption, RadSortOrder order) |
| { |
| ColumnName = columnName; |
| ColumnCaption = columnCaption; |
| Order = order; |
| } |
| |
| public string ColumnName { get; set; } |
| |
| public string ColumnCaption { get; set; } |
| |
| public RadSortOrder Order { get; set; } |
| |
| } |
| #endregion |
| |
| |
| } |
| |
| |
| |