From 061e1041b345bbd3dccb069f269bfe1c8c62fcb0 Mon Sep 17 00:00:00 2001 From: Flaky Date: Tue, 28 Apr 2026 11:42:07 +0100 Subject: [PATCH] improve dispose reliability + give hierarchytreeviewmodel its own tagdatabaseservice. --- UI/ViewModels/HierarchyTreeViewModel.cs | 57 +++++++++++-------------- UI/ViewModels/TagEditorViewModel.cs | 49 ++++++++++----------- 2 files changed, 48 insertions(+), 58 deletions(-) diff --git a/UI/ViewModels/HierarchyTreeViewModel.cs b/UI/ViewModels/HierarchyTreeViewModel.cs index 6c3c141..eb10bfc 100644 --- a/UI/ViewModels/HierarchyTreeViewModel.cs +++ b/UI/ViewModels/HierarchyTreeViewModel.cs @@ -16,6 +16,7 @@ public partial class HierarchyTreeViewModel : ViewModelBase, IDisposable private readonly Func, List> _getParentNamesById; private readonly MainWindowViewModel _mainWindow; + [ObservableProperty] private Dictionary> _childNodeMap = new(); [ObservableProperty] private TagItemViewModel? _contextMenuTag; @@ -35,18 +36,16 @@ public partial class HierarchyTreeViewModel : ViewModelBase, IDisposable public ICommand NewTagCommand => this._mainWindow.StartNewTagCommand; public ICommand ShowBulkAddDialogCommand => this._mainWindow.ShowBulkAddDialogCommand; + private TagDatabaseService TagDatabaseService => this._mainWindow.TagDatabaseService; public void Dispose() { - if (!this._mainWindow.TagDatabaseService.IsDatabaseOpen) return; - - this._mainWindow.TagDatabaseService.TagsWritten -= this.TagDatabaseService_OnTagsWritten; - GC.SuppressFinalize(this); + this.TagDatabaseService.TagsWritten -= this.TagDatabaseService_OnTagsWritten; } public async Task InitializeAsync() { - if (!this._mainWindow.TagDatabaseService.IsDatabaseOpen) return; + if (!this.TagDatabaseService.IsDatabaseOpen) return; await Dispatcher.UIThread.InvokeAsync(() => { @@ -56,7 +55,7 @@ public partial class HierarchyTreeViewModel : ViewModelBase, IDisposable }); - var topLevelTags = await Task.Run(() => this._mainWindow.TagDatabaseService.GetAllTags(true)); + var topLevelTags = await Task.Run(() => this.TagDatabaseService.GetAllTags(true)); await Dispatcher.UIThread.InvokeAsync(() => { @@ -87,7 +86,7 @@ public partial class HierarchyTreeViewModel : ViewModelBase, IDisposable } var childTags = - this._mainWindow.TagDatabaseService.GetAllTagChildren(tag.Id); + this.TagDatabaseService.GetAllTagChildren(tag.Id); foreach (var childNode in childTags.Select(child => new TagItemViewModel(child, this._getParentNamesById))) @@ -107,10 +106,7 @@ public partial class HierarchyTreeViewModel : ViewModelBase, IDisposable { if (!this.ViewModelMap.TryGetValue(parentId, out var parentViewModels)) return; - foreach (var parent in parentViewModels) - { - if (parent.CurrentChildren.Any(c => c.Id == tag.Id)) continue; - + foreach (var parent in parentViewModels.Where(parent => parent.CurrentChildren.All(c => c.Id != tag.Id))) await Dispatcher.UIThread.InvokeAsync(() => { var tagNode = new TagItemViewModel(tag, this._getParentNamesById); @@ -123,7 +119,6 @@ public partial class HierarchyTreeViewModel : ViewModelBase, IDisposable parent.CurrentChildren.Insert(index, tagNode); this.AddTagNodeToViewModelMap(tagNode); }); - } if (!this.ChildNodeMap.TryGetValue(tag.Id, out var set)) this.ChildNodeMap.Add(tag.Id, [parentId]); @@ -157,6 +152,17 @@ public partial class HierarchyTreeViewModel : ViewModelBase, IDisposable this.AddTagNodeToViewModelMap(newTopLevelTag); } + private int CompareTagNodes(TagItemViewModel a, TagItemViewModel b) + { + var nameCompare = string.Compare(a.CurrentName, b.CurrentName, StringComparison.CurrentCultureIgnoreCase); + if (nameCompare != 0) return nameCompare; + + var bindingA = a.Tag.TagBindings.FirstOrDefault() ?? string.Empty; + var bindingB = b.Tag.TagBindings.FirstOrDefault() ?? string.Empty; + + return string.Compare(bindingA, bindingB, StringComparison.CurrentCultureIgnoreCase); + } + private void DeleteChildNode(int parentId, int idToDelete) { if (!this.ViewModelMap.TryGetValue(parentId, out var parentViewModels)) return; @@ -167,7 +173,7 @@ public partial class HierarchyTreeViewModel : ViewModelBase, IDisposable parentTag.CurrentChildren.FirstOrDefault(t => t.Id == idToDelete)!; parentTag.CurrentChildren.Remove(foundChild); } - + if (!this.ChildNodeMap.TryGetValue(idToDelete, out var parentSet)) return; parentSet.Remove(parentId); } @@ -220,7 +226,8 @@ public partial class HierarchyTreeViewModel : ViewModelBase, IDisposable } // public ICommand NewTagCommand => this._mainWindow.NewTagCommand; - partial void OnSelectedTagChanged(TagItemViewModel? oldValue, TagItemViewModel? newValue) + // ReSharper disable once PartialMethodParameterNameMismatch + partial void OnSelectedTagChanged(TagItemViewModel? _, TagItemViewModel? newValue) { if (newValue is null || this._mainWindow.SelectedTagId == newValue.Id) return; this._mainWindow.SelectedTag = newValue; @@ -249,8 +256,8 @@ public partial class HierarchyTreeViewModel : ViewModelBase, IDisposable private void SubscribeToEvents() { - if (!this._mainWindow.TagDatabaseService.IsDatabaseOpen) return; - this._mainWindow.TagDatabaseService.TagsWritten += this.TagDatabaseService_OnTagsWritten; + if (!this.TagDatabaseService.IsDatabaseOpen) return; + this.TagDatabaseService.TagsWritten += this.TagDatabaseService_OnTagsWritten; } private async void TagDatabaseService_OnTagsWritten(object? sender, TagDatabaseService.TagWriteResult result) @@ -258,20 +265,19 @@ public partial class HierarchyTreeViewModel : ViewModelBase, IDisposable try { if (sender is not TagDatabaseService { IsDatabaseOpen: true }) return; - + foreach (var updatedTag in result.Updated) - await Dispatcher.UIThread.InvokeAsync(async () => await this.HandleTagUpdateAsync(updatedTag)); + await Dispatcher.UIThread.InvokeAsync(async () => await this.HandleTagUpdateAsync(updatedTag)); if (result.Added.Count > 0) await Dispatcher.UIThread.InvokeAsync(async () => await this.ProcessTagAdditionsAsync(result.Added)); - + foreach (var deletedTag in result.Deleted) { await Dispatcher.UIThread.InvokeAsync(() => this.WipeTagNodes(deletedTag.id)); if (this.SelectedTag?.Id == deletedTag.id) this.SelectedTag = null; } - } catch (Exception e) { @@ -291,15 +297,4 @@ public partial class HierarchyTreeViewModel : ViewModelBase, IDisposable this.DeleteTopLevelNode(idToDelete); } - - private int CompareTagNodes(TagItemViewModel a, TagItemViewModel b) - { - var nameCompare = string.Compare(a.CurrentName, b.CurrentName, StringComparison.CurrentCultureIgnoreCase); - if (nameCompare != 0) return nameCompare; - - var bindingA = a.Tag.TagBindings.FirstOrDefault() ?? string.Empty; - var bindingB = b.Tag.TagBindings.FirstOrDefault() ?? string.Empty; - - return string.Compare(bindingA, bindingB, StringComparison.CurrentCultureIgnoreCase); - } } \ No newline at end of file diff --git a/UI/ViewModels/TagEditorViewModel.cs b/UI/ViewModels/TagEditorViewModel.cs index dc17c8f..ea18058 100644 --- a/UI/ViewModels/TagEditorViewModel.cs +++ b/UI/ViewModels/TagEditorViewModel.cs @@ -10,42 +10,39 @@ namespace TagHierarchyManager.UI.ViewModels; public partial class TagEditorViewModel : ViewModelBase, IDisposable { - private readonly MainWindowViewModel? _mainWindow; + private readonly MainWindowViewModel _mainWindow; [ObservableProperty] private TagItemViewModel? _selectedTag; [ObservableProperty] private int _selectedTagId; [ObservableProperty] private bool _unsavedChanges; - public bool CanDeleteSelectedTag => this._mainWindow?.SelectedTag is not null - && this._mainWindow.SelectedTag.Id > 0 - && this.TagDatabaseService? - .GetAllTagChildren(this._mainWindow.SelectedTag.Id).Count == 0; - - private TagDatabaseService? TagDatabaseService => this._mainWindow?.TagDatabaseService; - public TagEditorViewModel(MainWindowViewModel mainWindow) { this._mainWindow = mainWindow; - this.TagDatabaseService?.TagsWritten += this.TagDatabaseService_OnTagsWritten; + this.TagDatabaseService.TagsWritten += this.TagDatabaseService_OnTagsWritten; } - + + public bool CanDeleteSelectedTag => this._mainWindow.SelectedTag is not null + && this._mainWindow.SelectedTag.Id > 0 + && this.TagDatabaseService + .GetAllTagChildren(this._mainWindow.SelectedTag.Id).Count == 0; + + private TagDatabaseService TagDatabaseService => this._mainWindow.TagDatabaseService; + public void Dispose() { - if (this._mainWindow is null || this.TagDatabaseService is null) return; - if (!this.TagDatabaseService.IsDatabaseOpen) return; this.TagDatabaseService.TagsWritten -= this.TagDatabaseService_OnTagsWritten; - GC.SuppressFinalize(this); } [RelayCommand(AllowConcurrentExecutions = false)] internal async Task NewTag() { - if (this._mainWindow is null || this.TagDatabaseService is null) return; var userWantsToSave = await this._mainWindow.ShowUnsavedChangesDialog(); if (userWantsToSave is null) return; this._mainWindow.SelectedTag = new TagItemViewModel( - new Tag { + new Tag + { Name = string.Empty, IsTopLevel = true, TagBindings = this.TagDatabaseService.DefaultTagBindings @@ -60,7 +57,6 @@ public partial class TagEditorViewModel : ViewModelBase, IDisposable { try { - if (this._mainWindow is null || this.TagDatabaseService is null) return; if (this._mainWindow.SelectedTag is null || !this.TagDatabaseService.IsDatabaseOpen || !this._mainWindow.IsDbEnabled) return; await this.TagDatabaseService.WriteTagsToDatabase([this._mainWindow.SelectedTag]); @@ -71,14 +67,13 @@ public partial class TagEditorViewModel : ViewModelBase, IDisposable } catch (Exception ex) { - this._mainWindow?.ShowErrorDialog(ex.Message); + this._mainWindow.ShowErrorDialog(ex.Message); } } [RelayCommand] private async Task CancelTagEditAsync() { - if (this._mainWindow is null) return; if (this.UnsavedChanges) { var userWantsToOverwrite = await this._mainWindow.ShowNullableBoolDialog(new UnsavedCancelDialog()); @@ -107,7 +102,7 @@ public partial class TagEditorViewModel : ViewModelBase, IDisposable partial void OnSelectedTagIdChanged(int value) { - if (value is 0 || this._mainWindow is null || this.TagDatabaseService is null) return; + if (value is 0) return; var tag = this.TagDatabaseService.GetTagById(value); this._mainWindow.SelectedTag = tag is null @@ -115,17 +110,17 @@ public partial class TagEditorViewModel : ViewModelBase, IDisposable : new TagItemViewModel(tag, this.TagDatabaseService.GetParentNamesByIds); } - private void TagDatabaseService_OnTagsWritten(object? sender, TagDatabaseService.TagWriteResult tags) - { - foreach (var deletedTag in tags.Deleted) - if (this._mainWindow?.SelectedTag?.Id == deletedTag.id) - this._mainWindow.SelectedTag = null; - } - [RelayCommand] private async Task StartTagDeletionAsync() { - if (this._mainWindow?.SelectedTag is null || !this.CanDeleteSelectedTag || this._mainWindow is null) return; + if (this._mainWindow.SelectedTag is null || !this.CanDeleteSelectedTag) return; await this._mainWindow.StartTagDeletionAsync(this._mainWindow.SelectedTag.Id); } + + private void TagDatabaseService_OnTagsWritten(object? sender, TagDatabaseService.TagWriteResult tags) + { + foreach (var deletedTag in tags.Deleted) + if (this._mainWindow.SelectedTag?.Id == deletedTag.id) + this._mainWindow.SelectedTag = null; + } } \ No newline at end of file -- 2.51.2