ideaki's blog

WinRT C#/XAML の開発について

UWP GridView/ListViewのスクロール位置を復元したい

探すとたくさんあるスクロール位置の復元ですが

  • ViewModel:スクロール位置の保持
  • View:スクロール位置の復元/監視

と役割分担が明確になっているサンプルがなかったのでそれっぽく作りました。

がっちゃんこできるBehaviorで実装

以下、
1. スクロール位置復元できるBehavior
2. 使ってみよう
3. サンプルsln


1. スクロール位置復元できるBehavior

public class ScrollOffsetControlBehavior : DependencyObject, IBehavior
{
    public int RestoreScrollOffset
    {
        get { return (int)GetValue(RestoreScrollOffsetProperty); }
        set { SetValue(RestoreScrollOffsetProperty, value); }
    }

    // Using a DependencyProperty as the backing store for RestoreScrollOffset.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty RestoreScrollOffsetProperty =
        DependencyProperty.Register("RestoreScrollOffset", typeof(int), typeof(ScrollOffsetControlBehavior), new PropertyMetadata(0));

    int CurrentItemViewIndex
    {
        get
        {
            var listviewbase = AssociatedObject as ListViewBase;
            var item = listviewbase.ItemsPanelRoot as ItemsStackPanel;
            return item.FirstVisibleIndex > 0 ? item.FirstVisibleIndex : 0;
        }
    }

    public DependencyObject AssociatedObject
    {
        get;
        set;
    }

    private ScrollViewer sv;

    public void Attach(DependencyObject associatedObject)
    {
        if ((associatedObject != AssociatedObject) && !Windows.ApplicationModel.DesignMode.DesignModeEnabled)
        {
            AssociatedObject = associatedObject;
            var listviewbase = AssociatedObject as ListViewBase;
            if (listviewbase != null)
            {
                listviewbase.DataContextChanged += Itemscontrol_DataContextChanged;
                listviewbase.Loaded += Listviewbase_Loaded;
            }
        }
    }

    private void Listviewbase_Loaded(object sender, RoutedEventArgs e)
    {
        var listviewbase = sender as ListViewBase;
        AutomationPeer ap = ListViewAutomationPeer.CreatePeerForElement(listviewbase);
        ScrollViewerAutomationPeer avap = (ScrollViewerAutomationPeer)ap.GetPattern(PatternInterface.Scroll);
        sv = (ScrollViewer)avap.Owner;
        sv.ViewChanged += sv_ViewChanged;
    }

    private void Itemscontrol_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
    {
        if (args.NewValue == null) return;
        var listviewbase = AssociatedObject as ListViewBase;
        listviewbase.ScrollIntoView(listviewbase.Items.ElementAt(RestoreScrollOffset), ScrollIntoViewAlignment.Leading);
    }

    private void sv_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
    {
        if (e.IsIntermediate) return;
        RestoreScrollOffset = CurrentItemViewIndex;
    }

    public void Detach()
    {
        var listviewbase = AssociatedObject as ListViewBase;
        listviewbase.Loaded -= Listviewbase_Loaded;
        sv.ViewChanged -= sv_ViewChanged;
        sv = null;
    }
}

2. 使ってみよう
MainPage.xaml

<Page
    x:Class="App1.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App1"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
      xmlns:Interactions="using:Microsoft.Xaml.Interactions.Core"
      mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="2*" />
            <ColumnDefinition Width="3*" />
        </Grid.ColumnDefinitions>
        <ListView x:Name="MasterListView"
                  ItemsSource="{x:Bind ViewModel.DetailViewModels}">
            <ListView.ItemTemplate>
                <DataTemplate x:DataType="local:DetailViewModel">
                    <TextBlock Text="{x:Bind Title}" />
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
        <ContentPresenter x:Name="DetailContent"
                          Grid.Column="1"
                          Content="{x:Bind Path=MasterListView.SelectedItem, Mode=OneWay}">
            <ContentPresenter.ContentTemplate>
                <DataTemplate x:Name="ListTemplate"
                              x:DataType="local:DetailViewModel">
                    <ListView ItemsSource="{x:Bind Items}">
                        <Interactivity:Interaction.Behaviors>
                            <local:ScrollOffsetControlBehavior RestoreScrollOffset="{x:Bind ReadOffset, Mode=TwoWay}" />
                        </Interactivity:Interaction.Behaviors>
                    </ListView>
                </DataTemplate>
            </ContentPresenter.ContentTemplate>
        </ContentPresenter>
    </Grid>
</Page>

MainPage.xaml.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

// 空白ページのアイテム テンプレートについては、http://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409 を参照してください

namespace App1
{
    /// <summary>
    /// それ自体で使用できる空白ページまたはフレーム内に移動できる空白ページ。
    /// </summary>
    public sealed partial class MainPage : Page
    {
        MainPageViewModel ViewModel => new MainPageViewModel();

        public MainPage()
        {
            this.InitializeComponent();
        }
    }

    public class MainPageViewModel
    {
        public List<DetailViewModel> DetailViewModels { get; set; }

        

        public MainPageViewModel()
        {
            DetailViewModels = Enumerable.Range(1, 10).Select(i => new DetailViewModel(i)).ToList();
        }
    }
    
