From be6d689fb54b78f55bc4ae6524a5cd503b2be916 Mon Sep 17 00:00:00 2001
From: Daniel Lerch <36048059+daniel-lerch@users.noreply.github.com>
Date: Sun, 4 Dec 2022 01:09:55 +0100
Subject: [PATCH] Improve data bindings and view model composition
---
src/Vocup.Core/ViewModels/BookViewModel.cs | 21 ++++++++++++++++++
.../ViewModels/MainFormViewModel.cs | 6 +++--
src/Vocup/Forms/PrintWordSelection.cs | 22 ++++++++++++++++---
src/Vocup/MainForm.cs | 10 +++++----
4 files changed, 50 insertions(+), 9 deletions(-)
create mode 100644 src/Vocup.Core/ViewModels/BookViewModel.cs
diff --git a/src/Vocup.Core/ViewModels/BookViewModel.cs b/src/Vocup.Core/ViewModels/BookViewModel.cs
new file mode 100644
index 0000000..1f19cae
--- /dev/null
+++ b/src/Vocup.Core/ViewModels/BookViewModel.cs
@@ -0,0 +1,21 @@
+using ReactiveUI;
+using Vocup.IO;
+using Vocup.Models;
+using Vocup.Settings;
+
+namespace Vocup.ViewModels;
+
+///
+/// Represents an opened vocabulary book. Multiple instances can be used simultaneously to implement tabs for books.
+///
+public class BookViewModel : ReactiveObject
+{
+ public BookViewModel(BookContext bookContext, IVocupSettings settings)
+ {
+ Book = bookContext.Book;
+ BookContext = bookContext;
+ }
+
+ public Book Book { get; }
+ public BookContext BookContext { get; }
+}
diff --git a/src/Vocup.Core/ViewModels/MainFormViewModel.cs b/src/Vocup.Core/ViewModels/MainFormViewModel.cs
index 002a6ef..4bd019f 100644
--- a/src/Vocup.Core/ViewModels/MainFormViewModel.cs
+++ b/src/Vocup.Core/ViewModels/MainFormViewModel.cs
@@ -23,7 +23,7 @@ public MainFormViewModel(IVocupSettings settings)
OpenCommand = ReactiveCommand.CreateFromTask(OpenCommandAction);
SaveCommand = ReactiveCommand.CreateFromTask(SaveCommandAction,
- this.WhenAnyValue(x => x.BookContext, x => x.BookContext.UnsavedChanges, (_, _) => BookContext?.UnsavedChanges ?? false));
+ this.WhenAnyValue(x => x.BookContext, x => x.BookContext!.UnsavedChanges, (_, _) => BookContext?.UnsavedChanges ?? false));
SaveAsCommand = ReactiveCommand.CreateFromTask(SaveAsCommandAction, this.WhenAnyValue(x => x.BookContext).Select(x => x != null));
CloseCommand = ReactiveCommand.CreateFromTask(CloseCommandAction, this.WhenAnyValue(x => x.BookContext).Select(x => x != null));
PracticeCommand = ReactiveCommand.CreateFromTask(PracticeCommandAction, this.WhenAnyValue(x => x.BookContext.Book.Unpracticed).Select(x => x > 0));
@@ -31,7 +31,7 @@ public MainFormViewModel(IVocupSettings settings)
BookSettingsCommand = ReactiveCommand.CreateFromTask(BookSettingsCommandAction, this.WhenAnyValue(x => x.BookContext).Select(x => x != null));
PrintCommand = ReactiveCommand.CreateFromTask(PrintCommandAction, this.WhenAnyValue(x => x.BookContext.Book.Words.Count).Select(x => x > 0));
OpenInExplorerCommand = ReactiveCommand.CreateFromTask(OpenInExplorerCommandAction,
- this.WhenAnyValue(x => x.BookContext, x => x.BookContext.FilePath, (_, _) => BookContext?.FilePath != null));
+ this.WhenAnyValue(x => x.BookContext, x => x.BookContext!.FilePath, (_, _) => BookContext?.FilePath != null));
this.WhenAnyValue(x => x.BookContext, x => x.BookContext!.FilePath, (_, _) => BookContext?.FilePath)
.Select(x => x == null ? "Vocup" : $"Vocup - {Path.GetFileNameWithoutExtension(x)}")
@@ -47,7 +47,9 @@ public MainFormViewModel(IVocupSettings settings)
.ToPropertyEx(this, x => x.FullyPracticed);
}
+ // TODO: Replace this nullable property with BookViewModel to have less nullability issues.
[Reactive] public BookContext? BookContext { get; set; }
+ [Reactive] public string SearchText { get; set; } = string.Empty;
[ObservableAsProperty] public string Title { get; } = "Vocup";
[ObservableAsProperty] public int Unpracticed { get; }
[ObservableAsProperty] public int WronglyPracticed { get; }
diff --git a/src/Vocup/Forms/PrintWordSelection.cs b/src/Vocup/Forms/PrintWordSelection.cs
index 056cb26..59f4959 100644
--- a/src/Vocup/Forms/PrintWordSelection.cs
+++ b/src/Vocup/Forms/PrintWordSelection.cs
@@ -26,7 +26,7 @@ public PrintWordSelection(BookContext bookContext)
ListBox.BeginUpdate();
foreach (Word word in book.Words)
- ListBox.Items.Add($"{word.MotherTongueCombined} - {word.ForeignLanguageCombined}", true);
+ ListBox.Items.Add(new WordListItem(word), true);
ListBox.EndUpdate();
CbUnpracticed.Enabled = book.Unpracticed > 0;
@@ -106,9 +106,10 @@ private void SetItemsChecked(Func predicate, bool value)
{
ListBox.BeginUpdate();
- for (int i = 0; i < book.Words.Count; i++)
+ for (int i = 0; i < ListBox.Items.Count; i++)
{
- if (predicate(book.Words[i]))
+ WordListItem item = (WordListItem)ListBox.Items[i];
+ if (predicate(item.Word))
{
ListBox.SetItemChecked(i, value);
}
@@ -204,4 +205,19 @@ private void PrintList_PrintPage(object sender, System.Drawing.Printing.PrintPag
pageNumber++;
}
+
+ private class WordListItem
+ {
+ public WordListItem(Word word)
+ {
+ Word = word;
+ }
+
+ public Word Word { get; }
+
+ public override string ToString()
+ {
+ return $"{Word.MotherTongueCombined} - {Word.ForeignLanguageCombined}";
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Vocup/MainForm.cs b/src/Vocup/MainForm.cs
index d6a14d8..d769097 100644
--- a/src/Vocup/MainForm.cs
+++ b/src/Vocup/MainForm.cs
@@ -27,6 +27,8 @@ public MainForm()
ViewModel = new MainFormViewModel(Program.Settings);
#pragma warning disable CA1416 // Validate platform compatibility
+ this.Bind(ViewModel, vm => vm.SearchText, x => x.TbSearchWord.Text);
+
this.OneWayBind(ViewModel, vm => vm.Title, x => x.Text);
this.OneWayBind(ViewModel, vm => vm.BookContext, x => x.GroupBook.Enabled, context => context != null);
@@ -38,10 +40,10 @@ public MainForm()
this.OneWayBind(ViewModel, vm => vm.BookContext.Book.Words.Count, x => x.GroupSearch.Enabled, count => count > 0);
this.OneWayBind(ViewModel, vm => vm.BookContext.Book.Words.Count, x => x.TsmiExport.Enabled, count => count > 0);
- this.OneWayBind(ViewModel, vm => vm.BookContext.Book.Unpracticed, x => x.StatisticsPanel.Unpracticed);
- this.OneWayBind(ViewModel, vm => vm.BookContext.Book.WronglyPracticed, x => x.StatisticsPanel.WronglyPracticed);
- this.OneWayBind(ViewModel, vm => vm.BookContext.Book.CorrectlyPracticed, x => x.StatisticsPanel.CorrectlyPracticed);
- this.OneWayBind(ViewModel, vm => vm.BookContext.Book.FullyPracticed, x => x.StatisticsPanel.FullyPracticed);
+ this.OneWayBind(ViewModel, vm => vm.Unpracticed, x => x.StatisticsPanel.Unpracticed);
+ this.OneWayBind(ViewModel, vm => vm.WronglyPracticed, x => x.StatisticsPanel.WronglyPracticed);
+ this.OneWayBind(ViewModel, vm => vm.CorrectlyPracticed, x => x.StatisticsPanel.CorrectlyPracticed);
+ this.OneWayBind(ViewModel, vm => vm.FullyPracticed, x => x.StatisticsPanel.FullyPracticed);
this.BindCommand(ViewModel, vm => vm.OpenCommand, x => x.TsmiOpenBook);
this.BindCommand(ViewModel, vm => vm.OpenCommand, x => x.TsbOpenBook);