Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1908-V85-KDGV-ComboBoxColumn-updates #1998

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -37,7 +34,9 @@ public class KryptonDataGridViewComboBoxCell : DataGridViewTextBoxCell
private string _displayMember;
private string _valueMember;
private object? _dataSource;

private List<object> _items;
private string _selectedItemText;
private bool _initialSelectedTextSet;
#endregion

#region Identity
Expand All @@ -46,14 +45,9 @@ public class KryptonDataGridViewComboBoxCell : DataGridViewTextBoxCell
/// </summary>
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;
Expand Down Expand Up @@ -86,7 +80,7 @@ public override string ToString() =>

/// <summary>Gets the items in the combobox.</summary>
/// <value>The items.</value>
public ComboBox.ObjectCollection Items => _paintingComboBox.ComboBox.Items;
public List<object> Items => _items;

/// <summary>
/// Clones a DataGridViewComboBoxCell cell, copies all the custom properties.
Expand Down Expand Up @@ -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();
}
Expand All @@ -304,30 +303,28 @@ public override void DetachEditingControl()
/// set according to the cell properties.
/// </summary>
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();
Expand All @@ -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

/// <summary>
/// Custom implementation of the PositionEditingControl method called by the DataGridView control when it
/// needs to relocate and/or resize the editing control.
/// </summary>
public override void PositionEditingControl(bool setLocation,
bool setSize,
Rectangle cellBounds,
Rectangle cellClip,
DataGridViewCellStyle cellStyle,
bool singleVerticalBorderAdded,
bool singleHorizontalBorderAdded,
bool isFirstDisplayedColumn,
bool isFirstDisplayedRow)
#region Protected
///<inheritdoc/>
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
/// <summary>
/// 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.
Expand Down Expand Up @@ -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 })
Expand All @@ -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
Expand Down Expand Up @@ -558,6 +571,116 @@ internal void SetDataSource(int rowIndex, object value)
EditingComboBox.DataSource = value;
}
}

/// <summary>
/// Type casted version of OwningColumn
/// </summary>
internal KryptonDataGridViewComboBoxColumn? KryptonOwningColumn => OwningColumn as KryptonDataGridViewComboBoxColumn;

/// <summary>
/// Resets the inital selected item text.
/// </summary>
internal void ResetInitialSelectedItemText()
{
_selectedItemText = string.Empty;
_initialSelectedTextSet = false;
}

/// <summary>
/// Sets the initial item text fetch from the cell value.<br/>
/// 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.<br/>
/// 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.
/// </summary>
/// <param name="rowIndex"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
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
}
}
Expand Down
Loading