الطباعة وإخراج PDF في تطبيقات أعمال Windows ── التمييز بين System.Drawing.Printing وWPF ومكتبات التقارير

· آخر تحديث: · · CSharp, .NET, WinForms, WPF, الطباعة, PDF, التقارير, تطوير Windows, الاستشارات التقنية

سجل التعديلات (2 تحديثات، آخر تحديث 2 Sep، 2026)

سجل بالتغييرات التي أُجريت على هذا المقال. وحيثما حُفظت نسخة سابقة، تبقى متاحة للقراءة عبر رابط دائم يحمل معرّف DOI.

أُعيدَت الترجمة العربية كترجمة كاملة عن النص الياباني الأصلي، وأُضيفَت خريطة المعرفة.
أعيدت الترجمة كترجمة كاملة عن النص الياباني الأصلي. كانت النسخة العربية السابقة مختصراً يسقط أبواباً وجداول ورسوم Mermaid وتعليقات الأشكال وFAQ. أُعيدت هذه العناصر وفق الأصل الياباني، والادّعاءات التقنية مطابقة للنسخة اليابانية.
النشر الأول
الاستشهاد بهذا المقال(DOI (الأرشيف المسجّل): 10.5281/zenodo.21621645)

تشير معرّفات DOI أدناه إلى إصدارات مؤرشفة سابقًا، وقد تختلف عن النص الحالي. للإشارة إلى النص الحالي، استخدم رابط هذه الصفحة.

غو كومورا (2026). الطباعة وإخراج PDF في تطبيقات أعمال Windows ── التمييز بين System.Drawing.Printing وWPF ومكتبات التقارير. شركة كومورا سوفت ذ.م.م.. https://comcomponent.com/ar/blog/windows-app-printing-pdf-guide/

DOI (الأرشيف المسجّل)
10.5281/zenodo.21621645
DOI (آخر إصدار مسجّل)
10.5281/zenodo.22241013

الطباعة في تطبيقات أعمال Windows ليست حديث «ترسم شيئاً بـ PrintDocument وتنتهي». فواصل الصفحات لا تعمل، الهوامش تنزاح، المعاينة تختلف عن الإخراج الفعلي، تريد PDF فقط فيتدخّل مربع حوار الطباعة ── هذه المشكلات سببها غالباً بدء التنفيذ مع سوء فهم لآلية الطباعة نفسها.

يرتب هذا المقال الخيارات الأربعة التالية من زاوية كيفية التمييز بينها حسب المتطلبات. سنشير إليها لاحقاً بهذه الأرقام.

# الخيار الفصل الذي يتناوله أساساً
System.Drawing.Printing (PrintDocument) في WinForms الفصل 3
FlowDocument / FixedDocument في WPF الفصل 4
التوليد المباشر بمكتبة إخراج PDF الفصل 5
التقارير عبر Excel / Word (Open XML SDK، أتمتة COM) الفصل 6

1. الخلاصة أولاً

  • للقوائم البسيطة أو امتداد أصول WinForms القائمة يكفي PrintDocument. فواصل الصفحات آلية تستدعي حدث PrintPage تكراراً حتى تصير HasMorePages إلى false، وإغفال ذلك يمنع إخراج الصفحة الثانية فما بعدها.12
  • DPI الطباعة شيء غير DPI الشاشة. يمثّل PageSettings.PrinterResolution دقة الطابعة، والهوامش مستويان: PageSettings.HardMarginX/HardMarginY (المنطقة الفيزيائية غير القابلة للطباعة التي تملكها الطابعة) وMargins (الهامش المنطقي الذي يحدّده التطبيق). إعادة استخدام إحداثيات الشاشة كما هي في الطباعة تزيح التخطيط بفرق DPI ونظامَي الإحداثيات هذين.345
  • في WPF ينقسم مسار الطباعة القياسي في Windows إلى مسارين: مسار طباعة GDI ومسار طباعة XPS. تطبيقات WPF تستخدم أصلاً مسار XPS، وعند الإرسال إلى طابعة لا تدعم XPSDrv يُحوَّل تلقائياً إلى صيغة GDI.67
  • وثائق WPF تختلف في فكر التصميم بين FlowDocument «التدفق» وFixedDocument «التخطيط الثابت». FlowDocument يعيد التخطيط حسب حجم النافذة والدقة إيثاراً للقراءة، وFixedDocument تكوين يقدّم أمانة جهاز العرض والطباعة.89
  • تعيين الورق والدرج يتم بـ PrintDialog وPrintTicket/PrintQueue في WPF. القاعدة قبل الطباعة التحقق والدمج مقابل قدرات الطابعة الفعلية بـ PrintQueue.MergeAndValidatePrintTicket.1011
  • Microsoft Print to PDF برنامج تشغيل طباعة يفترض تشغيلاً تفاعلياً. موضعه طابور طباعة وحزمة برنامج تشغيل محمّلان قياسياً في نظام التشغيل، والتكوين الأساسي أن يتدخّل مربع حوار اختيار وجهة الحفظ في كل تنفيذ، فلا يصلح للمعالجة الدفعية بلا إشراف.12
  • أتمتة Office COM من خادم أو خدمة Microsoft تعلن رسمياً أنها غير مدعومة وغير موصى بها. صُممت تطبيقات Office بافتراض سطح مكتب تفاعلي وملف تعريف مستخدم، وقد تتصرف بعدم استقرار أو تصل إلى deadlock في بيئة بلا إشراف وغير تفاعلية.13
  • خدمة Windows تعمل في الجلسة 0، فلا تستطيع عرض مربع حوار، ولا تعتمد على الطابعة الافتراضية المرتبطة بجلسة المستخدم. عند تصميم معالجة مقيمة تصاحبها طباعة، اجعل هذا الحد حاضراً أولاً.14
  • نسيان تحرير كائنات GDI (المقابض) في التشغيل الطويل يبلغ السقف لكل عملية فيتعطل. السقف قيمة محدودة يمكن ضبطها بسجل GDIProcessHandleQuota، وليست بلا نهاية.15

جدول قرار ── المتطلبات × الوسيلة

