...ing logging 4.0

はてなブログに移行しました。D言語の話とかいろいろ。

DFL: 印刷プレビューダイアログのサンプルコード(改)

PrintPreviewDialog 周りの実装が一通り終わったので、実験的モジュールから卒業しました。 見た目はあまり変わっていませんが、新しいサンプルコードを置いておきます。

以前の記事はこちら。 haru-s.hatenablog.com

主な変更点

  • 総ページ数が表示されるようになりました。
  • ウィンドウを閉じた後に再度表示したとき、閉じる前に表示していた画面が保持されていましたが、保持しないで再描画するようになりました。
  • Fit モードでは、表示されている用紙がウィンドウに収まらなくなるときだけ、用紙が縮小表示されるようになりました。以前のように右側に余計な空白を残したまま縮小が始まることはありません。
  • Fit モードでないとき、スクロールバーが表示され、用紙全体を見ることができるようになりました。
  • ユーザーコードで、ページごとに用紙方向を変更できるようになりました。
  • ページごとに用紙方向が混在している場合でも、重ならないように敷き詰めて表示するようになりました。
  • Graphics クラスに pageUnit が追加され、ユーザーコードで描画するときの座標系を選択できるようになりました。これに伴い、PrintPreviewDialog で描画するときのデフォルトの座標系が変わりました(破壊的変更)。
  • PrintPreviewDialog 以外の変更点
    • PageSetupDialog のダイアログを開いて設定変更したあと、もう一度ダイアログを開いたときに前回の設定が反映されるようになりました。

サンプルコード

SetProcessDPIAware() を使うとウィンドウがぼやけるのを回避することができるので、試しに使ってみています。

import dfl;
import std.conv;

version = DFL_DPI_AWARE;

version (DFL_DPI_AWARE)
{
    import core.sys.windows.windows;
    extern(Windows) nothrow @nogc
    {
        BOOL SetProcessDPIAware(); // winuser.h
    }
}

class MainForm : Form
{
    private Button _printButton;     /// 印刷ボタン
    private Button _pageSetupButton; /// ページ設定ボタン
    private Button _previewButton;   /// 印刷プレビューボタン
    
    private PrintDocument _document;           /// 印刷されるドキュメント
    private PageSetupDialog _pageSetupDialog;  /// ページ設定ダイアログ
    private PrintDialog _printDialog;          /// 印刷ダイアログ
    private PrintPreviewDialog _previewDialog; /// 印刷プレビューダイアログ

    this()
    {
        text = "Simple print"; // ウィンドウタイトルを設定
        size = Size(400, 300); // ウィンドウサイズを設定

        _document = new PrintDocument(); // 印刷されるドキュメントを生成
        _document.printRange ~= &doPrintRange; // ダイアログで選択された印刷範囲ごとにドキュメントの状態に応じたページ範囲を設定する
        _document.beginPrint ~= &doBeginPrint; // 印刷範囲の決定後、全体の印刷開始前に呼ばれる
        _document.queryPageSettings ~= &doQueryPageSettings; // 各ページのページ設定を決定する前に呼ばれる
        _document.printPage ~= &doPrintPage; // 各ページの印刷をするときに呼ばれる
        _document.endPrint ~= &doEndPrint; // 印刷が始まる前に、プリンタドライバへの印刷指示が終わった時点で呼ばれる(非同期処理)

        _pageSetupDialog = new PageSetupDialog(_document); // 印刷するドキュメントを渡して生成
        _printDialog = new PrintDialog(_document); // 印刷するドキュメントを渡して生成
        _previewDialog = new PrintPreviewDialog(_document); // 印刷するドキュメントを渡して生成

        // ページ設定ボタンの設定
        with(_pageSetupButton = new Button())
        {
            parent = this;
            text = "ページ設定...";
            location = Point(50, 50);
            size = Size(150, 40);
            click ~= &doPageSetupDialog;
        }

        // 印刷ボタンの設定
        with(_printButton = new Button())
        {
            parent = this;
            text = "印刷...";
            location = Point(50, 100);
            size = Size(150, 40);
            click ~= &doPrintDialog;
        }

        // 印刷プレビューボタンの設定
        with(_previewButton = new Button())
        {
            parent = this;
            text = "印刷プレビュー...";
            location = Point(50, 150);
            size = Size(150, 40);
            click ~= &doPrintPreview;
        }
    }

