読者です 読者をやめる 読者になる 読者になる

みかづきメモ

学習したことのメモとか、日記とか、備忘録。

ListView が空の時に、何かを表示する

先日、Twitterを眺めてたら、こんな記事が流れてきました。

qiita.com

少し前に、自分の作成している SNS クライアントでも同様のものを実装していたので、
軽く実装方法をメモっておきます。

もしかしたら、これよりもいい方法が在るかもしれません。


ListView に対して、 ItemsSource が空の場合に、任意のコントロールを表示します。
Reactive Extensions を使用していますので、 NuGet から入れておきます。

まずは Behavior を追加します。
UWP 標準だと Behavior`T は存在しないので、WinRTXamlToolKit などを参照しておいてください。

public class EmptyItemsBehavior : Behavior<ListView>
{
    public static readonly DependencyProperty TargetProperty =
        DependencyProperty.Register(
            nameof (Target),
            typeof (string),
            typeof (EmptyItemsBehavior),
            new PropertyMetadata(string.Empty));

    private int _count;
    private IDisposable _disposable;

    public string Target
    {
        get { return (string)GetValue(TargetProperty); }
        set { SetValue(TargetProperty, value); }
    }

    public EmptyListBehavior()
    {
        this._count = 0;
    }

    protected override void OnLoaded()
    {
        base.OnLoaded();
        var source = this.AssociatedObject.ItemsSource as INotifyCollectionChanged;
        if (source != null)
        {
            this._disposable = source.ToObservable().Subscribe(w =>
            {
                switch (w.EventArgs.Action)
                {
                    case NotifyCollectionChangedAction.Add:
                        this._count += w.EventArgs.NewItems.Count;
                        break;

                    case NotifyCollectionChangedAction.Remove:
                        this._count -= w.EventArgs.OldItems.Count;
                        break;

                    case NotifyCollectionChangedAction.Replace:
                    case NotifyCollectionChangedAction.Move:
                        break;

                    case NotifyCollectionChangedAction.Reset:
                        this._count = 0;
                        break;
                }
                this.Action();
            });
        }
    }

    protected override void OnUnloaded()
    {
        this._disposable.Dispose();
        base.OnUnloaded();
    }

    private void Action()
    {
        var frameworkElement = (FrameworkElement)((FrameworkElement)this.AssociatedObject.Parent).FindName(this.Target);
        if (frameworkElement != null)
        {
            if (this._count > 0)
            {
                this.Visibility = Visibility.Visible;
                frameworkElement.Visibility = Visibility.Collapsed;
            }
            else
            {
                this.Visibility = Visibility.Collapsed;
                frameworkElement.Visibility = Visibility.Visible;
            }
        }
    }
}


多少無理矢理感が否めませんが、次は XAML の方です。

<UserControl x:Class="Test.TestPage"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:behaviors="using:Test.Behaviors"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:i="using:WinRTXamlToolkit.Interactivity">
    <Grid>
        <TextBlock x:Name="NoItemsMessage" Text="No Tweets" />
        <ListView ItemsSource="{Binding Items}">
            <i:Interaction.Behaviors>
                <behaviors:EmptyItemsBehavior Target="NoItemsMessage" />
            </i:Interaction.Behaviors>
            <ListView.ItemTemplate>
                <DataTemplate>
                    <!-- いろいろ -->
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</UserControl>

こうしておけば、 ItemsSource が空の場合は、 No Tweets というメッセージが表示されます。
コントロールを直接 Binding 出来るような気がしますが、眠いのでこのままにしておきます。

先に貼った iOS のやつみたいにいろいろはできませんが、単純なものならこんな感じでいいかなと。