連続ページを実現する

Adobe Reader などのアプリケーションでよく見られる,全てのページを縦に並べてスクロールで移動する所謂「連続ページ」の実現方法を考えていたのでメモ.ListView などを使用してもよかったのですが,ScrollableControl の上に PictureBox を載せると言う単一ページの場合の形にできるだけ似せて実装しておきたかったので違った方法で試しています.

まずは,雛形としてデザイナーで以下のような形を作成します.

真ん中の部分は,System.Windows.Forms.Panel で AutoScroll を true に設定してあります.連続ページは,この Panel の上に PictureBox をどんどんと追加していく形で実現していきます.

DockStyle.Top を使用する方法

一番簡単な方法として,DockStyle.Top を指定した PictureBox を直接追加していく形が考えられます.

using System;
using System.Drawing;
using System.Windows.Forms;

public partial class MainForm : Form {
    public MainForm() {
        InitializeComponent();
    }
    
    /* ------------------------------------------------------------- */
    /// <summary>
    /// control にダミー画像を 10ページ分,連続ページとして表示する.
    /// </summary>
    /* ------------------------------------------------------------- */
    private void Create(Control control) {
        for (int i = 0; i < 10; i++) {
            var elem = new PictureBox();
            elem.Image = Properties.Resources.dummy;
            elem.Size = new Size(elem.Image.Width, elem.Image.Height);
            elem.Dock = DockStyle.Top;
            elem.Name = "Canvas";
            elem.BackColor = Color.White;
            elem.BorderStyle = BorderStyle.FixedSingle;
            control.Controls.Add(elem);
        }
    }
    
    private void OpenButton_Click(object sender, EventArgs e) {
        this.Create(this.MainPanel);
    }
}

ただ,この方法の場合,PictureBox の幅がウィンドウ(に貼り付けている Panel)の幅に強制的に調整されてしまうため(最大化すると右側に PictureBox の背景色が見えてしまっている)位置調整がやりづらいなどの問題があります.

TableLayoutPanel を使用する方法

別の方法として,System.Windows.Forms.TableLayoutPanel を利用すると言うものがあります.TableLayoutPanel は,領域をテーブル状に区切って,各セルを Panel のように扱えるようにするためのものだそうです.

例えば,上記の Create() メソッドを以下のように書き換えます.

private void Create(Control parent) {
    var control = new TableLayoutPanel();
    control.AutoScroll = true;
    control.GrowStyle = TableLayoutPanelGrowStyle.AddRows;
    control.Dock = DockStyle.Fill;
    control.BackColor = Color.Gray;
    
    for (int i = 0; i < 10; i++) {
        var elem = new PictureBox();
        elem.Image = Properties.Resources.dummy;
        elem.Size = new Size(elem.Image.Width, elem.Image.Height);
        elem.Name = "Canvas";
        elem.BackColor = Color.White;
        elem.BorderStyle = BorderStyle.FixedSingle;
        control.Controls.Add(elem);
    }
    
    parent.Controls.Add(control);
}

この方法だと PictureBox の幅は描画しているイメージと同じになるので,Margin などで位置調整が楽になります.

ページを指定してジャンプ

最後に,ユーザが指定したページへジャンプする機能を実装してみます.実現するには ScrollableControl.ScrollControlIntoView() メソッドを利用しています.尚,指定されたインデックスを基に TableLayoutPanel 内のコントロールを取得するメソッドが見つからなかったので,以下のような形で逃げています.

/* ------------------------------------------------------------- */
/// <summary>
/// 指定したページにジャンプする.
/// </summary>
/* ------------------------------------------------------------- */
private void MovePage(ScrollableControl control, int page) {
    if (control == null) return;
    
    int n = Math.Min(Math.Max(page, 1), control.Controls.Count);
    int i = 0;
    foreach (Control elem in control.Controls) {
        i++;
        if (i == page) {
            control.ScrollControlIntoView(elem);
            break;
        }
    }
}

private void SelectPageTextBox_KeyDown(object sender, KeyEventArgs e) {
    if (e.KeyCode != Keys.Enter) return;
    
    try {
        var page = int.Parse(this.SelectPageTextBox.Text);
        var control = (ScrollableControl)this.MainPanel.Controls["MultipageController"];
        this.MovePage(control, page);
    }
    catch (Exception /* err */) { }
}

今回作成したサンプルは http://cielquis.net/download/MultiPageview_20100907.zip からダウンロードできます.

このエントリをつぶやく このWebページのtweets