Windows 10 April 2018 Update (version 1803)*1でUWPにも引っ張って更新コントロールが標準実装されました。Windowsストアアプリと言われていた時代から数えて、約6年かかりました…
使いですが、docs.comを参照するのが手っ取り早いです。
Pull-to-refresh - UWP app developer | Microsoft Docs
このページに記載のサンプルをみると、なんだかうじゃうじゃ書いてますが必要なところだけ切り出すとこれだけです。
<RefreshContainer> <ListView /> </RefreshContainer>
サンプルにHeight
プロパティが設定されていたのが気になったのですが、これを指定しなくてもとりあえず問題にはならないようです。*2
今回実装されたRefreshContainerは、引っ張れればなんでもOKなのでListView以外に、今回追加されたTreeViewや、独自実装のItemsControl、ItemsControlですらないCanvasやGridでもOKです。 引っ張る方向も、上から下、右から左…と4種類前パターン選択可能です。
次に更新イベントのハンドリングですが、こんな感じになります。
private async void RefreshContainer_OnRefreshRequested(RefreshContainer sender, RefreshRequestedEventArgs args) { using (args.GetDeferral()) { await _vm.Refresh(); } }
using GetDeferral()
の中で更新処理を呼び出す。するとusing
を抜けるまで、画面上で更新中のステートが表示される。という仕組みです。ここにDeferral突っ込んでくるなんて、UWP的だなぁと思いました。
さて、実践投入したいのはやまやまなんですが、このコントロールが動くのは17134以降。リリースされてからまだ3日。さすがにターゲットバージョンを17134にはできない。ということで、15063でこっそり追加されていた"条件付きXAML"という機能を使って
17134未満は"Windows Community Toolkit"のPullToRefreshListView
を、17134以上はRefreshContainer
を使うようにしてみます。
プロジェクトには"Microsoft.Toolkit.Uwp.UI.Controls"をNuGetからインストールしておいてください。
プロジェクトのプロパティでターゲットバージョンを17134、最小バージョンを15063にします。
17134はRefreshContainer
に必要、15063は"条件付きXAML"に必要です。
次にXAML書きます。
<Page x:Class="App2.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:is16299="using:Microsoft.Toolkit.Uwp.UI.Controls?IsApiContractNotPresent(Windows.Foundation.UniversalApiContract, 6)" xmlns:is17134="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 6)" mc:Ignorable="d"> <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <is16299:PullToRefreshListView RefreshRequested="PullToRefreshListView_OnRefreshRequested" ItemsSource="{Binding Items}"/> <is17134:RefreshContainer RefreshRequested="RefreshContainer_OnRefreshRequested" x:Name="RefreshContainer"> <ListView ItemsSource="{Binding Items}" /> </is17134:RefreshContainer> </Grid> </Page>
?IsApiContractPresent
とかついてるあたりが条件付きXAML、この場合は条件付き名前空間と呼びます。is16299の行が波線になったりしますが実行できます。大丈夫です。
またReSharperが有効な環境では、条件付き名前空間が指定されたタグの中ではコード補完が効かなくなりますが大丈夫です。ちゃんと実行できます。
さらにさらに、条件付き名前空間のタグにx:Nameで名前を付けると、C#からその名前は見つかるけどメンバがReSharperのコード補完に出てこない。とかも起きますがちゃんと実行できます。コンパイルも大丈夫です。
ここまでで環境によって表示されるListViewを切り替えることができたので、更新処理とかを書いておきます。
using System; using System.Collections.ObjectModel; using System.Threading.Tasks; using Windows.UI.Xaml.Controls; namespace App2 { public sealed partial class MainPage : Page { public class ViewModel { public ObservableCollection<string> Items { get; } = new ObservableCollection<string>(new[] { "0", "1", "2" }); public async Task Refresh() { await Task.Delay(2000); Items.Insert(0, DateTime.Now.ToString()); } } private ViewModel _vm = new ViewModel(); public MainPage() { this.InitializeComponent(); DataContext = _vm; } private async void RefreshContainer_OnRefreshRequested(RefreshContainer sender, RefreshRequestedEventArgs args) { using (args.GetDeferral()) { await _vm.Refresh(); } } private async void PullToRefreshListView_OnRefreshRequested(object sender, EventArgs e) { _vm.Refresh(); } } }
PullToRefreshListViewは更新処理をCommandで書けるのでそちらを使うのがいいかと思います。 RefreshContainerはイベントしかないので、x:Bindでイベントバインディング*3するといいんじゃないでしょうか。
ItemTemplateもリソースに切り出して共有すればそんなに面倒なことにもならなさそうです。RefreshContainerと条件付きXAML、ぜひ活用してください。
*1:ビルド17134
*2:例えば仮想化されなくなる。とか考えられましたが外してみても特に問題はなかったです。
*3:https://docs.microsoft.com/ja-jp/windows/uwp/xaml-platform/x-bind-markup-extension#event-binding