Skip to content

Commit 68ee15c

Browse files
authored
Merge pull request w-ahmad#369 from w-ahmad/fix/memory-issues
fix: Improvements and fixes for memory management
2 parents 55e6b01 + 536b1d4 commit 68ee15c

12 files changed

Lines changed: 325 additions & 52 deletions

samples/WinUI.TableView.SampleApp/MainPage.xaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,8 @@
187187
<NavigationView.Content>
188188
<Grid>
189189
<Frame x:Name="rootFrame"
190-
IsNavigationStackEnabled="False"
190+
CacheSize="0"
191+
IsNavigationStackEnabled="True"
191192
Navigated="OnRootFrameNavigated"
192193
IsEnabled="{Binding Visibility, ElementName=loadingIndicator, Converter={StaticResource VisibilityToBool}}" />
193194
<StackPanel x:Name="loadingIndicator"

samples/WinUI.TableView.SampleApp/MainPage.xaml.cs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ public sealed partial class MainPage : Page
1717
public MainPage()
1818
{
1919
InitializeComponent();
20-
2120
App.Current.MainWindow.Activated += OnMainWindowActivated;
2221
App.Current.MainWindow.SetTitleBar(AppTitleBar);
2322

@@ -139,12 +138,17 @@ private async void OnRootFrameNavigated(object sender, NavigationEventArgs e)
139138
navigationView.SelectedItem = navItem;
140139
}
141140

142-
if (e.Content is Page { DataContext: null } page)
141+
if (e.Content is Page page)
143142
{
144-
await Task.Delay(10);
145-
page.DataContext = new ExampleViewModel();
143+
page.NavigationCacheMode = NavigationCacheMode.Disabled;
144+
145+
if (page.DataContext is null)
146+
{
147+
await Task.Delay(10);
148+
page.DataContext = new ExampleViewModel();
149+
}
146150
}
147151

148152
_canNavigate = true;
149153
}
150-
}
154+
}

samples/WinUI.TableView.SampleApp/Pages/ConditionalStylingPage.xaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
xmlns:tv="using:WinUI.TableView"
88
xmlns:controls="using:WinUI.TableView.SampleApp.Controls"
99
mc:Ignorable="d"
10-
Loaded="OnPageLoaded">
10+
Loaded="OnPageLoaded"
11+
Unloaded="OnPageUnloaded">
1112

1213
<Page.DataContext>
1314
<local:SalesViewModel />

samples/WinUI.TableView.SampleApp/Pages/ConditionalStylingPage.xaml.cs

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ private async void OnPageLoaded(object sender, RoutedEventArgs e)
2626
liveUpdates.IsOn = true;
2727
}
2828

29+
private void OnPageUnloaded(object sender, RoutedEventArgs e)
30+
{
31+
StopLiveUpdates();
32+
ViewModel.SalesData = null;
33+
}
34+
2935
private void OnLiveUpdatesToggled(object sender, RoutedEventArgs e)
3036
{
3137
if (liveUpdates.IsOn)
@@ -34,32 +40,52 @@ private void OnLiveUpdatesToggled(object sender, RoutedEventArgs e)
3440
}
3541
else
3642
{
37-
_liveUpdatesCST?.Cancel();
38-
_liveUpdatesCST = null;
43+
StopLiveUpdates();
3944
}
4045
}
4146