المتطلب الوسيلة المناسبة الخيار السبب
طباعة قائمة أو قسيمة بسيطة بعدة أوراق (WinForms) PrintDocument يكتمل بالأصناف القياسية، ويمكن إعادة استخدام أصول رسم GDI+ القائمة كما هي
تقرير كثير الخطوط، عدة أوراق، تخطيط معقّد مكتبة تقارير، أو تخطيط ثابت بـ FixedDocument ② أو ③ تكلفة كتابة حساب الإحداثيات والتحكم بفواصل الصفحات والتنضيد الياباني ذاتياً كبيرة
طباعة دفعية كثيرة (تشغيل ليلي بلا إشراف) إرسال مباشر برمجي إلى PrintDocument.Print() / XpsDocumentWriter ① أو ② يلزم الاكتمال بلا أي مربع حوار، وتجنّب الاعتماد على الطابعة الافتراضية أيضاً
حفظ PDF فقط (بلا طباعة) إنشاء PDF مباشرة بمكتبة توليد PDF لا يعتمد على مربع حوار الطباعة ولا الطابعة الافتراضية ولا حالة المُبَكِّر
المعاينة لازمة، ومسار التأكيد الداخلي مهم PrintPreviewDialog (WinForms)، DocumentViewer (WPF) ① أو ② واجهة التأكيد القياسية قبل الطباعة تُستخدم كما هي
إعادة استخدام قالب Excel قائم التوليد المباشر بـ Open XML SDK وغيره، أو أتمتة COM محدودة يمكن إعادة استخدام الأصل، لكن تجنّب أتمتة COM من خدمة مقيمة

فيما يلي تفاصيل كل بند.

في المخطّط، يشير الخطّ المتّصل إلى علاقة قائمة دائماً، ويشير الخطّ المتقطّع إلى علاقة مشروطة (شروط قيامها مذكورة في شرح كلّ علاقة في الصفحة التفصيليّة). القائمة الكاملة للعلاقات (المجموع 21، مع الأدلّة ودرجة اليقين) وتعريفات المفاهيم الرئيسة مجمّعة في صفحة تفاصيل خريطة المعرفة (باليابانية). البيانات: JSON-LD / Turtle

2. الحد الأدنى من آلية الطباعة في Windows

نعرف أولاً المصطلحات المستخدمة من هذا الفصل.

المصطلح المعنى
DPI (dots per inch) عدد النقاط لكل بوصة. وحدة الدقة. الشاشة عادة نحو 96 DPI، والطابعة نحو 600 DPI، فالرتبة مختلفة
المُبَكِّر (spooler) خدمة Windows تخزّن مؤقتاً مهام الطباعة الواردة من التطبيق وتسلّمها بالترتيب إلى برنامج تشغيل الطابعة. آلية تسمح لجانب التطبيق بالمضي حتى إذا توقفت الطابعة
EMF (ملف وصفي موسَّع) صيغة ملف تسجّل أوامر رسم GDI. في مسار طباعة GDI تُسلَّم بهذا الشكل إلى المُبَكِّر
WYSIWYG (What You See Is What You Get) حالة أن ما تراه على الشاشة يُخرَج كما هو. في الطباعة تعني «المعاينة تطابق الورق الفعلي»
الجلسة 0 منذ Windows Vista جلسة محجوزة للعمليات غير المرتبطة بمستخدم تفاعلي مثل الخدمات. مفصولة عن شاشة المستخدم المسجّل دخوله14
الهامش الصلب شريط طرف الورقة الذي لا تستطيع الطابعة الطباعة عليه فيزيائياً. لا يضيّقه التطبيق

معمارية الطباعة في Windows تتكوّن من مُبَكِّر الطباعة وبرامج تشغيل كل طابعة. يكفي أن يستدعي التطبيق دوالاً مستقلة عن الجهاز، فيرسل مهمة طباعة إلى وجهات متنوعة: طابعة ليزر، راسم متجه، طابعة نقطية، فاكس، وغيرها.16

مسار الطباعة مساران إجمالاً.

  • مسار طباعة GDI. عندما يطبع تطبيق Win32 GDI، يلفّ محرك رسوم GDI أوامر الرسم كـ EMF (ملف وصفي موسَّع) للمُبَكِّر، أو يعرض بالتعاون مع برنامج تشغيل الطابعة صورة قابلة للطباعة مباشرة ويرسلها إلى المُبَكِّر. تطبيقات WinForms التقليدية تمر بهذا المسار.166
  • مسار طباعة XPS. مسار لبرامج تشغيل الطابعات القائمة على XPS (XML Paper Specification). تطبيقات WPF تستخدم أصلاً هذا المسار وترسل إلى المُبَكِّر بصيغة وثيقة XPS. إذا لم تكن الطابعة المستهدفة برنامج تشغيل XPSDrv، حُوّلت تلقائياً إلى صيغة GDI ثم أُرسلت.67

إذا رُسم صار كالتالي. أيّاً كان المسار، المرور أخيراً بالمُبَكِّر وبرنامج التشغيل مشترك.

مسار طباعة GDI ومسار طباعة XPSتطبيق WinForms يمر بمسار GDI، وتطبيق WPF بمسار XPS. الإرسال من WPF إلى طابعة لا تدعم XPSDrv يُحوَّل في الطريق إلى صيغة GDI.طابعة تدعم XPSDrvطابعة لا تدعم XPSDrvتطبيق WinFormsالخيار ① PrintDocumentمسار طباعة GDIلف أوامر الرسم كـ EMF للمُبَكِّرتطبيق WPFالخيار ② FlowDocument وFixedDocumentمسار طباعة XPSاللف كوثيقة XPS للمُبَكِّرمُبَكِّر الطباعةتحويل تلقائي إلى صيغة GDIبرنامج تشغيل الطابعةطابعة وفاكسMicrosoft Print to PDF وغيرها

الشكل 1: مسار طباعة GDI ومسار طباعة XPS. الإرسال من WPF إلى طابعة لا تدعم XPSDrv يُحوَّل في الطريق إلى صيغة GDI.