    /// ページ設定ボタンをクリックしたとき
    private void doPageSetupDialog(Control sender, EventArgs e)
    {
        // ページ設定ダイアログの表示項目の設定
        _pageSetupDialog.showNetwork = true;      // ネットワークボタンを表示(OSによっては表示されない)
        _pageSetupDialog.showHelp = true;         // ヘルプボタンを表示
        _pageSetupDialog.allowMargins = true;     // 余白を変更可能にする
        _pageSetupDialog.allowOrientation = true; // 用紙方向を変更可能にする
        _pageSetupDialog.allowPaper = true;       // 用紙を変更可能にする
        _pageSetupDialog.allowPrinter = true;     // プリンタボタンを表示(OSによっては表示されない)
        _pageSetupDialog.minMargins = new Margins(100, 100, 100, 100); // 1/100インチ単位なので上下左右1インチ(25.4mm)を最小の余白とする

        // ページ設定ダイアログを表示
        DialogResult r = _pageSetupDialog.showDialog();
        if (r == DialogResult.OK)
        {
            // ページ設定ダイアログで設定した内容を表示
            string msg = "[";
            msg ~= "minMargins: " ~ to!string(_pageSetupDialog.minMargins) ~ ", ";
            msg ~= "defaultPageSettings: " ~ to!string(_pageSetupDialog.document.printerSettings.defaultPageSettings) ~ "]";
            msgBox(msg, "doPageSetupDialog");
        }
    }

    /// 印刷ボタンをクリックしたとき
    private void doPrintDialog(Control sender, EventArgs e)
    {
        // 印刷ダイアログを表示
        DialogResult r = _printDialog.showDialog();
        if (r == DialogResult.OK)
        {
            // OKボタンを押した後の処理があれば書く
        }
    }

    /// 印刷プレビューボタンをクリックしたとき
    private void doPrintPreview(Control sender, EventArgs e)
    {
        // 印刷プレビューダイアログを表示
        DialogResult r = _previewDialog.showDialog();
        if (r == DialogResult.OK)
        {
            // OKボタンを押した後の処理があれば書く
        }
    }

    ///
    private void doPrintRange(PrintDocument doc, PrintRangeEventArgs e)
    {
        final switch (e.printRange.kind)
        {
        case PrintRangeKind.ALL_PAGES:
            // 印刷ダイアログで「すべて」を選択したときのページ範囲を設定
            e.printRange.addPrintRange(PrintRange(1, 8));
            break;
        case PrintRangeKind.SELECTION:
            // 印刷ダイアログで「選択した部分」を選択したときのページ範囲を設定
            e.printRange.addPrintRange(PrintRange(1, 1));
            break;
        case PrintRangeKind.CURRENT_PAGE:
            // 印刷ダイアログで「現在のページ」を選択したときのページ範囲を設定
            e.printRange.addPrintRange(PrintRange(2, 2));
            break;
        case PrintRangeKind.SOME_PAGES:
            // 印刷ダイアログで「ページ指定」を選択したとき
            // ダイアログからページ範囲をもらうのでここでは何も書かない
        }
    }

    ///
    private void doBeginPrint(PrintDocument doc, PrintEventArgs e)
    {
        // msgBox("プリンタドライバへの印刷指示を開始");
    }

    ///
    private void doQueryPageSettings(PrintDocument doc, QueryPageSettingsEventArgs e)
    {
        // 印刷しようとしているページのページ設定をデフォルトから変更する
        // ここでe.pageSettingsを変更しても次のページには影響しない

        // 1、4ページ目だけ用紙を横向きにしたい
        import std.algorithm;
        if (e.currentPage.among(1, 4) != 0)
            e.pageSettings.landscape = true;
    }

