/// <summary> /// This is used to select an item in the drop-down as the current item /// </summary> /// <param name="selIdx">The index of the selected item</param> internal void SelectItem(int selIdx) { dgDropDown.ClearSelection(); if (selIdx != -1) { DataGridHelper.ConcedeFocus(dgDropDown); dgDropDown.CurrentRowIndex = selIdx; dgDropDown.Select(selIdx); } }
/// <summary> /// This can be used to auto-size all columns in the specified <see cref="System.Windows.Forms.DataGrid"/> /// control. /// </summary> /// <param name="dg">The data grid in which to size the columns</param> internal static void AutoSizeColumns(DataGrid dg) { MethodInfo mi = DataGridHelper.DataGridType(dg).GetMethod("ColAutoResize", BindingFlags.NonPublic | BindingFlags.Instance); DataGridTableStyle dgs = DataGridHelper.CurrentTableStyle(dg); for (int idx = 0; idx < dgs.GridColumnStyles.Count; idx++) { mi.Invoke(dg, new object[] { idx }); } }
/// <summary> /// Highlight the item under the mouse when it moves /// </summary> /// <param name="e">The event arguments</param> protected override void OnMouseMove(MouseEventArgs e) { // We tend to get lots of these even if the mouse doesn't move. It's an OS thing apparently. if (lastMousePos.X != e.X || lastMousePos.Y != e.Y) { if ((!this.IsSimpleStyle && this.MouseTracking) || e.Button == MouseButtons.Left) { lastMousePos = new Point(e.X, e.Y); DataGrid.HitTestInfo hti = base.HitTest(e.X, e.Y); if (hti.Type == DataGrid.HitTestType.Cell) { base.ResetSelection(); base.CurrentRowIndex = hti.Row; base.Select(hti.Row); } else if (hti.Type == DataGrid.HitTestType.None) { int newRow = base.CurrentRowIndex; // Scroll when out of bounds if (e.Y < base.Top && newRow > 0) { newRow--; } else if (e.Y > base.Bottom && newRow < DataGridHelper.RowCount(this) - 1) { newRow++; } if (newRow != base.CurrentRowIndex) { base.ResetSelection(); base.CurrentRowIndex = newRow; base.Select(newRow); } } } base.OnMouseMove(e); } }
/// <summary> /// This can be used to force the current edit column in the data grid to concede the focus. Changes /// will be saved if possible. /// </summary> /// <param name="dg">The data grid to use</param> internal static void ConcedeFocus(DataGrid dg) { Type type = DataGridHelper.DataGridType(dg); FieldInfo fi = type.GetField("editColumn", BindingFlags.NonPublic | BindingFlags.Instance); dg.EndEdit(null, 0, false); DataGridColumnStyle dgc = (DataGridColumnStyle)fi.GetValue(dg); if (dgc != null) { MethodInfo mi = dgc.GetType().GetMethod("ConcedeFocus", BindingFlags.NonPublic | BindingFlags.Instance); mi.Invoke(dgc, null); } }
/// <summary> /// Scroll the drop-down the specified number of rows /// </summary> /// <param name="rows">The number of rows to scroll</param> public void ScrollDropDown(int rows) { DataGridHelper.ScrollDown(dgDropDown, rows); dgDropDown.Invalidate(true); }
/// <summary> /// This is used to set the default selected item, size and position the drop-down, and show it /// </summary> public void ShowDropDown() { int idx; if (!hasInitialized) { this.CreateHandle(); } idx = startIndex = owner.SelectedIndex; dgDropDown.ClearSelection(); dragOffset = Point.Empty; // Sometimes it shows the edit control so clear it DataGridHelper.ConcedeFocus(dgDropDown); if (idx != -1 && DataGridHelper.RowCount(dgDropDown) > idx) { dgDropDown.Select(idx); dgDropDown.CurrentRowIndex = idx; } // The owner positions us when using the simple style. There's also an odd sequence of events // related to updates to bound controls that can cause this control to get disposed after setting // the data grid's row index above so we'll stop in that case too. if (this.IsDisposed || owner.DropDownStyle == ComboBoxStyle.Simple) { return; } if (this.Width < owner.Width) { this.Width = owner.Width; } // Make sure we are within the screen bounds. Take into account the working area of all screens. // Note that in certain setups, the leftmost/uppermost screen(s) may have negative coordinates. Rectangle workingArea, screen = new Rectangle(); foreach (Screen s in Screen.AllScreens) { workingArea = s.WorkingArea; if (workingArea.X < screen.X) { screen.X = workingArea.X; } if (workingArea.Y < screen.Y) { screen.Y = workingArea.Y; } if (Math.Abs(workingArea.X) + workingArea.Width > screen.Width) { screen.Width = Math.Abs(workingArea.X) + workingArea.Width; } if (Math.Abs(workingArea.Y) + workingArea.Height > screen.Height) { screen.Height = Math.Abs(workingArea.Y) + workingArea.Height; } } Point pOwner = owner.Parent.PointToScreen(owner.Location); pOwner.Y += owner.Height; Point p = pOwner; if (this.Width > screen.Width) { this.Width = screen.Width; } if (this.Height > screen.Height) { this.Height = screen.Height; } if (p.X < screen.X) { p.X = screen.X; } if (p.Y < screen.Y) { p.Y = screen.Y; } if (p.X + this.Width > screen.X + screen.Width) { p.X = screen.X + screen.Width - this.Width - 1; } if (p.Y + this.Height > screen.Y + screen.Height) { p.Y = screen.Y + screen.Height - this.Height - 1; } // If we are covering the combo box, move the location above the combo box if (p.Y < pOwner.Y) { idx = pOwner.Y - this.Height - owner.Height; // If we would go off the top of the screen, figure out whether we would have more rows visible // if we moved the form above the combo box or left it where it is at below the combo box and // shrink it to fit. if (idx < screen.Y) { if (this.Height + idx > this.Height - (pOwner.Y - p.Y)) { this.Height += idx; idx = screen.Y; } else { this.Height -= (pOwner.Y - p.Y); idx = pOwner.Y; } } p.Y = idx; } this.Location = p; UnsafeNativeMethods.SetParent(this.Handle, IntPtr.Zero); this.Show(); // NOTE: We lose capture pretty quickly as the mouse isn't inside the drop-down. MouseLeave grabs it // for us again. this.Capture = true; }
/// <summary> /// This is used to initialize the drop-down styles and data source /// </summary> private void InitDropDown() { DropDownTableStyle ddts = owner.DropDownFormat; int rowCount, rowHeight, headerHeight = 0, idx = 0, totalSize = 0, rowHeaderWidth = ddts.RowHeaderWidth; // Must check this before we set the data source otherwise, columns are added by default bool autoSize = (ddts.GridColumnStyles.Count == 0); dgDropDown.Font = owner.DropDownFont; dgDropDown.RightToLeft = owner.RightToLeft; this.BackColor = dgDropDown.BackgroundColor = owner.DropDownBackColor; this.Cursor = owner.Cursor; // Set the data source and apply style settings. If there is a data source, use it. ddts.MappingName = owner.MappingName; dgDropDown.DataMember = owner.BindingPath; if (owner.DataSource != null) { // If bound to a relationship, use the object collection if (ddts.MappingName == dgDropDown.DataMember && ddts.MappingName.IndexOf('.') != -1) { ddts.MappingName = "ObjectCollection"; dgDropDown.TableStyles.Add(ddts); dgDropDown.DataSource = owner.Items; } else { dgDropDown.TableStyles.Add(ddts); dgDropDown.DataSource = owner.DataSource; } } else if (ddts.MappingName == "ValueType" || ddts.MappingName == "String") { // Value and string types require a wrapper or they don't show up correctly ValueItem[] viArray = new ValueItem[owner.Items.Count]; for (int collIdx = 0; collIdx < owner.Items.Count; collIdx++) { viArray[collIdx] = new ValueItem(owner.Items[collIdx]); } ddts.MappingName = "ValueItem[]"; dgDropDown.TableStyles.Add(ddts); dgDropDown.DataSource = viArray; } else // It must be an object collection { dgDropDown.DataSource = owner.Items; } // Filter the columns if necessary and clear the headers if auto-sizing and they aren't visible. // Clearing the header text is necessary so that we get an accurate size. StringCollection filter = owner.ColumnFilter; if (filter.Count != 0 || (autoSize && !ddts.ColumnHeadersVisible)) { for (idx = 0; idx < ddts.GridColumnStyles.Count; idx++) { if (filter.Count != 0 && !filter.Contains(ddts.GridColumnStyles[idx].MappingName)) { ddts.GridColumnStyles.RemoveAt(idx); idx--; } else if (autoSize && !ddts.ColumnHeadersVisible) { ddts.GridColumnStyles[idx].HeaderText = String.Empty; } } } // Row Header Width and Column Headers Visible seem to get overridden so restore them ddts.RowHeaderWidth = rowHeaderWidth; dgDropDown.ColumnHeadersVisible = ddts.ColumnHeadersVisible; // Only auto-size if the user didn't specify any columns if (autoSize) { DataGridHelper.AutoSizeColumns(dgDropDown); } // Sum the width of the columns and take the opportunity to auto-size columns with a width of zero. // This only happens if the user specified column definitions. idx = 0; foreach (DataGridColumnStyle col in ddts.GridColumnStyles) { // Replace default null text where needed if (col.NullText == "(null)") { col.NullText = ddts.DefaultNullText; } if (col.Width == 0) { DataGridHelper.AutoSizeColumn(dgDropDown, idx); } totalSize += col.Width; idx++; } // Use the specified width or figure out the initial width if (owner.DropDownWidth != 0) { totalSize = owner.DropDownWidth; } else { if (ddts.RowHeadersVisible) { totalSize += rowHeaderWidth; } if (idx != 0 && totalSize < owner.Width - 2) { // Stretch the last column to fit the width ddts.GridColumnStyles[idx - 1].Width += owner.Width - totalSize - 2; totalSize = owner.Width; } else if (idx == 0) { totalSize = owner.Width; } else { totalSize += 2; } } // Enforce a minimum width if (totalSize < 30) { totalSize = 30; } this.Width = totalSize; // Set default height rowCount = DataGridHelper.RowCount(dgDropDown); if (rowCount != 0 && ddts.GridColumnStyles.Count > 0) { rowHeight = dgDropDown.GetCellBounds(0, 0).Height + 1; } else { rowHeight = dgDropDown.PreferredRowHeight; } // Figure out column header height if visible. This is a little ugly but it works. if (ddts.ColumnHeadersVisible) { while (dgDropDown.HitTest(rowHeaderWidth + 2, headerHeight).Type == DataGrid.HitTestType.ColumnHeader) { headerHeight++; } } if (rowCount > owner.MaxDropDownItems) { rowHeight = (rowHeight * owner.MaxDropDownItems) + headerHeight; // Adjust width to account for the scrollbar this.Width += MultiColumnComboBox.DropDownButtonWidth; } else { rowHeight = (rowHeight * rowCount) + headerHeight; } this.Height = rowHeight + this.Height - dgDropDown.Height; hasInitialized = true; }
/// <summary> /// Hide any edit control when the grid is scrolled /// </summary> /// <param name="sender">The sender of the event</param> /// <param name="e">The event arguments</param> private void DropDownDataGrid_Scroll(object sender, EventArgs e) { DataGridHelper.ConcedeFocus(this); }