لماذا ينزاح مظهر الشاشة عن نتيجة الطباعة. السبب الأكثر شيوعاً الخلط في DPI. Graphics الشاشة عادة نحو 96 DPI في نظام إحداثياتها، أما Graphics الطابعة فيعمل بدقة أعلى بكثير، غالباً نحو 600 DPI، تحدّدها PageSettings.PrinterResolution.3 تمرير إحداثيات بكسل محسوبة للشاشة كما هي إلى Graphics الطباعة يرسم أصغر كثيراً مما قُصد (أو أكبر بحسب إعداد الدقة العالية). في الطباعة الأسلم تجميع الإحداثيات دائماً بوحدة 1/100 بوصة (hundredths of an inch) أو بالقياس الفعلي (ملليمتر أو بوصة)، بتصميم لا يعتمد على دقة Graphics.

وما يسهل تفويته أيضاً المنطقة الفيزيائية غير القابلة للطباعة التي تملكها الطابعة. كثير من الطابعات لا تطبع نطاقاً بعدة ملليمترات من طرف الورقة. هذا القيد الفيزيائي يُحصَل عليه بـ PageSettings.HardMarginX/HardMarginY، وتشرحه الوثائق الرسمية بأنه «يمثّل الهامش الفيزيائي الذي تضبطه الطابعة».4 الهامش المنطقي الذي يحدّده جانب التطبيق هو PageSettings.Margins (الافتراضي بوصة واحدة من الجهات الأربع)، وحتى ضبطه على 0 لا يُخرج إلى داخل أضيق من الهامش الصلب للطابعة.5 كثير من أعطال «ضبطت الهامش على 0 فانقطع الطرف / انزاح أكثر مما ظننت» سببها عدم وعي وجود هذا الهامش الصلب.

المهم هنا أن هذين يُقرَّران منفصلين. الهامش الصلب تقرّره الطابعة، وMarginBounds يُحسب من Margins التي حدّدها التطبيق فقط. MarginBounds لا يُقرَّب تلقائياً إلى النطاق القابل للطباعة. العلاقة إذا رُسمت ليست تداخلاً متداخلاً، بل كالتالي.

MarginBounds والهامش الصلب يُقرَّران منفصلينيُحسب MarginBounds من Margins فقط ولا يُقرَّب إلى النطاق القابل للطباعة. إذا صغّرت Margins عن الهامش الصلب، خرج MarginBounds من النطاق القابل للطباعة.PageBounds ── حجم الورقة كلهاالنطاق القابل للطباعةPrintableArea وHardMarginX وHardMarginYتقرّره الطابعة. لا يضيّقه التطبيقMarginBoundsيُحسب من Margins فقطلا يُقرَّب إلى النطاق القابل للطباعةالنطاق الأكيد للخروج ── الجزء الذي يتداخل فيه الاثنان

الشكل 2: MarginBounds والهامش الصلب يُقرَّران منفصلين. إذا صغّرت Margins عن الهامش الصلب، خرج MarginBounds من النطاق القابل للطباعة.

  • PageBounds: حجم الورقة كلها.
  • الهامش الصلب: المنطقة الفيزيائية غير القابلة للطباعة التي تقرّرها الطابعة. يُحصَل عليه بـ HardMarginX / HardMarginY، ولا يضيّقه التطبيق.4 النطاق القابل للطباعة نفسه يُؤخذ بـ PageSettings.PrintableArea (وحدة 1/100 بوصة).17
  • MarginBounds: منطقة الرسم التي تعكس Margins التي حدّدها التطبيق (الافتراضي بوصة واحدة من الجهات الأربع).5

أي أنه عندما يكون الهامش المنطقي أصغر من الهامش الصلب كما عند ضبط Margins على 0، يخرج MarginBounds خارج النطاق القابل للطباعة. الرسم عندئذ بمعيار MarginBounds يقطع محتوى الشريط الخارج فلا يُطبع. هذا هو حقيقة «ضبطت الهامش على 0 فانقطع الطرف».

للميل إلى الجانب الآمن خذ أحد التالي.

  • اجعل الهامش المنطقي أكبر من الهامش الصلب أو مساوياً له. قارن كل ضلع من Margins بـ HardMarginX / HardMarginY، وارفع الأصغر
  • اطلب المستطيل المستخدَم في الرسم بتداخله مع PrintableArea. لا تستخدم MarginBounds كما هو، وخذ الجزء المشترك مع النطاق القابل للطباعة بـ Rectangle.Intersect
// PrintPage イベント内。単位はすべて100分の1インチ
private void OnPrintPage(object? sender, PrintPageEventArgs e)
{
    PageSettings page = e.PageSettings;

    // 用紙の左上から見た「印字可能な範囲」。左上の位置はハードマージンそのもの
    var printableOnPaper = new RectangleF(
        page.HardMarginX, page.HardMarginY,
        page.PrintableArea.Width, page.PrintableArea.Height);

    // MarginBounds は Margins からの計算値で、印字可能な範囲へは丸められていない。
    // 重なりを取れば、ハードマージンが大きい機種でも欠けない範囲が出る。
    // ここまでは「用紙の左上」を原点とした座標
    Rectangle safeOnPaper = Rectangle.Intersect(
        e.MarginBounds, Rectangle.Round(printableOnPaper));

    // ここが要注意。OriginAtMargins が既定の false のとき、e.Graphics の原点は
    // 「印字可能な範囲の左上」にあり、用紙の左上ではありません。用紙座標のまま
    // 描くと、ハードマージンのぶんだけ右下へずれ、右端と下端が印字可能な範囲から
    // はみ出して欠けます ── この例が防ごうとしている症状そのものです。
    // 描く前に Graphics の座標へ移します
    Rectangle safe = safeOnPaper;
    safe.Offset(
        -(int)Math.Round(page.HardMarginX),
        -(int)Math.Round(page.HardMarginY));

    e.Graphics!.DrawRectangle(Pens.Black, safe);
}

إذا صارت OriginAtMargins = true تغيّر موضع الأصل، فتغيّر هذا التصحيح أيضاً. قرّر بأي منهما ترسم أولاً ثم جمّع الإحداثيات. الخلط يصيّر عطلاً يصعب تتبّعه: الموضع ينزاح فقط عند تغيير الجهاز.

وثمة انتباه أيضاً لأصل الإحداثيات. افتراضي PrintDocument.OriginAtMargins هو false، وعندئذ أصل Graphics يُوضع في أعلى يسار النطاق القابل للطباعة، وقيمة Margins لا تُستخدم في تقرير الأصل.18 إذا شعرت أن «ضبطت Margins فلم ينزاح بذلك القدر» أو بالعكس «ينزاح زيادة»، فاشبه هنا أولاً. إذا أردت وضع الأصل داخل الهامش صرّح بـ OriginAtMargins = true.

