Compare commits

...

2 Commits

Author SHA1 Message Date
Gordon Lam (SH)
9d4d1bf383 2nd round 2025-07-04 12:42:39 +08:00
Gordon Lam (SH)
2a051e9c67 first commit 2025-07-04 11:40:34 +08:00
11 changed files with 192 additions and 25 deletions

View File

@@ -264,6 +264,17 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
if (model != null)
{
model.ItemsChanged -= Model_ItemsChanged;
if (model is IDisposable disposableModel)
{
try
{
disposableModel.Dispose();
}
catch
{
// Ignore exceptions during cleanup
}
}
}
}
}

View File

@@ -571,6 +571,17 @@ public partial class ListViewModel : PageViewModel, IDisposable
if (model != null)
{
model.ItemsChanged -= Model_ItemsChanged;
if (model is IDisposable disposableModel)
{
try
{
disposableModel.Dispose();
}
catch
{
// Ignore exceptions during cleanup
}
}
}
}
}

View File

@@ -243,6 +243,17 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
if (model != null)
{
model.PropChanged -= Model_PropChanged;
if (model is IDisposable disposableModel)
{
try
{
disposableModel.Dispose();
}
catch
{
// Ignore exceptions during cleanup
}
}
}
}
}

View File

@@ -18,7 +18,7 @@ using WinRT;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class ShellViewModel(IServiceProvider _serviceProvider, TaskScheduler _scheduler) : ObservableObject
public partial class ShellViewModel(IServiceProvider _serviceProvider, TaskScheduler _scheduler) : ObservableObject, IDisposable
{
[ObservableProperty]
public partial bool IsLoaded { get; set; } = false;
@@ -31,6 +31,8 @@ public partial class ShellViewModel(IServiceProvider _serviceProvider, TaskSched
private PageViewModel _currentPage = new LoadingPageViewModel(null, _scheduler);
private bool _isDisposed;
public PageViewModel CurrentPage
{
get => _currentPage;
@@ -201,6 +203,48 @@ public partial class ShellViewModel(IServiceProvider _serviceProvider, TaskSched
SetActiveExtension(null);
}
protected virtual void Dispose(bool disposing)
{
if (!_isDisposed)
{
_isDisposed = true;
if (disposing)
{
// Dispose managed resources
if (_mainListPage is IDisposable disposable)
{
try
{
disposable.Dispose();
}
catch (Exception)
{
}
}
if (_currentPage is IDisposable disposablePage)
{
try
{
disposablePage.Dispose();
}
catch
{
}
}
_mainListPage = null;
_activeExtension = null;
}
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
// You may ask yourself, why aren't we using CsWin32 for this?
// The CsWin32 projected version includes some object marshalling, like so:
//

View File

@@ -21,6 +21,8 @@ internal sealed partial class FallbackOpenFileItem : FallbackCommandItem, System
private uint _queryCookie = 10;
private bool _isDisposed;
public FallbackOpenFileItem()
: base(new NoOpCommand(), Resources.Indexer_Find_Path_fallback_display_title)
{
@@ -120,9 +122,17 @@ internal sealed partial class FallbackOpenFileItem : FallbackCommandItem, System
}
}
public void Dispose()
protected override void Dispose(bool disposing)
{
_searchEngine.Dispose();
GC.SuppressFinalize(this);
if (!_isDisposed)
{
_isDisposed = true;
if (disposing)
{
_searchEngine?.Dispose();
}
}
base.Dispose(disposing);
}
}

View File

@@ -21,6 +21,7 @@ internal sealed partial class ActionsListContextItem : CommandContextItem, IDisp
{
private readonly string fullPath;
private readonly List<CommandContextItem> actions = [];
private bool _isDisposed;
private static readonly Lock UpdateMoreCommandsLock = new();
private static ActionRuntime actionRuntime;
@@ -97,14 +98,24 @@ internal sealed partial class ActionsListContextItem : CommandContextItem, IDisp
}
}
public void Dispose()
protected override void Dispose(bool disposing)
{
lock (UpdateMoreCommandsLock)
if (!_isDisposed)
{
if (actionRuntime != null)
_isDisposed = true;
if (disposing)
{
actionRuntime.ActionCatalog.Changed -= ActionCatalog_Changed;
lock (UpdateMoreCommandsLock)
{
if (actionRuntime != null)
{
actionRuntime.ActionCatalog.Changed -= ActionCatalog_Changed;
actionRuntime = null;
}
}
}
}
base.Dispose(disposing);
}
}

View File

@@ -21,6 +21,8 @@ internal sealed partial class IndexerPage : DynamicListPage, IDisposable
private string initialQuery = string.Empty;
private bool _isDisposed;
public IndexerPage()
{
Id = "com.microsoft.indexer.fileSearch";
@@ -33,6 +35,7 @@ internal sealed partial class IndexerPage : DynamicListPage, IDisposable
public IndexerPage(string query, SearchEngine searchEngine, uint queryCookie, IList<IListItem> firstPageData)
{
disposeSearchEngine = false;
Icon = Icons.FileExplorer;
Name = Resources.Indexer_Title;
_searchEngine = searchEngine;
@@ -40,7 +43,6 @@ internal sealed partial class IndexerPage : DynamicListPage, IDisposable
_indexerListItems.AddRange(firstPageData);
initialQuery = query;
SearchText = query;
disposeSearchEngine = false;
}
public override void UpdateSearchText(string oldSearch, string newSearch)
@@ -76,12 +78,21 @@ internal sealed partial class IndexerPage : DynamicListPage, IDisposable
_searchEngine.Query(query, _queryCookie);
}
public void Dispose()
protected override void Dispose(bool disposing)
{
if (disposeSearchEngine)
if (!_isDisposed)
{
_searchEngine.Dispose();
GC.SuppressFinalize(this);
_isDisposed = true;
if (disposing)
{
if (disposeSearchEngine)
{
_searchEngine?.Dispose();
}
}
}
base.Dispose(disposing);
}
}

