diff --git a/Source/Krypton Components/Krypton.Toolkit/Controls Toolkit/KryptonDataGridViewComboBoxCell.cs b/Source/Krypton Components/Krypton.Toolkit/Controls Toolkit/KryptonDataGridViewComboBoxCell.cs index 8407bb1b0..6f30feba8 100644 --- a/Source/Krypton Components/Krypton.Toolkit/Controls Toolkit/KryptonDataGridViewComboBoxCell.cs +++ b/Source/Krypton Components/Krypton.Toolkit/Controls Toolkit/KryptonDataGridViewComboBoxCell.cs @@ -20,11 +20,8 @@ namespace Krypton.Toolkit public class KryptonDataGridViewComboBoxCell : DataGridViewTextBoxCell { #region Static Fields - [ThreadStatic] - private static KryptonComboBox _paintingComboBox; private static readonly Type _defaultEditType = typeof(KryptonDataGridViewComboBoxEditingControl); private static readonly Type _defaultValueType = typeof(string); - private static readonly Size _sizeLarge = new Size(10000, 10000); #endregion #region Instance Fields @@ -37,7 +34,9 @@ public class KryptonDataGridViewComboBoxCell : DataGridViewTextBoxCell private string _displayMember; private string _valueMember; private object? _dataSource; - + private List _items; + private string _selectedItemText; + private bool _initialSelectedTextSet; #endregion #region Identity @@ -46,14 +45,9 @@ public class KryptonDataGridViewComboBoxCell : DataGridViewTextBoxCell /// public KryptonDataGridViewComboBoxCell() { - // Create a thread specific KryptonComboBox control used for the painting of the non-edited cells - if (_paintingComboBox == null) - { - _paintingComboBox = new KryptonComboBox(); - _paintingComboBox.SetLayoutDisplayPadding(new Padding(0, 1, 1, 0)); - _paintingComboBox.StateCommon.ComboBox.Border.Width = 0; - _paintingComboBox.StateCommon.ComboBox.Border.Draw = InheritBool.False; - } + _items = []; + _selectedItemText = string.Empty; + _initialSelectedTextSet = false; _dropDownStyle = ComboBoxStyle.DropDown; _maxDropDownItems = 8; @@ -86,7 +80,7 @@ public override string ToString() => /// Gets the items in the combobox. /// The items. - public ComboBox.ObjectCollection Items => _paintingComboBox.ComboBox.Items; + public List Items => _items; /// /// Clones a DataGridViewComboBoxCell cell, copies all the custom properties. @@ -292,8 +286,13 @@ public override void DetachEditingControl() if (dataGridView.EditingControl is KryptonComboBox comboBox) { + _selectedItemText = comboBox.Text; comboBox.DataSource = null; } + else + { + _selectedItemText = string.Empty; + } base.DetachEditingControl(); } @@ -304,30 +303,28 @@ public override void DetachEditingControl() /// set according to the cell properties. /// public override void InitializeEditingControl(int rowIndex, - object initialFormattedValue, + object? initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle) { base.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle); - if (DataGridView.EditingControl is KryptonComboBox comboBox) + if (DataGridView!.EditingControl is KryptonComboBox comboBox) { - var comboColumn = OwningColumn as KryptonDataGridViewComboBoxColumn; - - if (comboColumn is not null && comboColumn.DataSource is null) + if (KryptonOwningColumn is not null && KryptonOwningColumn.DataSource is null) { - var strings = new object[comboColumn.Items.Count]; + var strings = new object[KryptonOwningColumn.Items.Count]; - for (var i = 0 ; i < strings.Length ; i++) + for (var i = 0; i < strings.Length; i++) { - strings[i] = comboColumn.Items[i]; + strings[i] = KryptonOwningColumn.Items[i]; } - + comboBox.Items.Clear(); comboBox.Items.AddRange(strings); - var autoAppend = new string[comboColumn.AutoCompleteCustomSource.Count]; - for (var j = 0 ; j < autoAppend.Length ; j++) + var autoAppend = new string[KryptonOwningColumn.AutoCompleteCustomSource.Count]; + for (var j = 0; j < autoAppend.Length; j++) { - autoAppend[j] = comboColumn.AutoCompleteCustomSource[j]; + autoAppend[j] = KryptonOwningColumn.AutoCompleteCustomSource[j]; } comboBox.AutoCompleteCustomSource.Clear(); @@ -342,45 +339,88 @@ public override void InitializeEditingControl(int rowIndex, comboBox.AutoCompleteMode = AutoCompleteMode; comboBox.DisplayMember = DisplayMember; comboBox.ValueMember = ValueMember; - comboBox.DataSource = comboColumn?.DataSource; + comboBox.DataSource = KryptonOwningColumn?.DataSource; - if (initialFormattedValue is not string initialFormattedValueStr) - { - comboBox.Text = string.Empty; - } - else + // Restore the state, if needed. + if (!(Value is DBNull || Value is null)) { - comboBox.Text = initialFormattedValueStr; + if (KryptonOwningColumn is not null + && KryptonOwningColumn.DataSource is not null + && ValueMember.Length > 0) + { + comboBox.SelectedValue = Value; + } + else if (comboBox.Items.Count > 0) + { + comboBox.SelectedIndex = comboBox.Items.IndexOf(Value.ToString()); + } } + + _selectedItemText = comboBox.Text; } } + #endregion - /// - /// Custom implementation of the PositionEditingControl method called by the DataGridView control when it - /// needs to relocate and/or resize the editing control. - /// - public override void PositionEditingControl(bool setLocation, - bool setSize, - Rectangle cellBounds, - Rectangle cellClip, - DataGridViewCellStyle cellStyle, - bool singleVerticalBorderAdded, - bool singleHorizontalBorderAdded, - bool isFirstDisplayedColumn, - bool isFirstDisplayedRow) + #region Protected + /// + 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) { - Rectangle editingControlBounds = PositionEditingPanel(cellBounds, cellClip, cellStyle, - singleVerticalBorderAdded, singleHorizontalBorderAdded, - isFirstDisplayedColumn, isFirstDisplayedRow); + if (!_initialSelectedTextSet) + { + _initialSelectedTextSet = SetInitialSelectedItemText(rowIndex); + }; - editingControlBounds = GetAdjustedEditingControlBounds(editingControlBounds, cellStyle); - DataGridView.EditingControl.Location = new Point(editingControlBounds.X, editingControlBounds.Y); - DataGridView.EditingControl.Size = new Size(editingControlBounds.Width, editingControlBounds.Height); - } + if (DataGridView is not null + && KryptonOwningColumn?.CellIndicatorImage is Image image) + { + int pos; + Rectangle textArea; + var righToLeft = DataGridView.RightToLeft == RightToLeft.Yes; - #endregion + if (righToLeft) + { + pos = cellBounds.Left; + + // The WinForms cell content always receives padding of one by default, custom padding is added tot that. + textArea = new Rectangle( + 1 + cellBounds.Left + cellStyle.Padding.Left + image.Width, + 1 + cellBounds.Top + cellStyle.Padding.Top, + cellBounds.Width - cellStyle.Padding.Left - cellStyle.Padding.Right - image.Width - 3, + cellBounds.Height - cellStyle.Padding.Top - cellStyle.Padding.Bottom - 2); + } + else + { + pos = cellBounds.Right - image.Width; + + // The WinForms cell content always receives padding of one by default, custom padding is added tot that. + textArea = new Rectangle( + 1 + cellBounds.Left + cellStyle.Padding.Left, + 1 + cellBounds.Top + cellStyle.Padding.Top, + cellBounds.Width - cellStyle.Padding.Left - cellStyle.Padding.Right - image.Width - 3, + cellBounds.Height - cellStyle.Padding.Top - cellStyle.Padding.Bottom - 2); + } + + // When the Krypton column is part of a WinForms DataGridView let the default paint routine paint the cell. + // Afterwards we paint the text and drop down image. + if (DataGridView is DataGridView) + { + base.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, null, string.Empty, errorText, cellStyle, advancedBorderStyle, paintParts); + } + + // Draw the drop down button, only if no ErrorText has been set. + // If the ErrorText is set, only the error icon is shown. Otherwise both are painted on the same spot. + if (ErrorText.Length == 0) + { + graphics.DrawImage(image, new Point(pos, textArea.Top)); + } + + // Cell display text + TextRenderer.DrawText(graphics, _selectedItemText, cellStyle.Font, textArea, cellStyle.ForeColor, + KryptonDataGridViewUtilities.ComputeTextFormatFlagsForCellStyleAlignment(righToLeft, cellStyle.Alignment, cellStyle.WrapMode)); + } + } - #region Protected /// /// Customized implementation of the GetErrorIconBounds function in order to draw the potential /// error icon next to the up/down buttons and not on top of them. @@ -428,31 +468,6 @@ protected override Size GetPreferredSize(Graphics graphics, DataGridViewCellStyl private KryptonDataGridViewComboBoxEditingControl EditingComboBox => DataGridView.EditingControl as KryptonDataGridViewComboBoxEditingControl; - private static Rectangle GetAdjustedEditingControlBounds(Rectangle editingControlBounds, - DataGridViewCellStyle cellStyle) - { - // Adjust the vertical location of the editing control: - var preferredHeight = _paintingComboBox.GetPreferredSize(_sizeLarge).Height + 2; - if (preferredHeight < editingControlBounds.Height) - { - switch (cellStyle.Alignment) - { - case DataGridViewContentAlignment.MiddleLeft: - case DataGridViewContentAlignment.MiddleCenter: - case DataGridViewContentAlignment.MiddleRight: - editingControlBounds.Y += (editingControlBounds.Height - preferredHeight) / 2; - break; - case DataGridViewContentAlignment.BottomLeft: - case DataGridViewContentAlignment.BottomCenter: - case DataGridViewContentAlignment.BottomRight: - editingControlBounds.Y += editingControlBounds.Height - preferredHeight; - break; - } - } - - return editingControlBounds; - } - private void OnCommonChange() { if (DataGridView is { IsDisposed: false, Disposing: false }) @@ -473,8 +488,6 @@ private bool OwnsEditingComboBox(int rowIndex) => && DataGridView is { EditingControl: KryptonDataGridViewComboBoxEditingControl control } && (rowIndex == ((IDataGridViewEditingControl)control).EditingControlRowIndex); - private static bool PartPainted(DataGridViewPaintParts paintParts, DataGridViewPaintParts paintPart) => (paintParts & paintPart) != 0; - #endregion #region Internal @@ -558,6 +571,116 @@ internal void SetDataSource(int rowIndex, object value) EditingComboBox.DataSource = value; } } + + /// + /// Type casted version of OwningColumn + /// + internal KryptonDataGridViewComboBoxColumn? KryptonOwningColumn => OwningColumn as KryptonDataGridViewComboBoxColumn; + + /// + /// Resets the inital selected item text. + /// + internal void ResetInitialSelectedItemText() + { + _selectedItemText = string.Empty; + _initialSelectedTextSet = false; + } + + /// + /// Sets the initial item text fetch from the cell value.
+ /// If the cell has a datasource connected this routine retrieves the value that should be shown in the cell. Which usually is the DisplayMember value.
+ /// If the items for the combo are supplied via the Items property, the cell value is checked to exist in the list for input consistency. + ///
+ /// + /// + /// + internal bool SetInitialSelectedItemText(int rowIndex) + { + var value = GetValue(rowIndex); + bool result = true; + var dataSource = KryptonOwningColumn?.DataSource; + + // Documentation describing the behaviour for DisplayMember and ValueMember + // https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.datagridviewcomboboxcolumn?view=windowsdesktop-9.0 + + if (value is not (DBNull or null) + && dataSource is not null + && ValueMember.Length > 0) + { + BindingMemberInfo bindingMemberInfo = new(ValueMember); + + if (DataGridView is not null + && DataGridView.BindingContext is not null + && DataGridView.BindingContext[dataSource, bindingMemberInfo.BindingPath] is CurrencyManager currencyManager + && currencyManager.List is IBindingList bindinglist + && bindinglist.SupportsSearching) + { + if (currencyManager.GetItemProperties().Find(bindingMemberInfo.BindingField, true) is PropertyDescriptor propertyDescriptor) + { + // Define the field used for displaymember. + // If DisplayMember is not set, Valuemember will be used for both. + var displayMember = DisplayMember.Length > 0 + ? DisplayMember + : ValueMember; + + // Find the index of the row that contains propertyDescriptor having value. + // The search stops on the first occurrence. + int index = bindinglist.Find(propertyDescriptor, value); + + if (index != -1 + && currencyManager.List[index] is System.Data.DataRowView dataRowView) + { + try + { + _selectedItemText = dataRowView[displayMember].ToString() ?? string.Empty; + } + catch + { + // Member is an unknow column + throw new ArgumentException($"The field '{displayMember}' specified as '{(DisplayMember.Length > 0 ? "DisplayMember" : "ValueMember")}' has not been found in the data source."); + } + } + else + { + // The row containing the given value was not found. + // Meaning there's a value mismatch between the combobox datasource and the cell value. + throw new ArgumentException($"The property descriptor '{propertyDescriptor.DisplayName}' having value '{value}' was not found in the data source."); + } + } + else + { + throw new ArgumentException($"The field '{ValueMember}' specified as ValueMember has not been found in the data source."); + } + } + else + { + _selectedItemText = string.Empty; + } + } + else if (value is not (DBNull or null) + && dataSource is null + && KryptonOwningColumn?.Items.Count > 0) + { + // DataSource is null and Items is populated + var valueStr = value.ToString(); + + if (valueStr is not null + && KryptonOwningColumn.Items.IndexOf(valueStr) == -1) + { + // The value was not found in the list + throw new ArgumentException($"The cell value {valueStr} was not found in the list with drop-down items."); + } + + _selectedItemText = valueStr ?? string.Empty; + } + else + { + _selectedItemText = value?.ToString() ?? string.Empty; + result = false; + } + + return result; + } #endregion } } diff --git a/Source/Krypton Components/Krypton.Toolkit/Controls Toolkit/KryptonDataGridViewComboBoxColumn.cs b/Source/Krypton Components/Krypton.Toolkit/Controls Toolkit/KryptonDataGridViewComboBoxColumn.cs index c9b3ca184..c8660909f 100644 --- a/Source/Krypton Components/Krypton.Toolkit/Controls Toolkit/KryptonDataGridViewComboBoxColumn.cs +++ b/Source/Krypton Components/Krypton.Toolkit/Controls Toolkit/KryptonDataGridViewComboBoxColumn.cs @@ -21,6 +21,11 @@ namespace Krypton.Toolkit [ToolboxBitmap(typeof(KryptonDataGridViewComboBoxColumn), "ToolboxBitmaps.KryptonComboBox.bmp")] public class KryptonDataGridViewComboBoxColumn : KryptonDataGridViewIconColumn { + #region Fields + // Cell indicator image instance + private KryptonDataGridViewCellIndicatorImage _kryptonDataGridViewCellIndicatorImage; + #endregion + #region Identity /// /// Initialize a new instance of the KryptonDataGridViewComboBoxColumn class. @@ -30,8 +35,15 @@ public KryptonDataGridViewComboBoxColumn() { Items = new List(); AutoCompleteCustomSource = new AutoCompleteStringCollection(); + _kryptonDataGridViewCellIndicatorImage = new(); } + /// + protected override void OnDataGridViewChanged() + { + _kryptonDataGridViewCellIndicatorImage.DataGridView = DataGridView as KryptonDataGridView; + base.OnDataGridViewChanged(); + } /// /// Returns a standard compact string representation of the column. /// @@ -399,13 +411,14 @@ public string DisplayMember if (DataGridView != null) { // Update all the existing KryptonDataGridViewComboBoxCell cells in the column accordingly. + DataGridViewRow dataGridViewRow; DataGridViewRowCollection dataGridViewRows = DataGridView.Rows; var rowCount = dataGridViewRows.Count; for (var rowIndex = 0; rowIndex < rowCount; rowIndex++) { // Be careful not to unshare rows unnecessarily. // This could have severe performance repercussions. - DataGridViewRow dataGridViewRow = dataGridViewRows.SharedRow(rowIndex); + dataGridViewRow = dataGridViewRows.SharedRow(rowIndex); if (dataGridViewRow.Cells[Index] is KryptonDataGridViewComboBoxCell dataGridViewCell) { dataGridViewCell.SetDisplayMember(rowIndex, value); @@ -444,13 +457,14 @@ public string ValueMember if (DataGridView != null) { // Update all the existing KryptonDataGridViewComboBoxCell cells in the column accordingly. + DataGridViewRow dataGridViewRow; DataGridViewRowCollection dataGridViewRows = DataGridView.Rows; var rowCount = dataGridViewRows.Count; for (var rowIndex = 0; rowIndex < rowCount; rowIndex++) { // Be careful not to unshare rows unnecessarily. // This could have severe performance repercussions. - DataGridViewRow dataGridViewRow = dataGridViewRows.SharedRow(rowIndex); + dataGridViewRow = dataGridViewRows.SharedRow(rowIndex); if (dataGridViewRow.Cells[Index] is KryptonDataGridViewComboBoxCell dataGridViewCell) { dataGridViewCell.SetValueMember(rowIndex, value); @@ -468,7 +482,7 @@ public string ValueMember [Description(@"Indicates the Datasource for the items in this control.")] [TypeConverter(@"System.Windows.Forms.Design.DataSourceConverter")] [Editor(@"System.Windows.Forms.Design.DataSourceListEditor", typeof(UITypeEditor))] - public object DataSource + public object? DataSource { get => @@ -485,6 +499,21 @@ public object DataSource // Update the template cell so that subsequent cloned cells use the new value. ComboBoxCellTemplate.DataSource = value; + + if (DataGridView is not null) + { + DataGridViewRow dataGridViewRow; + DataGridViewRowCollection dataGridViewRows = DataGridView.Rows; + int rowCount = dataGridViewRows.Count; + for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) + { + dataGridViewRow = dataGridViewRows.SharedRow(rowIndex); + if (dataGridViewRow.Cells[Index] is KryptonDataGridViewComboBoxCell dataGridViewCell) + { + dataGridViewCell.DataSource = value; + } + } + } } } #endregion @@ -494,8 +523,14 @@ public object DataSource /// Small utility function that returns the template cell as a KryptonDataGridViewComboBoxCell /// private KryptonDataGridViewComboBoxCell? ComboBoxCellTemplate => (KryptonDataGridViewComboBoxCell)CellTemplate; - #endregion + #region Internal + /// + /// Provides the cell indicator images to the cells from from this column instance.
+ /// For internal use only. + ///
+ internal Image? CellIndicatorImage => _kryptonDataGridViewCellIndicatorImage.Image; + #endregion Internal } } \ No newline at end of file