    ///
    private void doPrintPage(PrintDocument doc, PrintPageEventArgs e)
    {
        Graphics g = e.graphics;

        g.pageUnit = GraphicsUnit.MILLIMETER;
        g.drawRectangle(new Pen(Color.red, 1), 5, 5, 5, 5); // 1mm単位

        g.pageUnit = GraphicsUnit.DISPLAY;
        g.drawRectangle(new Pen(Color.blue, 1), 0, 0, 100, 100); // 1/100dpi単位
        g.drawText(
            "abcdefgあいうえお0123456789ABCDEFG",
            new Font("MS Gothic", 8/+pt+/ * 100 / 72),
            Color.black,
            Rect(0, 0, 100, 100)
        );

        // すべてのページに余白の枠を描く
        Rect marginRect = Rect(
            e.marginBounds.x, // e.marginBoundsは1/100dpi単位
            e.marginBounds.y,
            e.marginBounds.width,
            e.marginBounds.height);
        g.drawRectangle(new Pen(Color.green, 1), marginRect);

        if (e.currentPage == 1) // 1ページの印刷内容を描画
        {
            string str =
                "PrintDcoument.DocumentName: " ~ to!string(doc.documentName) ~ "\n\n" ~
                "PrintDcoument.defaultPageSettings: " ~ to!string(doc.printerSettings.defaultPageSettings) ~ "\n\n" ~
                "PrintDcoument.printerSettings: " ~ to!string(doc.printerSettings) ~ "\n\n" ~
                "PrintPageEventArgs.pageSettings: " ~ to!string(e.pageSettings) ~ "\n\n" ~ 
                "PrintPageEventArgs.pageBounds: " ~ to!string(e.pageBounds) ~ "\n\n" ~
                "PrintPageEventArgs.marginBounds: " ~ to!string(e.marginBounds);
            Rect paramPrintRect = Rect(
                e.marginBounds.x, // e.marginBoundsは1/100dpi単位
                e.marginBounds.y,
                e.marginBounds.width,
                e.marginBounds.height
            );
            g.drawText(
                str,
                new Font("MS Gothic", 10f),
                Color.black,
                paramPrintRect
            );
        }
        else if (e.currentPage >= 2 || e.currentPage <= 8) // 2-8ページの印刷内容を描画
        {
            Rect redRect = Rect(100, 100, 100, 100); // 1×1インチの正方形
            redRect.offset(marginRect.x, marginRect.y); // 余白の分だけ座標をオフセット(要改良)
            g.fillRectangle(new SolidBrush(Color.red), redRect);

            Rect blueRect = Rect(100, 100, 3 * 100, 3 * 100); // 3×3インチの正方形
            blueRect.offset(marginRect.x, marginRect.y); // 余白の分だけ座標をオフセット(要改良)
            g.drawRectangle(new Pen(Color.blue, 10), blueRect);

            Rect textRect = Rect(100, 100, 100, 100); // 1×1インチの正方形
            textRect.offset(marginRect.x, marginRect.y); // 余白の分だけ座標をオフセット(要改良)
            g.drawText(
                "ABCDEあいうえお",
                new Font("MS Gothic", 12/+pt+/ * 100 / 72), // 1ポイントは1/72インチ
                Color.black,
                textRect
            );

            Rect purpleRect = Rect(3 * 100, 3 * 100, 100, 100); // 1×1インチの正方形
            purpleRect.offset(marginRect.x, marginRect.y); // 余白の分だけ座標をオフセット(要改良)
            g.drawEllipse(new Pen(Color.purple, 10), purpleRect);

            Pen pen = new Pen(Color.black, 3);
            enum lineNum = 20;
            for (int x; x < lineNum; x++)
            {
                g.drawLine(
                    pen,
                    marginRect.x + cast(int)(x / 4.0 * 100),
                    e.marginBounds.y * 100 / 100,
                    marginRect.x + cast(int)((lineNum - x - 1)/4.0 * 100),
                    e.marginBounds.bottom * 100 / 100);
            }
        }
    }

    ///
    private void doEndPrint(PrintDocument doc, PrintEventArgs e)
    {
        // msgBox("印刷を指示しました");
    }
}

static this()
{
    Application.enableVisualStyles();
    version (DFL_DPI_AWARE)
    {
        SetProcessDPIAware();
    }
}

void main()
{
    Application.run(new MainForm());
}

DFLのダウンロード

github.com