diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs b/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs index 89bcf31c..3045d2e2 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs @@ -22,6 +22,49 @@ namespace LibationWinForms.ProcessQueue public static Color QueuedColor = SystemColors.Control; public static Color SuccessColor = Color.PaleGreen; + private ProcessBookViewModelBase m_Context; + public ProcessBookViewModelBase Context + { + get => m_Context; + set + { + if (m_Context != value) + { + OnContextChanging(); + m_Context = value; + OnContextChanged(); + } + } + } + + private void OnContextChanging() + { + if (Context is not null) + Context.PropertyChanged -= Context_PropertyChanged; + } + + private void OnContextChanged() + { + Context.PropertyChanged += Context_PropertyChanged; + Context_PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(null)); + } + + private void Context_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + SuspendLayout(); + if (e.PropertyName is null or nameof(Context.Cover)) + SetCover(Context.Cover as Image); + if (e.PropertyName is null or nameof(Context.Title) or nameof(Context.Author) or nameof(Context.Narrator)) + SetBookInfo($"{Context.Title}\r\nBy {Context.Author}\r\nNarrated by {Context.Narrator}"); + if (e.PropertyName is null or nameof(Context.Status) or nameof(Context.StatusText)) + SetStatus(Context.Status, Context.StatusText); + if (e.PropertyName is null or nameof(Context.Progress)) + SetProgress(Context.Progress); + if (e.PropertyName is null or nameof(Context.TimeRemaining)) + SetRemainingTime(Context.TimeRemaining); + ResumeLayout(); + } + public ProcessBookControl() { InitializeComponent(); diff --git a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.Designer.cs b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.Designer.cs index 477c8a39..85fdb856 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.Designer.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.Designer.cs @@ -162,7 +162,6 @@ this.virtualFlowControl2.Name = "virtualFlowControl2"; this.virtualFlowControl2.Size = new System.Drawing.Size(390, 424); this.virtualFlowControl2.TabIndex = 3; - this.virtualFlowControl2.VirtualControlCount = 0; // // panel1 // diff --git a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs index 50bc57c9..7a67d31f 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs @@ -1,9 +1,6 @@ using LibationFileManager; using LibationUiBase; -using LibationUiBase.ProcessQueue; using System; -using System.Collections; -using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Drawing; @@ -34,12 +31,11 @@ internal partial class ProcessQueueControl : UserControl numericUpDown1.Value = speedLimitMBps > numericUpDown1.Maximum || speedLimitMBps < numericUpDown1.Minimum ? 0 : speedLimitMBps; statusStrip1.Items.Add(PopoutButton); - virtualFlowControl2.RequestData += VirtualFlowControl1_RequestData; virtualFlowControl2.ButtonClicked += VirtualFlowControl2_ButtonClicked; ViewModel.LogWritten += (_, text) => WriteLine(text); ViewModel.PropertyChanged += ProcessQueue_PropertyChanged; - ViewModel.BookPropertyChanged += ProcessBook_PropertyChanged; + virtualFlowControl2.Items = ViewModel.Items; Load += ProcessQueueControl_Load; } @@ -60,15 +56,13 @@ internal partial class ProcessQueueControl : UserControl ViewModel.Queue.ClearQueue(); if (ViewModel.Queue.Current is not null) await ViewModel.Queue.Current.CancelAsync(); - virtualFlowControl2.VirtualControlCount = ViewModel.Queue.Count; - UpdateAllControls(); + virtualFlowControl2.RefreshDisplay(); } private void btnClearFinished_Click(object? sender, EventArgs e) { ViewModel.Queue.ClearCompleted(); - virtualFlowControl2.VirtualControlCount = ViewModel.Queue.Count; - UpdateAllControls(); + virtualFlowControl2.RefreshDisplay(); if (!ViewModel.Running) runningTimeLbl.Text = string.Empty; @@ -92,22 +86,13 @@ internal partial class ProcessQueueControl : UserControl #region View-Model update event handling - private void ProcessBook_PropertyChanged(object? sender, PropertyChangedEventArgs e) - { - if (sender is not ProcessBookViewModel pbvm) - return; - - int index = ViewModel.Queue.IndexOf(pbvm); - UpdateControl(index, e.PropertyName); - } - private void ProcessQueue_PropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName is null or nameof(ViewModel.QueuedCount)) { queueNumberLbl.Text = ViewModel.QueuedCount.ToString(); queueNumberLbl.Visible = ViewModel.QueuedCount > 0; - virtualFlowControl2.VirtualControlCount = ViewModel.Queue.Count; + virtualFlowControl2.RefreshDisplay(); } if (e.PropertyName is null or nameof(ViewModel.ErrorCount)) { @@ -134,107 +119,41 @@ internal partial class ProcessQueueControl : UserControl } } - /// - /// Index of the first visible in the - /// - private int FirstVisible = 0; - /// - /// Number of visible in the - /// - private int NumVisible = 0; - /// - /// Controls displaying the state, starting with - /// - private IReadOnlyList? Panels; - - /// - /// Updates the display of a single at within - /// - /// index of the within the - /// The nme of the property that needs updating. If null, all properties are updated. - private void UpdateControl(int queueIndex, string? propertyName = null) - { - try - { - int i = queueIndex - FirstVisible; - - if (Panels is null || i > NumVisible || i < 0) return; - - var proc = ViewModel.Queue[queueIndex]; - - Invoke(() => - { - Panels[i].SuspendLayout(); - if (propertyName is null or nameof(proc.Cover)) - Panels[i].SetCover(proc.Cover as Image); - if (propertyName is null or nameof(proc.Title) or nameof(proc.Author) or nameof(proc.Narrator)) - Panels[i].SetBookInfo($"{proc.Title}\r\nBy {proc.Author}\r\nNarrated by {proc.Narrator}"); - if (propertyName is null or nameof(proc.Status) or nameof(proc.StatusText)) - Panels[i].SetStatus(proc.Status, proc.StatusText); - if (propertyName is null or nameof(proc.Progress)) - Panels[i].SetProgress(proc.Progress); - if (propertyName is null or nameof(proc.TimeRemaining)) - Panels[i].SetRemainingTime(proc.TimeRemaining); - Panels[i].ResumeLayout(); - }); - } - catch (Exception ex) - { - Serilog.Log.Logger.Error(ex, "Error updating the queued item's display."); - } - } - - private void UpdateAllControls() - { - int numToShow = Math.Min(NumVisible, ViewModel.Queue.Count - FirstVisible); - - for (int i = 0; i < numToShow; i++) - UpdateControl(FirstVisible + i); - } - /// /// View notified the model that a botton was clicked /// - /// index of the within - /// The clicked control to update - private async void VirtualFlowControl2_ButtonClicked(int queueIndex, string buttonName, ProcessBookControl panelClicked) + /// the whose button was clicked + /// The name of the button clicked + private async void VirtualFlowControl2_ButtonClicked(object? sender, string buttonName) { + if (sender is not ProcessBookControl control || control.Context is not ProcessBookViewModel item) + return; + try { - var item = ViewModel.Queue[queueIndex]; - if (buttonName == nameof(panelClicked.cancelBtn)) + if (buttonName is nameof(ProcessBookControl.cancelBtn)) { - if (item is not null) + await item.CancelAsync(); + ViewModel.Queue.RemoveQueued(item); + virtualFlowControl2.RefreshDisplay(); + } + else + { + QueuePosition? position = buttonName switch { - await item.CancelAsync(); - if (ViewModel.Queue.RemoveQueued(item)) - virtualFlowControl2.VirtualControlCount = ViewModel.Queue.Count; + nameof(ProcessBookControl.moveFirstBtn) => QueuePosition.Fisrt, + nameof(ProcessBookControl.moveUpBtn) => QueuePosition.OneUp, + nameof(ProcessBookControl.moveDownBtn) => QueuePosition.OneDown, + nameof(ProcessBookControl.moveLastBtn) => QueuePosition.Last, + _ => null + }; + + if (position is not null) + { + ViewModel.Queue.MoveQueuePosition(item, position.Value); + virtualFlowControl2.RefreshDisplay(); } } - else if (buttonName == nameof(panelClicked.moveFirstBtn)) - { - ViewModel.Queue.MoveQueuePosition(item, QueuePosition.Fisrt); - UpdateAllControls(); - } - else if (buttonName == nameof(panelClicked.moveUpBtn)) - { - ViewModel.Queue.MoveQueuePosition(item, QueuePosition.OneUp); - UpdateControl(queueIndex); - if (queueIndex > 0) - UpdateControl(queueIndex - 1); - } - else if (buttonName == nameof(panelClicked.moveDownBtn)) - { - ViewModel.Queue.MoveQueuePosition(item, QueuePosition.OneDown); - UpdateControl(queueIndex); - if (queueIndex + 1 < ViewModel.Queue.Count) - UpdateControl(queueIndex + 1); - } - else if (buttonName == nameof(panelClicked.moveLastBtn)) - { - ViewModel.Queue.MoveQueuePosition(item, QueuePosition.Last); - UpdateAllControls(); - } } catch(Exception ex) { @@ -242,17 +161,6 @@ internal partial class ProcessQueueControl : UserControl } } - /// - /// View needs updating - /// - private void VirtualFlowControl1_RequestData(int firstIndex, int numVisible, IReadOnlyList panelsToFill) - { - FirstVisible = firstIndex; - NumVisible = numVisible; - Panels = panelsToFill; - UpdateAllControls(); - } - #endregion private void numericUpDown1_ValueChanged(object? sender, EventArgs e) diff --git a/Source/LibationWinForms/ProcessQueue/ProcessQueueViewModel.cs b/Source/LibationWinForms/ProcessQueue/ProcessQueueViewModel.cs index 6c4afd63..e8fb4d1e 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessQueueViewModel.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessQueueViewModel.cs @@ -1,11 +1,7 @@ using DataLayer; using LibationUiBase.ProcessQueue; using System; -using System.Collections; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Linq; +using System.Collections.Generic; #nullable enable namespace LibationWinForms.ProcessQueue; @@ -13,62 +9,16 @@ namespace LibationWinForms.ProcessQueue; internal class ProcessQueueViewModel : ProcessQueueViewModelBase { public event EventHandler? LogWritten; - /// - /// Fires when a ProcessBookViewModelBase in the queue has a property changed - /// - public event EventHandler? BookPropertyChanged; - private ObservableCollection Items { get; } + public List Items { get; } - public ProcessQueueViewModel() : base(CreateEmptyList()) + public ProcessQueueViewModel() : base(new List()) { - Items = Queue.UnderlyingList as ObservableCollection + Items = Queue.UnderlyingList as List ?? throw new ArgumentNullException(nameof(Queue.UnderlyingList)); - Items.CollectionChanged += Items_CollectionChanged; } - private void Items_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) - { - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - subscribe(e.NewItems); - break; - case NotifyCollectionChangedAction.Remove: - unubscribe(e.OldItems); - break; - } - - void subscribe(IList? items) - { - foreach (var item in e.NewItems?.OfType() ?? []) - item.PropertyChanged += Item_PropertyChanged; - } - - void unubscribe(IList? items) - { - foreach (var item in e.NewItems?.OfType() ?? []) - item.PropertyChanged -= Item_PropertyChanged; - } - } - - private void Item_PropertyChanged(object? sender, PropertyChangedEventArgs e) - => BookPropertyChanged?.Invoke(sender, e); - public override void WriteLine(string text) => Invoke(() => LogWritten?.Invoke(this, text.Trim())); protected override ProcessBookViewModelBase CreateNewProcessBook(LibraryBook libraryBook) => new ProcessBookViewModel(libraryBook, Logger); - - private static ObservableCollection CreateEmptyList() - => new ProcessBookCollection(); - - private class ProcessBookCollection : ObservableCollection - { - protected override void ClearItems() - { - //ObservableCollection doesn't raise Remove for each item on Clear, so we need to do it ourselves. - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, this)); - base.ClearItems(); - } - } } diff --git a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs index e0cadbc7..4149476b 100644 --- a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs +++ b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs @@ -1,44 +1,42 @@ -using System; +using LibationUiBase.ProcessQueue; +using System; using System.Collections.Generic; using System.Drawing; using System.Windows.Forms; namespace LibationWinForms.ProcessQueue { - - internal delegate void RequestDataDelegate(int queueIndex, int numVisible, IReadOnlyList panelsToFill); - internal delegate void ControlButtonClickedDelegate(int queueIndex, string buttonName, ProcessBookControl panelClicked); internal partial class VirtualFlowControl : UserControl { - /// - /// Triggered when the needs to update the displayed s - /// - public event RequestDataDelegate RequestData; /// /// Triggered when one of the 's buttons has been clicked /// - public event ControlButtonClickedDelegate ButtonClicked; + public event EventHandler ButtonClicked; + + private List m_Items; + public List Items + { + get => m_Items; + set + { + m_Items = value; + if (m_Items is not null) + RefreshDisplay(); + } + } + + public void RefreshDisplay() + { + AdjustScrollBar(); + DoVirtualScroll(); + } #region Dynamic Properties /// /// The number of virtual s in the /// - public int VirtualControlCount - { - get => _virtualControlCount; - set - { - if (_virtualControlCount == 0) - vScrollBar1.Value = 0; - - _virtualControlCount = value; - AdjustScrollBar(); - DoVirtualScroll(); - } - } - - private int _virtualControlCount; + public int VirtualControlCount => Items?.Count ?? 0; int ScrollValue => Math.Max(vScrollBar1.Value, 0); /// @@ -100,12 +98,7 @@ namespace LibationWinForms.ProcessQueue { InitializeComponent(); - panel1.Resize += (_, _) => - { - AdjustScrollBar(); - DoVirtualScroll(); - }; - + panel1.Resize += (_, _) => RefreshDisplay(); var control = InitControl(0); VirtualControlHeight = this.DpiUnscale(control.Height + control.Margin.Top + control.Margin.Bottom); @@ -151,9 +144,7 @@ namespace LibationWinForms.ProcessQueue while (form is not ProcessBookControl) form = form.Parent; - int clickedIndex = BookControls.IndexOf((ProcessBookControl)form); - - ButtonClicked?.Invoke(FirstVisibleVirtualIndex + clickedIndex, button.Name, BookControls[clickedIndex]); + ButtonClicked?.Invoke(form, button.Name); } /// @@ -186,7 +177,8 @@ namespace LibationWinForms.ProcessQueue /// /// Calculated the virtual controls that are in view at the currrent scroll position and windows size, - /// positions to simulate scroll activity, then fires to notify the model to update all visible controls + /// positions to simulate scroll activity, then fires updates the controls with + /// the context corresponding to the virtual scroll position /// private void DoVirtualScroll() { @@ -203,10 +195,15 @@ namespace LibationWinForms.ProcessQueue numVisible = Math.Min(numVisible, VirtualControlCount); numVisible = Math.Min(numVisible, VirtualControlCount - firstVisible); - RequestData?.Invoke(firstVisible, numVisible, BookControls); + for (int i = 0; i < numVisible; i++) + { + BookControls[i].Context = Items[firstVisible + i]; + } for (int i = 0; i < BookControls.Count; i++) + { BookControls[i].Visible = i < numVisible; + } } ///