Skip to content

Commit

Permalink
Merge pull request #1998 from giduac/1908-V85-KDGV-ComboBoxColumn-upd…
Browse files Browse the repository at this point in the history
…ates

1908-V85-KDGV-ComboBoxColumn-updates
  • Loading branch information
PWagner1 authored Dec 26, 2024
2 parents a9cb2eb + dd252b7 commit 1964d65
Show file tree
Hide file tree
Showing 2 changed files with 243 additions and 85 deletions.
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

0 comments on commit 1964d65

Please sign in to comment.