3. WinForms: الممارسة مع System.Drawing.Printing

طباعة WinForms تُجمع حول مكوّن PrintDocument. التدفق الأساسي: إنشاء PrintDocument، وضبط PrinterSettings وDefaultPageSettings، والرسم الفعلي في حدث PrintPage، وبدء الطباعة بمنهج Print().1

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Printing;
using System.Windows.Forms;

public sealed class ReportPrinter
{
    // 印刷対象データ。1行1件の伝票明細を想定
    private readonly IReadOnlyList<string> _lines;
    private int _lineIndex;

    public ReportPrinter(IReadOnlyList<string> lines) => _lines = lines;

    public void Print(string printerName)
    {
        using var document = new PrintDocument();
        document.DocumentName = "تفاصيل أمر الاستلام";
        if (!string.IsNullOrEmpty(printerName))
        {
            document.PrinterSettings.PrinterName = printerName;
        }

        // 未知のプリンター名を指定した場合にここでfalseになる(オフライン検知には使えない)
        if (!document.PrinterSettings.IsValid)
        {
            throw new InvalidOperationException(
                $"指定したプリンターが見つかりません: {printerName}");
        }

        _lineIndex = 0;
        document.PrintPage += Document_PrintPage;
        document.Print();
    }

    private void Document_PrintPage(object? sender, PrintPageEventArgs e)
    {
        // MarginBoundsは論理的な余白(Margins)を反映した描画可能領域。
        // PageBoundsは用紙全体で、ハードマージンより内側に描いても切れる
        using var font = new Font("メイリオ", 10);
        float lineHeight = font.GetHeight(e.Graphics);
        float y = e.MarginBounds.Top;

        while (_lineIndex < _lines.Count)
        {
            if (y + lineHeight > e.MarginBounds.Bottom)
            {
                // このページはここまで。続きがあるので次ページへ
                e.HasMorePages = true;
                return;
            }

            e.Graphics.DrawString(_lines[_lineIndex], font, Brushes.Black,
                e.MarginBounds.Left, y);
            y += lineHeight;
            _lineIndex++;
        }

        // 全行を描き終えたので、これが最後のページ
        e.HasMorePages = false;
    }
}

النمط النموذجي لعطل «الصفحة الثانية لا تخرج» نسيان ضبط HasMorePages صراحة على false (الافتراضي false)، أو بالعكس الخروج دائماً بـ false لخطأ في كتابة التفريع. حدث PrintPage يُستدعى تكراراً حتى تصير هذه الخاصية false، فاضبط بعد الحكم صراحة على «هل بقيت أسطر ينبغي رسمها».12 إذا أردت عكس إعدادات مختلفة لكل صفحة (مقاس الورق أو الاتجاه وغيرها) يمكن الاستفادة أيضاً من حدث QueryPageSettings إضافة إلى PrintPage.

النمط النموذجي لعطل «الهامش ينزاح» تجميع الإحداثيات بمعيار e.Graphics.VisibleClipBounds أو e.PageBounds، وتجاهل MarginBounds (المنطقة التي تعكس الهامش المنطقي) وHardMarginX/HardMarginY (القيد الفيزيائي للطابعة).4 خصوصاً أن طابعة جهاز التطوير قد يكون هامشها الصلب صغيراً فلا تظهر المشكلة مصادفة، وفي جهاز آخر عند التسليم يكون الهامش الصلب كبيراً فتبدو الخطوط والبنود مقطوعة. التحقق بالطباعة على أجهزة عدة مرحلة يسهل تفويتها عند التطوير.

عند تقديم معاينة استخدم PrintPreviewDialog وعيّن PrintDocument نفسه لخاصية Document، فيُعاد استخدام منطق حدث PrintPage كما هو.1 إذا أردت تبديل الإخراج حسب دقة الطابعة، احصل على الخيارات من PrinterSettings.PrinterResolutions وعيّنها في PageSettings.PrinterResolution.3

4. WPF: FlowDocument / FixedDocument وPrintDialog

في WPF يتغيّر محور FlowDocument أو FixedDocument بحسب طبيعة الوثيقة.

  • FlowDocument. وثيقة تقدّم القراءة، تعيد تخطيط المحتوى ديناميكياً حسب متغيرات وقت التشغيل مثل حجم النافذة وإعداد الخط. تملك قياسياً وظائف البحث والترقيم وتغيير وضع العرض.8
  • FixedDocument. وثيقة WYSIWYG تقدّم الأمانة، يتحكم جانب التطبيق في التخطيط تماماً. تناسب عندما تلزم إعادة إنتاج عالية الدقة لجهاز العرض والطباعة.9

الخط الأساسي: تقرير داخلي يقدّم سهولة القراءة بالتدفق فـ FlowDocument، وقسيمة أو ملصق تريد تثبيت موضع الخطوط والبنود بالبكسل فـ FixedDocument.

الطباعة الأساسية تتم بـ System.Windows.Controls.PrintDialog. هذا الصنف غير System.Windows.Forms.PrintDialog، وهو خاص بـ WPF.10

using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;

public static class FlowDocumentPrinter
{
    public static void Print(FlowDocument document, string jobName)
    {
        var printDialog = new PrintDialog();

        // ユーザーがOKを押した場合のみ印刷する
        if (printDialog.ShowDialog() != true)
        {
            return;
        }

        // FlowDocumentは表示用と印刷用でページサイズの前提が異なるため、
        // 印刷対象のページサイズ・余白をプリンターの印刷可能領域に合わせる
        var paginator = ((IDocumentPaginatorSource)document).DocumentPaginator;
        paginator.PageSize = new Size(
            printDialog.PrintableAreaWidth, printDialog.PrintableAreaHeight);

        printDialog.PrintDocument(paginator, jobName);
    }
}

إذا أردت تعيين مقاس الورق ودرج التغذية والطباعة على الوجهين تفصيلاً، استخدم PrintQueue وPrintTicket/PrintCapabilities. PrintTicket تعليمات لمهمة الطباعة، وPrintCapabilities الوظائف التي تدعمها الطابعة فعلاً، والإعداد المحدَّد القاعدة دمجه والتحقق منه إلى قيمة صالحة خاصة بالطابعة بـ PrintQueue.MergeAndValidatePrintTicket ثم استخدامه.711