4247
private async Task StartLiveUpdatesAsync()
4348
{
49+
StopLiveUpdates();
4450
_liveUpdatesCST = new CancellationTokenSource();
51+
var token = _liveUpdatesCST.Token;
4552

46-
while (ViewModel.SalesData is not null && !_liveUpdatesCST.IsCancellationRequested)
53+
try
54+
{
55+
while (ViewModel.SalesData is not null && !token.IsCancellationRequested)
56+
{
57+
await Task.Delay((int)updateInterval.Value, token);
58+
59+
var region = DataFaker.Region();
60+
var target = DataFaker.Integer(5_000, 30_000);
61+
var sales = DataFaker.Integer((int)(target * 0.5), (int)(target * 1.2));
62+
var growth = DataFaker.Integer(-10, 10);
63+
var status = sales >= target ? (growth >= 0 ? "Ahead" : "On Track") : (growth < 0 ? "Behind" : "On Track");
64+
var item = ViewModel.SalesData[Random.Shared.Next(ViewModel.SalesData.Count)];
65+
66+
item.Region = region;
67+
item.Target = target;
68+
item.Sales = sales;
69+
item.Growth = growth;
70+
item.Status = status;
71+
}
72+
}
73+
catch (OperationCanceledException)
4774
{
48-
await Task.Delay((int)updateInterval.Value, _liveUpdatesCST.Token);
49-
50-
var region = DataFaker.Region();
51-
var target = DataFaker.Integer(5_000, 30_000);
52-
var sales = DataFaker.Integer((int)(target * 0.5), (int)(target * 1.2));
53-
var growth = DataFaker.Integer(-10, 10);
54-
var status = sales >= target ? (growth >= 0 ? "Ahead" : "On Track") : (growth < 0 ? "Behind" : "On Track");
55-
var item = ViewModel.SalesData[Random.Shared.Next(ViewModel.SalesData.Count)];
56-
57-
item.Region = region;
58-
item.Target = target;
59-
item.Sales = sales;
60-
item.Growth = growth;
61-
item.Status = status;
6275
}
76+
finally
77+
{
78+
if (_liveUpdatesCST?.Token == token)
79+
{
80+
_liveUpdatesCST.Dispose();
81+
_liveUpdatesCST = null;
82+
}
83+
}
84+
}
85+
86+
private void StopLiveUpdates()
87+
{
88+
_liveUpdatesCST?.Cancel();
6389
}
6490

6591
private Predicate<TableViewConditionalCellStyleContext> SalesMetTargetPredicate =>

samples/WinUI.TableView.SampleApp/Pages/ExternalFilteringPage.xaml.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public ExternalFilteringPage()
1111
InitializeComponent();
1212

1313
tableView.FilterDescriptions.Add(new FilterDescription(string.Empty, Filter));
14+
Unloaded += OnPageUnloaded;
1415
}
1516

1617
private bool Filter(object? item)
@@ -46,12 +47,19 @@ private async Task RefreshFilter(CancellationToken token)
4647
{
4748
await Task.Delay(200, token);
4849
}
49-
catch
50+
catch (OperationCanceledException)
5051
{
5152
return;
5253
}
5354

5455
_token = null;
5556
tableView.RefreshFilter();
5657
}
58+
59+
private void OnPageUnloaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
60+
{
61+
_token?.Cancel();
62+
_token?.Dispose();
63+
_token = null;
64+
}
5765
}

samples/WinUI.TableView.SampleApp/Pages/LargeDataPage.xaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
xmlns:ui="using:CommunityToolkit.WinUI"
1111
xmlns:converters="using:WinUI.TableView.SampleApp.Converters"
1212
mc:Ignorable="d"
13-
Loaded="OnPageLoaded">
13+
Loaded="OnPageLoaded"
14+
Unloaded="OnPageUnloaded">
1415

1516
<Page.DataContext>
1617
<local:TransactionsViewModel />

samples/WinUI.TableView.SampleApp/Pages/LargeDataPage.xaml.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,18 @@ private void OnSetItemsSourceCliced(object sender, RoutedEventArgs e)
2323
{
2424
if (DataContext is TransactionsViewModel viewModel)
2525
{
26-
viewModel.TransacationsData = [.. TransactionsViewModel.TransacationsList];
26+
viewModel.TransacationsData = TransactionsViewModel.TransacationsList;
2727
((Button)sender).IsEnabled = false;
2828
}
2929
}
30+
31+
private void OnPageUnloaded(object sender, RoutedEventArgs e)
32+
{
33+
if (DataContext is TransactionsViewModel viewModel)
34+
{
35+
viewModel.TransacationsData = null;
36+
}
37+
38+
TransactionsViewModel.ReleaseItems();
39+
}
3040
}

