Quantcast
Channel: C# – grabacr.nét
Viewing all articles
Browse latest Browse all 30

Thumb コントロールで Photoshop のナビゲーターを再現する

$
0
0

@kurosawa0626 さんが Photoshop のナビゲーター的なものを作りたい、という話をしていて、過去に似たようなものを作ったことがあったので共有してみます。 記事内のコードは WPF で書いたものですが、WinRT でも一部を除いてほぼ同じようなコードで書けるはずです。

2015/04/12 追記:
@okazuki さんが、WinRT でやる場合の補足記事を書いてくださいました。
http://okazuki.hatenablog.com/entry/2015/04/11/195941

目標

今回は、Thumb コントロールを活用し、Photoshop のナビゲーターと同等のものを作ります。 ScrollViewer に表示されているコンテンツについて、以下を実現しましょう。

  • サムネイルの表示
  • Viewport (赤枠部分) の表示
  • 赤枠のドラッグで Viewport の位置変更

↓ みたいなやつ

Photoshop

最終的に完成したコードは、gist で公開しています。 WPF アプリケーションを作成し、MainWindow.xaml と MainWindow.xaml.cs を組み込めば実行できます。

ScrollViewer とか

大きなコンテンツを画面内に収めたいときは、ScrollViewer コントロールを使用しましょう。 表示領域よりもコンテンツのサイズの方が大きいとき、ScrollViewer はスクロール バーを表示して、可視領域を移動できるようになります。

このとき、実際に見えている領域のことを Viewport と呼びます。 それに対し、ScrollViewer 内の全域 (= コンテンツのサイズ) は Extent です。 それぞれ、ViewportWidth、ViewportHeight、ExtentWidth、ExtentHeight プロパティでサイズを取得できます。

余談ですが、ListBox などの ItemsControl 派生型は、内部で ScrollViewer を使用していることが多いですね。

ScrollViewer (WPF)
ScrollViewer (WinRT)

ひとまず、下記のように画像 (line 28-34) とサムネイル (line 19-21)、そして Viewport を示す Border (line 22-25) 配置する XAML コードを用意しました。


    
        
            
            
        

        
            
            
        

        
            
        
    

サムネイルと Viewport の表示

Bitmap

コンテンツ部分の Visual 要素をビットマップに書き込み、画像として表示する方法です。 WPF と WinRT でやり方が微妙に異なりますが、考え方は同じです。

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace ThumbsScrollViewer
{
    public partial class MainWindow
    {
        public MainWindow()
        {
            this.InitializeComponent();

            // Bitmap をダウンロードして
            var image = new BitmapImage(new Uri("http://www.hitachinoki.net/download/wp/2015_ta.jpg"));
            // Image コントロールの Source に設定 (サムネイルはコントロール自体のサイズを小さくすればよい)
            this.Image.Source = this.Thumbnail.Source = image;
        }
    }
}

VisualBrush (WPF のみ)

別の方法もあります。 Brush 派生型の VisualBrush を使用し、コンテンツ部分の Visual 要素でサムネイル領域を塗りつぶすことで、縮小されたサムネイルを表現できます。 残念ながら WinRT には VisualBrush がないため、WPF 限定です。

Viewport

