Skip to content

Commit f5c1941

Browse files
authored
Merge pull request #417 from w-ahmad/feat/improve-drag-selection
feat: allow drag selection to start from rows and empty space
2 parents e5bdc13 + bff9f82 commit f5c1941

15 files changed

Lines changed: 1205 additions & 381 deletions

‎samples/WinUI.TableView.SampleApp/App.xaml.cs‎

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Microsoft.Extensions.Logging;
22
using Microsoft.UI.Xaml;
3+
using System.Diagnostics;
34

45
namespace WinUI.TableView.SampleApp;
56

@@ -25,17 +26,17 @@ public App()
2526
#if DEBUG && WINDOWS
2627
private void DebugSettings_BindingFailed(object sender, BindingFailedEventArgs e)
2728
{
28-
System.Diagnostics.Debug.WriteLine(e.Message);
29+
Debug.WriteLine(e.Message);
2930
}
3031

3132
private void DebugSettings_XamlResourceReferenceFailed(DebugSettings sender, XamlResourceReferenceFailedEventArgs args)
3233
{
33-
System.Diagnostics.Debug.WriteLine(args.Message);
34+
Debug.WriteLine(args.Message);
3435
}
3536

3637
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
3738
{
38-
if (System.Diagnostics.Debugger.IsAttached) System.Diagnostics.Debugger.Break();
39+
if (Debugger.IsAttached) Debugger.Break();
3940
}
4041
#endif
4142

@@ -45,21 +46,27 @@ private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledEx
4546
/// <param name="args">Details about the launch request and process.</param>
4647
protected override void OnLaunched(LaunchActivatedEventArgs args)
4748
{
48-
#if DEBUG && !WINDOWS
49+
#if DEBUG
50+
if (Debugger.IsAttached)
51+
{
52+
DebugSettings.EnableFrameRateCounter = true;
53+
}
54+
#if !WINDOWS
4955
MainWindow.UseStudio();
5056
MainWindow.SetWindowIcon();
57+
#endif
5158
#endif
5259
MainWindow.Activate();
5360
}
5461

