かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

「Thumb コントロールで Photoshop のナビゲーターを再現する」をWinRTでやる場合

@Grabacr07さんの素敵な記事!

grabacr.net

WinRTでやるときはこうだよっていうのをちょっとだけ。主にScrollViewerがいけてない部分を補足するだけです。ScrollViewerのScrollChangedイベントはWinRTにはないので、ViewChangedイベントと、LayoutUpdatedイベントを組み合わせて同じような動きを再現させます。コード的には以下のような感じ。

private void ScrollViewer_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
    this.UpdateViewportSize();
}

private void ScrollViewer_LayoutUpdated(object sender, object e)
{
    this.UpdateViewportSize();
}

private void UpdateViewportSize()
{
    var xfactor = this.Thumbnail.ActualWidth / this.ScrollViewer.ExtentWidth;
    var yfactor = this.Thumbnail.ActualHeight / this.ScrollViewer.ExtentHeight;

    var left = this.ScrollViewer.HorizontalOffset * xfactor;
    var top = this.ScrollViewer.VerticalOffset * yfactor;

    var width = this.ScrollViewer.ViewportWidth * xfactor;
    if (width > this.Thumbnail.ActualWidth) { width = this.Thumbnail.ActualWidth; }

    var height = this.ScrollViewer.ViewportHeight * yfactor;
    if (height > this.Thumbnail.ActualHeight) { height = this.Thumbnail.ActualHeight; }

    Canvas.SetTop(this.Viewport, top);
    Canvas.SetLeft(this.Viewport, left);

    this.Viewport.Width = width;
    this.Viewport.Height = height;
}

これだけで、WinRTでも動くようになります。(試したのはWin10TPのUAPだけどたぶん大丈夫でしょう…)

f:id:okazuki:20150411195927p:plain

コード

一応XAMLとC#のコードをはっておきます。

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

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        
        <Canvas Margin="10"
                Width="{Binding Path=ActualWidth, ElementName=Thumbnail}"
                Height="{Binding Path=ActualHeight, ElementName=Thumbnail}"
                HorizontalAlignment="Center">
            <Image x:Name="Thumbnail" Width="100" Stretch="Uniform" Source="ms-appx:///Assets/tree.jpg" />
            <Thumb x:Name="Viewport"
                   DragDelta="Viewport_DragDelta">
                <Thumb.Template>
                    <ControlTemplate TargetType="Thumb">
                        <Border BorderThickness="2"
                                BorderBrush="Red"
                                Background="Transparent" />
                    </ControlTemplate>
                </Thumb.Template>
            </Thumb>
        </Canvas>
        <ScrollViewer x:Name="ScrollViewer" 
                      Grid.Column="1" 
                      VerticalScrollBarVisibility="Auto" 
                      HorizontalScrollBarVisibility="Auto"
                      ViewChanged="ScrollViewer_ViewChanged"
                      LayoutUpdated="ScrollViewer_LayoutUpdated">
            <Image Source="ms-appx:///Assets/tree.jpg" />
        </ScrollViewer>
    </Grid>
</Page>
using System;
using System.Collections.Generic;
using System.Diagnostics;
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;

// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409

namespace App24
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
        }

        private void ScrollViewer_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
        {
            this.UpdateViewportSize();
        }

        private void ScrollViewer_LayoutUpdated(object sender, object e)
        {
            this.UpdateViewportSize();
        }

        private void UpdateViewportSize()
        {
            var xfactor = this.Thumbnail.ActualWidth / this.ScrollViewer.ExtentWidth;
            var yfactor = this.Thumbnail.ActualHeight / this.ScrollViewer.ExtentHeight;

            var left = this.ScrollViewer.HorizontalOffset * xfactor;
            var top = this.ScrollViewer.VerticalOffset * yfactor;

            var width = this.ScrollViewer.ViewportWidth * xfactor;
            if (width > this.Thumbnail.ActualWidth) { width = this.Thumbnail.ActualWidth; }

            var height = this.ScrollViewer.ViewportHeight * yfactor;
            if (height > this.Thumbnail.ActualHeight) { height = this.Thumbnail.ActualHeight; }

            Canvas.SetTop(this.Viewport, top);
            Canvas.SetLeft(this.Viewport, left);

            this.Viewport.Width = width;
            this.Viewport.Height = height;
        }

        private void Viewport_DragDelta(object sender, DragDeltaEventArgs e)
        {
            this.ScrollViewer.ScrollToHorizontalOffset(
                this.ScrollViewer.HorizontalOffset + (e.HorizontalChange * this.ScrollViewer.ExtentWidth / this.Viewport.ActualWidth));
            this.ScrollViewer.ScrollToVerticalOffset(
                this.ScrollViewer.VerticalOffset + (e.VerticalChange * this.ScrollViewer.ExtentHeight / this.Viewport.ActualHeight));
        }
    }
}