やりたい事
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を継承したクラスを作成
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;
}
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を渡し、実行する
こんな感じですね。
これで実行すると、ドキュメントのサイズがはみ出していた場合、ドラッグ操作でスクロールできます。
サイズが収まっている場合であっても、特にエラートラップをしなくても
エラー吐いたりしませんので、気軽です。
ついでにマウスホイールでズーム率を変えることが出来きますので、
コピペすればすぐに効果が試せます。
あとがき
個人的には、なんでこの方法にたどり着いたのか、全然覚えてないのでモヤっとしています。
Windowsの機能で、ドラッグ操作できるプレビュー画面を見た事があり、
.NETは基本的にWinAPIの使いまわしみたいなもんだからって深堀したら出てきた。
みたいな感じだったのかな。いやー昔すぎてほんとに覚えてないです。
とにかくこれでドラッグ操作でスクロール出来るPrintPreviewControlの完成です。
わざわざ隠蔽化されてるので、何か非推奨な理由でもあるのでしょうか。
わからないのですが、この実装で10年以上困った事は一度もない為、大丈夫でしょう。
活用できそうならば、活用してみてください。