using System; using System.Linq; using System.Threading.Tasks; using CommunityToolkit.Diagnostics; using Microsoft.Toolkit.Uwp.UI.Controls; using StrixMusic.Sdk.WinUI.Controls.Items.Abstract; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls.Primitives; namespace StrixMusic.Sdk.WinUI.Controls.Collections.Abstract { /// /// A Templated Control base for showing items with progressive loading. /// /// The data type. /// The container type. [TemplatePart(Name = nameof(PART_Selector), Type = typeof(Selector))] public abstract partial class CollectionControl : Control where TData : class where TItem : ItemControl { /// /// The backing for the property. /// public static readonly DependencyProperty EmptyContentProperty = DependencyProperty.Register( nameof(EmptyContent), typeof(FrameworkElement), typeof(CollectionControl), new PropertyMetadata(null, (d, e) => ((CollectionControl)d).SetNoContentTemplate((FrameworkElement)e.NewValue))); /// /// Fired when the selected item changes /// public event EventHandler>? SelectionChanged; /// protected override void OnApplyTemplate() { base.OnApplyTemplate(); Loaded += CollectionControl_Loaded; } /// /// The content to show when this is empty. /// public FrameworkElement EmptyContent { get => (FrameworkElement)GetValue(EmptyContentProperty); set => SetValue(EmptyContentProperty, value); } private Control? PART_Selector { get; set; } private ScrollViewer? PART_Scroller { get; set; } private ContentPresenter? PART_EmptyContentPresenter { get; set; } /// /// Clears all selected items. /// public void ClearSelected() { if (PART_Selector == null) return; if (PART_Selector is Selector selector) selector.SelectedItem = null; else if (PART_Selector is DataGrid dataGrid) dataGrid.SelectedItem = null; } /// /// Perform incremental loading. /// /// A representing the asynchronous operation. protected abstract Task LoadMore(); /// /// Checks if the collection has no content. /// protected abstract void CheckAndToggleEmpty(); /// /// Sets the visibility of the empty content. /// protected void SetEmptyVisibility(Visibility visibility) { Guard.IsNotNull(PART_EmptyContentPresenter, nameof(PART_EmptyContentPresenter)); PART_EmptyContentPresenter.Visibility = visibility; } private void AttachHandlers() { Unloaded += CollectionControl_Unloaded; if (PART_Selector == null || PART_Scroller == null) return; if (PART_Selector is Selector selector) selector.SelectionChanged += SelectedItemChanged; else if (PART_Selector is DataGrid dataGrid) dataGrid.SelectionChanged += SelectedItemChanged; else return; PART_Scroller.ViewChanged += CollectionControl_ViewChanged; } private void DetachHandlers() { Loaded -= CollectionControl_Loaded; if (PART_Selector == null || PART_Scroller == null) return; if (PART_Selector is Selector selector) selector.SelectionChanged -= SelectedItemChanged; else if (PART_Selector is DataGrid dataGrid) dataGrid.SelectionChanged -= SelectedItemChanged; else return; PART_Scroller!.ViewChanged -= CollectionControl_ViewChanged; } private void CollectionControl_Loaded(object sender, RoutedEventArgs e) { // Find and set Selector PART_Selector = VisualTreeHelpers.GetDataTemplateChild(this, nameof(PART_Selector)); if (PART_Selector == null) { PART_Selector = VisualTreeHelpers.GetDataTemplateChild(this, nameof(PART_Selector)); } // Find and set Scroller PART_Scroller = VisualTreeHelpers.GetDataTemplateChild(this); AttachHandlers(); _ = LoadMore(); } private void CollectionControl_Unloaded(object sender, RoutedEventArgs e) { DetachHandlers(); } private void CollectionControl_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e) { CheckScrollPosition(); } private void CheckScrollPosition() { if (PART_Scroller == null) return; double fromBottom = PART_Scroller.ScrollableHeight - PART_Scroller.VerticalOffset; // If approaching the bottom of the list if (fromBottom < 100) { _ = LoadMore(); } } private void SelectedItemChanged(object sender, SelectionChangedEventArgs e) { foreach (var item in e.AddedItems) { TItem? container = GetItemFromData(item); if (container != null) { container.Selected = true; } } foreach (var item in e.RemovedItems) { TItem? container = GetItemFromData(item); if (container != null) { container.Selected = false; } } if (PART_Selector == null) return; // Get selected item // Invoke event TData data; if (PART_Selector is Selector selector) data = (TData)selector.SelectedItem; else if (PART_Selector is DataGrid dataGrid) data = (TData)dataGrid.SelectedItem; else return; Events.SelectionChangedEventArgs selectionChangedEventArgs = new Events.SelectionChangedEventArgs(data); SelectionChanged?.Invoke(this, selectionChangedEventArgs); } private TItem? GetItemFromData(object data) { if (PART_Selector is Selector selector) return VisualTreeHelpers.FindVisualChildren(selector.ContainerFromItem(data)).FirstOrDefault(); return null; } private void SetNoContentTemplate(FrameworkElement frameworkElement) { if (PART_EmptyContentPresenter != null) { PART_EmptyContentPresenter.Content = frameworkElement; } } } }