5562
public static void InitializeLogging()
5663
{
64+
5765
#if DEBUG
5866
var factory = LoggerFactory.Create(builder =>
5967
{
6068
#if __WASM__
6169
builder.AddProvider(new global::Uno.Extensions.Logging.WebAssembly.WebAssemblyConsoleLoggerProvider());
62-
// Note: DebugSettings.EnableFrameRateCounter requires an Application instance
6370
#elif !WINDOWS
6471
builder.AddConsole();
6572
#else

‎src/Extensions/ItemIndexRangeExtensions.cs‎

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Microsoft.UI.Xaml.Data;
1+
using Microsoft.UI.Xaml.Data;
22

33
namespace WinUI.TableView.Extensions;
44

@@ -21,11 +21,55 @@ public static bool IsInRange(this ItemIndexRange range, int index)
2121
/// <summary>
2222
/// Determines whether the given item index range is valid within the TableView.
2323
/// </summary>
24-
/// <param name="itemIndexRange">The ItemIndexRange to check.</param>
24+
/// <param name="range">The ItemIndexRange to check.</param>
2525
/// <param name="tableView">The TableView to check against.</param>
2626
/// <returns>True if the item index range of TableView is valid; otherwise, false.</returns>
27-
public static bool IsValid(this ItemIndexRange itemIndexRange, TableView tableView)
27+
public static bool IsValid(this ItemIndexRange range, TableView tableView)
28+
{
29+
return range.FirstIndex >= 0 && range.LastIndex < tableView?.Items.Count;
30+
}
31+
32+
/// <summary>
33+
/// Determines whether the specified range completely contains another range.
34+
/// </summary>
35+
/// <param name="range">The range to check.</param>
36+
/// <param name="other">The range to check against.</param>
37+
/// <returns>True if the range completely contains the other range; otherwise, false.</returns>
38+
public static bool Contains(this ItemIndexRange range, ItemIndexRange other)
2839
{
29-
return itemIndexRange.FirstIndex >= 0 && itemIndexRange.LastIndex < tableView?.Items.Count;
40+
return other.FirstIndex >= range.FirstIndex && other.LastIndex <= range.LastIndex;
41+
}
42+
43+
/// <summary>
44+
/// Subtracts another ItemIndexRange from the current range and returns the resulting range.
45+
/// </summary>
46+
/// <param name="range">The range to subtract from.</param>
47+
/// <param name="other">The range to subtract.</param>
48+
/// <returns>The resulting range after subtraction.</returns>
49+
public static IEnumerable<ItemIndexRange> Subtract(this ItemIndexRange range, ItemIndexRange other)
50+
{
51+
var start = range.FirstIndex;
52+
var end = start + (int)range.Length - 1;
53+
54+
var otherStart = other.FirstIndex;
55+
var otherEnd = otherStart + (int)other.Length - 1;
56+
57+
// No overlap.
58+
if (otherEnd < start || otherStart > end)
59+
{
60+
yield break;
61+
}
62+
63+
// Left remainder.
64+
if (otherStart > start)
65+
{
66+
yield return new ItemIndexRange(start, (uint)(otherStart - start));
67+
}
68+
69+
// Right remainder.
70+
if (otherEnd < end)
71+
{
72+
yield return new ItemIndexRange(otherEnd + 1, (uint)(end - otherEnd));
73+
}
3074
}
3175
}
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
namespace WinUI.TableView.Extensions;
2+
3+
/// <summary>
4+
/// Provides extension methods for the TableViewCellSlotRange type.
5+
/// </summary>
6+
internal static class TableViewCellSlotRangeExtensions
7+
{
8+
/// <summary>
9+
/// Determines whether a specified cell slot is within the range.
10+
/// </summary>
11+
/// <param name="range">The TableViewCellSlotRange to check.</param>
12+
/// <param name="slot">The cell slot to check.</param>
13+
/// <returns>True if the slot is within the range; otherwise, false.</returns>
14+
public static bool IsInRange(this TableViewCellSlotRange? range, TableViewCellSlot slot)
15+
{
16+
if (range is null || range.Length <= 0) return false;
17+
18+
var minRow = Math.Min(range.FirstRow, range.LastRow);
19+
var maxRow = Math.Max(range.FirstRow, range.LastRow);
20+
var minColumn = Math.Min(range.FirstColumn, range.LastColumn);
21+
var maxColumn = Math.Max(range.FirstColumn, range.LastColumn);
22+
23+
return slot.Row >= minRow && slot.Row <= maxRow
24+
&& slot.Column >= minColumn && slot.Column <= maxColumn;
25+
}
26+
27+
/// <summary>
28+
/// Determines whether a specified row index is within the range.
29+
/// </summary>
30+
/// <param name="range">The TableViewCellSlotRange to check.</param>
31+
/// <param name="row">The row index to check.</param>
32+
/// <returns>True if the row index is within the range; otherwise, false.</returns>
33+
public static bool IsRowInRange(this TableViewCellSlotRange? range, int row)
34+
{
35+
return range?.Length > 0
36+
&& row >= Math.Min(range.FirstRow, range.LastRow)
37+
&& row <= Math.Max(range.FirstRow, range.LastRow);
38+
}
39+
40+
/// <summary>
41+
/// Determines whether a specified column index is within the range.
42+
/// </summary>
43+
/// <param name="range">The TableViewCellSlotRange to check.</param>
44+
/// <param name="column">The column index to check.</param>
45+
/// <returns>True if the column index is within the range; otherwise, false.</returns>
46+
public static bool IsColumnInRange(this TableViewCellSlotRange? range, int column)
47+
{
48+
return range?.Length > 0
49+
&& column >= Math.Min(range.FirstColumn, range.LastColumn)
50+
&& column <= Math.Max(range.FirstColumn, range.LastColumn);
51+
}
52+
53+
/// <summary>
54+
/// Determines whether the given cell slot range is valid within the TableView.
55+
/// </summary>
56+
/// <param name="range">The TableViewCellSlotRange to check.</param>
57+
/// <param name="tableView">The TableView to check against.</param>
58+
/// <returns>True if the cell slot range of TableView is valid; otherwise, false.</returns>
59+
public static bool IsValid(this TableViewCellSlotRange range, TableView tableView)
60+
{
61+
return range.FirstSlot.IsValid(tableView) && range.LastSlot.IsValid(tableView);
62+
}
63+
64+
/// <summary>
65+
/// Returns all cell slots contained within this range, enumerated row by row.
66+
/// </summary>
67+
public static IEnumerable<TableViewCellSlot> GetSlots(this TableViewCellSlotRange range)
68+
{
69+
for (var row = range.FirstRow; row <= range.LastRow; row++)
70+
{
71+
for (var col = range.FirstColumn; col <= range.LastColumn; col++)
72+
{
73+
yield return new TableViewCellSlot(row, col);
74+
}
75+
}
76+
}
77+
78+
/// <summary>
79+
/// Determines whether a specific cell slot falls within this range.
80+
/// </summary>
81+
public static bool Contains(this TableViewCellSlotRange range, int rowIndex, int columnIndex)
82+
{
83+
return rowIndex >= range.FirstRow && rowIndex <= range.LastRow &&
84+
columnIndex >= range.FirstColumn && columnIndex <= range.LastColumn;
85+
}
86+
87+
/// <summary>
88+
/// Determines whether another TableViewCellSlotRange is completely contained within this range.
89+
/// </summary>
90+
public static bool Contains(this TableViewCellSlotRange? range, TableViewCellSlotRange? other)
91+
{
92+
if (range == null || other == null) return false;
93+
94+
return range.Contains(other.FirstRow, other.FirstColumn) &&
95+
range.Contains(other.LastRow, other.LastColumn);
96+
}
97+
98+
/// <summary>
99+
/// Determines whether another range intersects with this range.
100+
/// </summary>
101+
public static bool IntersectsWith(this TableViewCellSlotRange range, TableViewCellSlotRange other)
102+
{
103+
if (other == null) return false;
104+
105+
return range.FirstRow <= other.LastRow && range.LastRow >= other.FirstRow &&
106+
range.FirstColumn <= other.LastColumn && range.LastColumn >= other.FirstColumn;
107+
}
108+
109+
/// <summary>
110+
/// Subtracts another range from this range and returns the resulting ranges.
111+
/// </summary>
112+
/// <param name="range">The range to subtract from.</param>
113+
/// <param name="other">The range to subtract.</param>
114+
/// <returns>An enumerable of resulting ranges after subtraction.</returns>
115+
public static IEnumerable<TableViewCellSlotRange> Subtract(this TableViewCellSlotRange range, TableViewCellSlotRange other)
116+
{
117+
// No overlap.
118+
if (!range.IntersectsWith(other))
119+
{
120+
yield return range;
121+
yield break;
122+
}
123+
124+
// Intersection rectangle.
125+
var top = Math.Max(range.FirstRow, other.FirstRow);
126+
var left = Math.Max(range.FirstColumn, other.FirstColumn);
127+
var bottom = Math.Min(range.LastRow, other.LastRow);
128+
var right = Math.Min(range.LastColumn, other.LastColumn);
129+
130+
// Top strip.
131+
if (range.FirstRow < top)
132+
{
133+
yield return new TableViewCellSlotRange(
134+
range.FirstRow,
135+
range.FirstColumn,
136+
top - range.FirstRow,
137+
range.Columns);
138+
}
139+
140+
// Bottom strip.
141+
if (bottom < range.LastRow)
142+
{
143+
yield return new TableViewCellSlotRange(
144+
bottom + 1,
145+
range.FirstColumn,
146+
range.LastRow - bottom,
147+
range.Columns);
148+
}
149+
150+
// Left strip.
151+
if (range.FirstColumn < left)
152+
{
153+
yield return new TableViewCellSlotRange(
154+
top,
155+
range.FirstColumn,
156+
bottom - top + 1,
157+
left - range.FirstColumn);
158+
}
159+
160+
// Right strip.
161+
if (right < range.LastColumn)
162+
{
163+
yield return new TableViewCellSlotRange(
164+
top,
165+
right + 1,
166+
bottom - top + 1,
167+
range.LastColumn - right);
168+
}
169+
}
170+
171+
/// <summary>
172+
/// Merges two TableViewCellSlotRanges into a single range that encompasses both.
173+
/// </summary>
174+
public static TableViewCellSlotRange Merge(this TableViewCellSlotRange range, TableViewCellSlotRange other)
175+
{
176+
var firstRow = Math.Min(range.FirstRow, other.FirstRow);
177+
var firstColumn = Math.Min(range.FirstColumn, other.FirstColumn);
178+
var lastRow = Math.Max(range.LastRow, other.LastRow);
179+
var lastColumn = Math.Max(range.LastColumn, other.LastColumn);
180+
181+
return TableViewCellSlotRange.FromCoordinates(
182+
firstRow,
183+
firstColumn,
184+
lastRow,
185+
lastColumn);
186+
}
187+
}

‎src/Helpers/IndexRangeHelper.cs‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using Microsoft.UI.Xaml.Data;
2+
3+
namespace WinUI.TableView.Helpers;
4+
5+
/// <summary>
6+
/// Provides helper methods for working with index ranges in a TableView.
7+
/// </summary>
8+
internal static class IndexRangeHelper
9+
{
10+
/// <summary>
11+
/// Gets a list of contiguous index ranges from a collection of indexes.
12+
/// </summary>
13+
/// <param name="indexes">The collection of indexes to process.</param>
14+
/// <returns>A list of contiguous index ranges.</returns>
15+
public static List<ItemIndexRange> GetRanges(IEnumerable<int> indexes)
16+
{
17+
var sorted = indexes.Order().ToArray();
18+
19+
if (sorted.Length == 0)
20+
return [];
21+
22+
List<ItemIndexRange> ranges = [];
23+
24+
var first = sorted[0];
25+
var previous = sorted[0];
26+
27+
for (var i = 1; i < sorted.Length; i++)
28+
{
29+
if (sorted[i] == previous + 1)
30+
{
31+
previous = sorted[i];
32+
continue;
33+
}
34+
35+
ranges.Add(new ItemIndexRange(first, (uint)(previous - first + 1)));
36+
37+
first = previous = sorted[i];
38+
}
39+
40+
ranges.Add(new ItemIndexRange(first, (uint)(previous - first + 1)));
41+
42+
return ranges;
43+
}
44+
}

‎src/Helpers/TableViewTrace.cs‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System.Diagnostics;
2+
3+
namespace WinUI.TableView.Helpers;
4+
5+
internal static class TableViewTrace
6+
{
7+
[Conditional("DEBUG")]
8+
public static void Write(string message)
9+
{
10+
if (Debugger.IsAttached)
11+
{
12+
Debug.WriteLine($"[TableView] {message}");
13+
}
14+
}
15+
}

‎src/TableView.Events.cs‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
using Microsoft.UI.Xaml;
2-
using System;
3-
using System.ComponentModel;
1+
using Microsoft.UI.Xaml;
2+
using WinUI.TableView.Helpers;
43

54
namespace WinUI.TableView;
65

@@ -186,6 +185,7 @@ protected internal virtual void OnClearSorting(TableViewClearSortingEventArgs ar
186185
/// </summary>
187186
protected virtual void OnCellSelectionChanged(TableViewCellSelectionChangedEventArgs args)
188187
{
188+
TableViewTrace.Write($"TableViewCellSelectionChanged: Added={args.AddedCells.Count}, Removed={args.RemovedCells.Count}");
189189
CellSelectionChanged?.Invoke(this, args);
190190
}
191191

0 commit comments

Comments
 (0)