LoginSignup
1
1

More than 1 year has passed since last update.

WinForms PrintPreviewControl ドラッグしてスクロールの実装方法

Last updated at Posted at 2022-06-21

やりたい事

WinFormsで、PrintDocumentをPreviewするControlがあるじゃないですか。
あれ、地味に使いにくいですよね。何が使いにくいかって、拡大してはみ出してしまった時、
マウスでドラッグしてスクロールしたいじゃないですか。でも標準じゃそんな操作出来ないんですよ。
自力で実装するしかないのですが、本記事では簡単な実装方法を紹介します。

どっかのサイトで紹介記事を書いた記憶があるのですが、昔の事なので覚えてません。
また、この実装方法にどうやってたどり着いたのかも覚えていません。しかも10年以上前のお話です。
ですが、最新の.NET6.0で実装した所、ちゃんと動作したので、まだまだ使えるかなという事で紹介します。

解決方法

やり方は色々あると思います。ですがとある事実を知ってしまい、僕はこれが正攻法なのではと思うようになりました。
実は、PrintPreviewControlって、隠蔽化されてはいますが、内部に指定位置までスクロールさせる為の処理が存在します。
リフレクションを使いそれを呼び出せば、後はいい感じにやってくれるっていう実装方法です。
今回は、PrintPreviewControlを継承したクラスを作り、その中に実装する例を示します。

環境は下記の通りです。

Windows 10 Pro 64bit 21H1
Microsoft Visual Studio Community 2022

10年以上前から実装できたので、.NETFramework4.0系や、.NETCore、.NET全てで動作すると思います。
null警告とかは基本無視です。

1 PrintPreviewControlを継承したクラスを作成

C#
using System.Reflection;

 public class PrintPreviewControlEx:PrintPreviewControl
    {
        private Point oldPosition;//前回マウスポジ
        private Point currentPosition;//現在の位置
        private FieldInfo finfo_Position;//現在位置の取得用
        private MethodInfo minfo_SetPositionNoInvalidate;//魔法のスクロール処理

        public PrintPreviewControlEx()
        {
            //リフレクションを利用する為にTypeを取得する
            var _type = typeof(PrintPreviewControl);
            //隠蔽可された位置フィールド
            finfo_Position = _type.GetField("position", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.ExactBinding);
            //隠蔽可されたスクロールメソッド
            minfo_SetPositionNoInvalidate = _type.GetMethod("SetPositionNoInvalidate", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.ExactBinding);
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);
            //ドラッグ中だった場合
            if (e.Button == MouseButtons.Left)
            {
                //スクロール処理してみる 
                minfo_SetPositionNoInvalidate?.Invoke(this, new object[] { currentPosition + ((Size)(oldPosition - ((Size)e.Location))) });
            }
            else
            {
                //位置などを取っておく
                oldPosition = e.Location;
                currentPosition = (Point)finfo_Position?.GetValue(this);
            }

        }


        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);
            //OnMouseWheelを検知する為に選択状態にする
            this.Select();
        }

        //こういった操作感が欲しい人は、もちろんマウスホイールでズーム率変えたいよね!
        protected override void OnMouseWheel(MouseEventArgs e)
        {
            base.OnMouseWheel(e);
            //スクロールでズーム値変更する
            //増減量・方向はお好みで
            if (e.Delta > 0)
            {
                this.Zoom -= this.Zoom * 0.1;
            }
            else
            {
                this.Zoom += this.Zoom * 0.1;
            }

            //外部通知用にイベントを呼ぶ
            this.ZoomChanged?.Invoke(this, EventArgs.Empty);
        }

        //外部通知用のイベントハンドラを用意しておく。使わないなら上記と共に消して構わん。
        public event EventHandler<EventArgs>? ZoomChanged;
    }
VisualBasic

Imports System.Reflection

Public Class PrintPreviewControlEx
    Inherits PrintPreviewControl

    Private oldPosition As Point
    Private currentPosition As Point
    Private finfo_Position As FieldInfo
    Private minfo_SetPositionNoInvalidate As MethodInfo

    Sub New()
        Dim _type As Type = GetType(PrintPreviewControl)
        finfo_Position = _type.GetField("position", BindingFlags.Instance Or BindingFlags.NonPublic Or BindingFlags.ExactBinding)
        minfo_SetPositionNoInvalidate = _type.GetMethod("SetPositionNoInvalidate", BindingFlags.Instance Or BindingFlags.NonPublic Or BindingFlags.ExactBinding)
    End Sub

    Protected Overrides Sub OnMouseMove(e As MouseEventArgs)
        MyBase.OnMouseMove(e)
        If e.Button = MouseButtons.Left Then
            minfo_SetPositionNoInvalidate.Invoke(Me, New Object() {currentPosition + (oldPosition - e.Location)})
        Else
            oldPosition = e.Location
            currentPosition = finfo_Position.GetValue(Me)
        End If
    End Sub

    Protected Overrides Sub OnMouseDown(e As MouseEventArgs)
        MyBase.OnMouseDown(e)
        Me.Select()
    End Sub

    Protected Overrides Sub OnMouseWheel(e As MouseEventArgs)
        MyBase.OnMouseWheel(e)
        If e.Delta > 0 Then
            Me.Zoom -= Me.Zoom * 0.1
        Else
            Me.Zoom += Me.Zoom * 0.1
        End If

        RaiseEvent ZoomChanged(Me, EventArgs.Empty)
    End Sub

    Public Event ZoomChanged(sender As Object, e As EventArgs)

End Class

2 一度ビルドしてFormに張り付けて、適当にPrintDocumentを渡し、実行する

image.png
こんな感じですね。
これで実行すると、ドキュメントのサイズがはみ出していた場合、ドラッグ操作でスクロールできます。
サイズが収まっている場合であっても、特にエラートラップをしなくても
エラー吐いたりしませんので、気軽です。

ついでにマウスホイールでズーム率を変えることが出来きますので、
コピペすればすぐに効果が試せます。

あとがき

個人的には、なんでこの方法にたどり着いたのか、全然覚えてないのでモヤっとしています。
Windowsの機能で、ドラッグ操作できるプレビュー画面を見た事があり、
.NETは基本的にWinAPIの使いまわしみたいなもんだからって深堀したら出てきた。
みたいな感じだったのかな。いやー昔すぎてほんとに覚えてないです。

とにかくこれでドラッグ操作でスクロール出来るPrintPreviewControlの完成です。
わざわざ隠蔽化されてるので、何か非推奨な理由でもあるのでしょうか。
わからないのですが、この実装で10年以上困った事は一度もない為、大丈夫でしょう。

活用できそうならば、活用してみてください。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1