    public class DetailViewModel
    {
        public int ReadOffset { get; set; }
        public string Title { get; set; }
        public List<string> Items { get; set; }

        public DetailViewModel(int num)
        {
            Title = num.ToString();
            Items = Enumerable.Range(1, 100).Select(i => $"{num} : {i}").ToList();
        }
    }
}

3. サンプルsln
onedrive.live.com

UWP エクスプローラからのドラッグ&ドロップ

channel9.msdn.com
ユーザーは待っていたドラッグアンドドロップ

したサンプル

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" AllowDrop="True" DragOver="Grid_DragOver"Drop="Grid_Drop">
    <TextBlock x:Name="AATextBlock" FontFamily="MS PGothic" Text="Drag &amp; Drop Text File"  VerticalAlignment="Center" HorizontalAlignment="Center"/>
    <FontIcon/>
private void Grid_DragOver(object sender, DragEventArgs e)
{
    e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Copy;
    e.Handled = true;
}

private async void Grid_Drop(object sender, DragEventArgs e)
{
    var d = e.GetDeferral();

    var files = await e.DataView.GetStorageItemsAsync();
    var file = files.First();

    this.AATextBlock.Text = await FileIO.ReadTextAsync(file as IStorageFile);

    d.Complete();
}

はてなAPI ブックマークコメントのパーマリンクを取得する

はてなブックマークエントリー情報取得APIのレスポンスから作成することができます。

http://b.hatena.ne.jp/{user}/{timestamp}/#bookmark-{eid}

使い道として、
はてなスターカウントAPIブコメのリンクを指定して人気コメントの抽出とか。

#region 宣伝

はてなびゅぅゎぁ | Windows Phone Apps+Games Store (United States)
#endoregion

C# 動的なキーのJSONをデシリアライズする

はてなびゅぅゎぁのコードを部分的に紹介する記事。
#region 宣伝
はてなびゅぅゎぁ公開中です。

はてなびゅぅゎぁ | Windows Phone Apps+Games Store (United States)
#endregion

WP向けに公開中のはてなびゅぅゎぁはてなブックマークAPIを使用してます。
↓あたり

続きを読む

WinRT コードビハインド書かずにWebViewにプログレスバーつける

WebViewがコンテンツをロード中にプログレスバーを出しておくとユーザに親切だったりします。
ただこれ作るのにコードビハインド書かなくちゃいけなくて、めんどうだなあと思ってました。
実はXAMLだけで簡単に書けるみたいです。

<Border>
 <VisualStateManager.VisualStateGroups>
  <VisualStateGroup x:Name="WebViewVisualStateGroup">
   <VisualState x:Name="contentloading">
    <Storyboard>
     <ObjectAnimationUsingKeyFrames Storyboard.TargetName="progressBar" Storyboard.TargetProperty="(ProgressBar.IsIndeterminate)">
      <DiscreteObjectKeyFrame KeyTime="0">
       <DiscreteObjectKeyFrame.Value>
        <x:Boolean>True</x:Boolean>
       </DiscreteObjectKeyFrame.Value>
      </DiscreteObjectKeyFrame>
     </ObjectAnimationUsingKeyFrames>
     <FadeInThemeAnimation TargetName="border" />
    </Storyboard>
   </VisualState>
   <VisualState x:Name="contentloaded">
    <Storyboard>
     <FadeOutThemeAnimation TargetName="border" />
    </Storyboard>
   </VisualState>
  </VisualStateGroup>
 </VisualStateManager.VisualStateGroups>
 <Grid>
  <Grid.RowDefinitions>
   <RowDefinition Height="20" />
   <RowDefinition Height="1*" />
  </Grid.RowDefinitions>
  <WebView x:Name="WebViewObj"
     Grid.RowSpan="2"
     Source="{Binding SelectFeedItem.Uri}">
   <Interactivity:Interaction.Behaviors>
    <Core:EventTriggerBehavior EventName="NavigationCompleted">
     <Core:GoToStateAction StateName="contentloaded" />
    </Core:EventTriggerBehavior>
    <Core:EventTriggerBehavior EventName="NavigationStarting">
     <Core:GoToStateAction StateName="contentloading" />
    </Core:EventTriggerBehavior>
    <Core:EventTriggerBehavior EventName="NavigationFailed">
     <Core:GoToStateAction StateName="contentloaded" />
    </Core:EventTriggerBehavior>
   </Interactivity:Interaction.Behaviors>
  </WebView>
  <Border x:Name="border" Background="#B2F0F8FF">
   <ProgressBar x:Name="progressBar" />
  </Border>
 </Grid>
</Border>

参考になるエントリ

Blend for VS2013 RCにXAMLでのBehavior復活!!(ただしWindows 8.1のみのもよう) - かずきのBlog@hatena

MVVM開発をさらに加速させる ノンコーディングUI開発 - SSSSLIDE

WinRT C#/XAML お手軽に実装できる引っ張って更新コントロールの紹介

これは XAML Advent Calendar 2014 16日目の記事です。

@ideaki19です。WindowsストアをメインにC#/XAMLでアプリ開発を2年続けています。
2ちゃんねる専用ブラウザ sankaWindowsストアアプリで公開しています。
この記事ではsankaの一部機能を紹介します。
タイトルにがっつりネタバレしてありますが、引っ張って更新です!

続きを読む