using System.Linq;
using System.Printing;

public static class DuplexPrintTicketBuilder
{
    public static PrintTicket BuildDuplexTicket(PrintQueue printQueue)
    {
        PrintCapabilities capabilities = printQueue.GetPrintCapabilities();

        // 両面印刷(長辺綴じ)に対応しているプリンターかどうかを事前に確認する
        bool supportsDuplex = capabilities.DuplexingCapability
            .Contains(Duplexing.TwoSidedLongEdge);

        var requestedTicket = new PrintTicket();
        if (supportsDuplex)
        {
            requestedTicket.Duplexing = Duplexing.TwoSidedLongEdge;
        }

        // ユーザーの既定PrintTicketに対して、両面印刷の要求だけをマージする。
        // 未対応の項目は妥当な値に自動的に置き換えられる
        ValidationResult result = printQueue.MergeAndValidatePrintTicket(
            printQueue.UserPrintTicket, requestedTicket);

        return result.ValidatedPrintTicket;
    }
}

عند تجميع تقرير بتخطيط ثابت بـ FixedDocument وإرساله مباشرة إلى مسار طباعة XPS، تُضاف المهمة إلى PrintQueue بمنهجي Write/WriteAsync في XpsDocumentWriter. إذا كان المستهدف طابعة لا تدعم XPSDrv، حُوّل داخلياً تلقائياً إلى صيغة GDI ثم أُرسل، فلا حاجة لجانب الاستدعاء إلى وعي المسار.7

5. خيارات إخراج PDF

متطلب «أريد الإخراج بـ PDF» يتغيّر تصميمه بحسب أي من الثلاثة التالية يُقصد.

  1. كامتداد للطباعة، يكفي أن يختار المستخدم حفظ PDF يدوياً
  2. تريد توليد PDF من داخل التطبيق عبر مربع حوار الطباعة
  3. بلا طباعة أصلاً، تريد توليد ملفات PDF فقط كمّاً في دفعة بلا إشراف

Microsoft Print to PDF ميزة للنوعين 1 و2. ميزة محمَّلة قياسياً منذ Windows 10، موضعها طابور طباعة وحزمة برنامج تشغيل، وتبدو لتطبيق الطباعة كطابعة عادية واحدة.12 لكن حقيقتها آلية تفترض تشغيلاً تفاعلياً «يسأل عن اسم ملف الوجهة في كل طباعة»، والرسمي لا يقدّم وسيلة تحكم إلا بوحدة أن يستطيع المدير إزالة طابور الطباعة وحزمة برنامج التشغيل نفسها من الصورة.12 الاستخدام 3، إخراج PDF بهدوء بمسار ملف ثابت في معالجة دفعية كثيرة بلا إشراف، لا يناسب التصميم. إذا أردت تحقيق 3، فالبسيط استخدام مكتبة تولّد PDF مباشرة دون المرور بخط أنابيب الطباعة.

محاور المقارنة عند اختيار مكتبة توليد PDF، قبل تزكية منتج معيّن، رتّب متطلبات شركتك بالمحاور التالية حتى لا يميل الاختيار.

المحور ما تؤكده
الترخيص مفتوح المصدر (عائلة MIT/Apache أم عائلة GPL/AGPL) أم ترخيص تجاري. إذا وزّعت تطبيقك أو بعته خارج الشركة، أكد حتماً من المصدر الأولي (نص الترخيص وموقع المزوّد الرسمي) أن شروط ترخيص المكتبة (وجود التزام كشف الشيفرة، شروط إعادة التوزيع، جواز الاستخدام التجاري) لا تناقض شكل توزيع شركتك
التعامل مع الخطوط اليابانية هل يدعم تضمين مجموعة فرعية من الخطوط اليابانية، وهل يمكن جعل PDF يحمل كيان الخط دون تضمين (حتى لا تفسد الحروف إذا غاب الخط في بيئة العرض)، وحالة الدعم إذا لزم العمل كتابة عمودية أو أشكال بديلة (IVS)
وظائف موجَّهة للتقارير هل يدعم تعريف التقرير على أساس قالب، وتوليد الرموز الشريطية والثنائية الأبعاد، والتوقيع الإلكتروني، وPDF/A (معيار الحفظ الطويل)، وسهولة نقل تخطيط تقرير ورقي قائم كما هو
التبعيات وبيئة التشغيل هل يعمل في بيئة خادم (Windows Server بلا واجهة، حاوية)، وهل هناك تبعية لمكتبة أصلية، وسجل العمل في بيئة .NET Core/.NET (غير Framework)
الأداء استخدام الذاكرة عند التوليد الدفعي لصفحات ومهام كثيرة، والسلوك عند التوليد المتوازي

شروط الترخيص تتغيّر حسب المنتج والإصدار، فأكد حتماً وثائق المزوّد الرسمية أو نص الترخيص نفسه ثم احكم بالاعتماد. هذا المقال لا يزكي اسم مكتبة معيّنة أو منتجاً، ويعرض «محاور» المقارنة وأين تؤكد تلك المعلومات.

إجراء البحث عن المرشحين والمقارنة كالتالي. البحث بالمحاور وحدها يغيّر الخلاصة بحسب زمن كتابة المقال، فتثبيت ترتيب الرجوع إلى المصدر الأولي أضمن.

المرحلة أين تنظر ما تنظر إليه تحديداً
1. عدّ المرشحين وسم pdf في NuGet Gallery، بحث Categories (Tags) رتبة عدد التنزيلات. فرق رتبتين فأكثر له معنى كفرق سجل الاعتماد
2. تأكيد أنه حي «Version History» في صفحة حزمة NuGet هل صدر إصدار خلال السنة الماضية، وهل فترة الإصدار متباعدة زيادة
3. النظر إلى واقع التطوير مستودع GitHub (رابط «Source repository» في صفحة NuGet) تاريخ آخر إيداع، مدى بقاء Open Issue، وجود رد من الصائن على Issue
4. تأكيد دعم .NET عمود «Frameworks» في صفحة NuGet هل الهدف الذي تستخدمه شركتك مثل net8.0 ضمن النطاق. ما يقتصر على net472 لا يُستخدم في .NET (عائلة Core)
5. تأكيد الترخيص نص الترخيص نفسه في رابط «License» في صفحة NuGet جواز التوزيع التجاري، وجود التزام كشف الشيفرة. اقرأ النص لا مقالاً ملخِّصاً
6. التجريب بالعيّنة مشروع تحقق صغير تضمين الخطوط اليابانية، وزمن التوليد واستخدام الذاكرة بعدد الصفحات المتوقَّع

