c# Winfrom-DataGridView实现筛选功能
//应对用户需求,需要在DataGridView中直接进行筛选功能,在网上找了一些代码加上自己修改整理过后的类,仅供参考!
//上面代码可以直接创建类库项目生成DLL文件,下面代码为另外项目引用创建的DLL文件实现的效果.大致效果如下.(做的比较丑~~~~)


1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Diagnostics; 6 using System.Drawing; 7 using System.Text; 8 using System.Windows.Forms; 9 using System.Windows.Forms.VisualStyles; 10 using System.Collections; 11 using System.Reflection; 12 13 14 namespace DataGridViewAutoFilter 15 { 16 17 public class DataGridViewAutoFilterColumnHeaderCell : DataGridViewColumnHeaderCell 18 { 19 /// <summary> 20 /// The ListBox used for all drop-down lists. 21 /// </summary> 22 private FilterListBox dropDownListBox = new FilterListBox(); 23 24 /// <summary> 25 /// A list of filters available for the owning column stored as 26 /// formatted and unformatted string values. 27 /// </summary> 28 private System.Collections.Specialized.OrderedDictionary filters = 29 new System.Collections.Specialized.OrderedDictionary(); 30 31 /// <summary> 32 /// The drop-down list filter value currently in effect for the owning column. 33 /// </summary> 34 private String selectedFilterValue = String.Empty; 35 36 /// <summary> 37 /// The complete filter string currently in effect for the owning column. 38 /// </summary> 39 private String currentColumnFilter = String.Empty; 40 41 /// <summary> 42 /// Indicates whether the DataGridView is currently filtered by the owning column. 43 /// </summary> 44 private Boolean filtered; 45 46 /// <summary> 47 /// Initializes a new instance of the DataGridViewColumnHeaderCell 48 /// class and sets its property values to the property values of the 49 /// specified DataGridViewColumnHeaderCell. 50 /// </summary> 51 /// <param name="oldHeaderCell">The DataGridViewColumnHeaderCell to copy property values from.</param> 52 public DataGridViewAutoFilterColumnHeaderCell(DataGridViewColumnHeaderCell oldHeaderCell) 53 { 54 this.ContextMenuStrip = oldHeaderCell.ContextMenuStrip; 55 this.ErrorText = oldHeaderCell.ErrorText; 56 this.Tag = oldHeaderCell.Tag; 57 this.ToolTipText = oldHeaderCell.ToolTipText; 58 this.Value = oldHeaderCell.Value; 59 this.ValueType = oldHeaderCell.ValueType; 60 61 // Use HasStyle to avoid creating a new style object 62 // when the Style property has not previously been set. 63 if (oldHeaderCell.HasStyle) 64 { 65 this.Style = oldHeaderCell.Style; 66 } 67 68 // Copy this type's properties if the old cell is an auto-filter cell. 69 // This enables the Clone method to reuse this constructor. 70 DataGridViewAutoFilterColumnHeaderCell filterCell = 71 oldHeaderCell as DataGridViewAutoFilterColumnHeaderCell; 72 if (filterCell != null) 73 { 74 this.FilteringEnabled = filterCell.FilteringEnabled; 75 this.AutomaticSortingEnabled = filterCell.AutomaticSortingEnabled; 76 this.DropDownListBoxMaxLines = filterCell.DropDownListBoxMaxLines; 77 this.currentDropDownButtonPaddingOffset = 78 filterCell.currentDropDownButtonPaddingOffset; 79 } 80 } 81 82 /// <summary> 83 /// Initializes a new instance of the DataGridViewColumnHeaderCell 84 /// class. 85 /// </summary> 86 public DataGridViewAutoFilterColumnHeaderCell() 87 { 88 } 89 90 /// <summary> 91 /// Creates an exact copy of this cell. 92 /// </summary> 93 /// <returns>An object that represents the cloned DataGridViewAutoFilterColumnHeaderCell.</returns> 94 public override object Clone() 95 { 96 return new DataGridViewAutoFilterColumnHeaderCell(this); 97 } 98 99 /// <summary> 100 /// Called when the value of the DataGridView property changes 101 /// in order to perform initialization that requires access to the 102 /// owning control and column. 103 /// </summary> 104 /// 105 106 protected override void OnDataGridViewChanged() 107 { 108 // Continue only if there is a DataGridView. 109 if (this.DataGridView == null) 110 { 111 return; 112 } 113 114 // Disable sorting and filtering for columns that can't make 115 // effective use of them. 116 if (OwningColumn != null) 117 { 118 if (OwningColumn is DataGridViewImageColumn || 119 (OwningColumn is DataGridViewButtonColumn && 120 ((DataGridViewButtonColumn)OwningColumn).UseColumnTextForButtonValue) || 121 (OwningColumn is DataGridViewLinkColumn && 122 ((DataGridViewLinkColumn)OwningColumn).UseColumnTextForLinkValue)) 123 { 124 AutomaticSortingEnabled = false; 125 FilteringEnabled = false; 126 } 127 128 // Ensure that the column SortMode property value is not Automatic. 129 // This prevents sorting when the user clicks the drop-down button. 130 if (OwningColumn.SortMode == DataGridViewColumnSortMode.Automatic) 131 { 132 OwningColumn.SortMode = DataGridViewColumnSortMode.Programmatic; 133 } 134 } 135 136 // Confirm that the data source meets requirements. 137 VerifyDataSource(); 138 139 // Add handlers to DataGridView events. 140 HandleDataGridViewEvents(); 141 142 // Initialize the drop-down button bounds so that any initial 143 // column autosizing will accommodate the button width. 144 SetDropDownButtonBounds(); 145 146 // Call the OnDataGridViewChanged method on the base class to 147 // raise the DataGridViewChanged event. 148 base.OnDataGridViewChanged(); 149 } 150 151 /// <summary> 152 /// Confirms that the data source, if it has been set, is a BindingSource. 153 /// </summary> 154 private void VerifyDataSource() 155 { 156 // Continue only if there is a DataGridView and its DataSource has been set. 157 if (this.DataGridView == null || this.DataGridView.DataSource == null) 158 { 159 return; 160 } 161 162 // Throw an exception if the data source is not a BindingSource. 163 BindingSource data = this.DataGridView.DataSource as BindingSource; 164 if (data == null) 165 { 166 throw new NotSupportedException( 167 "The DataSource property of the containing DataGridView control " + 168 "must be set to a BindingSource."); 169 } 170 } 171 172 #region DataGridView events: HandleDataGridViewEvents, DataGridView event handlers, ResetDropDown, ResetFilter 173 174 /// <summary> 175 /// Add handlers to various DataGridView events, primarily to invalidate 176 /// the drop-down button bounds, hide the drop-down list, and reset 177 /// cached filter values when changes in the DataGridView require it. 178 /// </summary> 179 private void HandleDataGridViewEvents() 180 { 181 this.DataGridView.Scroll += new ScrollEventHandler(DataGridView_Scroll); 182 this.DataGridView.ColumnDisplayIndexChanged += new DataGridViewColumnEventHandler(DataGridView_ColumnDisplayIndexChanged); 183 this.DataGridView.ColumnWidthChanged += new DataGridViewColumnEventHandler(DataGridView_ColumnWidthChanged); 184 this.DataGridView.ColumnHeadersHeightChanged += new EventHandler(DataGridView_ColumnHeadersHeightChanged); 185 this.DataGridView.SizeChanged += new EventHandler(DataGridView_SizeChanged); 186 this.DataGridView.DataSourceChanged += new EventHandler(DataGridView_DataSourceChanged); 187 this.DataGridView.DataBindingComplete += new DataGridViewBindingCompleteEventHandler(DataGridView_DataBindingComplete); 188 189 // Add a handler for the ColumnSortModeChanged event to prevent the 190 // column SortMode property from being inadvertently set to Automatic. 191 this.DataGridView.ColumnSortModeChanged += new DataGridViewColumnEventHandler(DataGridView_ColumnSortModeChanged); 192 } 193 194 /// <summary> 195 /// Invalidates the drop-down button bounds when the user scrolls horizontally. 196 /// </summary> 197 /// <param name="sender">The object that raised the event.</param> 198 /// <param name="e">A ScrollEventArgs that contains the event data.</param> 199 private void DataGridView_Scroll(object sender, ScrollEventArgs e) 200 { 201 if (e.ScrollOrientation == ScrollOrientation.HorizontalScroll) 202 { 203 ResetDropDown(); 204 } 205 } 206 207 /// <summary> 208 /// Invalidates the drop-down button bounds when the column display index changes. 209 /// </summary> 210 /// <param name="sender"></param> 211 /// <param name="e"></param> 212 private void DataGridView_ColumnDisplayIndexChanged(object sender, DataGridViewColumnEventArgs e) 213 { 214 ResetDropDown(); 215 } 216 217 /// <summary> 218 /// Invalidates the drop-down button bounds when a column width changes 219 /// in the DataGridView control. A width change in any column of the 220 /// control has the potential to affect the drop-down button location, 221 /// depending on the current horizontal scrolling position and whether 222 /// the changed column is to the left or right of the current column. 223 /// It is easier to invalidate the button in all cases. 224 /// </summary> 225 /// <param name="sender">The object that raised the event.</param> 226 /// <param name="e">A DataGridViewColumnEventArgs that contains the event data.</param> 227 private void DataGridView_ColumnWidthChanged(object sender, DataGridViewColumnEventArgs e) 228 { 229 ResetDropDown(); 230 } 231 232 /// <summary> 233 /// Invalidates the drop-down button bounds when the height of the column headers changes. 234 /// </summary> 235 /// <param name="sender">The object that raised the event.</param> 236 /// <param name="e">An EventArgs that contains the event data.</param> 237 private void DataGridView_ColumnHeadersHeightChanged(object sender, EventArgs e) 238 { 239 ResetDropDown(); 240 } 241 242 /// <summary> 243 /// Invalidates the drop-down button bounds when the size of the DataGridView changes. 244 /// This prevents a painting issue that occurs when the right edge of the control moves 245 /// to the right and the control contents have previously been scrolled to the right. 246 /// </summary> 247 /// <param name="sender">The object that raised the event.</param> 248 /// <param name="e">An EventArgs that contains the event data.</param> 249 private void DataGridView_SizeChanged(object sender, EventArgs e) 250 { 251 ResetDropDown(); 252 } 253 254 /// <summary> 255 /// Invalidates the drop-down button bounds, hides the drop-down 256 /// filter list, if it is showing, and resets the cached filter values 257 /// if the filter has been removed. 258 /// </summary> 259 /// <param name="sender">The object that raised the event.</param> 260 /// <param name="e">A DataGridViewBindingCompleteEventArgs that contains the event data.</param> 261 private void DataGridView_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e) 262 { 263 if (e.ListChangedType == ListChangedType.Reset) 264 { 265 ResetDropDown(); 266 ResetFilter(); 267 } 268 } 269 270 /// <summary> 271 /// Verifies that the data source meets requirements, invalidates the 272 /// drop-down button bounds, hides the drop-down filter list if it is 273 /// showing, and resets the cached filter values if the filter has been removed. 274 /// </summary> 275 /// <param name="sender">The object that raised the event.</param> 276 /// <param name="e">An EventArgs that contains the event data.</param> 277 private void DataGridView_DataSourceChanged(object sender, EventArgs e) 278 { 279 VerifyDataSource(); 280 ResetDropDown(); 281 ResetFilter(); 282 } 283 284 /// <summary> 285 /// Invalidates the drop-down button bounds and hides the filter 286 /// list if it is showing. 287 /// </summary> 288 private void ResetDropDown() 289 { 290 InvalidateDropDownButtonBounds(); 291 if (dropDownListBoxShowing) 292 { 293 HideDropDownList(); 294 } 295 } 296 297 /// <summary> 298 /// Resets the cached filter values if the filter has been removed. 299 /// </summary> 300 private void ResetFilter() 301 { 302 if (this.DataGridView == null) return; 303 BindingSource source = this.DataGridView.DataSource as BindingSource; 304 if (source == null || String.IsNullOrEmpty(source.Filter)) 305 { 306 filtered = false; 307 selectedFilterValue = "(All)"; 308 currentColumnFilter = String.Empty; 309 } 310 } 311 312 /// <summary> 313 /// Throws an exception when the column sort mode is changed to Automatic. 314 /// </summary> 315 /// <param name="sender">The object that raised the event.</param> 316 /// <param name="e">A DataGridViewColumnEventArgs that contains the event data.</param> 317 private void DataGridView_ColumnSortModeChanged(object sender, DataGridViewColumnEventArgs e) 318 { 319 if (e.Column == OwningColumn && 320 e.Column.SortMode == DataGridViewColumnSortMode.Automatic) 321 { 322 throw new InvalidOperationException( 323 "A SortMode value of Automatic is incompatible with " + 324 "the DataGridViewAutoFilterColumnHeaderCell type. " + 325 "Use the AutomaticSortingEnabled property instead."); 326 } 327 } 328 329 #endregion DataGridView events 330 331 /// <summary> 332 /// Paints the column header cell, including the drop-down button. 333 /// </summary> 334 /// <param name="graphics">The Graphics used to paint the DataGridViewCell.</param> 335 /// <param name="clipBounds">A Rectangle that represents the area of the DataGridView that needs to be repainted.</param> 336 /// <param name="cellBounds">A Rectangle that contains the bounds of the DataGridViewCell that is being painted.</param> 337 /// <param name="rowIndex">The row index of the cell that is being painted.</param> 338 /// <param name="cellState">A bitwise combination of DataGridViewElementStates values that specifies the state of the cell.</param> 339 /// <param name="value">The data of the DataGridViewCell that is being painted.</param> 340 /// <param name="formattedValue">The formatted data of the DataGridViewCell that is being painted.</param> 341 /// <param name="errorText">An error message that is associated with the cell.</param> 342 /// <param name="cellStyle">A DataGridViewCellStyle that contains formatting and style information about the cell.</param> 343 /// <param name="advancedBorderStyle">A DataGridViewAdvancedBorderStyle that contains border styles for the cell that is being painted.</param> 344 /// <param name="paintParts">A bitwise combination of the DataGridViewPaintParts values that specifies which parts of the cell need to be painted.</param> 345 protected override void Paint( 346 Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, 347 int rowIndex, DataGridViewElementStates cellState, 348 object value, object formattedValue, string errorText, 349 DataGridViewCellStyle cellStyle, 350 DataGridViewAdvancedBorderStyle advancedBorderStyle, 351 DataGridViewPaintParts paintParts) 352 { 353 // Use the base method to paint the default appearance. 354 base.Paint(graphics, clipBounds, cellBounds, rowIndex, 355 cellState, value, formattedValue, 356 errorText, cellStyle, advancedBorderStyle, paintParts); 357 358 // Continue only if filtering is enabled and ContentBackground is 359 // part of the paint request. 360 if (!FilteringEnabled || 361 (paintParts & DataGridViewPaintParts.ContentBackground) == 0) 362 { 363 return; 364 } 365 366 // Retrieve the current button bounds. 367 Rectangle buttonBounds = DropDownButtonBounds; 368 369 // Continue only if the buttonBounds is big enough to draw. 370 if (buttonBounds.Width < 1 || buttonBounds.Height < 1) return; 371 372 // Paint the button manually or using visual styles if visual styles 373 // are enabled, using the correct state depending on whether the 374 // filter list is showing and whether there is a filter in effect 375 // for the current column. 376 if (Application.RenderWithVisualStyles) 377 { 378 ComboBoxState state = ComboBoxState.Normal; 379 380 if (dropDownListBoxShowing) 381 { 382 state = ComboBoxState.Pressed; 383 } 384 else if (filtered) 385 { 386 state = ComboBoxState.Hot; 387 } 388 ComboBoxRenderer.DrawDropDownButton( 389 graphics, buttonBounds, state); 390 } 391 else 392 { 393 // Determine the pressed state in order to paint the button 394 // correctly and to offset the down arrow. 395 Int32 pressedOffset = 0; 396 PushButtonState state = PushButtonState.Normal; 397 if (dropDownListBoxShowing) 398 { 399 state = PushButtonState.Pressed; 400 pressedOffset = 1; 401 } 402 ButtonRenderer.DrawButton(graphics, buttonBounds, state); 403 404 // If there is a filter in effect for the column, paint the 405 // down arrow as an unfilled triangle. If there is no filter 406 // in effect, paint the down arrow as a filled triangle. 407 if (filtered) 408 { 409 graphics.DrawPolygon(SystemPens.ControlText, new Point[] { 410 new Point( 411 buttonBounds.Width / 2 + 412 buttonBounds.Left - 1 + pressedOffset, 413 buttonBounds.Height * 3 / 4 + 414 buttonBounds.Top - 1 + pressedOffset), 415 new Point( 416 buttonBounds.Width / 4 + 417 buttonBounds.Left + pressedOffset, 418 buttonBounds.Height / 2 + 419 buttonBounds.Top - 1 + pressedOffset), 420 new Point( 421 buttonBounds.Width * 3 / 4 + 422 buttonBounds.Left - 1 + pressedOffset, 423 buttonBounds.Height / 2 + 424 buttonBounds.Top - 1 + pressedOffset) 425 }); 426 } 427 else 428 { 429 graphics.FillPolygon(SystemBrushes.ControlText, new Point[] { 430 new Point( 431 buttonBounds.Width / 2 + 432 buttonBounds.Left - 1 + pressedOffset, 433 buttonBounds.Height * 3 / 4 + 434 buttonBounds.Top - 1 + pressedOffset), 435 new Point( 436 buttonBounds.Width / 4 + 437 buttonBounds.Left + pressedOffset, 438 buttonBounds.Height / 2 + 439 buttonBounds.Top - 1 + pressedOffset), 440 new Point( 441 buttonBounds.Width * 3 / 4 + 442 buttonBounds.Left - 1 + pressedOffset, 443 buttonBounds.Height / 2 + 444 buttonBounds.Top - 1 + pressedOffset) 445 }); 446 } 447 } 448 449 } 450 451 /// <summary> 452 /// Handles mouse clicks to the header cell, displaying the 453 /// drop-down list or sorting the owning column as appropriate. 454 /// </summary> 455 /// <param name="e">A DataGridViewCellMouseEventArgs that contains the event data.</param> 456 protected override void OnMouseDown(DataGridViewCellMouseEventArgs e) 457 { 458 Debug.Assert(this.DataGridView != null, "DataGridView is null"); 459 460 // Continue only if the user did not click the drop-down button 461 // while the drop-down list was displayed. This prevents the 462 // drop-down list from being redisplayed after being hidden in 463 // the LostFocus event handler. 464 if (lostFocusOnDropDownButtonClick) 465 { 466 lostFocusOnDropDownButtonClick = false; 467 return; 468 } 469 470 // Retrieve the current size and location of the header cell, 471 // excluding any portion that is scrolled off screen. 472 Rectangle cellBounds = this.DataGridView 473 .GetCellDisplayRectangle(e.ColumnIndex, -1, false); 474 475 // Continue only if the column is not manually resizable or the 476 // mouse coordinates are not within the column resize zone. 477 if (this.OwningColumn.Resizable == DataGridViewTriState.True && 478 ((this.DataGridView.RightToLeft == RightToLeft.No && 479 cellBounds.Width - e.X < 6) || e.X < 6)) 480 { 481 return; 482 } 483 484 // Unless RightToLeft is enabled, store the width of the portion 485 // that is scrolled off screen. 486 Int32 scrollingOffset = 0; 487 if (this.DataGridView.RightToLeft == RightToLeft.No && 488 this.DataGridView.FirstDisplayedScrollingColumnIndex == 489 this.ColumnIndex) 490 { 491 scrollingOffset = 492 this.DataGridView.FirstDisplayedScrollingColumnHiddenWidth; 493 } 494 495 // Show the drop-down list if filtering is enabled and the mouse click occurred 496 // within the drop-down button bounds. Otherwise, if sorting is enabled and the 497 // click occurred outside the drop-down button bounds, sort by the owning column. 498 // The mouse coordinates are relative to the cell bounds, so the cell location 499 // and the scrolling offset are needed to determine the client coordinates. 500 if (FilteringEnabled && 501 DropDownButtonBounds.Contains( 502 e.X + cellBounds.Left - scrollingOffset, e.Y + cellBounds.Top)) 503 { 504 // If the current cell is in edit mode, commit the edit. 505 if (this.DataGridView.IsCurrentCellInEditMode) 506 { 507 // Commit and end the cell edit. 508 this.DataGridView.EndEdit(); 509 510 // Commit any change to the underlying data source. 511 BindingSource source = 512 this.DataGridView.DataSource as BindingSource; 513 if (source != null) 514 { 515 source.EndEdit(); 516 } 517 } 518 ShowDropDownList(); 519 } 520 else if (AutomaticSortingEnabled && 521 this.DataGridView.SelectionMode != 522 DataGridViewSelectionMode.ColumnHeaderSelect) 523 { 524 SortByColumn(); 525 } 526 527 base.OnMouseDown(e); 528 } 529 530 /// <summary> 531 /// Sorts the DataGridView by the current column if AutomaticSortingEnabled is true. 532 /// </summary> 533 private void SortByColumn() 534 { 535 Debug.Assert(this.DataGridView != null && OwningColumn != null, "DataGridView or OwningColumn is null"); 536 537 // Continue only if the data source supports sorting. 538 IBindingList sortList = this.DataGridView.DataSource as IBindingList; 539 if (sortList == null || 540 !sortList.SupportsSorting || 541 !AutomaticSortingEnabled) 542 { 543 return; 544 } 545 546 // Determine the sort direction and sort by the owning column. 547 ListSortDirection direction = ListSortDirection.Ascending; 548 if (this.DataGridView.SortedColumn == OwningColumn && 549 this.DataGridView.SortOrder == SortOrder.Ascending) 550 { 551 direction = ListSortDirection.Descending; 552 } 553 this.DataGridView.Sort(OwningColumn, direction); 554 } 555 556 #region drop-down list: Show/HideDropDownListBox, SetDropDownListBoxBounds, DropDownListBoxMaxHeightInternal 557 558 /// <summary> 559 /// Indicates whether dropDownListBox is currently displayed 560 /// for this header cell. 561 /// </summary> 562 private bool dropDownListBoxShowing; 563 564 /// <summary> 565 /// Displays the drop-down filter list. 566 /// </summary> 567 public void ShowDropDownList() 568 { 569 Debug.Assert(this.DataGridView != null, "DataGridView is null"); 570 571 // Ensure that the current row is not the row for new records. 572 // This prevents the new row from affecting the filter list and also 573 // prevents the new row from being added when the filter changes. 574 if (this.DataGridView.CurrentRow != null && 575 this.DataGridView.CurrentRow.IsNewRow) 576 { 577 this.DataGridView.CurrentCell = null; 578 } 579 580 // Populate the filters dictionary, then copy the filter values 581 // from the filters.Keys collection into the ListBox.Items collection, 582 // selecting the current filter if there is one in effect. 583 PopulateFilters(); 584 585 String[] filterArray = new String[filters.Count]; 586 filters.Keys.CopyTo(filterArray, 0); 587 dropDownListBox.Items.Clear(); 588 dropDownListBox.Items.AddRange(filterArray); 589 dropDownListBox.SelectedItem = selectedFilterValue; 590 591 // Add handlers to dropDownListBox events. 592 HandleDropDownListBoxEvents(); 593 594 // Set the size and location of dropDownListBox, then display it. 595 SetDropDownListBoxBounds(); 596 dropDownListBox.Visible = true; 597 dropDownListBoxShowing = true; 598 599 Debug.Assert(dropDownListBox.Parent == null, 600 "ShowDropDownListBox has been called multiple times before HideDropDownListBox"); 601 602 // Add dropDownListBox to the DataGridView. 603 this.DataGridView.Controls.Add(dropDownListBox); 604 605 // Set the input focus to dropDownListBox. 606 dropDownListBox.Focus(); 607 608 // Invalidate the cell so that the drop-down button will repaint 609 // in the pressed state. 610 this.DataGridView.InvalidateCell(this); 611 } 612 613 /// <summary> 614 /// Hides the drop-down filter list. 615 /// </summary> 616 public void HideDropDownList() 617 { 618 if (DataGridView != null) 619 { 620 Debug.Assert(this.DataGridView != null, "DataGridView is null"); 621 622 // Hide dropDownListBox, remove handlers from its events, and remove 623 // it from the DataGridView control. 624 dropDownListBoxShowing = false; 625 dropDownListBox.Visible = false; 626 UnhandleDropDownListBoxEvents(); 627 this.DataGridView.Controls.Remove(dropDownListBox); 628 629 // Invalidate the cell so that the drop-down button will repaint 630 // in the unpressed state. 631 this.DataGridView.InvalidateCell(this); 632 } 633 } 634 635 /// <summary> 636 /// Sets the dropDownListBox size and position based on the formatted 637 /// values in the filters dictionary and the position of the drop-down 638 /// button. Called only by ShowDropDownListBox. 639 /// </summary> 640 private void SetDropDownListBoxBounds() 641 { 642 Debug.Assert(filters.Count > 0, "filters.Count <= 0"); 643 644 // Declare variables that will be used in the calculation, 645 // initializing dropDownListBoxHeight to account for the 646 // ListBox borders. 647 Int32 dropDownListBoxHeight = 2; 648 Int32 currentWidth = 0; 649 Int32 dropDownListBoxWidth = 0; 650 Int32 dropDownListBoxLeft = 0; 651 652 // For each formatted value in the filters dictionary Keys collection, 653 // add its height to dropDownListBoxHeight and, if it is wider than 654 // all previous values, set dropDownListBoxWidth to its width. 655 using (Graphics graphics = dropDownListBox.CreateGraphics()) 656 { 657 foreach (String filter in filters.Keys) 658 { 659 SizeF stringSizeF = graphics.MeasureString( 660 filter, dropDownListBox.Font); 661 dropDownListBoxHeight += (Int32)stringSizeF.Height; 662 currentWidth = (Int32)stringSizeF.Width; 663 if (dropDownListBoxWidth < currentWidth) 664 { 665 dropDownListBoxWidth = currentWidth; 666 } 667 } 668 } 669 670 // Increase the width to allow for horizontal margins and borders. 671 dropDownListBoxWidth += 6; 672 673 // Constrain the dropDownListBox height to the 674 // DropDownListBoxMaxHeightInternal value, which is based on 675 // the DropDownListBoxMaxLines property value but constrained by 676 // the maximum height available in the DataGridView control. 677 if (dropDownListBoxHeight > DropDownListBoxMaxHeightInternal) 678 { 679 dropDownListBoxHeight = DropDownListBoxMaxHeightInternal; 680 681 // If the preferred height is greater than the available height, 682 // adjust the width to accommodate the vertical scroll bar. 683 dropDownListBoxWidth += SystemInformation.VerticalScrollBarWidth; 684 } 685 686 // Calculate the ideal location of the left edge of dropDownListBox 687 // based on the location of the drop-down button and taking the 688 // RightToLeft property value into consideration. 689 if (this.DataGridView.RightToLeft == RightToLeft.No) 690 { 691 dropDownListBoxLeft = DropDownButtonBounds.Right - 692 dropDownListBoxWidth + 1; 693 } 694 else 695 { 696 dropDownListBoxLeft = DropDownButtonBounds.Left - 1; 697 } 698 699 // Determine the left and right edges of the available horizontal 700 // width of the DataGridView control. 701 Int32 clientLeft = 1; 702 Int32 clientRight = this.DataGridView.ClientRectangle.Right; 703 if (this.DataGridView.DisplayedRowCount(false) < 704 this.DataGridView.RowCount) 705 { 706 if (this.DataGridView.RightToLeft == RightToLeft.Yes) 707 { 708 clientLeft += SystemInformation.VerticalScrollBarWidth; 709 } 710 else 711 { 712 clientRight -= SystemInformation.VerticalScrollBarWidth; 713 } 714 } 715 716 // Adjust the dropDownListBox location and/or width if it would 717 // otherwise overlap the left or right edge of the DataGridView. 718 if (dropDownListBoxLeft < clientLeft) 719 { 720 dropDownListBoxLeft = clientLeft; 721 } 722 Int32 dropDownListBoxRight = 723 dropDownListBoxLeft + dropDownListBoxWidth + 1; 724 if (dropDownListBoxRight > clientRight) 725 { 726 if (dropDownListBoxLeft == clientLeft) 727 { 728 dropDownListBoxWidth -= 729 dropDownListBoxRight - clientRight; 730 } 731 else 732 { 733 dropDownListBoxLeft -= 734 dropDownListBoxRight - clientRight; 735 if (dropDownListBoxLeft < clientLeft) 736 { 737 dropDownListBoxWidth -= clientLeft - dropDownListBoxLeft; 738 dropDownListBoxLeft = clientLeft; 739 } 740 } 741 } 742 743 // Set the ListBox.Bounds property using the calculated values. 744 dropDownListBox.Bounds = new Rectangle(dropDownListBoxLeft, 745 DropDownButtonBounds.Bottom, // top of drop-down list box 746 dropDownListBoxWidth, dropDownListBoxHeight); 747 } 748 749 /// <summary> 750 /// Gets the actual maximum height of the drop-down list, in pixels. 751 /// The maximum height is calculated from the DropDownListBoxMaxLines 752 /// property value, but is limited to the available height of the 753 /// DataGridView control. 754 /// </summary> 755 protected Int32 DropDownListBoxMaxHeightInternal 756 { 757 get 758 { 759 // Calculate the height of the available client area 760 // in the DataGridView control, taking the horizontal 761 // scroll bar into consideration and leaving room 762 // for the ListBox bottom border. 763 Int32 dataGridViewMaxHeight = this.DataGridView.Height - 764 this.DataGridView.ColumnHeadersHeight - 1; 765 if (this.DataGridView.DisplayedColumnCount(false) < 766 this.DataGridView.ColumnCount) 767 { 768 dataGridViewMaxHeight -= 769 SystemInformation.HorizontalScrollBarHeight; 770 } 771 772 // Calculate the height of the list box, using the combined 773 // height of all items plus 2 for the top and bottom border. 774 Int32 listMaxHeight = dropDownListBoxMaxLinesValue * dropDownListBox.ItemHeight + 2; 775 776 // Return the smaller of the two values. 777 if (listMaxHeight < dataGridViewMaxHeight) 778 { 779 return listMaxHeight; 780 } 781 else 782 { 783 return dataGridViewMaxHeight; 784 } 785 } 786 } 787 788 #endregion drop-down list 789 790 #region ListBox events: HandleDropDownListBoxEvents, UnhandleDropDownListBoxEvents, ListBox event handlers 791 792 /// <summary> 793 /// Adds handlers to ListBox events for handling mouse 794 /// and keyboard input. 795 /// </summary> 796 private void HandleDropDownListBoxEvents() 797 { 798 dropDownListBox.MouseClick += new MouseEventHandler(DropDownListBox_MouseClick); 799 dropDownListBox.LostFocus += new EventHandler(DropDownListBox_LostFocus); 800 dropDownListBox.KeyDown += new KeyEventHandler(DropDownListBox_KeyDown); 801 } 802 803 /// <summary> 804 /// Removes the ListBox event handlers. 805 /// </summary> 806 private void UnhandleDropDownListBoxEvents() 807 { 808 dropDownListBox.MouseClick -= new MouseEventHandler(DropDownListBox_MouseClick); 809 dropDownListBox.LostFocus -= new EventHandler(DropDownListBox_LostFocus); 810 dropDownListBox.KeyDown -= new KeyEventHandler(DropDownListBox_KeyDown); 811 } 812 813 /// <summary> 814 /// Adjusts the filter in response to a user selection from the drop-down list. 815 /// </summary> 816 /// <param name="sender">The object that raised the event.</param> 817 /// <param name="e">A MouseEventArgs that contains the event data.</param> 818 private void DropDownListBox_MouseClick(object sender, MouseEventArgs e) 819 { 820 Debug.Assert(this.DataGridView != null, "DataGridView is null"); 821 822 // Continue only if the mouse click was in the content area 823 // and not on the scroll bar. 824 if (!dropDownListBox.DisplayRectangle.Contains(e.X, e.Y)) 825 { 826 return; 827 } 828 829 UpdateFilter(); 830 HideDropDownList(); 831 } 832 833 /// <summary> 834 /// Indicates whether the drop-down list lost focus because the 835 /// user clicked the drop-down button. 836 /// </summary> 837 private Boolean lostFocusOnDropDownButtonClick; 838 839 /// <summary> 840 /// Hides the drop-down list when it loses focus. 841 /// </summary> 842 /// <param name="sender">The object that raised the event.</param> 843 /// <param name="e">An EventArgs that contains the event data.</param> 844 private void DropDownListBox_LostFocus(object sender, EventArgs e) 845 { 846 // If the focus was lost because the user clicked the drop-down 847 // button, store a value that prevents the subsequent OnMouseDown 848 // call from displaying the drop-down list again. 849 if (DropDownButtonBounds.Contains( 850 this.DataGridView.PointToClient(new Point( 851 Control.MousePosition.X, Control.MousePosition.Y)))) 852 { 853 lostFocusOnDropDownButtonClick = true; 854 } 855 HideDropDownList(); 856 } 857 858 /// <summary> 859 /// Handles the ENTER and ESC keys. 860 /// </summary> 861 /// <param name="sender">The object that raised the event.</param> 862 /// <param name="e">A KeyEventArgs that contains the event data.</param> 863 void DropDownListBox_KeyDown(object sender, KeyEventArgs e) 864 { 865 switch (e.KeyCode) 866 { 867 case Keys.Enter: 868 UpdateFilter(); 869 HideDropDownList(); 870 break; 871 case Keys.Escape: 872 HideDropDownList(); 873 break; 874 } 875 } 876 877 #endregion ListBox events 878 879 #region filtering: PopulateFilters, FilterWithoutCurrentColumn, UpdateFilter, RemoveFilter, AvoidNewRowWhenFiltering, GetFilterStatus 880 881 /// <summary> 882 /// Populates the filters dictionary with formatted and unformatted string 883 /// representations of each unique value in the column, accounting for all 884 /// filters except the current column's. Also adds special filter options. 885 /// </summary> 886 private void PopulateFilters() 887 { 888 // Continue only if there is a DataGridView. 889 if (this.DataGridView == null) 890 { 891 return; 892 } 893 894 // Cast the data source to a BindingSource. 895 BindingSource data = this.DataGridView.DataSource as BindingSource; 896 897 Debug.Assert(data != null && data.SupportsFiltering && OwningColumn != null, 898 "DataSource is not a BindingSource, or does not support filtering, or OwningColumn is null"); 899 900 // Prevent the data source from notifying the DataGridView of changes. 901 data.RaiseListChangedEvents = false; 902 903 // Cache the current BindingSource.Filter value and then change 904 // the Filter property to temporarily remove any filter for the 905 // current column. 906 String oldFilter = data.Filter; 907 data.Filter = FilterWithoutCurrentColumn(oldFilter); 908 909 // Reset the filters dictionary and initialize some flags 910 // to track whether special filter options are needed. 911 filters.Clear(); 912 Boolean containsBlanks = false; 913 Boolean containsNonBlanks = false; 914 915 // Initialize an ArrayList to store the values in their original 916 // types. This enables the values to be sorted appropriately. 917 ArrayList list = new ArrayList(data.Count); 918 919 // Retrieve each value and add it to the ArrayList if it isn't 920 // already present. 921 foreach (Object item in data) 922 { 923 Object value = null; 924 925 // Use the ICustomTypeDescriptor interface to retrieve properties 926 // if it is available; otherwise, use reflection. The 927 // ICustomTypeDescriptor interface is useful to customize 928 // which values are exposed as properties. For example, the 929 // DataRowView class implements ICustomTypeDescriptor to expose 930 // cell values as property values. 931 // 932 // Iterate through the property names to find a case-insensitive 933 // match with the DataGridViewColumn.DataPropertyName value. 934 // This is necessary because DataPropertyName is case- 935 // insensitive, but the GetProperties and GetProperty methods 936 // used below are case-sensitive. 937 ICustomTypeDescriptor ictd = item as ICustomTypeDescriptor; 938 if (ictd != null) 939 { 940 PropertyDescriptorCollection properties = ictd.GetProperties(); 941 foreach (PropertyDescriptor property in properties) 942 { 943 if (String.Compare(this.OwningColumn.DataPropertyName, 944 property.Name, true /*case insensitive*/, 945 System.Globalization.CultureInfo.InvariantCulture) == 0) 946 { 947 value = property.GetValue(item); 948 break; 949 } 950 } 951 } 952 else 953 { 954 PropertyInfo[] properties = item.GetType().GetProperties( 955 BindingFlags.Public | BindingFlags.Instance); 956 foreach (PropertyInfo property in properties) 957 { 958 if (String.Compare(this.OwningColumn.DataPropertyName, 959 property.Name, true /*case insensitive*/, 960 System.Globalization.CultureInfo.InvariantCulture) == 0) 961 { 962 value = property.GetValue(item, null /*property index*/); 963 break; 964 } 965 } 966 } 967 968 // Skip empty values, but note that they are present. 969 if (value == null || value == DBNull.Value) 970 { 971 containsBlanks = true; 972 continue; 973 } 974 975 // Add values to the ArrayList if they are not already there. 976 if (!list.Contains(value)) 977 { 978 list.Add(value); 979 } 980 } 981 982 // Sort the ArrayList. The default Sort method uses the IComparable 983 // implementation of the stored values so that string, numeric, and 984 // date values will all be sorted correctly. 985 list.Sort(); 986 987 // Convert each value in the ArrayList to its formatted representation 988 // and store both the formatted and unformatted string representations 989 // in the filters dictionary. 990 foreach (Object value in list) 991 { 992 // Use the cell's GetFormattedValue method with the column's 993 // InheritedStyle property so that the dropDownListBox format 994 // will match the display format used for the column's cells. 995 String formattedValue = null; 996 DataGridViewCellStyle style = OwningColumn.InheritedStyle; 997 formattedValue = (String)GetFormattedValue(value, -1, ref style, 998 null, null, DataGridViewDataErrorContexts.Formatting); 999 1000 if (String.IsNullOrEmpty(formattedValue)) 1001 { 1002 // Skip empty values, but note that they are present. 1003 containsBlanks = true; 1004 } 1005 else if (!filters.Contains(formattedValue)) 1006 { 1007 // Note whether non-empty values are present. 1008 containsNonBlanks = true; 1009 1010 // For all non-empty values, add the formatted and 1011 // unformatted string representations to the filters 1012 // dictionary. 1013 filters.Add(formattedValue, value.ToString()); 1014 } 1015 } 1016 1017 // Restore the filter to the cached filter string and 1018 // re-enable data source change notifications. 1019 if (oldFilter != null) data.Filter = oldFilter; 1020 data.RaiseListChangedEvents = true; 1021 1022 // Add special filter options to the filters dictionary 1023 // along with null values, since unformatted representations 1024 // are not needed. 1025 filters.Insert(0, "(All)", null); 1026 if (containsBlanks && containsNonBlanks) 1027 { 1028 filters.Add("(Blanks)", null); 1029 filters.Add("(NonBlanks)", null); 1030 } 1031 } 1032 1033 /// <summary> 1034 /// Returns a copy of the specified filter string after removing the part that filters the current column, if present. 1035 /// </summary> 1036 /// <param name="filter">The filter string to parse.</param> 1037 /// <returns>A copy of the specified filter string without the current column's filter.</returns> 1038 private String FilterWithoutCurrentColumn(String filter) 1039 { 1040 // If there is no filter in effect, return String.Empty. 1041 if (String.IsNullOrEmpty(filter)) 1042 { 1043 return String.Empty; 1044 } 1045 1046 // If the column is not filtered, return the filter string unchanged. 1047 if (!filtered) 1048 { 1049 return filter; 1050 } 1051 1052 if (filter.IndexOf(currentColumnFilter) > 0) 1053 { 1054 // If the current column filter is not the first filter, return 1055 // the specified filter value without the current column filter 1056 // and without the preceding " AND ". 1057 return filter.Replace( 1058 " AND " + currentColumnFilter, String.Empty); 1059 } 1060 else 1061 { 1062 if (filter.Length > currentColumnFilter.Length) 1063 { 1064 // If the current column filter is the first of multiple 1065 // filters, return the specified filter value without the 1066 // current column filter and without the subsequent " AND ". 1067 return filter.Replace( 1068 currentColumnFilter + " AND ", String.Empty); 1069 } 1070 else 1071 { 1072 // If the current column filter is the only filter, 1073 // return the empty string. 1074 return String.Empty; 1075 } 1076 } 1077 } 1078 1079 /// <summary> 1080 /// Updates the BindingSource.Filter value based on a user selection 1081 /// from the drop-down filter list. 1082 /// </summary> 1083 private void UpdateFilter() 1084 { 1085 // Continue only if the selection has changed. 1086 if (dropDownListBox.SelectedItem.ToString().Equals(selectedFilterValue)) 1087 { 1088 return; 1089 } 1090 1091 // Store the new selection value. 1092 selectedFilterValue = dropDownListBox.SelectedItem.ToString(); 1093 1094 // Cast the data source to an IBindingListView. 1095 IBindingListView data = 1096 this.DataGridView.DataSource as IBindingListView; 1097 1098 Debug.Assert(data != null && data.SupportsFiltering, 1099 "DataSource is not an IBindingListView or does not support filtering"); 1100 1101 // If the user selection is (All), remove any filter currently 1102 // in effect for the column. 1103 if (selectedFilterValue.Equals("(All)")) 1104 { 1105 data.Filter = FilterWithoutCurrentColumn(data.Filter); 1106 filtered = false; 1107 currentColumnFilter = String.Empty; 1108 return; 1109 } 1110 1111 // Declare a variable to store the filter string for this column. 1112 String newColumnFilter = null; 1113 1114 // Store the column name in a form acceptable to the Filter property, 1115 // using a backslash to escape any closing square brackets. 1116 String columnProperty = 1117 OwningColumn.DataPropertyName.Replace("]", @"\]"); 1118 1119 // Determine the column filter string based on the user selection. 1120 // For (Blanks) and (NonBlanks), the filter string determines whether 1121 // the column value is null or an empty string. Otherwise, the filter 1122 // string determines whether the column value is the selected value. 1123 switch (selectedFilterValue) 1124 { 1125 case "(Blanks)": 1126 newColumnFilter = String.Format( 1127 "LEN(ISNULL(CONVERT([{0}],'System.String'),''))=0", 1128 columnProperty); 1129 break; 1130 case "(NonBlanks)": 1131 newColumnFilter = String.Format( 1132 "LEN(ISNULL(CONVERT([{0}],'System.String'),''))>0", 1133 columnProperty); 1134 break; 1135 default: 1136 newColumnFilter = String.Format("[{0}]='{1}'", 1137 columnProperty, 1138 ((String)filters[selectedFilterValue]) 1139 .Replace("'", "''")); 1140 break; 1141 } 1142 1143 // Determine the new filter string by removing the previous column 1144 // filter string from the BindingSource.Filter value, then appending 1145 // the new column filter string, using " AND " as appropriate. 1146 String newFilter = FilterWithoutCurrentColumn(data.Filter); 1147 if (String.IsNullOrEmpty(newFilter)) 1148 { 1149 newFilter += newColumnFilter; 1150 } 1151 else 1152 { 1153 newFilter += " AND " + newColumnFilter; 1154 } 1155 1156 1157 // Set the filter to the new value. 1158 try 1159 { 1160 data.Filter = newFilter; 1161 } 1162 catch (InvalidExpressionException ex) 1163 { 1164 throw new NotSupportedException( 1165 "Invalid expression: " + newFilter, ex); 1166 } 1167 1168 // Indicate that the column is currently filtered 1169 // and store the new column filter for use by subsequent 1170 // calls to the FilterWithoutCurrentColumn method. 1171 filtered = true; 1172 currentColumnFilter = newColumnFilter; 1173 } 1174 1175 /// <summary> 1176 /// Removes the filter from the BindingSource bound to the specified DataGridView. 1177 /// </summary> 1178 /// <param name="dataGridView">The DataGridView bound to the BindingSource to unfilter.</param> 1179 public static void RemoveFilter(DataGridView dataGridView) 1180 { 1181 if (dataGridView == null) 1182 { 1183 throw new ArgumentNullException("dataGridView"); 1184 } 1185 1186 // Cast the data source to a BindingSource. 1187 BindingSource data = dataGridView.DataSource as BindingSource; 1188 1189 // Confirm that the data source is a BindingSource that 1190 // supports filtering. 1191 if (data == null || 1192 data.DataSource == null || 1193 !data.SupportsFiltering) 1194 { 1195 throw new ArgumentException("The DataSource property of the " + 1196 "specified DataGridView is not set to a BindingSource " + 1197 "with a SupportsFiltering property value of true."); 1198 } 1199 1200 // Ensure that the current row is not the row for new records. 1201 // This prevents the new row from being added when the filter changes. 1202 if (dataGridView.CurrentRow != null && dataGridView.CurrentRow.IsNewRow) 1203 { 1204 dataGridView.CurrentCell = null; 1205 } 1206 1207 // Remove the filter. 1208 data.Filter = null; 1209 } 1210 1211 /// <summary> 1212 /// Gets a status string for the specified DataGridView indicating the 1213 /// number of visible rows in the bound, filtered BindingSource, or 1214 /// String.Empty if all rows are currently visible. 1215 /// </summary> 1216 /// <param name="dataGridView">The DataGridView bound to the 1217 /// BindingSource to return the filter status for.</param> 1218 /// <returns>A string in the format "x of y records found" where x is 1219 /// the number of rows currently displayed and y is the number of rows 1220 /// available, or String.Empty if all rows are currently displayed.</returns> 1221 public static String GetFilterStatus(DataGridView dataGridView) 1222 { 1223 // Continue only if the specified value is valid. 1224 if (dataGridView == null) 1225 { 1226 throw new ArgumentNullException("dataGridView"); 1227 } 1228 1229 // Cast the data source to a BindingSource. 1230 BindingSource data = dataGridView.DataSource as BindingSource; 1231 1232 // Return String.Empty if there is no appropriate data source or 1233 // there is no filter in effect. 1234 if (String.IsNullOrEmpty(data.Filter) || 1235 data == null || 1236 data.DataSource == null || 1237 !data.SupportsFiltering) 1238 { 1239 return String.Empty; 1240 } 1241 1242 // Retrieve the filtered row count. 1243 Int32 currentRowCount = data.Count; 1244 1245 // Retrieve the unfiltered row count by 1246 // temporarily unfiltering the data. 1247 data.RaiseListChangedEvents = false; 1248 String oldFilter = data.Filter; 1249 data.Filter = null; 1250 Int32 unfilteredRowCount = data.Count; 1251 data.Filter = oldFilter; 1252 data.RaiseListChangedEvents = true; 1253 1254 Debug.Assert(currentRowCount <= unfilteredRowCount, 1255 "current count is greater than unfiltered count"); 1256 1257 // Return String.Empty if the filtered and unfiltered counts 1258 // are the same, otherwise, return the status string. 1259 if (currentRowCount == unfilteredRowCount) 1260 { 1261 return String.Empty; 1262 } 1263 return String.Format("{0}", 1264 currentRowCount, unfilteredRowCount); 1265 //of {1} records found 1266 } 1267 1268 #endregion filtering 1269 1270 #region button bounds: DropDownButtonBounds, InvalidateDropDownButtonBounds, SetDropDownButtonBounds, AdjustPadding 1271 1272 /// <summary> 1273 /// The bounds of the drop-down button, or Rectangle.Empty if filtering 1274 /// is disabled or the button bounds need to be recalculated. 1275 /// </summary> 1276 private Rectangle dropDownButtonBoundsValue = Rectangle.Empty; 1277 1278 /// <summary> 1279 /// The bounds of the drop-down button, or Rectangle.Empty if filtering 1280 /// is disabled. Recalculates the button bounds if filtering is enabled 1281 /// and the bounds are empty. 1282 /// </summary> 1283 protected Rectangle DropDownButtonBounds 1284 { 1285 get 1286 { 1287 if (!FilteringEnabled) 1288 { 1289 return Rectangle.Empty; 1290 } 1291 if (dropDownButtonBoundsValue == Rectangle.Empty) 1292 { 1293 SetDropDownButtonBounds(); 1294 } 1295 return dropDownButtonBoundsValue; 1296 } 1297 } 1298 1299 /// <summary> 1300 /// Sets dropDownButtonBoundsValue to Rectangle.Empty if it isn't already empty. 1301 /// This indicates that the button bounds should be recalculated. 1302 /// </summary> 1303 private void InvalidateDropDownButtonBounds() 1304 { 1305 if (!dropDownButtonBoundsValue.IsEmpty) 1306 { 1307 dropDownButtonBoundsValue = Rectangle.Empty; 1308 } 1309 } 1310 1311 /// <summary> 1312 /// Sets the position and size of dropDownButtonBoundsValue based on the current 1313 /// cell bounds and the preferred cell height for a single line of header text. 1314 /// </summary> 1315 private void SetDropDownButtonBounds() 1316 { 1317 // Retrieve the cell display rectangle, which is used to 1318 // set the position of the drop-down button. 1319 Rectangle cellBounds = 1320 this.DataGridView.GetCellDisplayRectangle( 1321 this.ColumnIndex, -1, false); 1322 1323 // Initialize a variable to store the button edge length, 1324 // setting its initial value based on the font height. 1325 Int32 buttonEdgeLength = this.InheritedStyle.Font.Height + 5; 1326 1327 // Calculate the height of the cell borders and padding. 1328 Rectangle borderRect = BorderWidths( 1329 this.DataGridView.AdjustColumnHeaderBorderStyle( 1330 this.DataGridView.AdvancedColumnHeadersBorderStyle, 1331 new DataGridViewAdvancedBorderStyle(), false, false)); 1332 Int32 borderAndPaddingHeight = 2 + 1333 borderRect.Top + borderRect.Height + 1334 this.InheritedStyle.Padding.Vertical; 1335 Boolean visualStylesEnabled = 1336 Application.RenderWithVisualStyles && 1337 this.DataGridView.EnableHeadersVisualStyles; 1338 if (visualStylesEnabled) 1339 { 1340 borderAndPaddingHeight += 3; 1341 } 1342 1343 // Constrain the button edge length to the height of the 1344 // column headers minus the border and padding height. 1345 if (buttonEdgeLength > 1346 this.DataGridView.ColumnHeadersHeight - 1347 borderAndPaddingHeight) 1348 { 1349 buttonEdgeLength = 1350 this.DataGridView.ColumnHeadersHeight - 1351 borderAndPaddingHeight; 1352 } 1353 1354 // Constrain the button edge length to the 1355 // width of the cell minus three. 1356 if (buttonEdgeLength > cellBounds.Width - 3) 1357 { 1358 buttonEdgeLength = cellBounds.Width - 3; 1359 } 1360 1361 // Calculate the location of the drop-down button, with adjustments 1362 // based on whether visual styles are enabled. 1363 Int32 topOffset = visualStylesEnabled ? 4 : 1; 1364 Int32 top = cellBounds.Bottom - buttonEdgeLength - topOffset; 1365 Int32 leftOffset = visualStylesEnabled ? 3 : 1; 1366 Int32 left = 0; 1367 if (this.DataGridView.RightToLeft == RightToLeft.No) 1368 { 1369 left = cellBounds.Right - buttonEdgeLength - leftOffset; 1370 } 1371 else 1372 { 1373 left = cellBounds.Left + leftOffset; 1374 } 1375 1376 // Set the dropDownButtonBoundsValue value using the calculated 1377 // values, and adjust the cell padding accordingly. 1378 dropDownButtonBoundsValue = new Rectangle(left, top, 1379 buttonEdgeLength, buttonEdgeLength); 1380 AdjustPadding(buttonEdgeLength + leftOffset); 1381 } 1382 1383 /// <summary> 1384 /// Adjusts the cell padding to widen the header by the drop-down button width. 1385 /// </summary> 1386 /// <param name="newDropDownButtonPaddingOffset">The new drop-down button width.</param> 1387 private void AdjustPadding(Int32 newDropDownButtonPaddingOffset) 1388 { 1389 // Determine the difference between the new and current 1390 // padding adjustment. 1391 Int32 widthChange = newDropDownButtonPaddingOffset - 1392 currentDropDownButtonPaddingOffset; 1393 1394 // If the padding needs to change, store the new value and 1395 // make the change. 1396 if (widthChange != 0) 1397 { 1398 // Store the offset for the drop-down button separately from 1399 // the padding in case the client needs additional padding. 1400 currentDropDownButtonPaddingOffset = 1401 newDropDownButtonPaddingOffset; 1402 1403 // Create a new Padding using the adjustment amount, then add it 1404 // to the cell's existing Style.Padding property value. 1405 Padding dropDownPadding = new Padding(0, 0, widthChange, 0); 1406 this.Style.Padding = Padding.Add( 1407 this.InheritedStyle.Padding, dropDownPadding); 1408 } 1409 } 1410 1411 /// <summary> 1412 /// The current width of the drop-down button. This field is used to adjust the cell padding. 1413 /// </summary> 1414 private Int32 currentDropDownButtonPaddingOffset; 1415 1416 #endregion button bounds 1417 1418 #region public properties: FilteringEnabled, AutomaticSortingEnabled, DropDownListBoxMaxLines 1419 1420 /// <summary> 1421 /// Indicates whether filtering is enabled for the owning column. 1422 /// </summary> 1423 private Boolean filteringEnabledValue = true; 1424 1425 /// <summary> 1426 /// Gets or sets a value indicating whether filtering is enabled. 1427 /// </summary> 1428 [DefaultValue(true)] 1429 public Boolean FilteringEnabled 1430 { 1431 get 1432 { 1433 // Return filteringEnabledValue if (there is no DataGridView 1434 // or if (its DataSource property has not been set. 1435 if (this.DataGridView == null || 1436 this.DataGridView.DataSource == null) 1437 { 1438 return filteringEnabledValue; 1439 } 1440 1441 // if (the DataSource property has been set, return a value that combines 1442 // the filteringEnabledValue and BindingSource.SupportsFiltering values. 1443 BindingSource data = this.DataGridView.DataSource as BindingSource; 1444 Debug.Assert(data != null); 1445 return filteringEnabledValue && data.SupportsFiltering; 1446 } 1447 set 1448 { 1449 // If filtering is disabled, remove the padding adjustment 1450 // and invalidate the button bounds. 1451 if (!value) 1452 { 1453 AdjustPadding(0); 1454 InvalidateDropDownButtonBounds(); 1455 } 1456 1457 filteringEnabledValue = value; 1458 } 1459 } 1460 1461 /// <summary> 1462 /// Indicates whether automatic sorting is enabled. 1463 /// </summary> 1464 private Boolean automaticSortingEnabledValue = true; 1465 1466 /// <summary> 1467 /// Gets or sets a value indicating whether automatic sorting is enabled for the owning column. 1468 /// </summary> 1469 [DefaultValue(true)] 1470 public Boolean AutomaticSortingEnabled 1471 { 1472 get 1473 { 1474 return automaticSortingEnabledValue; 1475 } 1476 set 1477 { 1478 automaticSortingEnabledValue = value; 1479 if (OwningColumn != null) 1480 { 1481 if (value) 1482 { 1483 OwningColumn.SortMode = DataGridViewColumnSortMode.Programmatic; 1484 } 1485 else 1486 { 1487 OwningColumn.SortMode = DataGridViewColumnSortMode.NotSortable; 1488 } 1489 } 1490 } 1491 } 1492 1493 /// <summary> 1494 /// The maximum number of lines in the drop-down list. 1495 /// </summary> 1496 private Int32 dropDownListBoxMaxLinesValue = 30; 1497 1498 /// <summary> 1499 /// Gets or sets the maximum number of lines to display in the drop-down filter list. 1500 /// The actual height of the drop-down list is constrained by the DataGridView height. 1501 /// </summary> 1502 [DefaultValue(30)] 1503 public Int32 DropDownListBoxMaxLines 1504 { 1505 get { return dropDownListBoxMaxLinesValue; } 1506 set { dropDownListBoxMaxLinesValue = value; } 1507 } 1508 1509 #endregion public properties 1510 1511 /// <summary> 1512 /// Represents a ListBox control used as a drop-down filter list 1513 /// in a DataGridView control. 1514 /// </summary> 1515 private class FilterListBox : ListBox 1516 { 1517 /// <summary> 1518 /// Initializes a new instance of the FilterListBox class. 1519 /// </summary> 1520 public FilterListBox() 1521 { 1522 Visible = false; 1523 IntegralHeight = true; 1524 BorderStyle = BorderStyle.FixedSingle; 1525 TabStop = false; 1526 } 1527 1528 /// <summary> 1529 /// Indicates that the FilterListBox will handle (or ignore) all 1530 /// keystrokes that are not handled by the operating system. 1531 /// </summary> 1532 /// <param name="keyData">A Keys value that represents the keyboard input.</param> 1533 /// <returns>true in all cases.</returns> 1534 protected override bool IsInputKey(Keys keyData) 1535 { 1536 return true; 1537 } 1538 1539 /// <summary> 1540 /// Processes a keyboard message directly, preventing it from being 1541 /// intercepted by the parent DataGridView control. 1542 /// </summary> 1543 /// <param name="m">A Message, passed by reference, that 1544 /// represents the window message to process.</param> 1545 /// <returns>true if the message was processed by the control; 1546 /// otherwise, false.</returns> 1547 protected override bool ProcessKeyMessage(ref Message m) 1548 { 1549 return ProcessKeyEventArgs(ref m); 1550 } 1551 1552 } 1553 1554 } 1555 1556 public class DataGridViewAutoFilterTextBoxColumn : DataGridViewTextBoxColumn 1557 { 1558 /// <summary> 1559 /// Initializes a new instance of the DataGridViewAutoFilterTextBoxColumn class. 1560 /// </summary> 1561 public DataGridViewAutoFilterTextBoxColumn() 1562 : base() 1563 { 1564 base.DefaultHeaderCellType = typeof(DataGridViewAutoFilterColumnHeaderCell); 1565 base.SortMode = DataGridViewColumnSortMode.Programmatic; 1566 } 1567 1568 #region public properties that hide inherited, non-virtual properties: DefaultHeaderCellType and SortMode 1569 1570 /// <summary> 1571 /// Returns the AutoFilter header cell type. This property hides the 1572 /// non-virtual DefaultHeaderCellType property inherited from the 1573 /// DataGridViewBand class. The inherited property is set in the 1574 /// DataGridViewAutoFilterTextBoxColumn constructor. 1575 /// </summary> 1576 [EditorBrowsable(EditorBrowsableState.Never), Browsable(false), 1577 DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] 1578 public new Type DefaultHeaderCellType 1579 { 1580 get 1581 { 1582 return typeof(DataGridViewAutoFilterColumnHeaderCell); 1583 } 1584 } 1585 1586 /// <summary> 1587 /// Gets or sets the sort mode for the column and prevents it from being 1588 /// set to Automatic, which would interfere with the proper functioning 1589 /// of the drop-down button. This property hides the non-virtual 1590 /// DataGridViewColumn.SortMode property from the designer. The inherited 1591 /// property is set in the DataGridViewAutoFilterTextBoxColumn constructor. 1592 /// </summary> 1593 [EditorBrowsable(EditorBrowsableState.Advanced), Browsable(false)] 1594 [DefaultValue(DataGridViewColumnSortMode.Programmatic)] 1595 public new DataGridViewColumnSortMode SortMode 1596 { 1597 get 1598 { 1599 return base.SortMode; 1600 } 1601 set 1602 { 1603 if (value == DataGridViewColumnSortMode.Automatic) 1604 { 1605 throw new InvalidOperationException( 1606 "A SortMode value of Automatic is incompatible with " + 1607 "the DataGridViewAutoFilterColumnHeaderCell type. " + 1608 "Use the AutomaticSortingEnabled property instead."); 1609 } 1610 else 1611 { 1612 base.SortMode = value; 1613 } 1614 } 1615 } 1616 1617 #endregion 1618 1619 #region public properties: FilteringEnabled, AutomaticSortingEnabled, DropDownListBoxMaxLines 1620 1621 /// <summary> 1622 /// Gets or sets a value indicating whether filtering is enabled for this column. 1623 /// </summary> 1624 [DefaultValue(true)] 1625 public Boolean FilteringEnabled 1626 { 1627 get 1628 { 1629 // Return the header-cell value. 1630 return ((DataGridViewAutoFilterColumnHeaderCell)HeaderCell) 1631 .FilteringEnabled; 1632 } 1633 set 1634 { 1635 // Set the header-cell property. 1636 ((DataGridViewAutoFilterColumnHeaderCell)HeaderCell) 1637 .FilteringEnabled = value; 1638 } 1639 } 1640 1641 /// <summary> 1642 /// Gets or sets a value indicating whether automatic sorting is enabled for this column. 1643 /// </summary> 1644 [DefaultValue(true)] 1645 public Boolean AutomaticSortingEnabled 1646 { 1647 get 1648 { 1649 // Return the header-cell value. 1650 return ((DataGridViewAutoFilterColumnHeaderCell)HeaderCell) 1651 .AutomaticSortingEnabled; 1652 } 1653 set 1654 { 1655 // Set the header-cell property. 1656 ((DataGridViewAutoFilterColumnHeaderCell)HeaderCell) 1657 .AutomaticSortingEnabled = value; 1658 } 1659 } 1660 1661 /// <summary> 1662 /// Gets or sets the maximum height of the drop-down filter list for this column. 1663 /// </summary> 1664 [DefaultValue(20)] 1665 public Int32 DropDownListBoxMaxLines 1666 { 1667 get 1668 { 1669 // Return the header-cell value. 1670 return ((DataGridViewAutoFilterColumnHeaderCell)HeaderCell) 1671 .DropDownListBoxMaxLines; 1672 } 1673 set 1674 { 1675 // Set the header-cell property. 1676 ((DataGridViewAutoFilterColumnHeaderCell)HeaderCell) 1677 .DropDownListBoxMaxLines = value; 1678 } 1679 } 1680 1681 #endregion public properties 1682 1683 #region public, static, convenience methods: RemoveFilter and GetFilterStatus 1684 1685 /// <summary> 1686 /// Removes the filter from the BindingSource bound to the specified DataGridView. 1687 /// </summary> 1688 /// <param name="dataGridView">The DataGridView bound to the BindingSource to unfilter.</param> 1689 public static void RemoveFilter(DataGridView dataGridView) 1690 { 1691 DataGridViewAutoFilterColumnHeaderCell.RemoveFilter(dataGridView); 1692 } 1693 1694 /// <summary> 1695 /// Gets a status string for the specified DataGridView indicating the 1696 /// number of visible rows in the bound, filtered BindingSource, or 1697 /// String.Empty if all rows are currently visible. 1698 /// </summary> 1699 /// <param name="dataGridView">The DataGridView bound to the 1700 /// BindingSource to return the filter status for.</param> 1701 /// <returns>A string in the format "x of y records found" where x is 1702 /// the number of rows currently displayed and y is the number of rows 1703 /// available, or String.Empty if all rows are currently displayed.</returns> 1704 public static String GetFilterStatus(DataGridView dataGridView) 1705 { 1706 return DataGridViewAutoFilterColumnHeaderCell.GetFilterStatus(dataGridView); 1707 } 1708 1709 #endregion 1710 } 1711 1712 public class DataGridViewComboEditBoxColumn : DataGridViewComboBoxColumn 1713 { 1714 public DataGridViewComboEditBoxColumn() 1715 { 1716 DataGridViewComboEditBoxCell obj = new DataGridViewComboEditBoxCell(); 1717 this.CellTemplate = obj; 1718 } 1719 public class DataGridViewComboEditBoxCell : DataGridViewComboBoxCell 1720 { 1721 public override void InitializeEditingControl(int rowIndex, object initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle) 1722 { 1723 base.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle); 1724 1725 ComboBox comboBox = (ComboBox)base.DataGridView.EditingControl; 1726 1727 if (comboBox != null) 1728 { 1729 comboBox.DropDownStyle = ComboBoxStyle.DropDown; 1730 comboBox.AutoCompleteMode = AutoCompleteMode.Suggest; 1731 comboBox.Validating += new CancelEventHandler(comboBox_Validating); 1732 } 1733 } 1734 1735 //protected override object GetFormattedValue(object value, int rowIndex, ref DataGridViewCellStyle cellStyle, TypeConverter valueTypeConverter, TypeConverter formattedValueTypeConverter, DataGridViewDataErrorContexts context) 1736 //{ 1737 // if (value != null) 1738 // { 1739 // if (value.ToString().Trim() != string.Empty) 1740 // { 1741 // if (Items.IndexOf(value) == -1) 1742 // { 1743 // Items.Add(value); 1744 // //DataGridViewComboBoxColumn col = (DataGridViewComboBoxColumn)OwningColumn; 1745 // //col.Items.Add(value); 1746 // } 1747 // } 1748 // } 1749 // return base.GetFormattedValue(value, rowIndex, ref cellStyle, valueTypeConverter, formattedValueTypeConverter, context); 1750 //} 1751 1752 void comboBox_Validating(object sender, System.ComponentModel.CancelEventArgs e) 1753 { 1754 DataGridViewComboBoxEditingControl cbo = (DataGridViewComboBoxEditingControl)sender; 1755 if (cbo.Text.Trim() == string.Empty) return; 1756 1757 DataGridView grid = cbo.EditingControlDataGridView; 1758 object value = cbo.Text; 1759 1760 // Add value to list if not there 1761 if (cbo.Items.IndexOf(value) == -1) 1762 { 1763 DataGridViewComboBoxColumn cboCol = (DataGridViewComboBoxColumn)grid.Columns[grid.CurrentCell.ColumnIndex]; 1764 // Must add to both the current combobox as well as the template, to avoid duplicate entries 1765 cbo.Items.Add(value); 1766 //cboCol.Items.Add(value); 1767 grid.CurrentCell.Value = value; 1768 } 1769 } 1770 } 1771 } 1772 /// <summary> 1773 /// 调用时所用*注意:筛选后Count数据需要用DataBindingComplete事件 1774 /// </summary> 1775 public class DataGridViewFunction 1776 { 1777 /// <summary> 1778 /// 查询时加载时所用-生成ComBox并返回行数.每查询一次执行一次 1779 /// </summary> 1780 /// <param name="DGV"></param> 1781 /// <returns></returns> 1782 public string GridViewHeaderFilter(DataGridView DGV) 1783 { 1784 string Number = "0";//默认数量为0 1785 if (DGV.DataSource != null && DGV.Columns.Count > 0) 1786 { 1787 foreach (DataGridViewColumn col in DGV.Columns) 1788 { 1789 col.HeaderCell = new 1790 DataGridViewAutoFilter.DataGridViewAutoFilterColumnHeaderCell(col.HeaderCell); 1791 } 1792 DGV.AutoResizeColumns(); 1793 DataGridViewAutoFilter.DataGridViewAutoFilterColumnHeaderCell filterCell =DGV.CurrentCell.OwningColumn.HeaderCell as DataGridViewAutoFilter.DataGridViewAutoFilterColumnHeaderCell; 1794 if (filterCell != null) 1795 { 1796 filterCell.ShowDropDownList(); 1797 //e.Handled = true; 1798 } 1799 String filterStatus = DataGridViewAutoFilter.DataGridViewAutoFilterColumnHeaderCell.GetFilterStatus(DGV); 1800 if (String.IsNullOrEmpty(filterStatus)) 1801 { 1802 Number = DGV.RowCount.ToString(); 1803 } 1804 else 1805 { 1806 Number = filterStatus; 1807 } 1808 } 1809 return Number; 1810 } 1811 /// <summary> 1812 /// 返回GridView数据行数 1813 /// </summary> 1814 /// <param name="DGV"></param> 1815 /// <returns></returns> 1816 public string GridViewDataCount(DataGridView DGV) 1817 { 1818 return DGV.RowCount.ToString(); 1819 } 1820 /// <summary> 1821 /// DataGridView填充数据 1822 /// </summary> 1823 /// <param name="dt"></param> 1824 /// <param name="DGV"></param> 1825 public void GridViewDataLoad(DataTable dt, DataGridView DGV) 1826 { 1827 DGV.DataSource = null; 1828 BindingSource dataSource = new BindingSource(dt, null); ; 1829 DGV.DataSource = dataSource; 1830 1831 } 1832 1833 } 1834 }
调用代码如下

1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Windows.Forms; 9 using DataGridViewAutoFilter; 10 11 namespace WindowsFormsApplication4 12 { 13 public partial class Form1 : Form 14 { 15 public Form1() 16 { 17 InitializeComponent(); 18 } 19 MESDB.MESDB GetDB = new MESDB.MESDB();//作者自用数据库链接接口(WebService) 20 DataGridViewAutoFilter.DataGridViewFunction Get = new DataGridViewFunction(); 21 /// <summary> 22 /// 窗体加载事件-并加载数据 23 /// </summary> 24 /// <param name="sender"></param> 25 /// <param name="e"></param> 26 private void Form1_Load(object sender, EventArgs e) 27 { 28 string Sql = "select c.Product_ID as '产品编号',f.Pn_Type as '生产类型',concat(c.Hold_Stage,'[',d.Stage_Name,']') as 'Hold站点',c.Stage_OldStatus as '旧状态' ,concat(c.Line_Type,'[',e.Type_Remark,']' )as '所属区域',c.Hold_Rmark as 'Hold原因' , c.Hold_Time as 'Hold时间' ,c.Hold_User as '操作用户' ,c.Stage_Status as '产品状态' ,substr(c.LevelProductName,1,8) as 'UnitP/N',c.LevelProductName as 'Hold品名',g.TESTTIME as 'CowTestTime' ,c.Change_Level as '升/降档',c.Release_Remark as '释放备注' ,c.Release_Time as '释放时间' , c.Release_User as '释放用户',c.Release_Owner as '释放部门',c.Transfer_Owner as '转移理由',c.Transfer_Time as '转移时间' ,c.Transfer_User as '转移用户',c.Exception_Type as '异常归类',c.Level_Handling as '升降档归类',c.Parameter_Type as '异常参数',c.Department_Owner as '责任归类',c.Treat_Remark as '归类备注',c.Remark_User as '归类用户',c.Remark_Time as '归类时间',c.Hold_Type as 'Hold类型' from mes.MES_Chip_Stage as d,mes.MES_Chip_LineType e,mes.MES_Lot_Attribute as f,mes.MES_Qc_Hold as c left join eda.cow_probe_sample_test_summary as g on c.Product_ID=g.CHIP_WaferID where f.Lot_Attribute=substr(c.Product_ID,1,1) and c.Hold_Stage=d.Stage_Code and c.Line_Type=e.Line_Type and c.Line_Type='PV' and c.Transfer_Time between '2016/05/08 17:52:29' and '2016/06/07 17:52:29' "; 29 DataTable dt = GetDB.MySqlDBSelct(Sql).Tables[0]; 30 Get.GridViewDataLoading(dt, dataGridView1);//填充DataGridView 31 label1.Text = Get.GridViewHeaderFilter(dataGridView1);//标题添加ComBox并返回行数 32 33 } 34 private void dataGridView1_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e) 35 { 36 37 label1.Text = Get.GridViewDataCount(dataGridView1);//筛选时动态加载行数 38 39 } 40 } 41 }
作者:刘涛(Bill)
本博客所有文章仅用于学习、研究和交流目的,欢迎非商业性质转载。
博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高,不足和错误之处在所难免,希望大家能够批评指出。
博主是利用读书、参考、引用、抄袭、复制和粘贴等多种方式打造成自己的文章,请原谅博主成为一个无耻的文档搬运工!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
· 张高兴的大模型开发实战:(一)使用 Selenium 进行网页爬虫