View File

@@ -34,6 +34,8 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
private IEnumerable<CatalogPackage>? _results;
private bool _disposed;
public static IconInfo WinGetIcon { get; } = IconHelpers.FromRelativePath("Assets\\WinGet.svg");
public static IconInfo ExtensionsIcon { get; } = IconHelpers.FromRelativePath("Assets\\Extension.svg");
@@ -189,7 +191,7 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
return [];
}
string searchDebugText = $"{query}{(HasTag ? "+" : string.Empty)}{_tag}";
var searchDebugText = $"{query}{(HasTag ? "+" : string.Empty)}{_tag}";
Logger.LogDebug($"Starting search for '{searchDebugText}'");
HashSet<CatalogPackage> results = new(new PackageIdCompare());
@@ -271,7 +273,19 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
return results;
}
public void Dispose() => throw new NotImplementedException();
protected override void Dispose(bool disposing)
{
if (!_disposed)
{
_disposed = true;
if (disposing)
{
_cancellationTokenSource?.Dispose();
}
}
base.Dispose(disposing);
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "I just like it")]

View File

@@ -46,21 +46,17 @@ internal sealed partial class WindowWalkerListPage : DynamicListPage, IDisposabl
public override IListItem[] GetItems() => Query(SearchText).ToArray();
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
public void Dispose(bool disposing)
protected override void Dispose(bool disposing)
{
if (!_disposed)
{
_disposed = true;
if (disposing)
{
_cancellationTokenSource?.Dispose();
_disposed = true;
}
}
base.Dispose(disposing);
}
}

View File

@@ -10,12 +10,39 @@ namespace Microsoft.CommandPalette.Extensions.Toolkit;
// asynchronously, so as to not block the extension app while it's being
// processed in the host app.
// (also consider this for ItemsChanged in ListPage)
public partial class BaseObservable : INotifyPropChanged
public partial class BaseObservable : INotifyPropChanged, IDisposable
{
private bool _isDisposed;
public event TypedEventHandler<object, IPropChangedEventArgs>? PropChanged;
// Protected implementation of Dispose pattern.
protected virtual void Dispose(bool disposing)
{
if (!_isDisposed)
{
_isDisposed = true;
if (disposing)
{
PropChanged = null;
}
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void OnPropertyChanged(string propertyName)
{
if (_isDisposed)
{
return;
}
try
{
// TODO #181 - This is dangerous! If the original host goes away,

View File

@@ -6,7 +6,7 @@ using Windows.Foundation;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class ListPage : Page, IListPage
public partial class ListPage : Page, IListPage, IDisposable
{
private string _placeholderText = string.Empty;
private string _searchText = string.Empty;
@@ -15,6 +15,7 @@ public partial class ListPage : Page, IListPage
private IFilters? _filters;
private IGridProperties? _gridProperties;
private ICommandItem? _emptyContent;
private bool _isDisposed;
public event TypedEventHandler<object, IItemsChangedEventArgs>? ItemsChanged;
@@ -94,8 +95,28 @@ public partial class ListPage : Page, IListPage
{
}
protected override void Dispose(bool disposing)
{
if (!_isDisposed)
{
_isDisposed = true;
if (disposing)
{
ItemsChanged = null;
}
}
base.Dispose(disposing);
}
protected void RaiseItemsChanged(int totalItems = -1)
{
if (_isDisposed)
{
return;
}
try
{
// TODO #181 - This is the same thing that BaseObservable has to deal with.