ما يسهل تفويته خصوصاً 4 و5. تقرير الأمر بمقال تقديم ياباني ثم اكتشاف «كان يدعم .NET Framework فقط» أو «كان AGPL فلم يمكن إدماجه في منتج الشركة» يعني رمي جهد التحقق كله. تمرير 4 و5 وحدهما قبل تضييق المرشحين إلى واحد يقلّل إعادة العمل.

6. خيار التقارير عبر Excel / Word

إذا كان لديك تقرير يعمل أصلاً بقالب Excel، فقد يكون تكوين الاستفادة من الأصل القائم أوقع من إعادة بنائه من الصفر. الأسلوبان إجمالاً اثنان.

  • توليد وتحرير xlsx/docx مباشرة بـ Open XML SDK وغيره. لا يشغّل ملف تنفيذ Excel/Word، ويقرأ ويكتب صيغة الملف (ZIP + XML) مباشرة، فيعمل بثبات في بيئة خادم أيضاً. يتوافق مع تكوين يُبقي خطوط القالب وتنسيقه كما هما ويدرج القيم فقط، والتفاصيل مرتّبة في «كيف تبني إخراج تقارير Excel».
  • تشغيل Excel.Application فعلاً عبر Excel COM Interop. يُختار عندما تريد إعادة استخدام مصنف Excel قائم كما هو بما فيه وحدات الماكرو وإعادة الحساب المعقّدة، لكن يسهل وقوع مشكلة بقاء عملية EXCEL.EXE بتسرّب مراجع COM، والإجراءات ومعيار الحكم متناولان في «مشكلة بقاء EXCEL.EXE عند التعامل مع Excel من C#».

المهم هنا أن أتمتة Office COM من جانب الخادم أو خدمة Windows Microsoft تعلن رسمياً أنها «غير موصى بها وغير مدعومة». تطبيقات Office مصممة بافتراض سطح مكتب تفاعلي وملف تعريف مستخدم مسجّل دخوله، ويُقال إنها قد تتصرف بعدم استقرار أو تصل إلى deadlock إذا نُفّذت في بيئة بلا إشراف وغير تفاعلية.13 ومن جهة الترخيص أيضاً، استخدام يقدّم وظائف Office للعميل بأتمتة في جانب الخادم ليس مفترضاً في EULA العادي.13

لذلك إذا أردت إدماج إخراج التقارير في خدمة مقيمة أو مهمة دفعية، حتى مع إعادة استخدام قالب Excel قائم، فالأسلم تجنّب التكوين الذي يشغّل Excel.exe فعلاً وقت التشغيل (COM Interop) والميل إلى التوليد المباشر للملف بـ Open XML SDK وغيره. إذا لزم COM Interop حتماً، فالتمييز الواقعي تكوينه تطبيقاً مقيماً يعمل في جلسة مستخدم مسجّل دخولاً تفاعلياً (وليس خدمة).

7. مطبات الممارسة

الطباعة من خدمة Windows والجلسة 0

الاستشارة بإدماج معالجة الطباعة في خدمة مقيمة ليست قليلة، لكن خدمة Windows تعمل في الجلسة 0. الجلسة 0 جلسة محجوزة منذ Windows Vista للخدمات والعمليات غير المرتبطة بمستخدم تفاعلي، ومفصولة عن الجلسة التفاعلية للمستخدم.14 بهذا العزل، حتى إذا عرضت الخدمة مربع حوار لم يره المستخدم، وشيفرة تنتظر إدخال المستخدم (انتظار عرض مربع حوار الطباعة مثلاً) تبدو معلَّقة.14

وما يسهل أن يصير مطبقاً عملياً أيضاً كيفية ظهور طابعة شبكة أو طابعة معيَّنة فردياً للمستخدم. طابعة الشبكة التي اتصل بها المستخدم بعد تسجيل الدخول كثيراً ما تظهر مرتبطة بجلسته التفاعلية، ومن سياق عملية الخدمة في الجلسة 0 (جلسة أخرى) لا تظهر بالمثل. معالجة مقيمة تصاحبها طباعة يلزم تأكيد «من أي حساب وأي جلسة تُرى أي طابعة» في أول التصميم. الصورة الكلية لعزل الجلسات مرتّبة في «كيف نفهم عزل الجلسات في Windows»، وأساس التنفيذ والتشغيل كخدمة في «كيفيّة إنشاء خدمات Windows وتشغيلها».

السلوك عند غياب الطابعة أو كونها غير متصلة

تعيين اسم طابعة غير موجودة في PrinterSettings.PrinterName، أو التنفيذ في بيئة بلا طابعة افتراضية مضبوطة، يُحدث استثناء أو فشلاً عند محاولة الطباعة. التحقق مسبقاً بـ PrinterSettings.IsValid، وتصميم إعادة المحاولة وإشعار الخطأ بافتراض فشل الطباعة، نقطتان يُراد تثبيتهما كحد أدنى. إذا كانت طابعة الشبكة غير متصلة مؤقتاً، قد تُكدَّس المهمة في المُبَكِّر ويبقى الإخراج الفعلي متوقفاً، فلا تُنهِ نتيجة الطباعة عند «أُرسلت»، وانظر عند الحاجة آلية تؤكد حالة المهمة.

خطر الاعتماد على الطابعة الافتراضية

تنفيذ لا يصرّح بـ PrinterSettings.PrinterName ويعتمد دائماً على «الطابعة الافتراضية» يصير خطراً كلما طال التشغيل. تغيّر البيئة ── المستخدم يغيّر طابعة أخرى إلى الافتراضية، أو يُخرج حاسوباً محمولاً إلى مكان آخر فتتغيّر الطابعة الافتراضية ── يرسل إلى وجهة غير مقصودة. التقارير المهمة للعمل ينبغي أن تحتفظ صراحة باسم طابعة الوجهة في ملف إعداد أو شاشة إعداد داخل التطبيق، بتصميم لا ينجرّ وراء تغيّر الطابعة الافتراضية.

