ListView/GridViewで表示範囲のItemVMのみを列挙する

[ソースコードだけ確認したい場合はこちら - Github gist](https://gist.github.com/tor4kichi/d46358618e577090eac84f3686ee3223)

## 用途：表示範囲内に限定して更新処理したい

- 例え1000件程度のデータだろうと表示範囲外コンテンツの更新処理が行われないようにしたい場合。


## 方法：ListViewBase.ContainerContentChanging イベントの活用

### InRecycleQueue で表示範囲の内外を判定

`ContainerContentChanging` イベントの `InRecycleQueue` が `true` の場合、表示範囲外にアイテムが出たと判定できます。

```csharp
private void CutListItemsListView_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
{
    var item = args.Item as StoryboardCutViewModel;
    if (args.InRecycleQueue)
    {
        _vm.RealizedVMItems.Remove(item);
        Debug.WriteLine($"{item} InRecycleQueue");
    }
    else
    {
        _vm.RealizedVMItems.Add(item);    
        Debug.WriteLine($"{item} Displaying!");
    }
}
```

### カスタム添付プロパティとして実装

```csharp:ListViewExtensions_RealizedCollection.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

#nullable enable
namespace SampleApp.Views.Extensions;

public static partial class ListViewExtensions
{
        public static ICollection<object>? GetRealizedCollection(DependencyObject obj)
    {
        return (ICollection<object>?)obj.GetValue(RealizedCollectionProperty);
    }

    public static void SetRealizedCollection(DependencyObject obj, ICollection<object> value)
    {
        obj.SetValue(RealizedCollectionProperty, value);
    }

    public static readonly DependencyProperty RealizedCollectionProperty =
        DependencyProperty.RegisterAttached("RealizedCollection", typeof(ICollection<object>), typeof(ListViewExtensions), new PropertyMetadata(null, OnRealizedCollectionChanged));

    private static void OnRealizedCollectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var list = (ListViewBase)d;
        if (e.OldValue is ICollection<object> oldCollection)
        {
            list.ContainerContentChanging -= List_ContainerContentChanging;
        }

        if (e.NewValue is ICollection<object> collection)
        {
            list.ContainerContentChanging += List_ContainerContentChanging;
        }
        
        static void List_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
        {
            var collection = GetRealizedCollection(sender);
            if (collection == null)
            {
                sender.ContainerContentChanging -= List_ContainerContentChanging;
                return;
            }

            if (args.InRecycleQueue)
            {
                collection.Remove(args.Item);
            }
            else
            {
                collection.Add(args.Item);
            }
        }
    }
}

```

DataContextに任意のViewModelインスタンスがセットされてる状態で、以下のように使用します。
```xml
<ListView
    ItemsSource="{Binding DisplayItems}"
    xmlns:myViewExt="using:SampleApp.Views.Extensions"
    myViewExt:ListViewExtensions.RealizedCollection="{Binding RealizedVMItems}"
    />
```


## おまけ：表示範囲にIn/Outしたタイミングでコマンドを実行するカスタム添付プロパティ

```csharp: ListViewExtensions_RealizedCommand.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

#nullable enable
namespace SampleApp.Views.Extensions;

public static partial class ListViewExtensions
{
    public static ICommand? GetRealizedCommand(DependencyObject obj)
    {
        return (ICommand?)obj.GetValue(RealizedCommandProperty);
    }
    
    public static void SetRealizedCommand(DependencyObject obj, ICommand? value)
    {
        obj.SetValue(RealizedCommandProperty, value);
    }
    
    public static readonly DependencyProperty RealizedCommandProperty =
        DependencyProperty.RegisterAttached("RealizedCommand", typeof(ICommand), typeof(ListViewExtensions), new PropertyMetadata(null, OnRealizeCommandPropertyChanged));
    
    
    public static ICommand? GetDerealizedCommand(DependencyObject obj)
    {
        return (ICommand?)obj.GetValue(DerealizedCommandProperty);
    }
    
    public static void SetDerealizedCommand(DependencyObject obj, ICommand? value)
    {
        obj.SetValue(DerealizedCommandProperty, value);
    }
    
    public static readonly DependencyProperty DerealizedCommandProperty =
        DependencyProperty.RegisterAttached("DerealizedCommand", typeof(ICommand), typeof(ListViewExtensions), new PropertyMetadata(null, OnRealizeCommandPropertyChanged));
    
    
    private static void OnRealizeCommandPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var list = (ListViewBase)d;
        if (e.OldValue is ICommand oldCommand)
        {
            list.ContainerContentChanging -= ForCommand_ContainerContentChanging;
        }
    
        if (e.NewValue is ICommand newCommand)
        {
            list.ContainerContentChanging -= ForCommand_ContainerContentChanging;
            list.ContainerContentChanging += ForCommand_ContainerContentChanging;
        }
    }
    
    private static void ForCommand_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
    {
        if ((args.InRecycleQueue ? GetDerealizedCommand(sender) : GetRealizedCommand(sender)) is { } command
            && command.CanExecute(args.Item)
            )
        {
            command.Execute(args.Item);
        }
    }
}

```