using Avalonia; using Avalonia.Controls; using Avalonia.Threading; using Avalonia.VisualTree; using LANCommander.Launcher.ViewModels; namespace LANCommander.Launcher.Controls; /// /// Attached property that saves/restores scroll position to a . /// Usage: <ScrollViewer controls:ScrollPersistence.ViewModel="{Binding}" /> /// On detach the current offset is written to ; /// on attach the offset is restored. /// public class ScrollPersistence : AvaloniaObject { public static readonly AttachedProperty ViewModelProperty = AvaloniaProperty.RegisterAttached("ViewModel"); static ScrollPersistence() { ViewModelProperty.Changed.AddClassHandler(OnViewModelChanged); } public static ViewModelBase? GetViewModel(ScrollViewer sv) => sv.GetValue(ViewModelProperty); public static void SetViewModel(ScrollViewer sv, ViewModelBase? value) => sv.SetValue(ViewModelProperty, value); private static void OnViewModelChanged(ScrollViewer sv, AvaloniaPropertyChangedEventArgs e) { sv.DetachedFromVisualTree -= OnDetached; sv.AttachedToVisualTree -= OnAttached; if (e.NewValue is ViewModelBase) { sv.AttachedToVisualTree += OnAttached; sv.DetachedFromVisualTree += OnDetached; // If already in the visual tree (property set after load), restore now if (sv.GetVisualRoot() != null) RestoreOffset(sv); } } private static void OnAttached(object? sender, VisualTreeAttachmentEventArgs e) { if (sender is ScrollViewer sv) Dispatcher.UIThread.Post(() => RestoreOffset(sv), DispatcherPriority.Loaded); } private static void OnDetached(object? sender, VisualTreeAttachmentEventArgs e) { if (sender is ScrollViewer sv && GetViewModel(sv) is { } vm) vm.SavedScrollOffset = sv.Offset; } private static void RestoreOffset(ScrollViewer sv) { if (GetViewModel(sv) is { } vm && vm.SavedScrollOffset != default) sv.Offset = vm.SavedScrollOffset; } }