تسرّب مقابض GDI في التشغيل الطويل

كتابة شيفرة داخل معالجة الطباعة لا تحرّر موارد GDI/GDI+ مثل Graphics وFont وBrush وPen بـ using أو Dispose() تؤدي بعد التشغيل الطويل إلى نضوب مقابض كائنات GDI، فتبدأ استدعاءات واجهات الرسم بالفشل. مقابض كائنات GDI سقف افتراضي لكل عملية، مورد محدود يمكن ضبطه في نطاق 256 إلى 65,536 بقيمة سجل GDIProcessHandleQuota.15 كلما كان التطبيق مقيماً يطبع بكثرة، مال هذا النوع من التسرّب إلى عطل لا يظهر إلا في بيئة الإنتاج. طريقة البحث عن تسرّب المقابض وعزله مشروحة بتفصيل في «حين يتعطّل تطبيق التحكّم بكاميرا صناعيّة فجأةً بعد شهر (الجزء الأوّل)» التي تناولت حالة تعطل تشغيل طويل لكاميرا صناعية.

8. الخلاصة

شجرة قرار ── أي الخيارات الأربعة تختار

① إلى ④ المذكورة في المقدمة تُضيَّق إذا طُرحت الأسئلة بالترتيب التالي.

  1. هل الطباعة لازمة حقاً. إذا كفى أن يكون المخرج ملف PDF، والورق يفعله المستخدم اختياراً، فـ ③ (التوليد المباشر بمكتبة PDF) الذي لا يمر بخط أنابيب الطباعة هو الأبسط. Microsoft Print to PDF يفترض تشغيلاً تفاعلياً يسأل عن وجهة الحفظ، ولا يصلح للدفعة بلا إشراف (الفصل 5).
  2. هل تريد إعادة استخدام قالب Excel/Word قائم. إن نعم فـ . لكن أتمتة COM من خدمة مقيمة أو دفعة Microsoft تعلن أنها غير مدعومة، فمل إلى التوليد المباشر بـ Open XML SDK وغيره (الفصل 6).
  3. هل الشاشة WinForms أم WPF. طابق إطار عمل التطبيق القائم. WinForms فـ ، WPF فـ . لا حاجة لخلط التقنيتين (الفصلان 3 و4).
  4. إذا اخترت WPF، تدفق أم تخطيط ثابت. تقرير داخلي يقدّم القراءة فـ FlowDocument، وقسيمة أو ملصق تريد تثبيت موضع الخطوط فـ FixedDocument (الفصل 4).

ما تؤكده قبل التنفيذ

  • قرّر الوحدة ونظام الإحداثيات أولاً. لا تُعد استخدام إحداثيات بكسل الشاشة في الطباعة كما هي. اجعل وحدة 1/100 بوصة أو القياس الفعلي (ملليمتر أو بوصة) معياراً (الفصل 2).
  • افهم أن الهامش مستويان. ارسم بمعيار MarginBounds لا بمعيار PageBounds. حتى ضبط Margins على 0 لا يُخرج إلا داخل الهامش الصلب (الفصل 2، الشكل 2).
  • اضبط HasMorePages صراحة. اضبط بعد الحكم على «هل بقيت أسطر للرسم». الافتراضي false، ونسيان الضبط السبب النموذجي لـ «الصفحة الثانية لا تخرج» (الفصل 3).
  • احتفظ صراحة بطابعة الوجهة. لا تعتمد على الطابعة الافتراضية، واجعل اسم الطابعة في ملف إعداد أو شاشة إعداد التطبيق (الفصل 7).
  • حرّر موارد GDI حتماً بـ using. تسرّب Font وBrush وPen لا يظهر إلا في بيئة إنتاج تشغيل طويل (الفصل 7).
  • الطباعة من خدمة أكّد الحد أولاً. في الجلسة 0 لا تُعرض مربعات الحوار، ولا تُرى طابعات الشبكة المرتبطة بالمستخدم (الفصل 7).
  • تحقق على أجهزة عدة. حجم الهامش الصلب يختلف حسب الجهاز، فقد لا تكون مشكلة في جهاز التطوير وتنقطع الخطوط عند التسليم (الفصل 3).

مقالات ذات صلة

مجالات استشارية ذات صلة

تتعامل شركة كومورا سوفت ذ.م.م. مع تصميم ميزات الطباعة وإخراج التقارير في تطبيقات أعمال Windows، وإعادة بناء التقارير بالاستفادة من أصول Excel القائمة، والاستشارة التقنية في تصميم الطباعة والإخراج من خدمة مقيمة.

