diff --git a/Core/Models/TagDatabase/TagDatabase.WriteMethods.cs b/Core/Models/TagDatabase/TagDatabase.WriteMethods.cs
index 6676717..d24575f 100644
--- a/Core/Models/TagDatabase/TagDatabase.WriteMethods.cs
+++ b/Core/Models/TagDatabase/TagDatabase.WriteMethods.cs
@@ -11,7 +11,6 @@ public partial class TagDatabase
///
/// The tag object.
/// The SqliteTransaction to execute queries on, will make its own if null.
- /// true if the tag has been saved.
/// Thrown if the tag already existed in the database.
public async Task WriteTagToDatabase(Tag tag, SqliteTransaction? transaction = null)
{
@@ -43,12 +42,12 @@ public partial class TagDatabase
if (index != -1)
{
this.Tags[index] = tag;
- TagUpdated?.Invoke(this, tag);
+ TagUpdated.Invoke(this, tag);
}
else
{
this.Tags.Add(tag);
- TagAdded?.Invoke(this, tag);
+ TagAdded.Invoke(this, tag);
}
}
catch (SqliteException)
@@ -62,6 +61,12 @@ public partial class TagDatabase
}
}
+ ///
+ /// Saves all tag objects to the database.
+ ///
+ /// The List of Tag objects.
+ /// The SqliteTransaction to execute queries on, will make its own if null.
+ /// Thrown if the tag already existed in the database.
public async Task WriteTagsToDatabase(List tags, SqliteTransaction? transaction = null)
{
this.CheckInitialisation();
diff --git a/UI/TagDatabaseService.cs b/UI/TagDatabaseService.cs
index a490dea..4f3a0a7 100644
--- a/UI/TagDatabaseService.cs
+++ b/UI/TagDatabaseService.cs
@@ -20,7 +20,7 @@ public class TagDatabaseService : ObservableObject
public event EventHandler? TagAdded;
public event EventHandler<(int id, string name)>? TagDeleted;
public event EventHandler>? TagsAdded;
- public event EventHandler? TagUpdated;
+ public event EventHandler>? TagsUpdated;
public string DatabaseName => this.Database?.Name ?? string.Empty;
public int DatabaseVersion => this.Database?.Version ?? 0;
@@ -142,6 +142,12 @@ public class TagDatabaseService : ObservableObject
this.NotifyDatabasePropertiesChanged();
}
+ public async Task WriteTagToDatabase(List tag)
+ {
+ if (this.Database is null) return;
+ await this.Database.WriteTagsToDatabase(tag);
+ }
+
public async Task WriteTagsToDatabase(List tags)
{
if (this.Database is null) return;
@@ -189,7 +195,7 @@ public class TagDatabaseService : ObservableObject
if (this.Database is null) return;
this.Database.TagAdded += this.TagDatabase_OnTagAdded;
this.Database.TagsAdded += this.TagDatabase_OnTagsAdded;
- this.Database.TagUpdated += this.TagDatabase_OnTagUpdated;
+ this.Database.TagsUpdated += this.TagDatabase_OnTagsUpdated;
this.Database.TagDeleted += this.TagDatabase_OnTagDeleted;
}
@@ -220,9 +226,9 @@ public class TagDatabaseService : ObservableObject
this.NotifyDatabasePropertiesChanged();
}
- private void TagDatabase_OnTagUpdated(object? sender, Tag updatedTag)
+ private void TagDatabase_OnTagsUpdated(object? sender, List updatedTags)
{
- this.TagUpdated?.Invoke(this, updatedTag);
+ this.TagsUpdated?.Invoke(this, updatedTags);
this.NotifyDatabasePropertiesChanged();
}
@@ -231,7 +237,7 @@ public class TagDatabaseService : ObservableObject
if (this.Database is null) return;
this.Database.TagAdded -= this.TagDatabase_OnTagAdded;
this.Database.TagsAdded -= this.TagDatabase_OnTagsAdded;
- this.Database.TagUpdated -= this.TagDatabase_OnTagUpdated;
+ this.Database.TagsUpdated -= this.TagDatabase_OnTagsUpdated;
this.Database.TagDeleted -= this.TagDatabase_OnTagDeleted;
}
}
\ No newline at end of file
diff --git a/UI/ViewModels/HierarchyTreeViewModel.cs b/UI/ViewModels/HierarchyTreeViewModel.cs
index 6c67958..08b8d05 100644
--- a/UI/ViewModels/HierarchyTreeViewModel.cs
+++ b/UI/ViewModels/HierarchyTreeViewModel.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
+using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using TagHierarchyManager.Models;
@@ -32,7 +33,7 @@ public partial class HierarchyTreeViewModel : ViewModelBase, IDisposable
{
if (!this._mainWindow.TagDatabaseService.IsDatabaseOpen) return;
- this._mainWindow.TagDatabaseService.TagUpdated -= this.TagDatabase_OnTagUpdated;
+ this._mainWindow.TagDatabaseService.TagsUpdated -= this.TagDatabase_OnTagsUpdated;
this._mainWindow.TagDatabaseService.TagAdded -= this.TagDatabase_OnTagAdded;
this._mainWindow.TagDatabaseService.TagDeleted -= this.TagDatabase_OnTagDeleted;
this._mainWindow.TagDatabaseService.TagsAdded -= this.TagDatabase_OnTagsAdded;
@@ -42,23 +43,34 @@ public partial class HierarchyTreeViewModel : ViewModelBase, IDisposable
public async Task InitializeAsync()
{
if (!this._mainWindow.TagDatabaseService.IsDatabaseOpen) return;
- this.ViewModelMap.Clear();
- this.TopLevelTagNodes.Clear();
- await Task.Run(() =>
+
+ await Dispatcher.UIThread.InvokeAsync(() =>
+ {
+ this.ViewModelMap.Clear();
+ this.ChildNodeMap.Clear();
+ this.TopLevelTagNodes.Clear();
+ });
+
+
+ var topLevelTags = await Task.Run(() => this._mainWindow.TagDatabaseService.GetAllTags(true));
+
+ await Dispatcher.UIThread.InvokeAsync(() =>
{
- var topLevelTags = this._mainWindow.TagDatabaseService.GetAllTags(true);
var buildingTopLevelNodes = new List();
foreach (var tagNode in
- topLevelTags!.Select(tag => new TagItemViewModel(tag, this._getParentNamesById)))
+ topLevelTags.Select(tag => new TagItemViewModel(tag, this._getParentNamesById)))
{
buildingTopLevelNodes.Add(tagNode);
this.AddTagNodeToViewModelMap(tagNode);
this.AddAllChildrenAsync(tagNode);
}
-
+
foreach (var tagNode in buildingTopLevelNodes)
this.TopLevelTagNodes.Add(tagNode);
});
+
+
+ await Task.CompletedTask;
}
private void AddAllChildrenAsync(TagItemViewModel tag, bool beingUpdated = false)
@@ -72,8 +84,7 @@ public partial class HierarchyTreeViewModel : ViewModelBase, IDisposable
var childTags =
this._mainWindow.TagDatabaseService.GetAllTagChildren(tag.Id);
-
- if (childTags is null) return;
+
foreach (var childNode in childTags.Select(child =>
new TagItemViewModel(child, this._getParentNamesById)))
{
@@ -96,7 +107,7 @@ public partial class HierarchyTreeViewModel : ViewModelBase, IDisposable
{
if (parent.Children.Any(c => c.Id == tag.Id)) continue;
- await Task.Run(() =>
+ await Dispatcher.UIThread.InvokeAsync(() =>
{
var tagNode = new TagItemViewModel(tag, this._getParentNamesById);
this.AddAllChildrenAsync(tagNode);
@@ -124,7 +135,7 @@ public partial class HierarchyTreeViewModel : ViewModelBase, IDisposable
}
else
{
- if (beingUpdated) tagNodeSet.Clear();
+ // if (beingUpdated) tagNodeSet.Clear();
tagNodeSet.Add(tagNode);
}
}
@@ -132,21 +143,22 @@ public partial class HierarchyTreeViewModel : ViewModelBase, IDisposable
private async Task AddTopLevelNode(Tag tag)
{
if (this.TopLevelTagNodes.Any(t => t.Id == tag.Id)) return;
- await Task.Run(() =>
- {
- var newTopLevelTag = new TagItemViewModel(tag, this._getParentNamesById);
- // this does create considerable delay, would be nice to speed it up somehow.
- this.AddAllChildrenAsync(newTopLevelTag);
-
- var index = 0;
- while (index < this.TopLevelTagNodes.Count
- && string.Compare(this.TopLevelTagNodes[index].Name, newTopLevelTag.Name,
- StringComparison.CurrentCultureIgnoreCase) < 0)
- index++;
-
- this.TopLevelTagNodes.Insert(index, newTopLevelTag);
- this.AddTagNodeToViewModelMap(newTopLevelTag);
- });
+
+ var newTopLevelTag = new TagItemViewModel(tag, this._getParentNamesById);
+ // this does create considerable delay, would be nice to speed it up somehow.
+ this.AddAllChildrenAsync(newTopLevelTag);
+
+ var index = 0;
+ while (index < this.TopLevelTagNodes.Count
+ && string.Compare(this.TopLevelTagNodes[index].Name, newTopLevelTag.Name,
+ StringComparison.CurrentCultureIgnoreCase) < 0)
+ index++;
+
+ this.TopLevelTagNodes.Insert(index, newTopLevelTag);
+ this.AddTagNodeToViewModelMap(newTopLevelTag);
+
+ await Task.CompletedTask;
+
}
private async Task DeleteChildNode(int parentId, int idToDelete)
@@ -154,22 +166,24 @@ public partial class HierarchyTreeViewModel : ViewModelBase, IDisposable
if (!this.ViewModelMap.TryGetValue(parentId, out var parentViewModels)) return;
foreach (var parentTag in parentViewModels)
- await Task.Run(() =>
- {
- var foundChild =
- parentTag.Children.FirstOrDefault(t => t.Id == idToDelete)!;
- parentTag.Children.Remove(foundChild);
- });
+ {
+ var foundChild =
+ parentTag.Children.FirstOrDefault(t => t.Id == idToDelete)!;
+ parentTag.Children.Remove(foundChild);
+ }
+
if (!this.ChildNodeMap.TryGetValue(idToDelete, out var parentSet)) return;
parentSet.Remove(parentId);
+ await Task.CompletedTask;
}
private async Task DeleteTopLevelNode(int idToDelete)
{
var topLevelNode = this.TopLevelTagNodes.FirstOrDefault(t => t.Id == idToDelete);
if (topLevelNode is null) return;
- await Task.Run(() => { this.TopLevelTagNodes.Remove(topLevelNode); });
+ this.TopLevelTagNodes.Remove(topLevelNode);
+ await Task.CompletedTask;
}
partial void OnSelectedTagChanged(TagItemViewModel? value)
@@ -193,7 +207,7 @@ public partial class HierarchyTreeViewModel : ViewModelBase, IDisposable
private void SubscribeToEvents()
{
if (!this._mainWindow.TagDatabaseService.IsDatabaseOpen) return;
- this._mainWindow.TagDatabaseService.TagUpdated += this.TagDatabase_OnTagUpdated;
+ this._mainWindow.TagDatabaseService.TagsUpdated += this.TagDatabase_OnTagsUpdated;
this._mainWindow.TagDatabaseService.TagAdded += this.TagDatabase_OnTagAdded;
this._mainWindow.TagDatabaseService.TagDeleted += this.TagDatabase_OnTagDeleted;
this._mainWindow.TagDatabaseService.TagsAdded += this.TagDatabase_OnTagsAdded;
@@ -203,7 +217,7 @@ public partial class HierarchyTreeViewModel : ViewModelBase, IDisposable
{
try
{
- await this.ProcessTagAdditions([newTag]);
+ await Dispatcher.UIThread.InvokeAsync(async () => await this.ProcessTagAdditions([newTag]));
}
catch (Exception e)
{
@@ -216,7 +230,7 @@ public partial class HierarchyTreeViewModel : ViewModelBase, IDisposable
{
try
{
- await this.WipeTagNodes(deletedTag.id);
+ await Dispatcher.UIThread.InvokeAsync(async () => await this.WipeTagNodes(deletedTag.id));
}
catch (Exception e)
{
@@ -238,48 +252,60 @@ public partial class HierarchyTreeViewModel : ViewModelBase, IDisposable
}
}
- private async void TagDatabase_OnTagUpdated(object? sender, Tag updatedTag)
+ private void TagDatabase_OnTagsUpdated(object? sender, List updatedTags)
{
- try
- {
- if (!this.ViewModelMap.TryGetValue(updatedTag.Id, out var tagViewModels)) return;
-
- HashSet oldParents = [];
- HashSet newParents = new(updatedTag.ParentIds);
+ Dispatcher.UIThread.Post(async void () =>
+ {
+ try
+ {
+ foreach (var updatedTag in updatedTags)
+ await this.HandleTagUpdate(updatedTag);
+ }
+ catch (Exception e)
+ {
+ var error = new ErrorDialogViewModel(e.Message);
+ error.ShowDialog();
+ }
+ });
+ }
- // grab old parents if they exist
- if (this.ChildNodeMap.TryGetValue(updatedTag.Id, out var parentList))
- oldParents = [..parentList];
+ private async Task HandleTagUpdate(Tag updatedTag)
+ {
+ if (!this.ViewModelMap.TryGetValue(updatedTag.Id, out var tagViewModels)) return;
+
+
+ foreach (var tag in tagViewModels)
+ {
+ tag.Tag = updatedTag;
+ tag.RefreshSelf(); tag.RefreshParentsString();
+ }
+
+ HashSet oldParents = [];
+ HashSet newParents = new(updatedTag.ParentIds);
- // add/remove parents as necessary
- var removedParents = oldParents.Except(newParents);
- var addedParents = newParents.Except(oldParents);
+ // grab old parents if they exist
+ if (this.ChildNodeMap.TryGetValue(updatedTag.Id, out var parentList))
+ oldParents = [..parentList];
- foreach (var parentId in removedParents)
- await this.DeleteChildNode(parentId, updatedTag.Id);
+ // add/remove parents as necessary
+ var removedParents = oldParents.Except(newParents);
+ var addedParents = newParents.Except(oldParents);
- foreach (var parentId in addedParents)
- await this.AddChildNode(updatedTag, parentId);
+ foreach (var parentId in removedParents)
+ await this.DeleteChildNode(parentId, updatedTag.Id);
- // refresh all tagViewModels
- foreach (var tag in tagViewModels)
- tag.RefreshSelf();
+ foreach (var parentId in addedParents)
+ await this.AddChildNode(updatedTag, parentId);
- // add/remove top level nodes as necessary
- if (updatedTag.IsTopLevel)
- await this.AddTopLevelNode(updatedTag);
- else
- await this.DeleteTopLevelNode(updatedTag.Id);
+ // add/remove top level nodes as necessary
+ if (updatedTag.IsTopLevel)
+ await this.AddTopLevelNode(updatedTag);
+ else
+ await this.DeleteTopLevelNode(updatedTag.Id);
- // clear child node map if tag has no parents
- if (newParents.Count == 0)
- this.ChildNodeMap.Remove(updatedTag.Id);
- }
- catch (Exception e)
- {
- var error = new ErrorDialogViewModel(e.Message);
- error.ShowDialog();
- }
+ // clear child node map if tag has no parents
+ if (newParents.Count == 0)
+ this.ChildNodeMap.Remove(updatedTag.Id);
}
private async Task WipeTagNodes(int idToDelete)
diff --git a/UI/ViewModels/ImportDialogViewModel.cs b/UI/ViewModels/ImportDialogViewModel.cs
index 07fd815..dded2d8 100644
--- a/UI/ViewModels/ImportDialogViewModel.cs
+++ b/UI/ViewModels/ImportDialogViewModel.cs
@@ -39,6 +39,9 @@ public partial class ImportDialogViewModel(MainWindowViewModel mainWindow) : Vie
[RelayCommand]
private async Task InitiateImport()
{
+ var userWantsToSave = await this.MainWindow.ShowUnsavedChangesDialog();
+ if (userWantsToSave is null) return;
+
try
{
if (string.IsNullOrEmpty(this.DatabaseFilePath) || string.IsNullOrEmpty(this.TemplateFilePath)) return;
@@ -63,9 +66,6 @@ public partial class ImportDialogViewModel(MainWindowViewModel mainWindow) : Vie
if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop ||
desktop.MainWindow is null) return;
- var userWantsToSave = await this.MainWindow.ShowUnsavedChangesDialog();
- if (userWantsToSave is null) return;
-
var storageProvider = desktop.MainWindow?.StorageProvider;
if (storageProvider is null) return;
@@ -94,10 +94,7 @@ public partial class ImportDialogViewModel(MainWindowViewModel mainWindow) : Vie
{
if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop ||
desktop.MainWindow is null) return;
-
- var userWantsToSave = await this.MainWindow.ShowUnsavedChangesDialog();
- if (userWantsToSave is null) return;
-
+
var storageProvider = desktop.MainWindow?.StorageProvider;
if (storageProvider is null) return;
diff --git a/UI/ViewModels/MainWindowViewModel.cs b/UI/ViewModels/MainWindowViewModel.cs
index 9988bdc..bb0f028 100644
--- a/UI/ViewModels/MainWindowViewModel.cs
+++ b/UI/ViewModels/MainWindowViewModel.cs
@@ -100,19 +100,26 @@ public partial class MainWindowViewModel : ViewModelBase
}
[RelayCommand]
- public async Task SaveSelectedTagAsync()
+ private async Task SaveSelectedTagAsync()
{
- if (this.SelectedTag is null || !this.TagDatabaseService.IsDatabaseOpen || !this.IsDbEnabled) return;
+ try
+ {
+ if (this.SelectedTag is null || !this.TagDatabaseService.IsDatabaseOpen || !this.IsDbEnabled) return;
- this.SelectedTag.CommitEdit();
- await this.TagDatabaseService.WriteTagsToDatabase([this.SelectedTag.Tag]);
- this.SelectedTag.RefreshParentsString();
- this.StatusBlockText = string.Format(Resources.StatusBlockTagSaveSuccessful, this.SelectedTag.Name);
- this.UnsavedChanges = false;
+ this.SelectedTag.CommitEdit();
+ await this.TagDatabaseService.WriteTagsToDatabase([this.SelectedTag.Tag]);
+ this.SelectedTag.RefreshParentsString();
+ this.StatusBlockText = string.Format(Resources.StatusBlockTagSaveSuccessful, this.SelectedTag.Name);
+ this.UnsavedChanges = false;
+ }
+ catch (Exception ex)
+ {
+ this.ShowErrorDialog(ex.Message);
+ }
}
[RelayCommand]
- public void ShowBulkAddDialog()
+ private void ShowBulkAddDialog()
{
if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
return;
@@ -126,7 +133,7 @@ public partial class MainWindowViewModel : ViewModelBase
}
[RelayCommand]
- public void ShowDatabaseSettings()
+ private void ShowDatabaseSettings()
{
if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
return;
@@ -140,7 +147,7 @@ public partial class MainWindowViewModel : ViewModelBase
}
[RelayCommand]
- public void ShowImportDialog()
+ private void ShowImportDialog()
{
if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
return;
@@ -153,7 +160,7 @@ public partial class MainWindowViewModel : ViewModelBase
dialog.ShowDialog(desktop.MainWindow!);
}
- public async Task ShowNullableBoolDialog(Window dialog)
+ private async Task ShowNullableBoolDialog(Window dialog)
{
if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
return null;
diff --git a/UI/ViewModels/TagItemViewModel.cs b/UI/ViewModels/TagItemViewModel.cs
index 0473ca0..02493c0 100644
--- a/UI/ViewModels/TagItemViewModel.cs
+++ b/UI/ViewModels/TagItemViewModel.cs
@@ -50,7 +50,7 @@ public partial class TagItemViewModel(Tag tag, Func, List>? ge
public ObservableCollection Children { get; set; } = [];
- internal Tag Tag { get; } = tag;
+ internal Tag Tag { get; set; } = tag;
private bool IsTopLevel => this.Tag.IsTopLevel;