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);