using System; using System.Drawing; using System.Globalization; using System.ComponentModel; using System.Windows.Forms; using System.Windows.Forms.VisualStyles; using System.Diagnostics; namespace DataGridViewRadioButtonElements { public class DataGridViewRadioButtonCell : DataGridViewComboBoxCell, IDataGridViewEditingCell { /// /// Convenient enumeration using privately for calculating preferred cell sizes. /// private enum DataGridViewRadioButtonFreeDimension { Both, Height, Width } // 4 pixels of margin on the left and right of error icons private const byte DATAGRIDVIEWRADIOBUTTONCELL_iconMarginWidth = 4; // 4 pixels of margin on the top and bottom of error icons private const byte DATAGRIDVIEWRADIOBUTTONCELL_iconMarginHeight = 4; // all icons are 12 pixels wide by default private const byte DATAGRIDVIEWRADIOBUTTONCELL_iconsWidth = 12; // all icons are 11 pixels tall by default private const byte DATAGRIDVIEWRADIOBUTTONCELL_iconsHeight = 11; // default value of MaxDisplayedItems property internal const int DATAGRIDVIEWRADIOBUTTONCELL_defaultMaxDisplayedItems = 5; // blank pixels around each radio button entry private const byte DATAGRIDVIEWRADIOBUTTONCELL_margin = 2; // codes used for the mouseLocationCode static variable: private const int DATAGRIDVIEWRADIOBUTTONCELL_mouseLocationGeneric = -3; private const int DATAGRIDVIEWRADIOBUTTONCELL_mouseLocationBottomScrollButton = -2; // mouse is over bottom scroll button private const int DATAGRIDVIEWRADIOBUTTONCELL_mouseLocationTopScrollButton = -1; // mouse is over top scroll button private DataGridViewRadioButtonCellLayout layout; // represents the current layout information of the cell private static int mouseLocationCode = DATAGRIDVIEWRADIOBUTTONCELL_mouseLocationGeneric; // -3 no particular location // -2 mouse over bottom scroll button // -1 mouse over top scroll button // 0-N mouse over radio button glyph private PropertyDescriptor displayMemberProperty; // Property descriptor for the DisplayMember property private PropertyDescriptor valueMemberProperty; // Property descriptor for the ValueMember property private CurrencyManager dataManager; // Currency manager for the cell's DataSource private int maxDisplayedItems; // Maximum number of radio buttons displayed by the cell private int selectedItemIndex; // Index of the currently selected radio button entry public int focusedItemIndex; // Index of the focused radio button entry private int pressedItemIndex; // Index of the currently pressed radio button entry private bool dataSourceInitializedHookedUp; // Indicates whether the DataSource's Initialized event is listened to private bool valueChanged; // Stores whether the cell's value was changed since it became the current cell private bool handledKeyDown; // Indicates whether the cell handled the key down notification private bool mouseUpHooked; // Indicates whether the cell listens to the grid's MouseUp event /// /// DataGridViewRadioButtonCell class constructor. /// public DataGridViewRadioButtonCell() { this.maxDisplayedItems = DATAGRIDVIEWRADIOBUTTONCELL_defaultMaxDisplayedItems; this.layout = new DataGridViewRadioButtonCellLayout(); this.selectedItemIndex = -1; this.focusedItemIndex = -1; this.pressedItemIndex = -1; } // Implementation of the IDataGridViewEditingCell interface starts here. /// /// Represents the cell's formatted value /// public virtual object EditingCellFormattedValue { get { return GetEditingCellFormattedValue(DataGridViewDataErrorContexts.Formatting); } set { if (this.FormattedValueType == null) { throw new ArgumentException("FormattedValueType property of a cell cannot be null."); } if (value == null || !this.FormattedValueType.IsAssignableFrom(value.GetType())) { // Assigned formatted value may not be of the good type, in cases where the app // is feeding wrong values to the cell in virtual / databound mode. throw new ArgumentException("The value provided for the DataGridViewRadioButtonCell has the wrong type."); } // Try to locate the item that corresponds to the 'value' provided. for (int itemIndex = 0; itemIndex < this.Items.Count; itemIndex++) { object item = this.Items[itemIndex]; object displayValue = GetItemDisplayValue(item); if (value.Equals(displayValue)) { // 'value' was found. It becomes the new selected item. this.selectedItemIndex = itemIndex; return; } } string strValue = value as string; if (strValue == string.Empty) { // Special case the empty string situation - reset the selected item this.selectedItemIndex = -1; return; } // 'value' could not be matched against an item in the Items collection. throw new ArgumentException(); } } /// /// Keeps track of whether the cell's value has changed or not. /// public virtual bool EditingCellValueChanged { get { return this.valueChanged; } set { this.valueChanged = value; } } /// /// Returns the current formatted value of the cell /// public virtual object GetEditingCellFormattedValue(DataGridViewDataErrorContexts context) { if (this.FormattedValueType == null) { throw new InvalidOperationException("FormattedValueType property of a cell cannot be null."); } if (this.selectedItemIndex == -1) { return null; } object item = this.Items[this.selectedItemIndex]; object displayValue = GetItemDisplayValue(item); // Making sure the returned value has an acceptable type if (this.FormattedValueType.IsAssignableFrom(displayValue.GetType())) { return displayValue; } else { return null; } } /// /// Called by the grid when the cell enters editing mode. /// public virtual void PrepareEditingCellForEdit(bool selectAll) { // This cell type has nothing to do here. } // Implementation of the IDataGridViewEditingCell interface stops here. /// /// Stores the CurrencyManager associated to the cell's DataSource /// private CurrencyManager DataManager { get { CurrencyManager cm = this.dataManager; if (cm == null && this.DataSource != null && this.DataGridView != null && this.DataGridView.BindingContext != null && !(this.DataSource == Convert.DBNull)) { ISupportInitializeNotification dsInit = this.DataSource as ISupportInitializeNotification; if (dsInit != null && !dsInit.IsInitialized) { // The datasource is not ready yet. Attaching to its Initialized event to be notified // when it's finally ready if (!this.dataSourceInitializedHookedUp) { dsInit.Initialized += new EventHandler(DataSource_Initialized); this.dataSourceInitializedHookedUp = true; } } else { cm = (CurrencyManager)this.DataGridView.BindingContext[this.DataSource]; this.DataManager = cm; } } return cm; } set { this.dataManager = value; } } /// /// Overrides the DataGridViewComboBox's implementation of the DataSource property to /// initialize the displayMemberProperty and valueMemberProperty members. /// public override object DataSource { get { return base.DataSource; } set { if (this.DataSource != value) { // Invalidate the currency manager this.DataManager = null; ISupportInitializeNotification dsInit = this.DataSource as ISupportInitializeNotification; if (dsInit != null && this.dataSourceInitializedHookedUp) { // If we previously hooked the datasource's ISupportInitializeNotification // Initialized event, then unhook it now (we don't always hook this event, // only if we needed to because the datasource was previously uninitialized) dsInit.Initialized -= new EventHandler(DataSource_Initialized); this.dataSourceInitializedHookedUp = false; } base.DataSource = value; // Update the displayMemberProperty and valueMemberProperty members. try { InitializeDisplayMemberPropertyDescriptor(this.DisplayMember); } catch { Debug.Assert(this.DisplayMember != null && this.DisplayMember.Length > 0); InitializeDisplayMemberPropertyDescriptor(null); } try { InitializeValueMemberPropertyDescriptor(this.ValueMember); } catch { Debug.Assert(this.ValueMember != null && this.ValueMember.Length > 0); InitializeValueMemberPropertyDescriptor(null); } if (value == null) { InitializeDisplayMemberPropertyDescriptor(null); InitializeValueMemberPropertyDescriptor(null); } } } } /// /// Overrides the DataGridViewComboBox's implementation of the DisplayMember property to /// update the displayMemberProperty member. /// public override string DisplayMember { get { return base.DisplayMember; } set { base.DisplayMember = value; InitializeDisplayMemberPropertyDescriptor(value); } } /// /// Overrides the base implementation to replace the 'complex editing experience' /// with a 'simple editing experience'. /// public override Type EditType { get { // Return null since no editing control is used for the editing experience. return null; } } /// /// Custom property that represents the maximum number of radio buttons shown by the cell. /// [ DefaultValue(DATAGRIDVIEWRADIOBUTTONCELL_defaultMaxDisplayedItems) ] public int MaxDisplayedItems { get { return this.maxDisplayedItems; } set { if (value < 1 || value > 100) { throw new ArgumentOutOfRangeException("MaxDisplayedItems"); } this.maxDisplayedItems = value; if (this.DataGridView != null && !this.DataGridView.IsDisposed && !this.DataGridView.Disposing) { if (this.RowIndex == -1) { // Invalidate and autosize column this.DataGridView.InvalidateColumn(this.ColumnIndex); // TODO: Add code to autosize the cell's column, the rows, the column headers // and the row headers depending on their autosize settings. // The DataGridView control does not expose a public method that takes care of this. } else { // The DataGridView control exposes a public method called UpdateCellValue // that invalidates the cell so that it gets repainted and also triggers all // the necessary autosizing: the cell's column and/or row, the column headers // and the row headers are autosized depending on their autosize settings. this.DataGridView.UpdateCellValue(this.ColumnIndex, this.RowIndex); } } } } /// /// Called internally by the DataGridViewRadioButtonColumn class to avoid the invalidation /// done by the MaxDisplayedItems setter above (for performance reasons). /// internal int MaxDisplayedItemsInternal { set { Debug.Assert(value >= 1 && value <= 100); this.maxDisplayedItems = value; } } /// /// Utility function that returns the standard thickness (in pixels) of the four borders of the cell. /// private Rectangle StandardBorderWidths { get { if (this.DataGridView != null) { DataGridViewAdvancedBorderStyle dataGridViewAdvancedBorderStylePlaceholder = new DataGridViewAdvancedBorderStyle(), dgvabsEffective; dgvabsEffective = AdjustCellBorderStyle(this.DataGridView.AdvancedCellBorderStyle, dataGridViewAdvancedBorderStylePlaceholder, false /*singleVerticalBorderAdded*/, false /*singleHorizontalBorderAdded*/, false /*isFirstDisplayedColumn*/, false /*isFirstDisplayedRow*/); return BorderWidths(dgvabsEffective); } else { return Rectangle.Empty; } } } /// /// Overrides the DataGridViewComboBox's implementation of the ValueMember property to /// update the valueMemberProperty member. /// public override string ValueMember { get { return base.ValueMember; } set { base.ValueMember = value; InitializeValueMemberPropertyDescriptor(value); } } /// /// Utility function that returns the cell state inherited from the owning row and column. /// private DataGridViewElementStates CellStateFromColumnRowStates(DataGridViewElementStates rowState) { Debug.Assert(this.DataGridView != null); Debug.Assert(this.ColumnIndex >= 0); DataGridViewElementStates orFlags = DataGridViewElementStates.ReadOnly | DataGridViewElementStates.Resizable | DataGridViewElementStates.Selected; DataGridViewElementStates andFlags = DataGridViewElementStates.Displayed | DataGridViewElementStates.Frozen | DataGridViewElementStates.Visible; DataGridViewElementStates cellState = (this.OwningColumn.State & orFlags); cellState |= (rowState & orFlags); cellState |= ((this.OwningColumn.State & andFlags) & (rowState & andFlags)); return cellState; } /// /// Custom implementation of the Clone method to copy over the special properties of the cell. /// public override object Clone() { DataGridViewRadioButtonCell dataGridViewCell = base.Clone() as DataGridViewRadioButtonCell; if (dataGridViewCell != null) { dataGridViewCell.MaxDisplayedItems = this.MaxDisplayedItems; } return dataGridViewCell; } /// /// Computes the layout of the cell and optionally paints it. /// private void ComputeLayout(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates cellState, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts, bool paint) { if (paint && DataGridViewRadioButtonCell.PartPainted(paintParts, DataGridViewPaintParts.Border)) { // Paint the borders first PaintBorder(graphics, clipBounds, cellBounds, cellStyle, advancedBorderStyle); } // Discard the space taken up by the borders. Rectangle borderWidths = BorderWidths(advancedBorderStyle); Rectangle valBounds = cellBounds; valBounds.Offset(borderWidths.X, borderWidths.Y); valBounds.Width -= borderWidths.Right; valBounds.Height -= borderWidths.Bottom; SolidBrush backgroundBrush = null; try { Point ptCurrentCell = this.DataGridView.CurrentCellAddress; bool cellCurrent = ptCurrentCell.X == this.ColumnIndex && ptCurrentCell.Y == rowIndex; bool cellSelected = (cellState & DataGridViewElementStates.Selected) != 0; bool mouseOverCell = cellBounds.Contains(this.DataGridView.PointToClient(Control.MousePosition)); if (DataGridViewRadioButtonCell.PartPainted(paintParts, DataGridViewPaintParts.SelectionBackground) && cellSelected) { backgroundBrush = new SolidBrush(cellStyle.SelectionBackColor); } else { backgroundBrush = new SolidBrush(cellStyle.BackColor); } if (paint && DataGridViewRadioButtonCell.PartPainted(paintParts, DataGridViewPaintParts.Background) && backgroundBrush.Color.A == 255) { Rectangle backgroundRect = valBounds; backgroundRect.Intersect(clipBounds); graphics.FillRectangle(backgroundBrush, backgroundRect); } // Discard the space taken up by the padding area. if (cellStyle.Padding != Padding.Empty) { valBounds.Offset(cellStyle.Padding.Left, cellStyle.Padding.Top); valBounds.Width -= cellStyle.Padding.Horizontal; valBounds.Height -= cellStyle.Padding.Vertical; } Rectangle errorBounds = valBounds; Rectangle scrollBounds = valBounds; this.layout.ScrollingNeeded = GetScrollingNeeded(graphics, rowIndex, cellStyle, valBounds.Size); if (this.layout.ScrollingNeeded) { this.layout.ScrollButtonsSize = ScrollBarRenderer.GetSizeBoxSize(graphics, ScrollBarState.Normal); // Discard the space required for displaying the 2 scroll buttons valBounds.Width -= this.layout.ScrollButtonsSize.Width; } valBounds.Inflate(-DATAGRIDVIEWRADIOBUTTONCELL_margin, -DATAGRIDVIEWRADIOBUTTONCELL_margin); // Layout / paint the radio buttons themselves this.layout.RadioButtonsSize = RadioButtonRenderer.GetGlyphSize(graphics, RadioButtonState.CheckedNormal); this.layout.DisplayedItemsCount = 0; this.layout.TotallyDisplayedItemsCount = 0; if (valBounds.Width > 0 && valBounds.Height > 0) { this.layout.FirstDisplayedItemLocation = new Point(valBounds.Left + DATAGRIDVIEWRADIOBUTTONCELL_margin, valBounds.Top + DATAGRIDVIEWRADIOBUTTONCELL_margin); int textHeight = cellStyle.Font.Height; int itemIndex = this.layout.FirstDisplayedItemIndex; Rectangle radiosBounds = valBounds; while (itemIndex < this.Items.Count && itemIndex < this.layout.FirstDisplayedItemIndex + this.maxDisplayedItems && radiosBounds.Height > 0) { if (paint && DataGridViewRadioButtonCell.PartPainted(paintParts, DataGridViewPaintParts.ContentBackground)) { Rectangle itemRect = radiosBounds; itemRect.Intersect(clipBounds); if (!itemRect.IsEmpty) { bool itemReadOnly = (cellState & DataGridViewElementStates.ReadOnly) != 0; bool itemSelected = false; if (formattedValue != null) { object displayValue = GetItemDisplayValue(this.Items[itemIndex]); if (formattedValue.Equals(displayValue)) { itemSelected = true; } } PaintItem(graphics, radiosBounds, rowIndex, itemIndex, cellStyle, itemReadOnly, itemSelected, mouseOverCell, cellCurrent && this.focusedItemIndex == itemIndex && DataGridViewRadioButtonCell.PartPainted(paintParts, DataGridViewPaintParts.Focus)); } } itemIndex++; radiosBounds.Y += textHeight + DATAGRIDVIEWRADIOBUTTONCELL_margin; radiosBounds.Height -= (textHeight + DATAGRIDVIEWRADIOBUTTONCELL_margin); if (radiosBounds.Height >= 0) { this.layout.TotallyDisplayedItemsCount++; } this.layout.DisplayedItemsCount++; } this.layout.ContentBounds = new Rectangle(this.layout.FirstDisplayedItemLocation, new Size(this.layout.RadioButtonsSize.Width, this.layout.DisplayedItemsCount * (textHeight + DATAGRIDVIEWRADIOBUTTONCELL_margin))); } else { this.layout.FirstDisplayedItemLocation = new Point(-1, -1); this.layout.ContentBounds = Rectangle.Empty; } if (this.layout.ScrollingNeeded) { // Layout / paint the 2 scroll buttons Rectangle rectArrow = new Rectangle(scrollBounds.Right - this.layout.ScrollButtonsSize.Width, scrollBounds.Top, this.layout.ScrollButtonsSize.Width, this.layout.ScrollButtonsSize.Height); this.layout.UpButtonLocation = rectArrow.Location; if (paint && DataGridViewRadioButtonCell.PartPainted(paintParts, DataGridViewPaintParts.ContentBackground)) { ScrollBarRenderer.DrawArrowButton(graphics, rectArrow, GetScrollBarArrowButtonState(true, mouseOverCell ? mouseLocationCode : DATAGRIDVIEWRADIOBUTTONCELL_mouseLocationGeneric, this.layout.FirstDisplayedItemIndex > 0 /*enabled*/)); } rectArrow.Y = scrollBounds.Bottom - this.layout.ScrollButtonsSize.Height; this.layout.DownButtonLocation = rectArrow.Location; if (paint && DataGridViewRadioButtonCell.PartPainted(paintParts, DataGridViewPaintParts.ContentBackground)) { ScrollBarRenderer.DrawArrowButton(graphics, rectArrow, GetScrollBarArrowButtonState(false, mouseOverCell ? mouseLocationCode : DATAGRIDVIEWRADIOBUTTONCELL_mouseLocationGeneric, this.layout.FirstDisplayedItemIndex + this.layout.TotallyDisplayedItemsCount < this.Items.Count /*enabled*/)); } } // Finally paint the potential error icon if (paint && DataGridViewRadioButtonCell.PartPainted(paintParts, DataGridViewPaintParts.ErrorIcon) && !(cellCurrent && this.DataGridView.IsCurrentCellInEditMode) && this.DataGridView.ShowCellErrors) { PaintErrorIcon(graphics, clipBounds, errorBounds, errorText); } } finally { if (backgroundBrush != null) { backgroundBrush.Dispose(); } } } /// /// Returns whether calling the OnContentClick method would force the owning row to be unshared. /// protected override bool ContentClickUnsharesRow(DataGridViewCellEventArgs e) { Point ptCurrentCell = this.DataGridView.CurrentCellAddress; return ptCurrentCell.X == this.ColumnIndex && ptCurrentCell.Y == e.RowIndex && this.DataGridView.IsCurrentCellInEditMode; } /// /// Raised when the owning grid gets a MouseUp notification /// private void DataGridView_MouseUp(object sender, MouseEventArgs e) { // Unhook the event handler this.DataGridView.MouseUp -= new MouseEventHandler(DataGridView_MouseUp); this.mouseUpHooked = false; // Reset the pressed item index. Since the mouse was released, no item can be pressed anymore. this.pressedItemIndex = -1; } /// /// Raised when the cell's DataSource is initialized. /// private void DataSource_Initialized(object sender, EventArgs e) { Debug.Assert(sender == this.DataSource); Debug.Assert(this.DataSource is ISupportInitializeNotification); Debug.Assert(this.dataSourceInitializedHookedUp); ISupportInitializeNotification dsInit = this.DataSource as ISupportInitializeNotification; // Unhook the Initialized event. if (dsInit != null) { dsInit.Initialized -= new EventHandler(DataSource_Initialized); } // The wait is over: the DataSource is initialized. this.dataSourceInitializedHookedUp = false; // Check the DisplayMember and ValueMember values - will throw if values don't match existing fields. InitializeDisplayMemberPropertyDescriptor(this.DisplayMember); InitializeValueMemberPropertyDescriptor(this.ValueMember); } /// /// Returns whether calling the OnEnter method would force the owning row to be unshared. /// protected override bool EnterUnsharesRow(int rowIndex, bool throughMouseClick) { return this.focusedItemIndex == -1; } /// /// Custom implementation of the GetContentBounds method which delegates most of the work to the ComputeLayout function. /// protected override Rectangle GetContentBounds(Graphics graphics, DataGridViewCellStyle cellStyle, int rowIndex) { if (this.DataGridView == null || rowIndex < 0 || this.OwningColumn == null) { return Rectangle.Empty; } // First determine the effective border style of this cell. bool singleVerticalBorderAdded = !this.DataGridView.RowHeadersVisible && this.DataGridView.AdvancedCellBorderStyle.All == DataGridViewAdvancedCellBorderStyle.Single; bool singleHorizontalBorderAdded = !this.DataGridView.ColumnHeadersVisible && this.DataGridView.AdvancedCellBorderStyle.All == DataGridViewAdvancedCellBorderStyle.Single; DataGridViewAdvancedBorderStyle dataGridViewAdvancedBorderStylePlaceholder = new DataGridViewAdvancedBorderStyle(); Debug.Assert(rowIndex > -1 && this.OwningColumn != null); DataGridViewAdvancedBorderStyle dgvabsEffective = AdjustCellBorderStyle(this.DataGridView.AdvancedCellBorderStyle, dataGridViewAdvancedBorderStylePlaceholder, singleVerticalBorderAdded, singleHorizontalBorderAdded, rowIndex == this.DataGridView.Rows.GetFirstRow(DataGridViewElementStates.Displayed) /*isFirstDisplayedRow*/, this.ColumnIndex == this.DataGridView.Columns.GetFirstColumn(DataGridViewElementStates.Displayed).Index /*isFirstDisplayedColumn*/); // Next determine the state of this cell. DataGridViewElementStates rowState = this.DataGridView.Rows.GetRowState(rowIndex); DataGridViewElementStates cellState = CellStateFromColumnRowStates(rowState); cellState |= this.State; // Then the bounds of this cell. Rectangle cellBounds = new Rectangle(new Point(0, 0), GetSize(rowIndex)); // Finally compute the layout of the cell and return the resulting content bounds. ComputeLayout(graphics, cellBounds, cellBounds, rowIndex, cellState, null /*formattedValue*/, // contentBounds is independent of formattedValue null /*errorText*/, // contentBounds is independent of errorText cellStyle, dgvabsEffective, DataGridViewPaintParts.ContentForeground, false /*paint*/); return this.layout.ContentBounds; } /// /// Utility function that converts a constraintSize provided to GetPreferredSize into a /// DataGridViewRadioButtonFreeDimension enum value. /// private static DataGridViewRadioButtonFreeDimension GetFreeDimensionFromConstraint(Size constraintSize) { if (constraintSize.Width < 0 || constraintSize.Height < 0) { throw new ArgumentException("InvalidArgument=Value of '" + constraintSize.ToString() + "' is not valid for 'constraintSize'."); } if (constraintSize.Width == 0) { if (constraintSize.Height == 0) { return DataGridViewRadioButtonFreeDimension.Both; } else { return DataGridViewRadioButtonFreeDimension.Width; } } else { if (constraintSize.Height == 0) { return DataGridViewRadioButtonFreeDimension.Height; } else { throw new ArgumentException("InvalidArgument=Value of '" + constraintSize.ToString() + "' is not valid for 'constraintSize'."); } } } /// /// Utility function that returns the display value of an item given the /// display/value property descriptors and display/value property names. /// private object GetItemDisplayValue(object item) { Debug.Assert(item != null); bool displayValueSet = false; object displayValue = null; if (this.displayMemberProperty != null) { displayValue = this.displayMemberProperty.GetValue(item); displayValueSet = true; } else if (this.valueMemberProperty != null) { displayValue = this.valueMemberProperty.GetValue(item); displayValueSet = true; } else if (!string.IsNullOrEmpty(this.DisplayMember)) { PropertyDescriptor propDesc = TypeDescriptor.GetProperties(item).Find(this.DisplayMember, true /*caseInsensitive*/); if (propDesc != null) { displayValue = propDesc.GetValue(item); displayValueSet = true; } } else if (!string.IsNullOrEmpty(this.ValueMember)) { PropertyDescriptor propDesc = TypeDescriptor.GetProperties(item).Find(this.ValueMember, true /*caseInsensitive*/); if (propDesc != null) { displayValue = propDesc.GetValue(item); displayValueSet = true; } } if (!displayValueSet) { displayValue = item; } return displayValue; } /// /// Utility function that returns the value of an item given the /// display/value property descriptors and display/value property names. /// private object GetItemValue(object item) { bool valueSet = false; object value = null; if (this.valueMemberProperty != null) { value = this.valueMemberProperty.GetValue(item); valueSet = true; } else if (this.displayMemberProperty != null) { value = this.displayMemberProperty.GetValue(item); valueSet = true; } else if (!string.IsNullOrEmpty(this.ValueMember)) { PropertyDescriptor propDesc = TypeDescriptor.GetProperties(item).Find(this.ValueMember, true /*caseInsensitive*/); if (propDesc != null) { value = propDesc.GetValue(item); valueSet = true; } } if (!valueSet && !string.IsNullOrEmpty(this.DisplayMember)) { PropertyDescriptor propDesc = TypeDescriptor.GetProperties(item).Find(this.DisplayMember, true /*caseInsensitive*/); if (propDesc != null) { value = propDesc.GetValue(item); valueSet = true; } } if (!valueSet) { value = item; } return value; } /// /// Returns the code identifying the part of the cell which is underneath the mouse pointer. /// private int GetMouseLocationCode(Graphics graphics, int rowIndex, DataGridViewCellStyle cellStyle, int mouseX, int mouseY) { // First determine this cell's effective border style. bool singleVerticalBorderAdded = !this.DataGridView.RowHeadersVisible && this.DataGridView.AdvancedCellBorderStyle.All == DataGridViewAdvancedCellBorderStyle.Single; bool singleHorizontalBorderAdded = !this.DataGridView.ColumnHeadersVisible && this.DataGridView.AdvancedCellBorderStyle.All == DataGridViewAdvancedCellBorderStyle.Single; bool isFirstDisplayedColumn = this.ColumnIndex == this.DataGridView.Columns.GetFirstColumn(DataGridViewElementStates.Displayed).Index; bool isFirstDisplayedRow = rowIndex == this.DataGridView.Rows.GetFirstRow(DataGridViewElementStates.Displayed); DataGridViewAdvancedBorderStyle dataGridViewAdvancedBorderStylePlaceholder = new DataGridViewAdvancedBorderStyle(), dataGridViewAdvancedBorderStyleEffective; dataGridViewAdvancedBorderStyleEffective = AdjustCellBorderStyle(this.DataGridView.AdvancedCellBorderStyle, dataGridViewAdvancedBorderStylePlaceholder, singleVerticalBorderAdded, singleHorizontalBorderAdded, isFirstDisplayedColumn, isFirstDisplayedRow); // Then its size. Rectangle cellBounds = this.DataGridView.GetCellDisplayRectangle(this.ColumnIndex, rowIndex, false /*cutOverflow*/); Debug.Assert(GetSize(rowIndex) == cellBounds.Size); // Recompute the layout of the cell. ComputeLayout(graphics, cellBounds, cellBounds, rowIndex, DataGridViewElementStates.None, null /*formattedValue*/, null /*errorText*/, cellStyle, dataGridViewAdvancedBorderStyleEffective, DataGridViewPaintParts.None, false /*paint*/); // Deduce the cell part beneath the mouse pointer. Point mousePosition = this.DataGridView.PointToClient(Control.MousePosition); Rectangle rect; if (this.layout.ScrollingNeeded) { // Is the mouse over the bottom scroll button? rect = new Rectangle(this.layout.DownButtonLocation, this.layout.ScrollButtonsSize); if (rect.Contains(mousePosition)) { return DATAGRIDVIEWRADIOBUTTONCELL_mouseLocationBottomScrollButton; } // Is the mouse over the upper scroll button? rect = new Rectangle(this.layout.UpButtonLocation, this.layout.ScrollButtonsSize); if (rect.Contains(mousePosition)) { return DATAGRIDVIEWRADIOBUTTONCELL_mouseLocationTopScrollButton; } } if (this.layout.DisplayedItemsCount > 0) { Point radioButtonLocation = this.layout.FirstDisplayedItemLocation; int textHeight = cellStyle.Font.Height; int itemIndex = this.layout.FirstDisplayedItemIndex; Rectangle radioButtonBounds = new Rectangle(radioButtonLocation, this.layout.RadioButtonsSize); while (itemIndex < this.Items.Count && itemIndex < this.layout.FirstDisplayedItemIndex + this.maxDisplayedItems && itemIndex - this.layout.FirstDisplayedItemIndex < this.layout.DisplayedItemsCount) { if (radioButtonBounds.Contains(mousePosition)) { // The mouse is over a radio button return itemIndex - this.layout.FirstDisplayedItemIndex; } itemIndex++; radioButtonBounds.Y += textHeight + DATAGRIDVIEWRADIOBUTTONCELL_margin; } } return DATAGRIDVIEWRADIOBUTTONCELL_mouseLocationGeneric; } /// /// Returns a ScrollBarArrowButtonState state given the current mouse location. /// private ScrollBarArrowButtonState GetScrollBarArrowButtonState(bool upButton, int mouseLocationCode, bool enabled) { if (!enabled) { if (upButton) { return ScrollBarArrowButtonState.UpDisabled; } else { return ScrollBarArrowButtonState.DownDisabled; } } if (mouseLocationCode == DATAGRIDVIEWRADIOBUTTONCELL_mouseLocationTopScrollButton) { // Mouse is over upper button if (Control.MouseButtons == MouseButtons.Left) { if (upButton) { return ScrollBarArrowButtonState.UpPressed; } else { return ScrollBarArrowButtonState.DownNormal; } } else { if (upButton) { return ScrollBarArrowButtonState.UpHot; } else { return ScrollBarArrowButtonState.DownNormal; } } } else if (mouseLocationCode == DATAGRIDVIEWRADIOBUTTONCELL_mouseLocationBottomScrollButton) { // Mouse is over bottom button if (Control.MouseButtons == MouseButtons.Left) { if (upButton) { return ScrollBarArrowButtonState.UpNormal; } else { return ScrollBarArrowButtonState.DownPressed; } } else { if (upButton) { return ScrollBarArrowButtonState.UpNormal; } else { return ScrollBarArrowButtonState.DownHot; } } } else if (upButton) { return ScrollBarArrowButtonState.UpNormal; } else { return ScrollBarArrowButtonState.DownNormal; } } /// /// Custom implementation of the GetPreferredSize method. /// protected override Size GetPreferredSize(Graphics graphics, DataGridViewCellStyle cellStyle, int rowIndex, Size constraintSize) { if (this.DataGridView == null) { return new Size(-1, -1); } DataGridViewRadioButtonFreeDimension freeDimension = DataGridViewRadioButtonCell.GetFreeDimensionFromConstraint(constraintSize); Rectangle borderWidthsRect = this.StandardBorderWidths; int borderAndPaddingWidths = borderWidthsRect.Left + borderWidthsRect.Width + cellStyle.Padding.Horizontal; int borderAndPaddingHeights = borderWidthsRect.Top + borderWidthsRect.Height + cellStyle.Padding.Vertical; int preferredHeight = 0, preferredWidth = 0; // Assuming here that all radio button states use the same size. Size radioButtonGlyphSize = RadioButtonRenderer.GetGlyphSize(graphics, RadioButtonState.CheckedNormal); if (freeDimension != DataGridViewRadioButtonFreeDimension.Width) { preferredHeight = Math.Min(this.Items.Count, this.MaxDisplayedItems) * (Math.Max(cellStyle.Font.Height, radioButtonGlyphSize.Height) + DATAGRIDVIEWRADIOBUTTONCELL_margin) + DATAGRIDVIEWRADIOBUTTONCELL_margin; preferredHeight += 2 * DATAGRIDVIEWRADIOBUTTONCELL_margin + borderAndPaddingHeights; } if (freeDimension != DataGridViewRadioButtonFreeDimension.Height) { TextFormatFlags flags = TextFormatFlags.Top | TextFormatFlags.Left | TextFormatFlags.SingleLine | TextFormatFlags.EndEllipsis | TextFormatFlags.PreserveGraphicsClipping | TextFormatFlags.NoPrefix; if (this.Items.Count > 0) { // Figure out the width of the longest entry int maxPreferredItemWidth = -1, preferredItemWidth; foreach (object item in this.Items) { string formattedValue = GetFormattedValue(GetItemValue(item), rowIndex, ref cellStyle, null, null, DataGridViewDataErrorContexts.Formatting | DataGridViewDataErrorContexts.PreferredSize) as string; if (formattedValue != null) { preferredItemWidth = DataGridViewCell.MeasureTextSize(graphics, formattedValue, cellStyle.Font, flags).Width; } else { preferredItemWidth = DataGridViewCell.MeasureTextSize(graphics, " ", cellStyle.Font, flags).Width; } if (preferredItemWidth > maxPreferredItemWidth) { maxPreferredItemWidth = preferredItemWidth; } } preferredWidth = maxPreferredItemWidth; } if (freeDimension == DataGridViewRadioButtonFreeDimension.Width) { Size contentSize = new Size(Int32.MaxValue, constraintSize.Height - borderAndPaddingHeights); if (GetScrollingNeeded(graphics, rowIndex, cellStyle, contentSize)) { // Accommodate the scrolling buttons preferredWidth += ScrollBarRenderer.GetSizeBoxSize(graphics, ScrollBarState.Normal).Width; } } preferredWidth += radioButtonGlyphSize.Width + 5 * DATAGRIDVIEWRADIOBUTTONCELL_margin + borderAndPaddingWidths; } if (this.DataGridView.ShowCellErrors) { // Making sure that there is enough room for the potential error icon if (freeDimension != DataGridViewRadioButtonFreeDimension.Height) { preferredWidth = Math.Max(preferredWidth, borderAndPaddingWidths + DATAGRIDVIEWRADIOBUTTONCELL_iconMarginWidth * 2 + DATAGRIDVIEWRADIOBUTTONCELL_iconsWidth); } if (freeDimension != DataGridViewRadioButtonFreeDimension.Width) { preferredHeight = Math.Max(preferredHeight, borderAndPaddingHeights + DATAGRIDVIEWRADIOBUTTONCELL_iconMarginHeight * 2 + DATAGRIDVIEWRADIOBUTTONCELL_iconsHeight); } } return new Size(preferredWidth, preferredHeight); } /// /// Helper function that determines if scrolling buttons should be displayed /// private bool GetScrollingNeeded(Graphics graphics, int rowIndex, DataGridViewCellStyle cellStyle, Size contentSize) { if (this.Items.Count <= 1) { return false; } if (this.MaxDisplayedItems >= this.Items.Count && this.Items.Count * (cellStyle.Font.Height + DATAGRIDVIEWRADIOBUTTONCELL_margin) + DATAGRIDVIEWRADIOBUTTONCELL_margin <= contentSize.Height /*- borderAndPaddingHeights*/) { // There is enough vertical room to display all the radio buttons return false; } // Is there enough room to display the scroll buttons? //Size sizeBoxSize = ScrollBarRenderer.GetSizeBoxSize(graphics, ScrollBarState.Normal); Size sizeBoxSize = sizeBoxSize = new System.Drawing.Size(20, 20); if (2 * DATAGRIDVIEWRADIOBUTTONCELL_margin + sizeBoxSize.Width > contentSize.Width || 2 * sizeBoxSize.Height > contentSize.Height) { // There isn't enough room to show the scroll buttons. return false; } // Scroll buttons are required and there is enough room for them. return true; } /// /// Helper function that sets the displayMemberProperty member based on the DataSource and the provided displayMember field name /// private void InitializeDisplayMemberPropertyDescriptor(string displayMember) { if (this.DataManager != null) { if (String.IsNullOrEmpty(displayMember)) { this.displayMemberProperty = null; } else { BindingMemberInfo displayBindingMember = new BindingMemberInfo(displayMember); // make the DataManager point to the sublist inside this.DataSource this.DataManager = this.DataGridView.BindingContext[this.DataSource, displayBindingMember.BindingPath] as CurrencyManager; PropertyDescriptorCollection props = this.DataManager.GetItemProperties(); PropertyDescriptor displayMemberProperty = props.Find(displayBindingMember.BindingField, true); if (displayMemberProperty == null) { throw new ArgumentException("Field called '" + displayMember + "' does not exist."); } else { this.displayMemberProperty = displayMemberProperty; } } } } /// /// Helper function that sets the valueMemberProperty member based on the DataSource and the provided valueMember field name /// private void InitializeValueMemberPropertyDescriptor(string valueMember) { if (this.DataManager != null) { if (String.IsNullOrEmpty(valueMember)) { this.valueMemberProperty = null; } else { BindingMemberInfo valueBindingMember = new BindingMemberInfo(valueMember); // make the DataManager point to the sublist inside this.DataSource this.DataManager = this.DataGridView.BindingContext[this.DataSource, valueBindingMember.BindingPath] as CurrencyManager; PropertyDescriptorCollection props = this.DataManager.GetItemProperties(); PropertyDescriptor valueMemberProperty = props.Find(valueBindingMember.BindingField, true); if (valueMemberProperty == null) { throw new ArgumentException("Field called '" + valueMember + "' does not exist."); } else { this.valueMemberProperty = valueMemberProperty; } } } } /// /// Helper function that invalidates the entire area of an entry /// private void InvalidateItem(int itemIndex, int rowIndex) { if (itemIndex >= this.layout.FirstDisplayedItemIndex && itemIndex < this.layout.FirstDisplayedItemIndex + this.layout.DisplayedItemsCount) { DataGridViewCellStyle cellStyle = GetInheritedStyle(null, rowIndex, false /* includeColors */); Point radioButtonLocation = this.layout.FirstDisplayedItemLocation; int textHeight = cellStyle.Font.Height; radioButtonLocation.Y += (textHeight + DATAGRIDVIEWRADIOBUTTONCELL_margin) * (itemIndex - this.layout.FirstDisplayedItemIndex); Size cellSize = GetSize(rowIndex); this.DataGridView.Invalidate(new Rectangle(radioButtonLocation.X, radioButtonLocation.Y, cellSize.Width, Math.Max(textHeight + DATAGRIDVIEWRADIOBUTTONCELL_margin, this.layout.RadioButtonsSize.Height))); } } /// /// Helper function that invalidates the glyph section of an entry /// private void InvalidateRadioGlyph(int itemIndex, DataGridViewCellStyle cellStyle) { if (itemIndex >= this.layout.FirstDisplayedItemIndex && itemIndex < this.layout.FirstDisplayedItemIndex + this.layout.DisplayedItemsCount) { Point radioButtonLocation = this.layout.FirstDisplayedItemLocation; int textHeight = cellStyle.Font.Height; radioButtonLocation.Y += (textHeight + DATAGRIDVIEWRADIOBUTTONCELL_margin) * (itemIndex - this.layout.FirstDisplayedItemIndex); this.DataGridView.Invalidate(new Rectangle(radioButtonLocation, this.layout.RadioButtonsSize)); } } /// /// Returns whether calling the OnKeyDown method would force the owning row to be unshared. /// protected override bool KeyDownUnsharesRow(KeyEventArgs e, int rowIndex) { if (!e.Alt && !e.Control && !e.Shift) { if (this.handledKeyDown) { return true; } if (e.KeyCode == Keys.Down && this.focusedItemIndex < this.Items.Count - 1) { return true; } else if (e.KeyCode == Keys.Up && this.focusedItemIndex > 0) { return true; } } return false; } /// /// Returns whether calling the OnKeyUp method would force the owning row to be unshared. /// protected override bool KeyUpUnsharesRow(KeyEventArgs e, int rowIndex) { if (!e.Alt && !e.Control && !e.Shift) { if (e.KeyCode == Keys.Down && this.focusedItemIndex < this.Items.Count - 1 && this.handledKeyDown) { return true; } else if (e.KeyCode == Keys.Up && this.focusedItemIndex > 0 && this.handledKeyDown) { return true; } } if (this.handledKeyDown) { return true; } return false; } /// /// Returns whether calling the OnMouseDown method would force the owning row to be unshared. /// protected override bool MouseDownUnsharesRow(DataGridViewCellMouseEventArgs e) { if (this.DataGridView == null) { return false; } if (e.Button == MouseButtons.Left) { int mouseLocationCode = GetMouseLocationCode(this.DataGridView.CreateGraphics(), e.RowIndex, GetInheritedStyle(null, e.RowIndex, false /* includeColors */), e.X, e.Y); switch (mouseLocationCode) { case DATAGRIDVIEWRADIOBUTTONCELL_mouseLocationGeneric: break; case DATAGRIDVIEWRADIOBUTTONCELL_mouseLocationBottomScrollButton: if (this.layout.FirstDisplayedItemIndex + this.layout.DisplayedItemsCount < this.Items.Count) { return true; } break; case DATAGRIDVIEWRADIOBUTTONCELL_mouseLocationTopScrollButton: if (this.layout.FirstDisplayedItemIndex > 0) { return true; } break; default: if (this.pressedItemIndex != mouseLocationCode + this.layout.FirstDisplayedItemIndex) { return true; } break; } } return false; } /// /// Returns whether calling the OnMouseLeave method would force the owning row to be unshared. /// protected override bool MouseLeaveUnsharesRow(int rowIndex) { return this.pressedItemIndex != -1 && !this.mouseUpHooked; } /// /// Returns whether calling the OnMouseUp method would force the owning row to be unshared. /// protected override bool MouseUpUnsharesRow(DataGridViewCellMouseEventArgs e) { return e.Button == MouseButtons.Left && this.pressedItemIndex != -1; } /// /// Method that declares the cell dirty and notifies the grid of the value change. /// private void NotifyDataGridViewOfValueChange() { this.valueChanged = true; Debug.Assert(this.DataGridView != null); this.DataGridView.NotifyCurrentCellDirty(true); } /// /// Potentially updates the selected item and notifies the grid of the change. /// protected override void OnContentClick(DataGridViewCellEventArgs e) { if (this.DataGridView == null) { return; } Point ptCurrentCell = this.DataGridView.CurrentCellAddress; if (ptCurrentCell.X == this.ColumnIndex && ptCurrentCell.Y == e.RowIndex && this.DataGridView.IsCurrentCellInEditMode) { if (mouseLocationCode >= 0 && UpdateFormattedValue(this.layout.FirstDisplayedItemIndex + mouseLocationCode, e.RowIndex)) { NotifyDataGridViewOfValueChange(); } } } /// /// Updates the property descriptors when the cell gets attached to the grid. /// protected override void OnDataGridViewChanged() { if (this.DataGridView != null) { // Will throw an error if DataGridView is set and a member is invalid InitializeDisplayMemberPropertyDescriptor(this.DisplayMember); InitializeValueMemberPropertyDescriptor(this.ValueMember); } base.OnDataGridViewChanged(); } /// /// Makes sure that there is a focused item when the cell becomes the current one. /// protected override void OnEnter(int rowIndex, bool throughMouseClick) { if (this.focusedItemIndex == -1) { this.focusedItemIndex = this.layout.FirstDisplayedItemIndex; } base.OnEnter(rowIndex, throughMouseClick); } /// /// Handles the KeyDown notification when it can result in a value change. /// protected override void OnKeyDown(KeyEventArgs e, int rowIndex) { if (this.DataGridView == null) { return; } if (!e.Alt && !e.Control && !e.Shift) { if (this.handledKeyDown) { this.handledKeyDown = false; } if (e.KeyCode == Keys.Down && this.focusedItemIndex < this.Items.Count - 1) { this.handledKeyDown = true; e.Handled = true; } else if (e.KeyCode == Keys.Up && this.focusedItemIndex > 0) { this.handledKeyDown = true; e.Handled = true; } } } /// /// Handles the KeyUp notification to change the cell's value. /// protected override void OnKeyUp(KeyEventArgs e, int rowIndex) { if (this.DataGridView == null) { return; } if (!e.Alt && !e.Control && !e.Shift && this.handledKeyDown) { if (e.KeyCode == Keys.Down && this.focusedItemIndex < this.Items.Count - 1) { // Handle the Down Arrow key if (UpdateFormattedValue(this.focusedItemIndex+1, rowIndex)) { NotifyDataGridViewOfValueChange(); } else if (this.selectedItemIndex == this.focusedItemIndex+1) { this.focusedItemIndex++; } if (this.focusedItemIndex >= this.layout.FirstDisplayedItemIndex + this.layout.TotallyDisplayedItemsCount) { this.layout.FirstDisplayedItemIndex++; } while (this.focusedItemIndex < this.layout.FirstDisplayedItemIndex) { this.layout.FirstDisplayedItemIndex--; } this.DataGridView.InvalidateCell(this.ColumnIndex, rowIndex); e.Handled = true; } else if (e.KeyCode == Keys.Up && this.focusedItemIndex > 0) { // Handle the Up Arrow key if (UpdateFormattedValue(this.focusedItemIndex - 1, rowIndex)) { NotifyDataGridViewOfValueChange(); } else if (this.selectedItemIndex == this.focusedItemIndex - 1) { this.focusedItemIndex--; } if (this.focusedItemIndex < this.layout.FirstDisplayedItemIndex) { this.layout.FirstDisplayedItemIndex--; } while (this.focusedItemIndex >= this.layout.FirstDisplayedItemIndex + this.layout.TotallyDisplayedItemsCount) { this.layout.FirstDisplayedItemIndex++; } this.DataGridView.InvalidateCell(this.ColumnIndex, rowIndex); e.Handled = true; } } // Always reset the flag that indicates if the KeyDown was handled. if (this.handledKeyDown) { this.handledKeyDown = false; } } /// /// Custom implementation of the MouseDown notification to update the cell's value or scroll the entries. /// protected override void OnMouseDown(DataGridViewCellMouseEventArgs e) { if (this.DataGridView == null) { return; } if (e.Button == MouseButtons.Left) { int mouseLocationCode = GetMouseLocationCode(this.DataGridView.CreateGraphics(), e.RowIndex, GetInheritedStyle(null, e.RowIndex, false /* includeColors */), e.X, e.Y); switch (mouseLocationCode) { case DATAGRIDVIEWRADIOBUTTONCELL_mouseLocationGeneric: break; case DATAGRIDVIEWRADIOBUTTONCELL_mouseLocationBottomScrollButton: if (this.layout.FirstDisplayedItemIndex + this.layout.TotallyDisplayedItemsCount < this.Items.Count) { // Scroll the entries down. this.layout.FirstDisplayedItemIndex++; this.DataGridView.Invalidate(new Rectangle(this.layout.DownButtonLocation, this.layout.ScrollButtonsSize)); } break; case DATAGRIDVIEWRADIOBUTTONCELL_mouseLocationTopScrollButton: if (this.layout.FirstDisplayedItemIndex > 0) { // Scroll the entries up. this.layout.FirstDisplayedItemIndex--; this.DataGridView.Invalidate(new Rectangle(this.layout.UpButtonLocation, this.layout.ScrollButtonsSize)); } break; default: if (this.pressedItemIndex != mouseLocationCode + this.layout.FirstDisplayedItemIndex) { // Update the value of the cell. InvalidateItem(this.pressedItemIndex, e.RowIndex); this.pressedItemIndex = mouseLocationCode + this.layout.FirstDisplayedItemIndex; InvalidateItem(this.pressedItemIndex, e.RowIndex); } break; } } } /// /// Makes sure the radio button gets hot when the mouse gets over it /// protected override void OnMouseEnter(int rowIndex) { if (this.DataGridView == null) { return; } if (this.pressedItemIndex != -1) { InvalidateRadioGlyph(this.pressedItemIndex, GetInheritedStyle(null, rowIndex, false /* includeColors */)); } } /// /// Invalidates part of the cell as needed /// protected override void OnMouseLeave(int rowIndex) { if (this.DataGridView == null) { return; } int oldMouseLocationCode = mouseLocationCode; if (oldMouseLocationCode != DATAGRIDVIEWRADIOBUTTONCELL_mouseLocationGeneric) { mouseLocationCode = DATAGRIDVIEWRADIOBUTTONCELL_mouseLocationGeneric; if (oldMouseLocationCode == DATAGRIDVIEWRADIOBUTTONCELL_mouseLocationTopScrollButton && this.layout.FirstDisplayedItemIndex > 0) { this.DataGridView.Invalidate(new Rectangle(this.layout.UpButtonLocation, this.layout.ScrollButtonsSize)); } else if (oldMouseLocationCode == DATAGRIDVIEWRADIOBUTTONCELL_mouseLocationBottomScrollButton && this.layout.FirstDisplayedItemIndex + this.layout.DisplayedItemsCount < this.Items.Count) { this.DataGridView.Invalidate(new Rectangle(this.layout.DownButtonLocation, this.layout.ScrollButtonsSize)); } else if (oldMouseLocationCode >= 0) { InvalidateRadioGlyph(oldMouseLocationCode + this.layout.FirstDisplayedItemIndex, GetInheritedStyle(null, rowIndex, false /* includeColors */)); } } if (this.pressedItemIndex != -1) { if (!this.mouseUpHooked) { // Hookup the grid's MouseUp event so that this.pressedItemIndex can be reset when the user releases the mouse button. this.DataGridView.MouseUp += new MouseEventHandler(DataGridView_MouseUp); this.mouseUpHooked = true; } InvalidateRadioGlyph(this.pressedItemIndex, GetInheritedStyle(null, rowIndex, false /* includeColors */)); } } /// /// Invalidates part of the cell as needed /// protected override void OnMouseMove(DataGridViewCellMouseEventArgs e) { if (this.DataGridView == null) { return; } DataGridViewCellStyle cellStyle = GetInheritedStyle(null, e.RowIndex, false /* includeColors */); int oldMouseLocationCode = mouseLocationCode; mouseLocationCode = GetMouseLocationCode(this.DataGridView.CreateGraphics(), e.RowIndex, cellStyle, e.X, e.Y); if (oldMouseLocationCode != mouseLocationCode) { if ((oldMouseLocationCode == DATAGRIDVIEWRADIOBUTTONCELL_mouseLocationTopScrollButton || mouseLocationCode == DATAGRIDVIEWRADIOBUTTONCELL_mouseLocationTopScrollButton) && this.layout.FirstDisplayedItemIndex > 0) { this.DataGridView.Invalidate(new Rectangle(this.layout.UpButtonLocation, this.layout.ScrollButtonsSize)); } else if ((oldMouseLocationCode == DATAGRIDVIEWRADIOBUTTONCELL_mouseLocationBottomScrollButton || mouseLocationCode == DATAGRIDVIEWRADIOBUTTONCELL_mouseLocationBottomScrollButton) && this.layout.FirstDisplayedItemIndex + this.layout.DisplayedItemsCount < this.Items.Count) { this.DataGridView.Invalidate(new Rectangle(this.layout.DownButtonLocation, this.layout.ScrollButtonsSize)); } else { if ((this.DataGridView.Rows.SharedRow(e.RowIndex).Cells[e.ColumnIndex].GetInheritedState(e.RowIndex) & DataGridViewElementStates.ReadOnly) != 0) { return; } if (oldMouseLocationCode >= 0) { InvalidateRadioGlyph(oldMouseLocationCode + this.layout.FirstDisplayedItemIndex, cellStyle); } if (mouseLocationCode >= 0) { InvalidateRadioGlyph(mouseLocationCode + this.layout.FirstDisplayedItemIndex, cellStyle); } } } } /// /// Invalidates the potential pressed radio button. /// protected override void OnMouseUp(DataGridViewCellMouseEventArgs e) { if (this.DataGridView == null) { return; } if (e.Button == MouseButtons.Left && this.pressedItemIndex != -1) { InvalidateItem(this.pressedItemIndex, e.RowIndex); this.pressedItemIndex = -1; } } /// /// Paints the entire cell. /// protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates cellState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts) { ComputeLayout(graphics, clipBounds, cellBounds, rowIndex, cellState, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts, true /*paint*/); } /// /// Paints a single item. /// private void PaintItem(Graphics graphics, Rectangle radiosBounds, int rowIndex, int itemIndex, DataGridViewCellStyle cellStyle, bool itemReadOnly, bool itemSelected, bool mouseOverCell, bool paintFocus) { object itemFormattedValue = GetFormattedValue(GetItemValue(this.Items[itemIndex]), rowIndex, ref cellStyle, null /*valueTypeConverter*/, null /*formattedValueTypeConverter*/, DataGridViewDataErrorContexts.Display); string itemFormattedText = itemFormattedValue as string; if (string.IsNullOrEmpty(itemFormattedText)) { return; } else { //Paint the glyph & caption Point glyphLocation = new Point(radiosBounds.Left + DATAGRIDVIEWRADIOBUTTONCELL_margin, radiosBounds.Top + DATAGRIDVIEWRADIOBUTTONCELL_margin); //TextFormatFlags flags = TextFormatFlags.Top | TextFormatFlags.Left | TextFormatFlags.SingleLine | TextFormatFlags.EndEllipsis | TextFormatFlags.PreserveGraphicsClipping | TextFormatFlags.NoPrefix; TextFormatFlags flags = TextFormatFlags.Top | TextFormatFlags.Left | TextFormatFlags.SingleLine | TextFormatFlags.EndEllipsis | TextFormatFlags.PreserveGraphicsClipping | TextFormatFlags.NoPrefix; Rectangle textBounds = new Rectangle(radiosBounds.Left + 2 * DATAGRIDVIEWRADIOBUTTONCELL_margin + this.layout.RadioButtonsSize.Width, radiosBounds.Top + DATAGRIDVIEWRADIOBUTTONCELL_margin, radiosBounds.Width - (2 * DATAGRIDVIEWRADIOBUTTONCELL_margin + this.layout.RadioButtonsSize.Width), cellStyle.Font.Height + 1 /*radiosBounds.Height - 2 * DATAGRIDVIEWRADIOBUTTONCELL_margin*/); int localMouseLocationCode = mouseOverCell ? mouseLocationCode : DATAGRIDVIEWRADIOBUTTONCELL_mouseLocationGeneric; using (Region clipRegion = graphics.Clip) { graphics.SetClip(radiosBounds); RadioButtonState radioButtonState; if (itemSelected) { if (itemReadOnly) { radioButtonState = RadioButtonState.CheckedDisabled; } else { if (mouseOverCell && this.pressedItemIndex == itemIndex) { if (localMouseLocationCode + this.layout.FirstDisplayedItemIndex == this.pressedItemIndex) { radioButtonState = RadioButtonState.CheckedPressed; } else { radioButtonState = RadioButtonState.CheckedHot; } } else { if (localMouseLocationCode + this.layout.FirstDisplayedItemIndex == itemIndex && this.pressedItemIndex == -1) { radioButtonState = RadioButtonState.CheckedHot; } else { radioButtonState = RadioButtonState.CheckedNormal; } } } } else { if (itemReadOnly) { radioButtonState = RadioButtonState.UncheckedDisabled; } else { if (mouseOverCell && this.pressedItemIndex == itemIndex) { if (localMouseLocationCode + this.layout.FirstDisplayedItemIndex == this.pressedItemIndex) { radioButtonState = RadioButtonState.UncheckedPressed; } else { radioButtonState = RadioButtonState.UncheckedHot; } } else { if (localMouseLocationCode + this.layout.FirstDisplayedItemIndex == itemIndex && this.pressedItemIndex == -1) { radioButtonState = RadioButtonState.UncheckedHot; } else { radioButtonState = RadioButtonState.UncheckedNormal; } } } } // Note: The cell should only show the focus rectangle when this.DataGridView.ShowFocusCues is true. However that property is // protected and can't be accessed directly. A custom grid derived from DataGridView could expose this notion publicly. RadioButtonRenderer.DrawRadioButton(graphics, glyphLocation, textBounds, itemFormattedText, cellStyle.Font, flags, paintFocus && /* this.DataGridView.ShowFocusCues && */ this.DataGridView.Focused, radioButtonState); graphics.Clip = clipRegion; } } } /// /// Helper function that indicates if a paintPart needs to be painted. /// private static bool PartPainted(DataGridViewPaintParts paintParts, DataGridViewPaintParts paintPart) { return (paintParts & paintPart) != 0; } /// /// Custom implementation that follows the standard representation of cell types. /// public override string ToString() { return "DataGridViewRadioButtonCell { ColumnIndex=" + this.ColumnIndex.ToString(CultureInfo.CurrentCulture) + ", RowIndex=" + this.RowIndex.ToString(CultureInfo.CurrentCulture) + " }"; } /// /// Returns true if the provided item successfully became the selected item. /// private bool UpdateFormattedValue(int newSelectedItemIndex, int rowIndex) { if (this.FormattedValueType == null || newSelectedItemIndex == this.selectedItemIndex) { return false; } IDataGridViewEditingCell editingCell = (IDataGridViewEditingCell)this; Debug.Assert(newSelectedItemIndex >= 0); Debug.Assert(newSelectedItemIndex < this.Items.Count); object item = this.Items[newSelectedItemIndex]; object displayValue = GetItemDisplayValue(item); if (this.FormattedValueType.IsAssignableFrom(displayValue.GetType())) { editingCell.EditingCellFormattedValue = displayValue; this.focusedItemIndex = this.selectedItemIndex; this.DataGridView.InvalidateCell(this.ColumnIndex, rowIndex); } return true; } } }