前回【RichEditor】紆余曲折の結果...文字列の検索と置換はC#(WinForms)のRichTextBoxクラスで実装されていない「文字列の検索と置換」について、下方検索は良いのですが、上方検索になると検索完了の戻り値(-1)が返ってこない(Microsoftの「手抜き」)問題とその克服物語について触れました。

 

今回はもっと深刻で、「C#ライブラリーでは書けずに、Win32 APIを動員して印刷メソッドを作るしかない」()という問題について書きます。

:とはいえ、総てのWin32とC++のコードをC#へ移植する訳ではなく、一部は(あまり馴染みのない)C#のオブジェクトコンポーネントに組み合わせなければならないのですから、もっと面倒です。

 

C#を始めた頃、TextBoxでテキストエディターは組んだことが有りますが、プリンターを使わない私にとって「印刷」機能はまず使わないので(はっきり言ってRichTextBoxのみならず、あらゆる印刷関連機能について)全くの無知であり、(昔取った杵柄のWin32とC++の知見から)手始めに印刷関連のクラスやメンバーについて調べることから始めました。

 

Win32 API には「ファイルを開く」ダイアログのような汎用の「コモンダイアログ」というものがあり、それに「印刷ダイアログ」「ページダイアログ」があったので、C#におけるそれらから調査して行きました。

 

その結果PrintDialogPageSetupDialogというクラスがありましたが、それらに関連するPrintDocumentPrinterSettingsPageSettingsというデータクラスもあり、更にこれらには多くのプロパティがあるので、先ずこれらの概要を理解し、相互の関係を整理するとともに全体像を把握して、何はともあれ試行錯誤するサンプルコードを書いてみました。

 

しかし、役者多くして相関関係は分かり難くい

 

事から、またもや五里霧中となると頼れるのはChat-GPT様、今回もご相談させていただきました。これに関わる以下(↓)の「当初の私とChat-GPTのやり取り抜粋」から長い旅が始まります。

 

(私)C#でRichTexxtBoxを使って印刷を行おうと次のようなコードを組むと、コンパイル時に「error CS1061: 'System.Windows.Forms.RichTextBox' に 'Print' の定義が含まれておらず、型 'System.Windows.Forms.RichTextBox' の最初の引数を受け付ける拡張メソッドが見つかりませんでした。using ディレクティブまたはアセンブリ参照が不足しています。」というエラーがでます。C#のVersionの問題でしょうか?

 

        //印刷ダイアログを表示する場合

        PrintDialog prtDlg = new PrintDialog();

        prtDlg.Document = new PrintDocument();

        //ダイアログを表示し、ユーザーがプリンターを選択できるようにする

        if(prtDlg.ShowDialog() == DialogResult.OK)

        {

            //ユーザーがOKを押したら印刷を実行

            rtxtBox.Print(printDialog.Document); // PrintDialog.Documentにはユーザーが選択したプリンターの情報が格納されている

       }

        prtDlg.Dispose();

 

(Chat-GPT)とても良い質問です。
結論から言うと、エラーの原因はC#や.NETのバージョンではなく、
RichTextBox クラスに Print() メソッドが存在しないためです。


🔍 詳細な説明

System.Windows.Forms.RichTextBox は WinForms のコントロールであり、印刷機能を 直接サポートしていません
そのため、rtxtBox.Print() のようなメソッドは存在せず、C# のバージョンに関係なくコンパイルエラーになります。

(後略)


✅ 解決方法(WinForms版 RichTextBox の印刷)

WinFormsの RichTextBox を印刷するには、Windows API(EM_FORMATRANGE メッセージ)を使って手動でレンダリングする必要があります。これを簡略化したクラスを自作して対応するのが一般的です。

 

目 == ・(目が点)何のためのオブジェクト志向プログラミングなんだ!

 

22年前にWin32APIでBCCSkeltonを作り、そのCREDITクラスで印刷機能を実装したので、C++でリッチエディットコントロールの「面倒な印刷処理」を卒業していましたが、なんとまぁ、再度これをC#でやれ、ということになるとは思いませんでした。

 

ということで、

 

先ずは20余年前に自作したWin32のRIch Edit Controlに関わるC++のCEDITクラスのコードをリビューし(だって忘れちゃったんだもん)、Win32 APIでは何をどうしているのかの大まかな流れを探ります。

 

BCCSkeltonのCREDITクラスのコードを見ると、

 

1.「プリンター設定」ダイアログ(PRINTDLG)で印刷用設定データを取得
(1)プリンターデバイスのデバイスコンテキストを取得
(2)引数であるPRINTDLG構造体でプリンター用紙の物理的幅、そのピクセル数を記録
(3)左右マージン(1 inch == 1440 twips)をtwip単位で設定

 

2.印刷
(1)印刷文書情報構造体(DOCINFO)に文書名、
(2)範囲成形構造体(FORMATRANGE)にデバイスコンテキスト、物理的サイズ、ページサイズ、文書長さ、開始文字インデックス、終了文字インデックスを入れて、
(3)StartDoc関数で印刷を開始
(4)印刷はページ単位でページごとの情報(EM_FORMATRANGE)をイメージとして作成(EM_DISPLAYBAND)し、

(5)文書の終わりまでStartPage関数、EndPage関数ループさせ、
(6)最後に"SendMessage(m_hWnd, EM_FORMATRANGE, TRUE, NULL);"によりコントロールによってキャッシュされた情報を解放


という流れです。


C#では、


(1)PrintDialogが構造体を使って関数として使うWin32とやや異なり、独立したクラスになっている。


しかし、これはWin32 APIのPrinter Setupダイアログとビミョーに異なる。

(左がC#で「印刷」、右がWin32で「プリンター設定」)

 

(2)C#の印刷ダイアログクラスではPrintDocumentクラスインスタンスを使って印刷するようである。
(3)印刷ダイアログクラスの他に、ページ設定ダイアログ(PageSetupDIalog)クラスもあるが、印刷処理におけるこれらの関係が不明瞭である。(印刷ダイアログクラスにもページプロパティが設定できる。)


(4)印刷ダイアログクラスには既定ページ設定(DefaultPageSettings)情報、プリンター設定(
PrinterSettings)クラス情報と印刷文書(PrintDocument)クラス情報をプロパティに設定して使うようである。
(5)印刷自体は印刷ダイアログクラスのメソッドではなく、印刷文書クラスの印刷(Print)メソッドを使うようである。

 

Microsoft Learnを見ると、PrintDocument.Print()メソッドにサンプルコードが出てますが、テキストファイルの印刷をストリームで行う例しか出ておらず、文書をグラフィック化して印刷するRIchTextBoxの場合には余り参考にはなりません。

ということで、更に色々と調べていったところ、最後に実用的に参考になるサンプルを見つけたので、これを応用しました。

但し、このサンプルはページ印刷後にキャッシュを開放していない点に注意する必要があります。使うことをご検討されているの方は、今後ご紹介する私のコードを参考にしてください。

 

これで一応印刷することが出来るようになりましたが、Win32の「プリンター設定」と「印刷」という2アクションのメニューやツールバーボタンを1アクションに纏め、「印刷」メニューまたはボタンを押すと、

 

ページ設定ダイアログが出て、OKボタンを押すと、

 

印刷ダイアログが出てOKボタンを押すと印刷される流れにしました。

 

まぁ、何とか印刷できるようになったので、いよいよ次回からはコードの解説をしてゆきましょうか?