HotPDF, Delphi PDF görüntüleyicisini iki parçaya ayırır: pencere tanıtıcısı bağımlılığı olmayan, yakınlaştırma, döndürme, arama, vurgulama ve gezinme durumunu tutan sade bir sınıf olan THPDFViewerModel ve bu durumu piksellere dönüştüren TScrollBox tabanlı bir kontrol olan THPDFViewer. Bu ayrım, görüntüleyici mantığının hiçbir zaman bir form oluşturmadan çalışmasını ve test edilmesini sağlar
Çoğu özel görüntüleyici kontrolü böyle görünmez. Yakınlaştırma seviyesi kontrol üzerinde özel bir alanda yaşar, sayfa gezinmesi sınırlarını bir düğmenin OnClick işleyicisi içinde sıkıştırır ve Ctrl+kaydırmanın bir yakınlaştırma tavanına uyup uymadığını bilmenin tek yolu uygulamayı çalıştırmak, tıklamak ve bakmaktır. Bu şekilde inşa edilmiş bir kontrol, bir regresyon paketine ya da ikinci bir barındırıcıya — bir yazdırma önizleme diyaloğu, bir küçük resim şeridi, hiç görünür penceresi olmayan toplu bir inceleyici — ihtiyaç duyana kadar gayet iyi çalışır; ve ihtiyacınız olan durumun, herhangi bir şey yapmadan önce gerçek bir tanıtıcı bekleyen bir TWinControl'a kaynaşmış olduğu ortaya çıkar
Bir PDF görüntüleyici kontrolünün neden bir MVC ayrımına ihtiyacı var?
Bir PDF görüntüleyicinin bu tür bir ayrıma ihtiyacı vardır çünkü durumu ve sunumu farklı nedenlerle ve farklı hızlarda değişir. Sayfa dizini, yakınlaştırma, görünüm döndürmesi, arama sonuçları ve vurgulama bölgeleri iş durumudur: ekranda tek bir piksel olmadan hesaplanabilir, doğrulanabilir ve serileştirilebilirler. Bir bit eşlemeyi boyamak, fareyi yakalamak ve bir seçim dikdörtgeni çizmek, yalnızca bir kontrol var olduğunda anlam kazanan sunum kaygılarıdır. HotPDF ilk grubu, VCL pencereleme atası hiç olmayan bir sınıf olan THPDFViewerModel'de tutar; ikinci grubu ise bir model örneğine sahip olan ve ona tepki veren THPDFViewer'da tutar — ayrı bir Controller sınıfı olmadığı ve THPDFViewer'ın kendisi ham klavye ve fare olaylarını model çağrılarına dönüştürdüğü için ders kitabındaki üç katmanlı MVC'den ziyade bir Model-View çiftine daha yakın. Etiketten daha önemli olan bağımlılık yönüdür: THPDFViewerModel hakkında hiçbir şey bir Handle, bir mesaj döngüsü veya görünür bir masaüstü gerektirmez; bu tam olarak HotPDF'in kendi test paketinin bir pencere açmadan sayfalama, yakınlaştırma sıkıştırma, klavye komutları ve koordinat gidiş-dönüşlerini DUnitX üzerinden çalıştırmasını sağlayan şeydir
uses
DUnitX.TestFramework,
HPDFDoc, HPDFViewerModel;
type
[TestFixture]
TViewerModelTests = class
public
[Test]
procedure ZoomInStopsAtTheTopPresetLevel;
end;
procedure TViewerModelTests.ZoomInStopsAtTheTopPresetLevel;
var
Doc: THotPDF;
Model: THPDFViewerModel;
begin
Doc := THotPDF.Create(nil);
Model := THPDFViewerModel.Create;
try
Doc.LoadFromFile('sample.pdf');
Model.Document := Doc;
Model.Zoom := 64.0; // top of the preset table (6400%)
Model.ZoomIn; // already at the ceiling
Assert.AreEqual(64.0, Model.Zoom, 0.0001);
finally
Model.Free;
Doc.Free;
end;
end;
THPDFViewerModel gerçekte neyi sahiplenir?
THPDFViewerModel, ekranda şu anda ne olması gerektiğine yanıt vermek için bir görüntüleyicinin ihtiyaç duyduğu her şeyi, bunu nasıl çizeceğini sahiplenmeden sahiplenir. PageIndex, PageNumber ve PageCount konumu izler; Zoom ve ZoomMode (vzmActualSize, vzmFitPage, vzmFitWidth, vzmCustom) ölçeği izler; ViewRotation, sayfanın kendi /Rotate girişine hiç dokunmayan, yıkıcı olmayan bir ekran-üstü döndürmeyi izler. Gezinme yöntemleri — FirstPage, PriorPage, NextPage, LastPage — ve yakınlaştırma yöntemleri — ZoomIn, ZoomOut, %5'ten %6400'e sabit on dokuz ön ayarlı seviye tablosunda gezinen — de burada yaşar; metin araması için FindAll/FindNext/FindPrevious ve çağıranın oluşturmalar arasında saklamak istediği kalıcı sayfa açıklamaları için AddHighlightRegion/RemoveHighlightRegion/ClearHighlightRegions de burada yaşar. Model çıktıyı da girdi kadar sahiplenir: CreateCurrentPageSnapshot ve CreateCurrentPageMetafile, ekranda şu anda olan sayfayı tam olarak dışa aktarır ve PrintCurrentView aynı bu geçerli görünümü — geçerli sayfa, geçerli yakınlaştırmadan türetilen DPI, geçerli döndürme — bir TPrinter'a gönderir; bu, HotPDF'in TPrinter yazdırma anlatımında ele alınan belge geneli yazdırma boru hattından daha dar, görünüme özgü bir iştir. Önemli olan her değişiklik eşleşen bir olay da tetikler — OnPageChange, OnZoomChange, OnSearchChange, OnHighlightChange, OnViewRotationChange — böylece bir abone, yoklamadan neyin değiştiğini öğrenir
THPDFViewer ne zaman yeniden boyayacağını nasıl bilir?
THPDFViewer ne zaman yeniden boyayacağını bilir çünkü tahmin etmek yerine modele abone olur. THPDFViewer'ın oluşturucusu özel bir THPDFViewerModel oluşturur, ardından her bir bildirim olayını — OnBeginUpdate, OnEndUpdate, OnHighlightChange, OnPageChange, OnSearchChange, OnViewRotationChange, OnZoomChange — eşleşen özel bir işleyiciye bağlar. Her işleyicinin işi küçüktür: HotPDF'in sayfa-bit eşlemeye oluşturma iç işleyişinde anlatılan aynı önbelleğe alınmış sayfa oluşturucu üzerinden geçerli sayfayı gerçekten rasterleştiren yöntem olan RefreshDocument'i çağırmak, ardından üzerine vurgulama kutularını ve arama sonuçlarını birleştirmek ve geçerli görünüm döndürmesini uygulamak. PageIndex, Zoom, ZoomMode ve ViewRotation gibi yayınlanan özellikler ince yönlendiricilerdir — getter FModel.PageIndex'i okur, setter FModel.PageIndex'i yazar — böylece Nesne İnceleyicisinden veya koddan bakıldığında, kontrol durumu doğrudan tutuyormuş gibi görünür; oysa o durumun gerçekten yaşadığı tek yer THPDFViewerModel'dir. Çağıranlar yönlendirilen alt kümeyle de sınırlı değildir: THPDFViewer, modelin kendisini salt okunur bir Model: THPDFViewerModel özelliği üzerinden ortaya çıkarır; böylece kontrolün yeniden ortaya çıkarmadığı FindFormFieldAt veya PrefetchCurrentPageSnapshots'ı isteyen kod, sarmalayıcının ötesine geçip modeli doğrudan çağırabilir
procedure THPDFViewer.RefreshDocument;
var
Bitmap: TBitmap;
DPI: Integer;
begin
// simplified: the real method also resolves fit-mode DPI
// and composites highlight and search-hit rectangles first
if (FModel.Document = nil) or (FModel.PageIndex < 0) then Exit;
DPI := Round(96 * FModel.Zoom);
Bitmap := FModel.Document.RenderLoadedPageToBitmapCached(FModel.PageIndex, DPI);
try
FModel.ApplyViewRotation(Bitmap);
FImage.Picture.Bitmap.Assign(Bitmap);
finally
Bitmap.Free;
end;
end;
BeginUpdate ve EndUpdate: yeniden çizim fırtınalarını durdurmak
BeginUpdate ve EndUpdate var çünkü tek bir mantıksal değişiklik genellikle aynı anda birkaç durum parçasına dokunur ve her parçadan sonra yeniden boyamak israf olur ve görsel olarak gürültülü olur. Yüklenen belgeyi değiştirmek en açık örnektir: THPDFViewerModel.Document'e atama yapmak görünüm döndürmesini sıfırlar, arama sonuçlarını temizler, vurgulama bölgelerini temizler ve birinci sayfaya atlar; ve bu adımların her biri normalde kendi değişiklik olayını tetikler. THPDFViewerModel bu diziyi, iç içe çağrıların yalnızca en dıştaki çağrıya geçişte OnBeginUpdate'i ve geri çıkışta OnEndUpdate'i tetiklediği referans sayımlı bir çift olan BeginUpdate/EndUpdate içine sarar. THPDFViewer kendi tarafında aynı derinliği izler ve sayı sıfırın üzerindeyken her ayrıntılı olay için RefreshDocument'i atlar, ardından toplu işlem kapandığında tam olarak bir kez yeniden boyar. Ayrıntılı olaylar toplu işlem sırasında yine de tetiklenir; bu yüzden yalnızca OnSearchChange'i önemseyen bir abone yine de bunu duyar; yalnızca kontrolün kendi yeniden boyaması dört çağrı yerine bire indirgenir
Seçim vurgulama, bir fare sürüklemesini PDF koordinatlarına nasıl geri eşler?
Seçim vurgulama, bir fare sürüklemesini tam olarak bu gidiş-dönüş için inşa edilmiş bir çift model yöntemi üzerinden PDF koordinatlarına geri eşler: PagePointToView ve ViewPointToPage. Her ikisi de bir sayfa dizini, bir DPI ve bir nokta alır ve her ikisi de dönüşümü iki aşamada çözer — önce sayfanın kendi /Rotate girişi ve sol alt PDF orijini, ardından görünümün ayrı, yıkıcı olmayan ViewRotation'ı ve görüntüleyicinin sol üst aygıt orijini — özellikle ters yönün bu iki aşamayı kesinlikle ters sırada geri alabilmesi ve sayfa döndürmesi ile görünüm döndürmesinin tüm on altı kombinasyonunda doğru gidiş-dönüş yapabilmesi için. THPDFViewer, kullanıcı vimHighlight etkileşim modunda bir dikdörtgeni sürükledikten sonra fareyi bıraktığında ViewPointToPage'i çağırır, iki aygıt noktasını sayfa alanında bir THPDFRectangle'a dönüştürür ve Model.AddHighlightRegion'a verir. Buna benzer bir şey inşa ederseniz bilmeye değer bir ayrıntı: fare yakalama, bit eşlemenin boyandığı alt TImage'a değil, TScrollBox'tan türeyen görüntüleyiciye aittir, çünkü TControl.MouseCapture korumalıdır ve yalnızca ebeveyn kontrol onu talep edebilir — bu yüzden düğme kalkmadan önce görüntünün sınırlarından çıkan bir sürükleme, alt kontrol tarafından sessizce düşürülmek yerine yine de görüntüleyicinin kendi geçersiz kılınmış MouseMove/MouseUp'ı üzerinden çözülür
var
ViewPt, PagePt: THPDFViewerPoint;
Rect: THPDFRectangle;
begin
ViewPt.X := 240; // device pixels inside the rendered image
ViewPt.Y := 96;
if Model.ViewPointToPage(Model.PageIndex, ViewPt, PagePt,
RenderedDPI) then // DPI you last rendered at
begin
Rect.Left := PagePt.X - 40; Rect.Bottom := PagePt.Y - 10;
Rect.Right := PagePt.X + 40; Rect.Top := PagePt.Y + 10;
Model.AddHighlightRegion(Model.PageIndex, Rect);
end;
end;
Bu ayrım yeşil bir test paketinin ötesinde size ne kazandırır
Kazanım, masaüstü oturumu olmayan bir CI işinde geçen testlerle sınırlı değildir. THPDFViewer, mantığını çoğaltmak yerine THPDFViewerModel'e yönlendirdiği için, HotPDF üçüncü bir tüketici eklemeyi başardı — THPDFViewerAction ve THPDFZoomInAction, THPDFFindNextAction gibi somut alt sınıflar — bunlar gezinme, yakınlaştırma, arama ve döndürmeyi standart bir Delphi TActionList'e takar; böylece bir araç çubuğu düğmesi veya bir menü öğesi, bir görüntüleyicinin şu anda eylemin hedefi olarak çözülüp çözülmediğine göre kendini otomatik olarak etkinleştirerek görüntüleyiciyi bildirimsel olarak yönlendirebilir. Bu katmanın hiçbiri bit eşlemeler veya GDI hakkında hiçbir şey bilmek zorunda kalmadı; Viewer.NextPage veya Viewer.Model.FindNext'i çağırır ve mevcut olay zinciri yeniden boyamayı halleder. Ve THPDFViewerModel'de hiçbir şey TScrollBox, TImage veya bir pencere tanıtıcısına referans vermediği için, altındaki durum makinesi de o tek kontrole kaynaşmış değildir — aynı model, gezinme, yakınlaştırma veya arama mantığının tek bir satırına dokunmadan farklı bir oluşturma yüzeyinin arkasında oturabilir
Oluşturma önbelleğinin yardımcı olduğu ve olmadığı yerler
THPDFViewerModel'in oluşturma önbelleği, yüklenmiş bir belge içinde yardımcı olur, ancak o belgeyi ilk etapta yüklemenin maliyetini değiştirmez. CreatePageSnapshot, CreateCurrentPageSnapshot ve önceden getirme yöntemleri PrefetchPageSnapshots/PrefetchCurrentPageSnapshots, hepsi sayfa ve DPI ile anahtarlanan aynı önbelleğe alınmış oluşturucu üzerinden yönlendirilir; böylece aynı yakınlaştırma seviyesinde daha önce görüntülediğiniz bir sayfaya geri sayfalamak yeniden oluşturmak yerine bir önbellek isabetidir ve komşu sayfaların küçük bir yarıçapını önceden getirmek, bir okuyucunun bir seferde bir sayfa ileri sayfalamasının yaygın durumunu yumuşatır. Ancak bunların hiçbiri ilk LoadFromFile çağrısının maliyetine dokunmaz ve kullanıcının üzerine sürükleyeceği her şeyi açmak için inşa edilmiş bir görüntüleyici, er ya da geç bu çağrıyı gerçek darboğaz haline getirecek kadar büyük bir dosyayla karşılaşır. Tam yüklemeye kademeli, tanıtıcı tabanlı alternatif için — o gün gelmeden önce bilmeye değer — bkz. büyük PDF'ler için Doğrudan Dosya API'si üzerine tamamlayıcı makale
Burada anlatılan Model ve View sınıfları, Delphi ve C++Builder için HotPDF Bileşeni'nde her yerde kullanılan aynı yüklenmiş belge yüzeyinin iki parçası daha olup; bir formdan, bir TActionList'ten veya hiçbirinden yönlendirilmek üzere inşa edilmiştir