自定义 MultiColumnComboBox[转]

  1 // taken from a control written by Nishant Sivakumar.
  2 // http://www.codeproject.com/cs/combobox/DotNetMultiColumnComboBox.asp
  3 // http://www.51aspx.com/CodeFile/FengfanSell/Market/MultiColumnComboBox.cs.html
  4 // Bugfixes or Suggestions can be sent to dcaillouet@littlerock.org
  5 
  6 
  7 using System;
  8 using System.Windows.Forms;
  9 using System.Collections;
 10 using System.Collections.ObjectModel;
 11 using System.ComponentModel;
 12 using System.Drawing;
 13 using System.Globalization;
 14 
 15 namespace WindowsFormsApplication1
 16 {
 17     public class MultiColumnComboBox : ComboBox
 18     {
 19         private bool _AutoComplete;
 20         private bool _AutoDropdown;
 21         private Color _BackColorEven = Color.White;
 22         private Color _BackColorOdd = Color.White;
 23         private string _ColumnNameString = "";
 24         private int _ColumnWidthDefault = 75;
 25         private string _ColumnWidthString = "";
 26         private int _LinkedColumnIndex;
 27         private TextBox _LinkedTextBox;
 28         private int _TotalWidth = 0;
 29         private int _ValueMemberColumnIndex = 0;
 30 
 31         private Collection<string> _ColumnNames = new Collection<string>();
 32         private Collection<int> _ColumnWidths = new Collection<int>();
 33 
 34         public MultiColumnComboBox()
 35         {
 36             DrawMode = DrawMode.OwnerDrawVariable;
 37 
 38             // If all of your boxes will be RightToLeft, uncomment 
 39             // the following line to make RTL the default.
 40             //RightToLeft = RightToLeft.Yes;
 41 
 42             // Remove the Context Menu to disable pasting 
 43             ContextMenu = new ContextMenu();
 44         }
 45 
 46         public event System.EventHandler OpenSearchForm;
 47 
 48         public bool AutoComplete
 49         {
 50             get
 51             {
 52                 return _AutoComplete;
 53             }
 54             set
 55             {
 56                 _AutoComplete = value;
 57             }
 58         }
 59 
 60         public bool AutoDropdown
 61         {
 62             get
 63             {
 64                 return _AutoDropdown;
 65             }
 66             set
 67             {
 68                 _AutoDropdown = value;
 69             }
 70         }
 71 
 72         public Color BackColorEven
 73         {
 74             get
 75             {
 76                 return _BackColorEven;
 77             }
 78             set
 79             {
 80                 _BackColorEven = value;
 81             }
 82         }
 83 
 84         public Color BackColorOdd
 85         {
 86             get
 87             {
 88                 return _BackColorOdd;
 89             }
 90             set
 91             {
 92                 _BackColorOdd = value;
 93             }
 94         }
 95 
 96         public Collection<string> ColumnNameCollection
 97         {
 98             get
 99             {
100                 return _ColumnNames;
101             }
102         }
103 
104         public string ColumnNames
105         {
106             get
107             {
108                 return _ColumnNameString;
109             }
110 
111             set
112             {
113                 // If the column string is blank, leave it blank.
114                 // The default width will be used for all columns.
115                 if (!Convert.ToBoolean(value.Trim().Length))
116                 {
117                     _ColumnNameString = "";
118                 }
119                 else if (value != null)
120                 {
121                     char[] delimiterChars = { ',', ';', ':' };
122                     string[] columnNames = value.Split(delimiterChars);
123 
124                     if (!DesignMode)
125                     {
126                         _ColumnNames.Clear();
127                     }
128 
129                     // After splitting the string into an array, iterate
130                     // through the strings and check that they're all valid.
131                     foreach (string s in columnNames)
132                     {
133                         // Does it have length?
134                         if (Convert.ToBoolean(s.Trim().Length))
135                         {
136                             if (!DesignMode)
137                             {
138                                 _ColumnNames.Add(s.Trim());
139                             }
140                         }
141                         else // The value is blank
142                         {
143                             throw new NotSupportedException("Column names can not be blank.");
144                         }
145                     }
146                     _ColumnNameString = value;
147                 }
148             }
149         }
150 
151         public Collection<int> ColumnWidthCollection
152         {
153             get
154             {
155                 return _ColumnWidths;
156             }
157         }
158 
159         public int ColumnWidthDefault
160         {
161             get
162             {
163                 return _ColumnWidthDefault;
164             }
165             set
166             {
167                 _ColumnWidthDefault = value;
168             }
169         }
170 
171         public string ColumnWidths
172         {
173             get
174             {
175                 return _ColumnWidthString;
176             }
177 
178             set
179             {
180                 // If the column string is blank, leave it blank.
181                 // The default width will be used for all columns.
182                 if (!Convert.ToBoolean(value.Trim().Length))
183                 {
184                     _ColumnWidthString = "";
185                 }
186                 else if (value != null)
187                 {
188                     char[] delimiterChars = { ',', ';', ':' };
189                     string[] columnWidths = value.Split(delimiterChars);
190                     string invalidValue = "";
191                     int invalidIndex = -1;
192                     int idx = 1;
193                     int intValue;
194 
195                     // After splitting the string into an array, iterate
196                     // through the strings and check that they're all integers
197                     // or blanks
198                     foreach (string s in columnWidths)
199                     {
200                         // If it has length, test if it's an integer
201                         if (Convert.ToBoolean(s.Trim().Length))
202                         {
203                             // It's not an integer. Flag the offending value.
204                             if (!int.TryParse(s, out intValue))
205                             {
206                                 invalidIndex = idx;
207                                 invalidValue = s;
208                             }
209                             else // The value was okay. Increment the item index.
210                             {
211                                 idx++;
212                             }
213                         }
214                         else // The value is a space. Use the default width.
215                         {
216                             idx++;
217                         }
218                     }
219 
220                     // If an invalid value was found, raise an exception.
221                     if (invalidIndex > -1)
222                     {
223                         string errMsg;
224 
225                         errMsg = "Invalid column width '" + invalidValue + "' located at column " + invalidIndex.ToString();
226                         throw new ArgumentOutOfRangeException(errMsg);
227                     }
228                     else // The string is fine
229                     {
230                         _ColumnWidthString = value;
231 
232                         // Only set the values of the collections at runtime.
233                         // Setting them at design time doesn't accomplish 
234                         // anything and causes errors since the collections 
235                         // don't exist at design time.
236                         if (!DesignMode)
237                         {
238                             _ColumnWidths.Clear();
239                             foreach (string s in columnWidths)
240                             {
241                                 // Initialize a column width to an integer
242                                 if (Convert.ToBoolean(s.Trim().Length))
243                                 {
244                                     _ColumnWidths.Add(Convert.ToInt32(s));
245                                 }
246                                 else // Initialize the column to the default
247                                 {
248                                     _ColumnWidths.Add(_ColumnWidthDefault);
249                                 }
250                             }
251 
252                             // If the column is bound to data, set the column widths
253                             // for any columns that aren't explicitly set by the 
254                             // string value entered by the programmer
255                             if (DataManager != null)
256                             {
257                                 InitializeColumns();
258                             }
259                         }
260                     }
261                 }
262             }
263         }
264 
265         public new DrawMode DrawMode
266         {
267             get
268             {
269                 return base.DrawMode;
270             }
271             set
272             {
273                 if (value != DrawMode.OwnerDrawVariable)
274                 {
275                     throw new NotSupportedException("Needs to be DrawMode.OwnerDrawVariable");
276                 }
277                 base.DrawMode = value;
278             }
279         }
280 
281         public new ComboBoxStyle DropDownStyle
282         {
283             get
284             {
285                 return base.DropDownStyle;
286             }
287             set
288             {
289                 if (value != ComboBoxStyle.DropDown)
290                 {
291                     throw new NotSupportedException("ComboBoxStyle.DropDown is the only supported style");
292                 }
293                 base.DropDownStyle = value;
294             }
295         }
296 
297         public int LinkedColumnIndex
298         {
299             get
300             {
301                 return _LinkedColumnIndex;
302             }
303             set
304             {
305                 if (value < 0)
306                 {
307                     throw new ArgumentOutOfRangeException("A column index can not be negative");
308                 }
309                 _LinkedColumnIndex = value;
310             }
311         }
312 
313         public TextBox LinkedTextBox
314         {
315             get
316             {
317                 return _LinkedTextBox;
318             }
319             set
320             {
321                 _LinkedTextBox = value;
322 
323                 if (_LinkedTextBox != null)
324                 {
325                     // Set any default properties of the Linked Textbox here
326                     _LinkedTextBox.ReadOnly = true;
327                     _LinkedTextBox.TabStop = false;
328                 }
329             }
330         }
331 
332         public int TotalWidth
333         {
334             get
335             {
336                 return _TotalWidth;
337             }
338         }
339 
340         protected override void OnDataSourceChanged(EventArgs e)
341         {
342             base.OnDataSourceChanged(e);
343 
344             InitializeColumns();
345         }
346 
347         protected override void OnDrawItem(DrawItemEventArgs e)
348         {
349             base.OnDrawItem(e);
350 
351             if (DesignMode)
352                 return;
353 
354             e.DrawBackground();
355 
356             Rectangle boundsRect = e.Bounds;
357             int lastRight = 0;
358 
359             Color brushForeColor;
360             if ((e.State & DrawItemState.Selected) == 0)
361             {
362                 // Item is not selected. Use BackColorOdd & BackColorEven
363                 Color backColor;
364                 backColor = Convert.ToBoolean(e.Index % 2) ? _BackColorOdd : _BackColorEven;
365                 using (SolidBrush brushBackColor = new SolidBrush(backColor))
366                 {
367                     e.Graphics.FillRectangle(brushBackColor, e.Bounds);
368                 }
369                 brushForeColor = Color.Black;
370             }
371             else
372             {
373                 // Item is selected. Use ForeColor = White
374                 brushForeColor = Color.White;
375             }
376 
377             using (Pen linePen = new Pen(SystemColors.GrayText))
378             {
379                 using (SolidBrush brush = new SolidBrush(brushForeColor))
380                 {
381                     if (!Convert.ToBoolean(_ColumnNames.Count))
382                     {
383                         e.Graphics.DrawString(Convert.ToString(Items[e.Index]), Font, brush, boundsRect);
384                     }
385                     else
386                     {
387                         // If the ComboBox is displaying a RightToLeft language, draw it this way.
388                         if (RightToLeft.Equals(RightToLeft.Yes))
389                         {
390                             // Define a StringFormat object to make the string display RTL.
391                             StringFormat rtl = new StringFormat();
392                             rtl.Alignment = StringAlignment.Near;
393                             rtl.FormatFlags = StringFormatFlags.DirectionRightToLeft;
394 
395                             // Draw the strings in reverse order from high column index to zero column index.
396                             for (int colIndex = _ColumnNames.Count - 1; colIndex >= 0; colIndex--)
397                             {
398                                 if (Convert.ToBoolean(_ColumnWidths[colIndex]))
399                                 {
400                                     string item = Convert.ToString(FilterItemOnProperty(Items[e.Index], _ColumnNames[colIndex]));
401 
402                                     boundsRect.X = lastRight;
403                                     boundsRect.Width = (int)_ColumnWidths[colIndex];
404                                     lastRight = boundsRect.Right;
405 
406                                     // Draw the string with the RTL object.
407                                     e.Graphics.DrawString(item, Font, brush, boundsRect, rtl);
408 
409                                     if (colIndex > 0)
410                                     {
411                                         e.Graphics.DrawLine(linePen, boundsRect.Right, boundsRect.Top, boundsRect.Right, boundsRect.Bottom);
412                                     }
413                                 }
414                             }
415                         }
416                         // If the ComboBox is displaying a LeftToRight language, draw it this way.
417                         else
418                         {
419                             // Display the strings in ascending order from zero to the highest column.
420                             for (int colIndex = 0; colIndex < _ColumnNames.Count; colIndex++)
421                             {
422                                 if (Convert.ToBoolean(_ColumnWidths[colIndex]))
423                                 {
424                                     string item = Convert.ToString(FilterItemOnProperty(Items[e.Index], _ColumnNames[colIndex]));
425 
426                                     boundsRect.X = lastRight;
427                                     boundsRect.Width = (int)_ColumnWidths[colIndex];
428                                     lastRight = boundsRect.Right;
429                                     e.Graphics.DrawString(item, Font, brush, boundsRect);
430 
431                                     if (colIndex < _ColumnNames.Count - 1)
432                                     {
433                                         e.Graphics.DrawLine(linePen, boundsRect.Right, boundsRect.Top, boundsRect.Right, boundsRect.Bottom);
434                                     }
435                                 }
436                             }
437                         }
438                     }
439                 }
440             }
441 
442             e.DrawFocusRectangle();
443         }
444 
445         protected override void OnDropDown(EventArgs e)
446         {
447             base.OnDropDown(e);
448 
449             if (_TotalWidth > 0)
450             {
451                 if (Items.Count > MaxDropDownItems)
452                 {
453                     // The vertical scrollbar is present. Add its width to the total.
454                     // If you don't then RightToLeft languages will have a few characters obscured.
455                     this.DropDownWidth = _TotalWidth + SystemInformation.VerticalScrollBarWidth;
456                 }
457                 else
458                 {
459                     this.DropDownWidth = _TotalWidth;
460                 }
461             }
462         }
463 
464         protected override void OnKeyDown(KeyEventArgs e)
465         {
466             // Use the Delete or Escape Key to blank out the ComboBox and
467             // allow the user to type in a new value
468             if ((e.KeyCode == Keys.Delete) ||
469                 (e.KeyCode == Keys.Escape))
470             {
471                 SelectedIndex = -1;
472                 Text = "";
473                 if (_LinkedTextBox != null)
474                 {
475                     _LinkedTextBox.Text = "";
476                 }
477             }
478             else if (e.KeyCode == Keys.F3)
479             {
480                 // Fire the OpenSearchForm Event
481                 if (OpenSearchForm != null)
482                 {
483                     OpenSearchForm(this, System.EventArgs.Empty);
484                 }
485             }
486         }
487 
488         // Some of the code for OnKeyPress was derived from some VB.NET code  
489         // posted by Laurent Muller as a suggested improvement for another control.
490         // http://www.codeproject.com/vb/net/autocomplete_combobox.asp?df=100&forumid=3716&select=579095#xx579095xx
491         protected override void OnKeyPress(KeyPressEventArgs e)
492         {
493             int idx = -1;
494             string toFind;
495 
496             DroppedDown = _AutoDropdown;
497             if (!Char.IsControl(e.KeyChar))
498             {
499                 if (_AutoComplete)
500                 {
501                     toFind = Text.Substring(0, SelectionStart) + e.KeyChar;
502                     idx = FindStringExact(toFind);
503 
504                     if (idx == -1)
505                     {
506                         // An exact match for the whole string was not found
507                         // Find a substring instead.
508                         idx = FindString(toFind);
509                     }
510                     else
511                     {
512                         // An exact match was found. Close the dropdown.
513                         DroppedDown = false;
514                     }
515 
516                     if (idx != -1) // The substring was found.
517                     {
518                         SelectedIndex = idx;
519                         SelectionStart = toFind.Length;
520                         SelectionLength = Text.Length - SelectionStart;
521                     }
522                     else // The last keystroke did not create a valid substring.
523                     {
524                         // If the substring is not found, cancel the keypress
525                         e.KeyChar = (char)0;
526                     }
527                 }
528                 else // AutoComplete = false. Treat it like a DropDownList by finding the
529                 // KeyChar that was struck starting from the current index
530                 {
531                     idx = FindString(e.KeyChar.ToString(), SelectedIndex);
532 
533                     if (idx != -1)
534                     {
535                         SelectedIndex = idx;
536                     }
537                 }
538             }
539 
540             // Do no allow the user to backspace over characters. Treat it like
541             // a left arrow instead. The user must not be allowed to change the 
542             // value in the ComboBox. 
543             if ((e.KeyChar == (char)(Keys.Back)) &&  // A Backspace Key is hit
544                 (_AutoComplete) &&                   // AutoComplete = true
545                 (Convert.ToBoolean(SelectionStart))) // And the SelectionStart is positive
546             {
547                 // Find a substring that is one character less the the current selection.
548                 // This mimicks moving back one space with an arrow key. This substring should
549                 // always exist since we don't allow invalid selections to be typed. If you're
550                 // on the 3rd character of a valid code, then the first two characters have to 
551                 // be valid. Moving back to them and finding the 1st occurrence should never fail.
552                 toFind = Text.Substring(0, SelectionStart - 1);
553                 idx = FindString(toFind);
554 
555                 if (idx != -1)
556                 {
557                     SelectedIndex = idx;
558                     SelectionStart = toFind.Length;
559                     SelectionLength = Text.Length - SelectionStart;
560                 }
561             }
562 
563             // e.Handled is always true. We handle every keystroke programatically.
564             e.Handled = true;
565         }
566 
567         protected override void OnSelectedValueChanged(EventArgs e)
568         {
569             base.OnSelectedValueChanged(e); //Added after version 1.3 on 01/31/2008
570 
571             if (_LinkedTextBox != null)
572             {
573                 if (_LinkedColumnIndex < _ColumnNames.Count)
574                 {
575                     _LinkedTextBox.Text = Convert.ToString(FilterItemOnProperty(SelectedItem, _ColumnNames[_LinkedColumnIndex]));
576                 }
577             }
578         }
579 
580         protected override void OnValueMemberChanged(EventArgs e)
581         {
582             base.OnValueMemberChanged(e);
583 
584             InitializeValueMemberColumn();
585         }
586 
587         private void InitializeColumns()
588         {
589             if (!Convert.ToBoolean(_ColumnNameString.Length))
590             {
591                 PropertyDescriptorCollection propertyDescriptorCollection = DataManager.GetItemProperties();
592 
593                 _TotalWidth = 0;
594                 _ColumnNames.Clear();
595 
596                 for (int colIndex = 0; colIndex < propertyDescriptorCollection.Count; colIndex++)
597                 {
598                     _ColumnNames.Add(propertyDescriptorCollection[colIndex].Name);
599 
600                     // If the index is greater than the collection of explicitly
601                     // set column widths, set any additional columns to the default
602                     if (colIndex >= _ColumnWidths.Count)
603                     {
604                         _ColumnWidths.Add(_ColumnWidthDefault);
605                     }
606                     _TotalWidth += _ColumnWidths[colIndex];
607                 }
608             }
609             else
610             {
611                 _TotalWidth = 0;
612 
613                 for (int colIndex = 0; colIndex < _ColumnNames.Count; colIndex++)
614                 {
615                     // If the index is greater than the collection of explicitly
616                     // set column widths, set any additional columns to the default
617                     if (colIndex >= _ColumnWidths.Count)
618                     {
619                         _ColumnWidths.Add(_ColumnWidthDefault);
620                     }
621                     _TotalWidth += _ColumnWidths[colIndex];
622                 }
623 
624             }
625 
626             // Check to see if the programmer is trying to display a column
627             // in the linked textbox that is greater than the columns in the 
628             // ComboBox. I handle this error by resetting it to zero.
629             if (_LinkedColumnIndex >= _ColumnNames.Count)
630             {
631                 _LinkedColumnIndex = 0; // Or replace this with an OutOfBounds Exception
632             }
633         }
634 
635         private void InitializeValueMemberColumn()
636         {
637             int colIndex = 0;
638             foreach (String columnName in _ColumnNames)
639             {
640                 if (String.Compare(columnName, ValueMember, true, CultureInfo.CurrentUICulture) == 0)
641                 {
642                     _ValueMemberColumnIndex = colIndex;
643                     break;
644                 }
645                 colIndex++;
646             }
647         }
648     }
649 }

 

posted on 2014-04-21 17:35  z5337  阅读(518)  评论(0编辑  收藏  举报