روابط مرجعية

  1. Microsoft Learn, PrintDocument Class. حول دور PrintDocument (ضبط DocumentName وPrinterSettings وبدء الطباعة بـ Print())، والإرشاد إلى استخدام مساحة الأسماء System.Printing عند الطباعة من WPF.  2 3 4

  2. Microsoft Learn, PrintPageEventArgs.HasMorePages Property. حول أن الافتراضي false، وأن حدث PrintPage يتكرر حتى تصير هذه الخاصية false 2

  3. Microsoft Learn, PageSettings.PrinterResolution Property. حول أن القيمة الافتراضية لدقة طباعة الصفحة هي الدقة الافتراضية للطابعة، وإمكانية الحصول على قائمة الدقات القابلة للاختيار من PrinterSettings.PrinterResolutions 2 3

  4. Microsoft Learn, PageSettings.HardMarginX Property. حول أن الهامش الصلب يمثّل هامشاً فيزيائياً تضبطه الطابعة.  2 3 4

  5. Microsoft Learn, PageSettings.Margins Property. حول أن القيمة الافتراضية لهامش الصفحة بوصة واحدة من الجهات الأربع، وإمكانية حساب المنطقة القابلة للطباعة في حدث PrintPage مع خاصية Bounds 2 3

  6. Microsoft Learn, Windows Print Path Overview. حول وجود مساري طباعة رئيسين في Windows: مسار طباعة GDI (من تطبيقات Win32) ومسار طباعة XPS (من تطبيقات WPF أو XPS Print API).  2 3

  7. Microsoft Learn, Printing documents overview. حول استخدام تطبيقات WPF مسار طباعة XPS، والطباعة الأساسية بـ PrintDialog، والطباعة المتقدّمة بـ PrintTicket/PrintCapabilities/PrintQueue/XpsDocumentWriter، والتحويل التلقائي إلى GDI لطابعة لا تدعم XPSDrv.  2 3 4

  8. Microsoft Learn, Flow Document Overview. حول أن FlowDocument وثيقة تقدّم القراءة، تعيد تخطيط المحتوى حسب متغيرات وقت التشغيل مثل حجم النافذة والدقة.  2

  9. Microsoft Learn, FixedDocument Class. حول أن FixedDocument تصميم WYSIWYG يقدّم إعادة الإنتاج الأمينة لجهاز العرض والطباعة، وأن فكر تصميمه يختلف عن FlowDocument 2

  10. Microsoft Learn, PrintDialog Class. حول أن System.Windows.Controls.PrintDialog مربع حوار يكوّن PrintTicket وPrintQueue حسب إدخال المستخدم، وأنه صنف غير System.Windows.Forms.PrintDialog 2

  11. Microsoft Learn, How to: Validate and Merge PrintTickets. حول تأكيد وظائف الطابعة المدعومة بـ PrintQueue.GetPrintCapabilities، وإجراء دمج الطلب والتحقق منه إلى PrintTicket صالح خاص بالطابعة بـ MergeAndValidatePrintTicket 2

  12. Microsoft Learn, RemoveMPDW. حول أن Microsoft Print to PDF ميزة اختيارية تُثبَّت افتراضياً، وتُكوَّن وتُحذف بوحدة طابور الطباعة وحزمة برنامج التشغيل.  2 3

  13. Microsoft Support, Considerations for server-side Automation of Office. حول أن Microsoft لا توصي بأتمتة Office من تطبيقات عميل بلا إشراف وغير تفاعلية تشمل ASP/ASP.NET/DCOM/خدمات NT ولا تدعمها، وأن Office مصمَّم بافتراض سطح مكتب تفاعلي وملف تعريف مستخدم.  2 3

  14. Microsoft Learn, Service Changes for Windows Vista - Session 0 Isolation. حول أنه منذ Windows Vista حُجزت الجلسة 0 للخدمات والتطبيقات غير المرتبطة بمستخدم تفاعلي، وتعذّر عرض الخدمة لمربع حوار مباشرة.  2 3 4

  15. Microsoft Learn, GDI Objects. حول أن مقابض كائنات GDI تملك سقفاً افتراضياً لكل عملية، وإمكانية ضبط السقف في نطاق 256 إلى 65,536 بقيمة سجل GDIProcessHandleQuota 2

  16. Microsoft Learn, Introduction to printing. حول أن معمارية الطباعة في Windows تتكوّن من المُبَكِّر وبرنامج تشغيل الطابعة، وآلية لف أوامر رسم تطبيق Win32 GDI كـ EMF للمُبَكِّر.  2

  17. Microsoft Learn, PageSettings.PrintableArea Property. حول تمثيل النطاق الذي تستطيع الطابعة الطباعة فيه بوحدة 1/100 بوصة، وأن هذه الخاصية تتيح الطباعة خارج هامش الصفحة وداخل النطاق القابل للطباعة. 

  18. Microsoft Learn, PrintDocument.OriginAtMargins Property. حول أن الافتراضي false، وأنه عند false يُستخدم النطاق القابل للطباعة وحده في تقرير الأصل وتُتجاهل PageSettings.Margins

أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.

ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.

الأسئلة الشائعة

أسئلة شائعة حول موضوع هذه المقالة.

أيهما ينبغي استخدامه: PrintDocument أم طباعة WPF؟
إذا كانت الشاشة WinForms فاستخدم PrintDocument ببساطة، وإذا كانت WPF فاستخدم FlowDocument/FixedDocument. لا حاجة لخلط التقنيتين، والمعيار العملي مطابقة إطار عمل الشاشة القائم في التطبيق. وإذا اخترت WPF في تطوير جديد، فتقرير محور FlowDocument أو FixedDocument مسبقاً بحسب كون التقرير تدفقاً أو تخطيطاً ثابتاً يمنع التردد لاحقاً.
هل ينبغي شراء مكتبة تقارير؟
إذا كانت تكلفة بناء تقارير معقّدة كثيرة الخطوط، ودعم مقاسات ورق متعددة، وتضمين خطوط يابانية أو كتابة عمودية، ذاتياً برسم GDI+/WPF كبيرة، فغالباً ما تكون تكلفة إدخال مكتبة تقارير أرخص. وبالعكس، لتقرير بسيط بحجم قائمة A4 غالباً يكفي التنفيذ الذاتي عبر PrintDocument أو FixedDocument، فالأسلم تقدير تعقيد المتطلبات أولاً ثم الحكم.
هل يجوز تكوين ينشئ PDF فقط دون استخدام الطباعة؟
جائز تماماً. PDF صيغة إخراج واحدة، والتوليد المباشر بمكتبة PDF لا يعتمد على مربع حوار الطباعة أو الطابعة الافتراضية، وغالباً ما يتوافق أفضل مع المعالجة الدفعية والأتمتة. أما Microsoft Print to PDF فبرنامج تشغيل يفترض تشغيلاً تفاعلياً، ولا يصلح لتوليد PDF بلا إشراف.
هل يمكن دعم طابعات الملصقات أو طابعات الإيصالات؟
ممكن، لكن قد يصعب أحياناً كامتداد لـ PrintDocument/FixedDocument العام. فكثير من طابعات الملصقات لها لغة أوامر خاصة أو SDK، وكثيراً ما تُتحكَّم عبر مسار منفصل عن مسار الطباعة القياسي في Windows. ابدأ بتأكيد هل الجهاز المستهدف يتصرف كبرنامج تشغيل طابعة عادي في Windows، أم يحتاج تحكماً عبر SDK مخصّص.

الملف الشخصي للمؤلف

صفحة الملف الشخصي لمؤلف المقالة.

غو كومورا

مؤسّس شركة كومورا سوفت ذ.م.م.

يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.

العودة إلى المدونة