مستندان مفتوحان في الوقت نفسه، ونفس رقم الصفحة، وكل واحد داخل لوحة قابلة للتمرير خاصة به: هذا هو جوهر عارض المقارنة. يقدّم PDFium VCL ذلك عبر نموذج كائنات مباشر يكون فيه TPdf مالك الملف وTPdfView مالك العرض. مستند واحد، TPdf واحد، TPdfView واحد. إذا أردت ثلاث لوحات فستحتاج ثلاثة أزواج. ليست الصعوبة في استدعاءات الـ API؛ بل في حسابات التخطيط عند تغيير حجم النافذة ومنطق مزامنة الصفحات عندما تقرر أي عرض يجب أن يتبع أي عرض.
تخطيط النموذج
يحمل نموذج VCL ثلاثة حاويات TScrollBox جنبًا إلى جنب، وفي كل واحدة TPdfView مضبوط على alClient بحيث يملأ الصندوق. توجد مكوّنان TSplitter بين الصناديق حتى يتمكن المستخدم من ضبط عرض الأعمدة وقت التشغيل. ويحتوي شريط أدوات فوق اللوحات على أزرار الفتح، وعناصر التكبير، ومبدّل العرض الثنائي / الثلاثي.
وضع العرض الثلاثي قيمة منطقية يحتفظ بها النموذج داخليًا. عندما تتبدل، تعيد حساب العروض وتعرض العمود الثالث أو تخفيه. أبسط أسلوب هو إلغاء جميع خصائص Align، وإخفاء الـ splitters، ثم تعيين المواضع المطلقة:
procedure TFormMain.UpdateLayout;
var
TotalWidth: Integer;
begin
TotalWidth := ClientWidth;
if ThreeViewMode then
begin
ScrollBox3.Visible := True;
ScrollBox1.Left := 0;
ScrollBox1.Width := TotalWidth div 3;
ScrollBox2.Left := ScrollBox1.Width;
ScrollBox2.Width := TotalWidth div 3;
ScrollBox3.Left := ScrollBox2.Left + ScrollBox2.Width;
ScrollBox3.Width := TotalWidth - ScrollBox3.Left;
// Apply the same (ClientHeight - toolbar height) to all three Height values
end
else
begin
ScrollBox3.Visible := False;
ScrollBox1.Left := 0;
ScrollBox1.Width := TotalWidth div 2;
ScrollBox2.Left := ScrollBox1.Width;
ScrollBox2.Width := TotalWidth - ScrollBox2.Left;
end;
end;
تعيين Align := alNone على الصناديق الثلاثة قبل الحسابات الصحيحة بالأعداد الصحيحة يمنع محرك قيود VCL من منازعة تعييناتك. أعد إظهار الـ splitters بعد التموضع إذا كنت تريد السحب لتغيير الحجم في وضع العرض الثنائي.
ارتفاع كل ScrollBox هو مساحة العميل ناقص ارتفاع لوحة شريط الأدوات. وبما أن شريط الأدوات مثبت أعلى النموذج باستخدام alTop، فإن ClientHeight - PanelButtons.Height يعطيك المساحة العمودية المتاحة. عيّن هذا الارتفاع للصناديق الثلاثة داخل استدعاء UpdateLayout نفسه حتى لا توجد لحظة تكون فيها إحدى اللوحات أعلى من الأخرى وتسبب وميضًا في التخطيط.
فتح مستند
كل زوج من اللوحات يحتاج إلى إجراء فتح خاص به. النمط قصير: عطّل المكوّن، واضبط اسم الملف، وحاول التفعيل، والتقط EPdfError إذا كان الملف يتطلب كلمة مرور. لاحظ أن TPdfView.Active هو ما يتحكم في العرض، لكن TPdf.Active هو ما يفتح الملف فعليًا؛ وهما مستقلان. ضبط PdfView.Active := True عندما لا يكون TPdf المرتبط به نشطًا بعد أمر غير ضار لكنه لا يعرض شيئًا.
procedure TFormMain.OpenPdfFile(PdfComponent: TPdf;
PdfViewComponent: TPdfView);
var
Password: string;
begin
if not OpenDialog.Execute then
Exit;
PdfComponent.Active := False;
PdfComponent.FileName := OpenDialog.FileName;
PdfComponent.Password := '';
try
PdfComponent.Active := True;
except
on E: EPdfError do
begin
if InputQuery('Password', 'Enter document password:', Password) then
begin
PdfComponent.Password := Password;
PdfComponent.Active := True;
end
else
raise;
end;
end;
if PdfComponent.Active then
begin
PdfViewComponent.PageNumber := 1;
SetActivePdfView(PdfViewComponent);
end;
end;
تحقق دائمًا من PdfComponent.Active بعد الإسناد؛ لأن ملفًا تالفًا أو كلمة مرور خاطئة يتسببان في فشل التحميل بصمت من دون رمي استثناء في المسار الافتراضي. ضبط PdfViewComponent.PageNumber := 1 صراحةً بعد فتح ناجح يمنع بقاء رقم صفحة قديم من المستند السابق.
شفرة التعامل مع كلمة المرور أعلاه ترمي الاستثناء على أي خطأ آخر غير رسالة كلمة المرور المعروفة. هذا مقصود: تريد أن تظهر الملفات التالفة أو غير المدعومة فورًا بدل أن تُبتلع داخل لوحة فارغة صامتة. المستخدم الذي لا يرى شيئًا لا يعرف إن كان الملف قد حُمّل وهو فارغ أصلًا أم أن المكوّن رفضه. الرمي يبقي الخطأ مرئيًا.
تتبع اللوحة النشطة
عندما ينقر المستخدم داخل لوحة، تصبح تلك اللوحة نشطة. يحتفظ النموذج بحقل خاص FActivePdfView: TPdfView. أما التغذية البصرية فهي تغيير لون الإطار في TScrollBox الحاوي: اجعله clHighlight للوحة النشطة وclWindow للبقية. اربط ذلك بكل TPdfView.OnClick وبإجراء الفتح حتى يتبع التركيز المستند الذي فتحته للتو.
بعض العمليات تنطبق على جميع اللوحات المرئية بدلًا من اللوحة النشطة فقط. متغير منطقي FAllViewsMode في النموذج يقود هذا الفرع. عندما يكون صحيحًا، تنتشر تغييرات التكبير والتنقل بين الصفحات إلى كل لوحة تملك مستندًا نشطًا:
procedure TFormMain.ApplyZoomToAll(NewZoom: Double);
begin
if PdfView1.Active then PdfView1.Zoom := NewZoom;
if PdfView2.Active then PdfView2.Zoom := NewZoom;
if ThreeViewMode and PdfView3.Active then PdfView3.Zoom := NewZoom;
end;
تنقل الصفحات المتزامن
التنقل المتزامن اختياري لكنه مفيد في سير عمل مراجعة المستندات حيث يغطي الملفان نفس نطاق الصفحات. يخص المنطق معالج حدث يعمل بعد تنقل المستخدم في إحدى اللوحات. عندما تغيّر لوحة مصدرية PageNumber، ينقل المعالج هذا الرقم إلى اللوحات الأخرى، مع شرط واحد: يجب أن تكون لوحة الهدف تملك العدد نفسه من الصفحات على الأقل، وإلا يتم التجاوز.
رقم الصفحة على TPdfView وعلى TPdf مستقلان. TPdf.PageNumber يتتبع الصفحة التي يراها مكوّن المستند الحالية؛ وTPdfView.PageNumber يتتبع ما يظهر على الشاشة. لأغراض التنقل تريد خاصية العرض، لا خاصية المستند.
مربع اختيار بعنوان مثل "Sync pages" يمنح المستخدم التحكم. عندما يكون غير محدد، تتنقل كل لوحة بشكل مستقل ويخرج المعالج فورًا. هذه الاستقلالية مهمة لحالات الاستخدام التي يختلف فيها عدد الصفحات بين المستندين، أو حين يريد المستخدم العثور على المقطع المقابل في ترجمة تبدأ من صفحة مختلفة. فرض التزامن دائمًا سيجعل الأداة أصعب استخدامًا من ترتيب سطح مكتب عادي بنافذتين.
هناك أمر يجب الانتباه إليه: ضبط PdfView.PageNumber برمجيًا داخل معالج التزامن سيطلق بدوره حدث التغيير على تلك اللوحة. احمِ نفسك من الاستدعاء التكراري اللانهائي بعلم منطقي تضبطه قبل الإسناد وتمسحه مباشرة بعده. هذا العلم خاص بالنموذج كله، لا بكل لوحة على حدة، لأن اللوحات الثلاث تشترك في المعالج نفسه.
التكبير لكل لوحة
يحمل كل TPdfView خاصية Zoom الخاصة به، وهي Double بالنسب المئوية حيث 1.0 تعني 100%. ضبطها يتجاوز أي FitMode نشط. لزر ملاءمة العرض في اللوحة النشطة، اقرأ قيمة الملاءمة من PdfView.PageWidthZoom[PdfView.PageNumber] وعيّنها. ولملاءمة الصفحة استخدم PageZoom[PageNumber]. كلاهما خاصيتا مصفوفة مفهرستان برقم صفحة يبدأ من 1، لذا احمِ نفسك من رقم صفحة صفر قبل الوصول إليهما.
عندما تصدّر الصفحة الحالية إلى صورة، اقرأ التدوير من العرض لكن استدعِ RenderPage على مكوّن TPdf لا على العرض نفسه. الشكل النقطي لـ TPdf.RenderPage يأخذ أبعاد بكسل صريحة بالإضافة إلى قيمة TRotation ومجموعة TRenderOptions. أما النسخة التي تعيد قيمة فترجع TBitmap يملكه المستدعي وتحرره بنفسك بعد الحفظ:
procedure TFormMain.SaveActiveViewAsImage;
var
Pdf: TPdf;
Bmp: TBitmap;
Jpeg: TJpegImage;
begin
if not Assigned(FActivePdfView) or not FActivePdfView.Active then
Exit;
Pdf := FActivePdfView.Pdf;
Pdf.PageNumber := FActivePdfView.PageNumber;
Bmp := Pdf.RenderPage(
0, 0,
Round(Pdf.PageWidth * 2),
Round(Pdf.PageHeight * 2),
FActivePdfView.Rotation, [], clWhite);
try
if SavePictureDialog.Execute then
begin
Jpeg := TJpegImage.Create;
try
Jpeg.Assign(Bmp);
Jpeg.CompressionQuality := 90;
Jpeg.SaveToFile(SavePictureDialog.FileName);
finally
Jpeg.Free;
end;
end;
finally
Bmp.Free;
end;
end;
المضاعف 2x على العرض والارتفاع يعطي خرجًا أوضح للمستندات ذات النص الدقيق. والـ try/finally حول تحرير الـ bitmap ليس اختياريًا؛ فإلغاء TSaveDialog لا يمنع دخول كتلة finally، وتريد تحرير الـ bitmap مهما فعل المستخدم.
متطلبات DLL
يغلف PDFium VCL مكتبة pdfium الأصلية. عملية مضيفة 32-bit تحتاج pdfium32.dll؛ وعملية مضيفة 64-bit تحتاج pdfium64.dll. الإصدارات التي تضيف محرك JavaScript V8 تضيف اللاحقة v8 ويبلغ حجمها تقريبًا 23-27 MB مقابل 5-6 MB للإصدارات القياسية. بالنسبة إلى عارض المقارنة الذي يعطل تعبئة النماذج (Pdf.FormFill := False)، تكفي النسخة القياسية غير V8 وتبقي التوزيع أصغر.
ضع DLL في المجلد نفسه الذي يوجد فيه الملف التنفيذي، أو في أي مجلد على PATH الخاص بالنظام. يحمّله المكوّن عند الطلب عندما يُفعّل أول TPdf، لذلك يظهر DLL المفقود عند تلك اللحظة بدلًا من أن يظهر عند بدء التطبيق. إذا كنت تشحن مثبتًا، فأكثر الأساليب موثوقية هو نسخ DLL إلى مجلد التطبيق أثناء التثبيت بدلًا من الاعتماد على مجلد نظام قد ينظفه مدير النظام لاحقًا.
إصدارات V8 مفيدة أساسًا عندما تحتاج إلى التفاعل مع إجراءات JavaScript الخاصة بـ PDF، مثل تشغيل حقول الحساب أو معالجات الإرسال. عارض المقارنة السلبي لا يحتاج إلى تشغيل JavaScript؛ وضبط Pdf.FormFill := False قبل Active := True يتجاوز بيئة تعبئة النماذج بالكامل، ما يعني أيضًا عدم تهيئة أي محرك JS حتى لو كنت تستخدم النسخة القياسية. هذا هو الافتراض الافتراضي الصحيح لعارض للقراءة فقط بغض النظر عن نسخة DLL التي تشحنها.
للمزيد من التفاصيل حول مكوّن PDFium VCL وواجهته الكاملة، زر صفحة المنتج مكوّن Delphi PDFium VCL.