diff --git a/Source/FileManager/BackgroundFileSystem.cs b/Source/FileManager/BackgroundFileSystem.cs index e4ec7db2..fc3e1033 100644 --- a/Source/FileManager/BackgroundFileSystem.cs +++ b/Source/FileManager/BackgroundFileSystem.cs @@ -72,8 +72,8 @@ private void Init() fileSystemWatcher.Renamed += FileSystemWatcher_Changed; fileSystemWatcher.Error += FileSystemWatcher_Error; - backgroundScanner = new Task(BackgroundScanner); - backgroundScanner.Start(); + backgroundScanner = Task.Factory.StartNew(BackgroundScanner, TaskCreationOptions.LongRunning); + //backgroundScanner.Start(); } private void Stop() { diff --git a/Source/LibationUiBase/ViewModels/Player/PlayerEvents.cs b/Source/LibationUiBase/ViewModels/Player/PlayerEvents.cs index b8b0eb13..9a95d5cc 100644 --- a/Source/LibationUiBase/ViewModels/Player/PlayerEvents.cs +++ b/Source/LibationUiBase/ViewModels/Player/PlayerEvents.cs @@ -8,7 +8,6 @@ public class BookAddedToPlaylist : PubSubEvent public ILibraryBookEntry Book { get; } public BookAddedToPlaylist() {} - public BookAddedToPlaylist(ILibraryBookEntry book) { Book = book; @@ -20,7 +19,6 @@ public class BookRemovedFromPlaylist : PubSubEvent public ILibraryBookEntry Book { get; } public BookRemovedFromPlaylist() {} - public BookRemovedFromPlaylist(ILibraryBookEntry book) { Book = book; diff --git a/Source/LibationUiBase/ViewModels/Player/PlayerViewModel.cs b/Source/LibationUiBase/ViewModels/Player/PlayerViewModel.cs index 3040b59b..76e137f3 100644 --- a/Source/LibationUiBase/ViewModels/Player/PlayerViewModel.cs +++ b/Source/LibationUiBase/ViewModels/Player/PlayerViewModel.cs @@ -8,8 +8,6 @@ namespace LibationUiBase.ViewModels.Player; public class PlayerViewModel : ViewModelBase { - const string CurrentIndicator = "▶"; - private readonly IEventAggregator eventAggregator; private BindingList playlistItems = new(); /// @@ -37,20 +35,28 @@ public PlayerViewModel(IEventAggregator eventAggregator) { playlistItems.MoveUp(SelectedBook); RenumberPlaylist(); - }, _ => SelectedBook != null && playlistItems.IndexOf(SelectedBook) > 0); + var temp = SelectedBook; + SelectedBook = null; + SelectedBook = temp; + }, _ => SelectedBook != null && playlistItems.IndexOf(SelectedBook) > 0 && + IsInPlaylist(SelectedBook)); MoveDownCommand = new RelayCommand(_ => { playlistItems.MoveDown(SelectedBook); RenumberPlaylist(); - }, _ => SelectedBook != null && playlistItems.IndexOf(SelectedBook) < playlistItems.Count - 1); + var temp = SelectedBook; + SelectedBook = null; + SelectedBook = temp; + }, _ => SelectedBook != null && playlistItems.IndexOf(SelectedBook) < playlistItems.Count - 1 && + IsInPlaylist(SelectedBook)); this.eventAggregator = eventAggregator; } private void RenumberPlaylist() { - for (var i = 0; playlistItems.Count > 0; i++) + for (var i = 0; i < playlistItems.Count; i++) playlistItems[i].Sequence = i + 1; } @@ -60,7 +66,7 @@ public async ValueTask AddToPlaylist(ILibraryBookEntry book) await plevm.Init(book); plevm.Sequence = playlistItems.Count + 1; playlistItems.Add(plevm); - eventAggregator.GetEvent().Publish(book); + //eventAggregator.GetEvent().Publish(book); } public ValueTask RemoveFromPlaylist(ILibraryBookEntry book) @@ -69,27 +75,36 @@ public ValueTask RemoveFromPlaylist(ILibraryBookEntry book) if (plevm != null) { playlistItems.Remove(plevm); - eventAggregator.GetEvent().Publish(book); + InvalidateCommands(); + //eventAggregator.GetEvent().Publish(book); } return ValueTask.CompletedTask; } + private void InvalidateCommands() + { + MoveDownCommand.RaiseCanExecuteChanged(); + MoveUpCommand.RaiseCanExecuteChanged(); + } + public bool IsInPlaylist(ILibraryBookEntry book) => PlaylistItems.Any(item => item.BookEntry.AudibleProductId == book.AudibleProductId); - public override string ToString() => - string.Join(", ", playlistItems); + public bool IsInPlaylist(PlaylistEntryViewModel book) => + PlaylistItems.Any(item => item.BookEntry.AudibleProductId == book.BookEntry.AudibleProductId); + + + public override string ToString() => $"{nameof(PlayerViewModel)}: {string.Join(", ", playlistItems)}"; protected override void OnPropertyChanging(string propertyName) { switch (propertyName) { case nameof(SelectedBook): - //SelectedBook.IsCurrent = false; - //SelectedBook.IsCurrentStr = null; - MoveUpCommand.RaiseCanExecuteChanged(); - MoveDownCommand.RaiseCanExecuteChanged(); + if (SelectedBook != null) + SelectedBook.IsCurrent = false; + InvalidateCommands(); break; } } @@ -99,11 +114,10 @@ protected override void OnPropertyChanged(string propertyName) switch (propertyName) { case nameof(SelectedBook): - MoveDownCommand.RaiseCanExecuteChanged(); - MoveUpCommand.RaiseCanExecuteChanged(); - //SelectedBook.IsCurrent = true; - //SelectedBook.IsCurrentStr = CurrentIndicator; + if (SelectedBook != null) + SelectedBook.IsCurrent = true; + InvalidateCommands(); break; } } -} \ No newline at end of file +} diff --git a/Source/LibationUiBase/ViewModels/Player/PlaylistEntryViewModel.cs b/Source/LibationUiBase/ViewModels/Player/PlaylistEntryViewModel.cs index 1d8f6c99..eadb180d 100644 --- a/Source/LibationUiBase/ViewModels/Player/PlaylistEntryViewModel.cs +++ b/Source/LibationUiBase/ViewModels/Player/PlaylistEntryViewModel.cs @@ -5,6 +5,8 @@ namespace LibationUiBase.ViewModels.Player; public class PlaylistEntryViewModel : ViewModelBase { + const string CurrentIndicator = "▶"; + private int sequence; public int Sequence { @@ -50,4 +52,21 @@ public async ValueTask Init(ILibraryBookEntry bookEntry) } public override string ToString() => Title; + + protected override void OnPropertyChanged(string propertyName) + { + switch (propertyName) + { + case nameof(IsCurrent): + IsCurrentStr = IsCurrent ? CurrentIndicator : null; + break; + } + } + + public override bool Equals(object obj) + { + if (obj is PlaylistEntryViewModel plevm && bookEntry != null) + return bookEntry?.AudibleProductId == plevm?.bookEntry.AudibleProductId; + return false; + } } \ No newline at end of file diff --git a/Source/LibationUiBase/ViewModels/ViewModelBase.cs b/Source/LibationUiBase/ViewModels/ViewModelBase.cs index bae4abc5..7795bb37 100644 --- a/Source/LibationUiBase/ViewModels/ViewModelBase.cs +++ b/Source/LibationUiBase/ViewModels/ViewModelBase.cs @@ -8,8 +8,8 @@ public abstract class ViewModelBase : INotifyPropertyChanged, INotifyPropertyCha { public ViewModelBase() { - this.PropertyChanged += (s, e) => OnPropertyChanged(e.PropertyName); - this.PropertyChanging += (s, e) => OnPropertyChanging(e.PropertyName); + this.PropertyChanged += (_, e) => OnPropertyChanged(e.PropertyName); + this.PropertyChanging += (_, e) => OnPropertyChanging(e.PropertyName); } protected virtual void OnPropertyChanging(string propertyName) { } @@ -17,7 +17,7 @@ protected virtual void OnPropertyChanged(string propertyName) { } public TRet RaiseAndSetIfChanged(ref TRet backingField, TRet newValue, [CallerMemberName] string propertyName = null) { - //if (EqualityComparer.Default.Equals(backingField, newValue)) return newValue; + if (EqualityComparer.Default.Equals(backingField, newValue)) return newValue; RaisePropertyChanging(propertyName); backingField = newValue; diff --git a/Source/LibationWinForms/GridView/DataGridViewEx.cs b/Source/LibationWinForms/GridView/DataGridViewEx.cs index a1fde966..3577338b 100644 --- a/Source/LibationWinForms/GridView/DataGridViewEx.cs +++ b/Source/LibationWinForms/GridView/DataGridViewEx.cs @@ -1,157 +1,200 @@ using System; +using System.ComponentModel; using System.Linq.Expressions; using System.Windows.Forms; -namespace LibationWinForms.GridView +namespace LibationWinForms.GridView; + +/// +/// A DataGridView with a bindable SelectedItem property. +/// +public class DataGridViewEx : DataGridView, INotifyPropertyChanged { + private BindingSource bindingSource; + + private object selectedItem; /// - /// A DataGridView with a bindable SelectedItem property. + /// Can bind to this. /// - public class DataGridViewEx : DataGridView + public object SelectedItem { - private BindingSource bindingSource; + get => DbNullToNull(selectedItem); + set => SetCurrentItem(DbNullToNull(value)); + } - // Can bind to this! - public object SelectedItem - { - get => GetCurrent(); - set => SetCurrentItem(value); - } + public DataGridViewEx() + { + EditMode = DataGridViewEditMode.EditProgrammatically; + MultiSelect = false; + ColumnHeadersDefaultCellStyle.SelectionBackColor = + ColumnHeadersDefaultCellStyle.BackColor; + SelectionMode = DataGridViewSelectionMode.FullRowSelect; + this.DataSourceChanged += DataGridViewEx_DataSourceChanged; + } - public DataGridViewEx() + public void AddSelectedItemBinding(T vm, Expression> selectedItemProperty) + { + try { - ColumnHeadersDefaultCellStyle.SelectionBackColor = - ColumnHeadersDefaultCellStyle.BackColor; - SelectionMode = DataGridViewSelectionMode.FullRowSelect; - this.DataSourceChanged += DataGridViewEx_DataSourceChanged; + var prop = GetPropertyName(selectedItemProperty); + this.DataBindings.Add(nameof(SelectedItem), vm, prop); } - - public void AddSelectedItemBinding(T vm, Expression> selectedItemProperty) + catch (Exception e) { - try - { - var prop = GetPropertyName(selectedItemProperty); - this.DataBindings.Add(nameof(SelectedItem), vm, prop); - } - catch (Exception e) - { - Console.WriteLine(e); - throw; - } + Console.WriteLine(e); + throw; } + } - static string GetPropertyName(Expression> propertyExpression) - { - MemberExpression memberExpression = propertyExpression.Body as MemberExpression; + static string GetPropertyName(Expression> propertyExpression) + { + var memberExpression = propertyExpression.Body as MemberExpression; - if (memberExpression == null) + if (memberExpression == null) + { + if (propertyExpression.Body is UnaryExpression unaryExpression) { - if (propertyExpression.Body is UnaryExpression unaryExpression) - { - memberExpression = unaryExpression.Operand as MemberExpression; - } + memberExpression = unaryExpression.Operand as MemberExpression; } + } - if (memberExpression != null) - return memberExpression.Member.Name; + if (memberExpression != null) + return memberExpression.Member.Name; - throw new ArgumentException("Expression is not a member access", nameof(propertyExpression)); - } + throw new ArgumentException($"Property expression is not a member access: {propertyExpression}", nameof(propertyExpression)); + } - private void DataGridViewEx_DataSourceChanged(object sender, EventArgs e) + private void DataGridViewEx_DataSourceChanged(object sender, EventArgs e) + { + try { - try + // Remove events from old binding source. + if (this.bindingSource is not null) { - if (this.bindingSource is not null) - this.bindingSource.CurrencyManager.CurrentChanged -= CurrencyManager_CurrentChanged; - - if (DataSource is BindingSource bs) - { - if (bindingSource != bs) - { - this.bindingSource = bs; - bs.CurrencyManager.CurrentChanged += CurrencyManager_CurrentChanged; - } - } - else - throw new InvalidOperationException($"{nameof(DataGridViewEx)} data source must be a BindingSource in order to handle currency"); + this.bindingSource.CurrencyManager.PositionChanged -= CurrencyManager_PositionChanged; + this.bindingSource.CurrencyManager.ListChanged -= CurrencyManager_ListChanged; } - catch (Exception exception) + + if (DataSource is BindingSource bs) { - Console.WriteLine(exception); - throw; + // Set up events on new binding source. + this.bindingSource = bs; + bs.CurrencyManager.PositionChanged += CurrencyManager_PositionChanged; + bs.CurrencyManager.ListChanged += CurrencyManager_ListChanged; + this.DataBindings.DefaultDataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged; + RaisePropertyChanged(nameof(SelectedItem)); } + else + throw new InvalidOperationException($"{nameof(DataGridViewEx)} data source must be a BindingSource in order to handle selection."); } - - private void CurrencyManager_CurrentChanged(object sender, EventArgs e) + catch (Exception exception) { - try - { - // Set SelectedItem property - object current = GetCurrent(); - if (current != SelectedItem) - SelectedItem = current; - } - catch (Exception exception) - { - Console.WriteLine(exception); - throw; - } + Console.WriteLine(exception); + throw; } + } - private object GetCurrent() + private void CurrencyManager_PositionChanged(object sender, EventArgs args) + { + var current = GetCurrent(); + if (current != SelectedItem) + SelectedItem = current; + } + + private void CurrencyManager_ListChanged(object sender, ListChangedEventArgs args) + { + if (args.ListChangedType == ListChangedType.Reset || + args.ListChangedType == ListChangedType.ItemDeleted) { - try + if (Rows.Count > 0 && SelectedRows.Count == 0) { - return bindingSource.Count == 0 || - bindingSource.CurrencyManager.Position == -1 - ? null - : bindingSource.CurrencyManager.Current; + this.bindingSource.Position = 0; } - catch (Exception e) - { - Console.WriteLine(e); - throw; - } + if (SelectedRows.Count > 0) + BeginInvoke(new MethodInvoker(() => + { + if (SelectedRows.Count > 0) + SelectedItem = SelectedRows[0].DataBoundItem; + })); + } + } + private object GetCurrent() + { + try + { + return bindingSource.Count == 0 || + bindingSource.CurrencyManager.Position == -1 || + bindingSource.CurrencyManager.Position >= Rows.Count + ? null + : DbNullToNull(Rows[bindingSource.CurrencyManager.Position].DataBoundItem); } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + } - private void SetCurrentItem(object dataItem) + private void SetCurrentItem(object dataItem) + { + try { - try + if (dataItem == null) { - if (dataItem == null) + if (selectedItem != null) { - if (SelectedItem != null) - { - SelectedItem = null; - ClearSelection(); - } - return; + selectedItem = null; + if (SelectedRows.Count > 0) + BeginInvoke(new MethodInvoker(ClearSelection)); } + return; + } - if (SelectedItem != dataItem) + selectedItem = dataItem; + + for (var index = 0; index < Rows.Count; index++) + { + var row = Rows[index]; + + // Change the physically selected row in the grid. + if (CurrentRow == null || + row.DataBoundItem == dataItem) { - foreach (DataGridViewRow row in Rows) - { - if (row.DataBoundItem == dataItem) - { - SelectedItem = dataItem; - CurrentCell = row.Cells[0]; - return; - } - } - if (SelectedItem != null) - { - SelectedItem = null; - ClearSelection(); - } + BeginInvoke(new MethodInvoker(() => + CurrentCell = row.Cells[0])); + return; } } - catch (Exception e) - { - Console.WriteLine(e); - throw; - } } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + finally + { + RaisePropertyChanged(nameof(SelectedItem)); + } + } + + private object DbNullToNull(object dataItem) + { + return dataItem is DBNull ? null: dataItem; + } + + public event PropertyChangedEventHandler PropertyChanged; + private void RaisePropertyChanged(string propertyName) + { + try + { + var ev = new PropertyChangedEventArgs(propertyName); + PropertyChanged?.Invoke(this, ev); + } + catch (Exception e) + { + Console.WriteLine(e); + // Eat DBNull conversion bug. + } + } -} +} \ No newline at end of file diff --git a/Source/LibationWinForms/GridView/ProductsGrid.cs b/Source/LibationWinForms/GridView/ProductsGrid.cs index 5f3c9a9b..37254fe2 100644 --- a/Source/LibationWinForms/GridView/ProductsGrid.cs +++ b/Source/LibationWinForms/GridView/ProductsGrid.cs @@ -23,7 +23,7 @@ namespace LibationWinForms.GridView public partial class ProductsGrid : UserControl { - IEventAggregator eventAggregator; + //IEventAggregator eventAggregator; PlayerViewModel _player = ServiceLocator.Get(); /// Number of visible rows has changed @@ -73,7 +73,7 @@ public ProductsGrid() Configuration.Instance.PropertyChanged -= Configuration_FontScaleChanged; }; - eventAggregator = ServiceLocator.Get(); + //eventAggregator = ServiceLocator.Get(); //eventAggregator.GetEvent().Subscribe(InvalidateBookEntry, ThreadOption.PublisherThread); //eventAggregator.GetEvent().Subscribe(InvalidateBookEntry, ThreadOption.PublisherThread); } @@ -609,21 +609,21 @@ private void gridEntryDataGridView_CellClick(object sender, DataGridViewCellEven } } - //private void InvalidateBookEntry(ILibraryBookEntry bookEntry) - //{ - - // for (int row = 0; row < gridEntryDataGridView.Rows.Count; row++) - // { - // if (gridEntryDataGridView.Rows[row].DataBoundItem is ILibraryBookEntry be && - // be.AudibleProductId == bookEntry.AudibleProductId) - // { - // //bindingList.ResetItem(row); - // //gridEntryDataGridView.InvalidateRow(row); - // //playlistColumn.AutoSizeMode = DataGridViewAutoSizeColumnMode.None; - // //playlistColumn.AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells; - // return; - // } - // } - //} + private void InvalidateBookEntry(ILibraryBookEntry bookEntry) + { + + for (int row = 0; row < gridEntryDataGridView.Rows.Count; row++) + { + if (gridEntryDataGridView.Rows[row].DataBoundItem is ILibraryBookEntry be && + be.AudibleProductId == bookEntry.AudibleProductId) + { + //bindingList.ResetItem(row); + //gridEntryDataGridView.InvalidateRow(row); + playlistColumn.AutoSizeMode = DataGridViewAutoSizeColumnMode.None; + playlistColumn.AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells; + return; + } + } + } } }