Skip to content

Commit aa8ff46

Browse files
committed
* DataGridView Grouper panel
* Implemented #590
1 parent ba8b89b commit aa8ff46

10 files changed

Lines changed: 2920 additions & 1608 deletions

Documents/Help/Changelog.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66

77
## 2026-11-xx - Build 2611 - November 2026
88

9+
* Implemented [#590](https://github.com/Krypton-Suite/Extended-Toolkit/issues/590), `DataGridView` Grouper panel
910
* Implemented [#494](https://github.com/Krypton-Suite/Extended-Toolkit/issues/494), Gantt Chart Control
1011
* Implemented [#544](https://github.com/Krypton-Suite/Extended-Toolkit/issues/544), Implement a `DropZone` component
1112
* Resolved [#351](https://github.com/Krypton-Suite/Extended-Toolkit/issues/351), Closing a floating toolbar window causes the toolbar to disappear - Closing the floating toolbar or menustrip window (e.g. via the close button) now returns the control to its original host instead of making it disappear. Updated `FloatableToolStrip` and `FloatableMenuStrip` to re-parent the control back to the host in the FormClosing handler, consistent with double-clicking the title bar.
1213
* Implemented [#57](https://github.com/Krypton-Suite/Extended-Toolkit/issues/57), `Alert.ShowMessage` optional header/title
1314
- **New API** - `Alert.ShowMessage(string message, string? headerText = null)` shows a message with an optional header or title
14-
- When `headerText` is provided, the alert displays the header in bold with the message below; when omitted, only the message is shown (same behaviour as existing typed methods)
15+
- When `headerText` is provided, the alert displays the header in bold with the message below; when omitted, only the message is shown (same behavior as existing typed methods)
1516
* Resolved [#56](https://github.com/Krypton-Suite/Extended-Toolkit/issues/56), Alert.ShowMessage should be within the bounds of the parent application
1617
- **Positioning Fix** - Alerts now appear within the parent application bounds instead of the primary screen bottom-right (fixes unnoticed alerts on 4K monitors or RDP sessions when the app is in a small window)
1718
- **Optional Owner Parameter** - All `Alert` methods now accept an optional `IWin32Window? owner` parameter for explicit parent binding

Source/Krypton Toolkit/Krypton Toolkit Suite Extended 2022 - VS2022 - NuGet.sln

Lines changed: 1307 additions & 1216 deletions
Large diffs are not rendered by default.

Source/Krypton Toolkit/Krypton Toolkit Suite Extended 2022 - VS2022.sln

Lines changed: 48 additions & 391 deletions
Large diffs are not rendered by default.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#region MIT License
2+
/*
3+
* MIT License
4+
*
5+
* Copyright (c) 2026 - 2026 Krypton Suite
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy
8+
* of this software and associated documentation files (the "Software"), to deal
9+
* in the Software without restriction, including without limitation the rights
10+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the Software is
12+
* furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in all
15+
* copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
* SOFTWARE.
24+
*/
25+
#endregion
26+
27+
using System.Drawing;
28+
29+
namespace Krypton.Toolkit.Suite.Extended.Grid.Grouper;
30+
31+
/// <summary>
32+
/// Visual and behavioural options for grouped grid rows.
33+
/// </summary>
34+
public sealed class DataGridViewGrouperOptions
35+
{
36+
/// <summary>When grouping is applied, new group nodes expand or collapse initially.</summary>
37+
public bool StartCollapsed { get; set; }
38+
39+
/// <summary>Include counts in grouped header captions such as Region: North (4).</summary>
40+
public bool IncludeChildCountInHeader { get; set; } = true;
41+
42+
/// <summary>If true and the source is omitted for a caption cell, repeats the grouped value in bound columns.</summary>
43+
public bool PromotePrimaryGroupValueIntoFirstColumn { get; set; } = true;
44+
45+
/// <summary>Pixel height applied to synthesized group rows (data rows leave grid default).</summary>
46+
public int GroupRowHeight { get; set; } = 28;
47+
48+
/// <summary>Font delta for group captions (applied as style adjustment over inherited font).</summary>
49+
public FontStyle GroupHeaderFontStyle { get; set; } = FontStyle.Bold;
50+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#region MIT License
2+
/*
3+
* MIT License
4+
*
5+
* Copyright (c) 2026 - 2026 Krypton Suite
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy
8+
* of this software and associated documentation files (the "Software"), to deal
9+
* in the Software without restriction, including without limitation the rights
10+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the Software is
12+
* furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in all
15+
* copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
* SOFTWARE.
24+
*/
25+
#endregion
26+
27+
namespace Krypton.Toolkit.Suite.Extended.Grid.Grouper;
28+
29+
internal static class DataGridViewGrouperSchema
30+
{
31+
public const string IsGroup = "__KG_IsGroup";
32+
public const string GroupLevel = "__KG_GroupLevel";
33+
public const string GroupPath = "__KG_GroupPath";
34+
public const string SummaryText = "__KG_SummaryText";
35+
}
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
#region MIT License
2+
/*
3+
* MIT License
4+
*
5+
* Copyright (c) 2026 - 2026 Krypton Suite
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy
8+
* of this software and associated documentation files (the "Software"), to deal
9+
* in the Software without restriction, including without limitation the rights
10+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the Software is
12+
* furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in all
15+
* copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
* SOFTWARE.
24+
*/
25+
#endregion
26+
27+
using System;
28+
using System.Collections;
29+
using System.Collections.Generic;
30+
using System.ComponentModel;
31+
using System.Data;
32+
using System.Linq;
33+
using System.Windows.Forms;
34+
35+
namespace Krypton.Toolkit.Suite.Extended.Grid.Grouper;
36+
37+
internal static class GroupedDataSourceResolver
38+
{
39+
public static bool TryResolve(object? dataSource, string? dataMember, out GroupedResolvedSource resolved)
40+
{
41+
resolved = default;
42+
43+
switch (dataSource)
44+
{
45+
case null:
46+
return false;
47+
48+
case DataTable table:
49+
resolved = GroupedResolvedSource.ForDataRows(CopyLiveRows(table), table);
50+
return true;
51+
52+
case DataView view:
53+
resolved = GroupedResolvedSource.ForDataRows(CopyLiveRows(view), view.Table);
54+
return true;
55+
56+
case BindingSource bs when bs.List is IList list:
57+
resolved = FromIListSnapshot(list, InferTable(bs));
58+
return true;
59+
60+
case IList loose:
61+
resolved = FromIListSnapshot(loose, schemaTable: null);
62+
return true;
63+
64+
default:
65+
return TryRelatedList(dataSource, dataMember, out resolved);
66+
}
67+
}
68+
69+
private static List<DataRow> CopyLiveRows(DataTable table)
70+
{
71+
var rows = new List<DataRow>(table.Rows.Count);
72+
foreach (DataRow row in table.Rows)
73+
{
74+
if (row.RowState != DataRowState.Deleted)
75+
{
76+
rows.Add(row);
77+
}
78+
}
79+
80+
return rows;
81+
}
82+
83+
private static List<DataRow> CopyLiveRows(DataView view)
84+
{
85+
var rows = new List<DataRow>(view.Count);
86+
foreach (DataRowView drv in view)
87+
{
88+
rows.Add(drv.Row);
89+
}
90+
91+
return rows;
92+
}
93+
94+
private static bool TryRelatedList(object dataSource, string? _, out GroupedResolvedSource resolved)
95+
{
96+
resolved = default;
97+
98+
try
99+
{
100+
if (dataSource == null)
101+
{
102+
return false;
103+
}
104+
105+
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(dataSource);
106+
PropertyDescriptor? listProp = props["List"];
107+
108+
object? cand = listProp?.GetValue(dataSource);
109+
if (cand is IList list)
110+
{
111+
resolved = FromIListSnapshot(list, schemaTable: null);
112+
return true;
113+
}
114+
}
115+
#pragma warning disable CA1031
116+
catch { }
117+
#pragma warning restore CA1031
118+
119+
return false;
120+
}
121+
122+
private static DataTable? InferTable(BindingSource bs) => bs.DataSource switch
123+
{
124+
DataTable t => t,
125+
DataView v => v.Table,
126+
_ => null
127+
};
128+
129+
public static GroupedResolvedSource FromIListSnapshot(IList list, DataTable? schemaTable)
130+
{
131+
if (list.Count == 0)
132+
{
133+
if (schemaTable != null)
134+
{
135+
return GroupedResolvedSource.ForDataRows(Array.Empty<DataRow>(), schemaTable);
136+
}
137+
138+
return new GroupedResolvedSource(
139+
GroupedResolvedSource.SourceKind.Objects,
140+
Array.Empty<object?>(),
141+
null,
142+
inferredElementType: null);
143+
}
144+
145+
switch (list[0])
146+
{
147+
case DataRowView:
148+
{
149+
var rows = new List<DataRow>(list.Count);
150+
foreach (var entry in list)
151+
{
152+
if (entry is DataRowView drv)
153+
{
154+
rows.Add(drv.Row);
155+
}
156+
}
157+
158+
return GroupedResolvedSource.ForDataRows(
159+
rows,
160+
rows.Count > 0 ? rows[0].Table : schemaTable
161+
?? throw new InvalidOperationException(
162+
nameof(DataRowView)
163+
+ " list is empty but no schema table could be inferred; bind a BindingSource.DataSource typed as "
164+
+ nameof(DataTable)
165+
+ " or supply rows first."));
166+
}
167+
168+
case DataRow:
169+
{
170+
var rows = new List<DataRow>(list.Count);
171+
foreach (var entry in list)
172+
{
173+
if (entry is DataRow dr)
174+
{
175+
rows.Add(dr);
176+
}
177+
}
178+
179+
return GroupedResolvedSource.ForDataRows(
180+
rows,
181+
rows.Count > 0 ? rows[0].Table : schemaTable
182+
?? throw new InvalidOperationException(nameof(DataRow)
183+
+ " list is empty without a fallback schema."));
184+
}
185+
186+
default:
187+
return GroupedResolvedSource.ForObjectListSnapshot(list);
188+
}
189+
}
190+
}
191+
192+
internal readonly struct GroupedResolvedSource
193+
{
194+
public GroupedResolvedSource(SourceKind kind, IReadOnlyList<object?> snapshot, DataTable? tableTemplate, Type? inferredElementType)
195+
{
196+
Kind = kind;
197+
Snapshot = snapshot;
198+
TableTemplate = tableTemplate;
199+
InferredElementType = inferredElementType;
200+
}
201+
202+
public SourceKind Kind { get; }
203+
204+
public IReadOnlyList<object?> Snapshot { get; }
205+
206+
/// <summary>Present when grouping <see cref="DataRow"/> rows so schemas clone cleanly.</summary>
207+
public DataTable? TableTemplate { get; }
208+
209+
/// <summary>When list items are CLR objects we infer property schema from this type.</summary>
210+
public Type? InferredElementType { get; }
211+
212+
public static GroupedResolvedSource ForDataRows(IReadOnlyList<DataRow> rows, DataTable schemaTable) =>
213+
new(SourceKind.Table, rows.Cast<object?>().ToList(), schemaTable, inferredElementType: null);
214+
215+
public static GroupedResolvedSource ForObjectListSnapshot(IList list)
216+
{
217+
var buf = new List<object?>(list.Count);
218+
foreach (var item in list)
219+
{
220+
buf.Add(item);
221+
}
222+
223+
Type? hinted = buf.FirstOrDefault(s => s is not null)?.GetType();
224+
225+
DataTable? templateFromProps = hinted is null ? null : BuildReflectiveTemplate(hinted);
226+
227+
return new GroupedResolvedSource(SourceKind.Objects, buf, templateFromProps, hinted);
228+
}
229+
230+
private static DataTable BuildReflectiveTemplate(Type elementType)
231+
{
232+
var props = TypeDescriptor.GetProperties(elementType);
233+
var table = new DataTable();
234+
foreach (PropertyDescriptor p in props)
235+
{
236+
if (!p.IsBrowsable || p.PropertyType.Namespace == nameof(System.Reflection))
237+
{
238+
continue;
239+
}
240+
241+
Type t = Nullable.GetUnderlyingType(p.PropertyType) ?? p.PropertyType;
242+
if (t.IsGenericTypeDefinition || t == typeof(void))
243+
{
244+
table.Columns.Add(p.Name, typeof(object));
245+
continue;
246+
}
247+
248+
try
249+
{
250+
table.Columns.Add(p.Name, t.IsEnum ? Enum.GetUnderlyingType(t) : t);
251+
}
252+
#pragma warning disable CA1031
253+
catch
254+
#pragma warning restore CA1031
255+
{
256+
table.Columns.Add(p.Name);
257+
}
258+
}
259+
260+
return table;
261+
}
262+
263+
internal enum SourceKind
264+
{
265+
Table,
266+
Objects
267+
}
268+
}

0 commit comments

Comments
 (0)