サムネイル上に ScrollViewer が表示している範囲 (Viewport) を示すためには、…手計算です! (説明省略

private void UpdateThumbnailViewport(object sender, ScrollChangedEventArgs e)
{
    // ExtentWidth/Height が ScrollViewer 内の広さ
    // ViewportWidth/Height が ScrollViewer で実際に表示されているサイズ

    var xfactor = this.Thumbnail.ActualWidth / e.ExtentWidth;
    var yfactor = this.Thumbnail.ActualHeight / e.ExtentHeight;

    var left = e.HorizontalOffset * xfactor;
    var top = e.VerticalOffset * yfactor;

    var width = e.ViewportWidth * xfactor;
    if (width > this.Thumbnail.ActualWidth) width = this.Thumbnail.ActualWidth;

    var height = e.ViewportHeight * yfactor;
    if (height > this.Thumbnail.ActualHeight) height = this.Thumbnail.ActualHeight;

    // Canvas (親パネル) 上での Viewport の位置を、Left/Top 添付プロパティで設定
    // (XAML で言う  みたいなやつ)
    Canvas.SetLeft(this.Viewport, left);
    Canvas.SetTop(this.Viewport, top);

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

この時点で、ScrollViewer 内に画像を表示し、画像のサムネイルを表示し、更に ScrollViewer で実際に見えている範囲を赤枠で表示することができました。

sample

CombinedGeometry

これは、WPF 限定です。

サムネイルと同じサイズの図形と、Viewport (赤枠) 部分と同じサイズの図形を、それぞれ RectangleGeometry で表現し、それを CombinedGeometry で結合させます。 その際、GeometryCombineMode=”Xor” を指定することによって、「Viewport 以外の部分のみを塗りつぶす」ことが可能です。

方法 : 結合したジオメトリを作成する
https://msdn.microsoft.com/ja-jp/library/ms746682(v=vs.110).aspx


    
    
        
            
        
    
    
public MainWindow()
{
    this.InitializeComponent();

    var image = new BitmapImage(new Uri("http://www.hitachinoki.net/download/wp/2015_ta.jpg"));
    image.DownloadCompleted += (sender, e) =>
    {
        // サムネイルを表示する Image コントロールのサイズで RectangleGeometry を作成し、
        // CombinedGeometry の 1 番目のジオメトリに設定
        var rect = new Rect(0, 0, this.Thumbnail.ActualWidth, this.Thumbnail.ActualHeight);
        this.CombinedGeometry.Geometry1 = new RectangleGeometry(rect);
    };

    this.Image.Source = this.Thumbnail.Source = image;
}
private void UpdateThumbnailViewport(object sender, ScrollChangedEventArgs e)
{
    // 中略...

    // Viewport を計算したとき、そのサイズで RectangleGeometry を作成し、
    // CombinedGeometry の 2 番目のジオメトリに設定
    this.CombinedGeometry.Geometry2 = new RectangleGeometry(new Rect(left, top, width, height));
}

sample2

Thumb コントロール

今回のミソである Viewport (赤枠) を、サムネイル上で動かせるようにしましょう。 もっとも簡単な方法は、Thumb コントロールを使用することです。

System.Windows.Controls.Primitives.Thumb (WPF)
Windows.UI.Xaml.Controls.Primitives.Thumb (WinRT)

Thumb コントロールは、ユーザーがドラッグによってコントロールを動かす機能を簡単に実現するためのものです。 これは、ScrollBar や Slider でユーザーがドラッグする部分 (“つまみ” の部分) で使われているものです。

DragStarted、DragCompleted、および DragDelta イベントを使用し、それぞれドラッグの開始と終了、ドラッグの移動量を検知することができます。 今回のサンプルでは、赤枠部分を Thumb コントロールに置き換え、DragDelta イベントを使用し移動量を ScrollViewer に反映します。 Thumb コントロールは他のコントロールと同様に ControlTemplate を使用し外観をカスタマイズできるため、赤枠は Thumb の外観として表現します。


    
    
        
            
                
            
        
    
private void OnDragDelta(object sender, DragDeltaEventArgs e)
{
    this.ScrollViewer.ScrollToHorizontalOffset(
        this.ScrollViewer.HorizontalOffset + (e.HorizontalChange * this.ScrollViewer.ExtentWidth / this.Thumbnail.ActualWidth));

    this.ScrollViewer.ScrollToVerticalOffset(
        this.ScrollViewer.VerticalOffset + (e.VerticalChange * this.ScrollViewer.ExtentHeight / this.Thumbnail.ActualHeight));
}

これで完成です!

viewport

まとめ

実は、1 年前に「Primitive コントロール探訪」という形で Controls.Primitives 名前空間以下の便利コントロールを紹介しようと書きかけていた記事の一つだったりします (忙しくて頓挫してますが!)。

今回は、@kurosawa0626 さんのリクエストで復活しました。 ありがとうございます (?)。

前述のとおり、Thumb コントロールは、ScrollBar や Slider の “つまみ” の部分で利用されているものです。 うまく活用すれば、今回のようにドラッグでコントロールを動かしたい場合や、リサイズ処理等に応用できます。 使ってみてください!

今回のサンプルは、gist で公開しています。 WPF アプリケーションを作成し、MainWindow.xaml と MainWindow.xaml.cs を組み込めば実行できます。
https://gist.github.com/Grabacr07/988bc04fb7f16aaa4fdc


Viewing all articles
Browse latest Browse all 30

Trending Articles