samples/WinUI.TableView.SampleApp/TransactionsViewModel.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,11 @@ await Task.Run(() =>
1010
{
1111
var startId = 7_475_327;
1212
var startDate = new DateTime(2010, 1, 1);
13-
14-
TransacationsList.Clear();
13+
var items = new List<TransactionModel>(1_000_000);
1514

1615
for (var i = 0; i < 1_000_000; i++)
1716
{
18-
TransacationsList.Add(new TransactionModel
17+
items.Add(new TransactionModel
1918
{
2019
Id = startId++,
2120
Date = startDate.AddMinutes(i),
@@ -30,11 +29,18 @@ await Task.Run(() =>
3029
Mcc = DataFaker.Integer(1700, 9500)
3130
});
3231
}
32+
33+
TransacationsList = items;
3334
});
3435
}
3536

37+
public static void ReleaseItems()
38+
{
39+
TransacationsList = [];
40+
}
41+
3642
[ObservableProperty]
3743
public partial IList<TransactionModel>? TransacationsData { get; set; }
3844

39-
public static IList<TransactionModel> TransacationsList { get; set; } = [];
40-
}
45+
public static IList<TransactionModel> TransacationsList { get; private set; } = [];
46+
}

src/TableView.cs

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public partial class TableView : ListView
3636
private RowDefinition? _headerRowDefinition;
3737
private bool _shouldThrowSelectionModeChangedException;
3838
private bool _ensureColumns = true;
39+
private bool _isItemsSourceSuspended;
3940
private readonly List<TableViewRow> _rows = [];
4041
private readonly CollectionView _collectionView = [];
4142

@@ -108,6 +109,12 @@ protected override void PrepareContainerForItemOverride(DependencyObject element
108109
{
109110
if (element is TableViewRow row)
110111
{
112+
if (!_rows.Contains(row))
113+
{
114+
_rows.Add(row);
115+
}
116+
117+
row.TableView = this;
111118
row.EnsureCellsStyle(default, item);
112119
row.ApplyCellsSelectionState();
113120
row.RowPresenter?.ApplyDetailsPaneState(item);
@@ -120,6 +127,18 @@ protected override void PrepareContainerForItemOverride(DependencyObject element
120127
});
121128
}
122129

130+
/// <inheritdoc/>
131+
protected override void ClearContainerForItemOverride(DependencyObject element, object item)
132+
{
133+
if (element is TableViewRow row)
134+
{
135+
_rows.Remove(row);
136+
row.TableView = null;
137+
}
138+
139+
base.ClearContainerForItemOverride(element, item);
140+
}
141+
123142
/// <inheritdoc/>
124143
protected override DependencyObject GetContainerForItemOverride()
125144
{
@@ -363,6 +382,7 @@ private void OnScrollViewerLoaded(object sender, RoutedEventArgs e)
363382
/// </summary>
364383
private void OnLoaded(object sender, RoutedEventArgs e)
365384
{
385+
ResumeItemsSource();
366386
EnsureAutoColumns();
367387
}
368388

@@ -375,6 +395,43 @@ private void OnUnloaded(object sender, RoutedEventArgs e)
375395
{
376396
currentCell.EndEditing(TableViewEditAction.Commit);
377397
}
398+
399+
SuspendItemsSource();
400+
}
401+
402+
/// <summary>
403+
/// Suspends subscriptions to the current items source while the control is unloaded.
404+
/// </summary>
405+
private void SuspendItemsSource()
406+
{
407+
if (_isItemsSourceSuspended)
408+
{
409+
return;
410+
}
411+
412+
_collectionView.ItemPropertyChanged -= OnItemPropertyChanged;
413+
_collectionView.Source = Enumerable.Empty<object>();
414+
_isItemsSourceSuspended = true;
415+
}
416+
417+
/// <summary>
418+
/// Restores subscriptions to the current items source when the control is loaded.
419+
/// </summary>
420+
private void ResumeItemsSource()
421+
{
422+
if (!_isItemsSourceSuspended)
423+
{
424+
return;
425+
}
426+
427+
_collectionView.ItemPropertyChanged += OnItemPropertyChanged;
428+
429+
if (ItemsSource is IEnumerable source)
430+
{
431+
_collectionView.Source = source;
432+
}
433+
434+
_isItemsSourceSuspended = false;
378435
}
379436

380437
/// <summary>
@@ -749,7 +806,10 @@ private void ItemsSourceChanged(DependencyPropertyChangedEventArgs e)
749806
{
750807
EnsureAutoColumns();
751808

752-
_collectionView.Source = source;
809+
if (!_isItemsSourceSuspended)
810+
{
811+
_collectionView.Source = source;
812+
}
753813
}
754814
}
755815

0 commit comments

Comments
 (0)