Using the printer/pl
│
Deutsch (de) │
English (en) │
español (es) │
日本語 (ja) │
polski (pl) │
中文(中国大陆) (zh_CN) │
Wstęp
Drukowanie jest łatwe w Lazarus/FreePascal. Musisz jednak wykonać kilka wymaganych kroków. Najpierw pierwsze kroki. Po tych pierwszych krokach możesz przejść do bardziej zaawansowanego drukowania. W tym artykule omówiono wymagane kroki zebrane z różnych stron forum i przykładów. Po wykonaniu podstawowych kroków wydrukujemy trochę tekstu. Na koniec podana jest wskazówka dotycząca bardziej zaawansowanego drukowania.
Podstawowe kroki
Aby móc korzystać z drukarek, musisz wykonać następujące czynności:
- Dodaj pakiet Printer4Lazarus do wymagań projektu.
- Dodaj moduł Printers do sekcji uses w swoim module.
- Użyj istniejącego obiektu Printer.
Dodanie pakietu Printer4Lazarus do wymagań projektu
Pakiet Printer4Lazarus definiuje podstawową drukarkę i zapewnia drukowanie niezależne od platformy. Dzięki temu na różnych platformach można korzystać z poniższych rzeczy.
W Lazarus IDE wykonaj następujące czynności:
- W menu Projekt kliknij Inspektor projektu. Wyświetlane jest okno z widokiem drzewa, a jedna z gałęzi nazywa się Wymagane pakiety. Domyślnie gałąź Wymagane pakiety pokazuje pakiet LCL.
- Kliknij przycisk Dodaj, czyli przycisk ze znakiem plus u góry okna.
- Otwórz stronę Nowy wymóg.
- Z listy Nazwa pakietu wybierz Printer4Lazarus.
- Kliknij OK.
- Printer4Lazarus jest teraz pokazywany w gałęzi Wymagane pakiety.
Dodanie modułu Printers do sekcji uses twojego modułu
Ten krok jest prosty, a wynik może wyglądać tak:
unit MainUnit;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Forms, Printers;
Korzystanie z istniejącego obiektu Printer
Załóżmy, że chcesz kliknąć przycisk, aby wydrukować tekst. W formularzu umieść przycisk o nazwie PrintBtn, a w zdarzeniu OnClick możesz teraz użyć:
procedure TForm1.PrintBtnClick(Sender: TObject);
const
LEFTMARGIN = 100;
HEADLINE = 'Data i czas mojego pierwszego wydruku to: ';
var
YPos, LineHeight, VerticalMargin: Integer;
SuccessString: String;
begin
with Printer do
try
BeginDoc;
Canvas.Font.Name := 'Courier New';
Canvas.Font.Size := 10;
Canvas.Font.Color := clBlack;
LineHeight := Round(1.2 * Abs(Canvas.TextHeight('I')));
VerticalMargin := 4 * LineHeight;
// No to jedziemy
YPos := VerticalMargin;
SuccessString := HEADLINE + DateTimeToStr(Now);
Canvas.TextOut(LEFTMARGIN, YPos, SuccessString);
finally
EndDoc;
end;
end;
Czy napisałem podstawowy i prosty? Powyższy przykład jest nieco złożony. Oprócz podstawowego tekstu wyjściowego w pogrubionej linii, zawiera również przykład formatowania tekstu.
Od słów kluczowych begin do end; dzieją się następuje rzeczy:
- Wraz z Printer.BeginDoc; rozpoczynasz drukowanie - jednak nic nie jest wysyłane do drukarki, dopóki nie zakończysz czynności poleceniem Printer.EndDoc;.
- Drukarka używa płótna (canvas) do rysowania wydruku. To właśnie ten rysunek trafia na wydrukowaną stronę. Canvas.Font to obiekt czcionki używany na wyjściowym płótnie. Oznacza to, że wywołanie TextOut, którego użyjemy później, wyświetli w tym momencie tekst przy użyciu ustawień obiektu czcionki.
- Wszystko, co rysujesz na płótnie, musi być ustawione za pomocą współrzędnych. Tak więc obliczamy LineHeight, aby ustawić tekst w pionie. Możesz zrobić to samo dla pozycji poziomej, którą tutaj zostawiłem jako LEFTMARGIN.
- Tekst jest rysowany za pomocą wywołania TextOut.
- Ten wspaniały wynik jest wysyłany do drukarki przez Printer.EndDoc.
Uwaga: Na niektórych forach sugeruje się, że do prawidłowego funkcjonowania wymagane jest użycie PrintDialog (okno dialogowe wyboru drukarki), ale nie uznałem tego prawdę (nigdy więcej).
Porada: Niestety jest to prawda, choć nie pełna. Przy pierwszym uruchomieniu powyższego przykładu może się zdarzyć, że pojawi się błąd Access Violation. Nie wiem jeszcze jaka jest przyczyna tego błędu. Gdy położymy PrintDialog na formatce, to błąd już się nie pojawia. Co więcej, można ten PrintDialog usunąć, a błąd już nie wraca. (Ten problem zaobserwowałem w systemie Fedora. Niestety teraz nie mogę go odtworzyć. Prawdopodobnie problem dotyczy plików konfiguracyjnych w katalogu .lazarus)
Następne kroki
Po wykonaniu powyższych podstawowych kroków, możesz wykonać kolejne kroki. Zostawiam to tobie czytelniku, abyś spróbował:
- Zrób rysunki na papierze.
- Ładnie sformatuj tekst.
- Wybierz inną drukarkę i porównaj wyniki.
W dystrybucji Lazarusa znajduje się przykładowy projekt, który wykorzystuje do drukarki drukowanie w trybie Raw. Ten przykład można znaleźć w następującej lokalizacji $(lazarusdir)\components\printers\samples\rawmode\rawmodetest.lpi. Kolejny przykład pokazuje, jak wybrać inną drukarkę: $(lazarusdir)\components\printers\samples\dialogs\selectprinter.lpi.
Zaawansowane kroki: sterowanie drukowaniem
Obiekt drukarki umożliwia przede wszystkim rysowanie na płótnie i wysyłanie tego obrazu do drukarki. Jeśli będziemy kontynuować ścieżką pokazaną powyżej, użyjesz metod płótna drukarki, aby narysować tekst, elipsy, romby i co tylko chcesz.
Nie może to jednak zainteresować żadnego poważnego programisty. Pracujesz nad doskonalszym programem CAD lub jeszcze innym sortownikiem plików graficznych i chcesz wydrukować wspaniały rezultat swojego programu na swojej drukarce. Próba przetłumaczenia idealnego obrazu na wywołania metod na płótnie nie jest dobrą drogą: ponieważ ty już masz gotowy obraz.
Każda kontrolka, którą umieścisz w formularzu, również rysuje obraz na obiekcie TCanvas, podobnie jak drukarka. Możemy to wykorzystać do przeniesienia obrazu z ekranu do drukarki.
Wyobraź sobie, że zamierzasz stworzyć program podglądu wydruku. Tworzysz formularz i na tym formularzu umieszczasz TPanel. Ten panel zapewni ładne szarawe tło podglądu. Na panelu umieszczasz kolejny obiekt TPanel o nazwie page (strona). Ten panel page będzie biały i reprezentuje papier. Możesz ładnie dopasować rozmiar strony.
Na tej stronie umieściliśmy obiekt TShape, na przykład ładny czerwony, zaokrąglony prostokąt. Teraz spróbuj wykonać następujące czynności w metodzie zdarzenia PrintBtnClick:
MyPrinter.BeginDoc;
page.PaintTo(myPrinter.Canvas, 0, 0);
MyPrinter.EndDoc;
Co tu się dzieje?
- BeginDoc rozpoczyna drukowanie (ale nic nie zostało jeszcze wysłane).
- page.PaintTo wysyła wyjście naszego obiektu TPanel reprezentującego stronę na płótno drukarki. Zwróć uwagę na następujące kwestie:
- Możesz użyć metody PaintTo dowolnej kontrolki w hierarchii kontrolek. Możesz również wysłać dane wyjściowe całego okna do drukarki, jeśli chcesz.
- Możesz wysłać wynik dowolnej kontrolki za pomocą metody PaintTo do drukarki, dzięki czemu możesz być kreatywny. Aby wysłać dane wyjściowe z sortera obrazów do drukarki, można wysłać dane wyjściowe TImage do drukarki.
- TCanvas również posiada metodę kopiowania prostokątów z innego płótna. Jednak możesz to zrobić tylko wtedy, gdy obiekt naprawdę narysowany jest na płótnie. Myślę, że większość kontrolek polega na kontenerach, aby zapewnić prawdziwe płótno, więc nie można kopiować prostokątów z dowolnej kontrolki. Przynajmniej dla mnie to nie zadziałało.
- Upewnij się, że kontrolka, którą chcesz pomalować na drukarce, jest widoczna. Jeśli kontrolka nie jest widoczna, nic nie zostanie namalowane nawet na drukarce.
- EndDoc wysyła rysunek do drukarki.
Zmiana rozmiaru papieru
Drukarka zużywa znacznie więcej pikseli na cal na papierze niż monitor używa pikseli na cal na ekranie. W rezultacie wydruk końcowy, który jest przekierowywany z ekranu do drukarki, ma na papierze rozmiar raczej niewielki. Skalowanie i kontrolowanie układu jest ważne dla dobrego wyglądu wydruku. Byłoby miło, gdybyś mógł mieć kopię tego, co widzisz na ekranie w dokładnym rozmiarze.
Na razie nie dążymy do ideału, tylko do idei. Jak wspomniano wcześniej, kontrolki nie mają własnego płótna, ale opierają się na płótnie kontenera lub właściciela. Istnieją jednak komponenty, które mają własne płótno. Gdy wybrałem TBitMap, działa w następujący sposób:
procedure TForm1.PrintBtnClick(Sender: TObject);
var
MyPrinter : TPrinter;
myBitMap : TBitMap;
begin
myBitMap := TBitMap.Create;
myBitMap.Width := page.Width;
myBitMap.Height := page.Height;
page.BorderStyle:=bsNone;
page.PaintTo(myBitMap.Canvas, 0, 0);
page.BorderStyle:=bsSingle;
//
MyPrinter := Printer;
MyPrinter.BeginDoc;
//page.PaintTo(myPrinter.Canvas, 0, 0);
//myPrinter.Canvas.Draw(0,0, myBitMap);
myPrinter.Canvas.CopyRect(Classes.Rect(0, 0, myPrinter.PaperSize.Width, myPrinter.PaperSize.Height),
myBitMap.Canvas, Classes.Rect(0, 0, myBitMap.Width, myBitMap.Height));
MyPrinter.EndDoc;
myBitMap.Free;
end;
Aby to zadziałało, nie używaj modułu Windows. Moduł Windows ma inne definicje dla Rect. To, co widzisz w przykładzie, działa w następujący spsób:
- Tworzona jest mapa bitowa o takim samym rozmiarze jak kontrolka page.
- Aby uniknąć drukowania ramki, to BorderStyle kontrolki page jest wyłączany przed namalowaniem jej na bitmapie, a następnie przywracany do poprzedniej wartości.
- Następnie rozpoczyna się drukowanie, a zawartość płótna BitMap jest kopiowana na płótno drukarki.
Ale zauważ jedną rzecz, że w tym procesie strona (page) jest powiększana. Printer.papersize jest znacznie większy niż rozmiar mapy bitowej (bitmap), ale proces kopiowania ładnie wypełnia docelowy prostokąt. Tak więc, kiedy chcemy to zrobić, musimy upewnić się, że wymiary page mają taki sam stosunek szerokości do wysokości, jak wymiar papieru. Możesz dowiedzieć się, jak to zrobić.
Problem z tym sposobem pracy polega oczywiście na tym, że na wydrukowanym papierze pojawią się piksele ekranu. Jak powiedziałem, nie jest to sposób idealny, ale pokazuje zasadę działania. Kontrolki nie mają własnego płótna; aby wydrukować kontrolki, najpierw malujemy je na obiekcie, który ma swoje własne płótno: TBitMap. Teraz wiesz, jak to działa, możesz wymyślić sposób na tworzenie pięknych dzieł sztuki lub dokumentów. Będzie to wymagało obiektów, które rysują się poprawnie w TPanel o niskiej rozdzielczości, ale także na TBitMap o wysokiej rozdzielczości. Ale to jest temat na inny artykuł.
Zwykłe zadania
Pamiętaj, że Printer: TPrinter to singleton (może istnieć tylko jedna instancja obiektu tej klasy). Jest to pojedynczy obiekt, który obsługuje wszystkie drukarki zainstalowane w systemie. Konkretna realizacja w systemie (WinAPI, Cocoa itp.) powinna implementować ten obiekt.
Wyliczanie dostępnych drukarek
Lista drukarek systemowych jest dostępna we właściwości Printer.Printers.
Właściwość Printers jest typu TStrings, która zawiera nazwę każdej drukarki.
procedure TForm1.FormShow(Sender: TObject);
begin
ListBox1.Items.AddStrings(Printer.Printers);
end;
Wybranej nazwy podanej we właściwości Printers należy użyć ustawiając drukarkę za pomocą metody SetPrinter().
Internals: każda klasa implementująca klasę TPrinter powinna implementować metodę DoEnumPrinters(). DoEnumPrinters powinien wypełnić przekazane parametry listy nazwami drukarek. Oczekuje się, że nazwa drukarki jest unikalna.
DoEnumPrinters() powinien zwrócić domyślną drukarkę systemową jako pierwszy wpis (indeks 0) na liście.
Zmiana/Wybór drukarki
Domyślnie obiekt Printer powinien mieć wstępnie wybraną drukarkę „domyślną systemową”. (Z tym, że w systemie Windows 10 i nowszych może nie być ustawiona drukarka domyślna.)
W razie potrzeby drukarka może zostać zmieniona przez użytkownika za pośrednictwem PrinterDialogs lub programowo.
W celu programowej zmiany drukarki należy użyć metody SetPrinter lub właściwości PrinterIndex.
Obie metody opierają się na drukarkach wymienionych w PrintersProperty.
- PrinterIndex określa nazwę drukarki na podstawie określonego indeksu z PrintersList, a następnie wywołuje metodę SetPrinter.
procedure TForm1.Button2Click(Sender: TObject);
begin
Printer.SetPrinter(ListBox1.ItemIndex);
end;
- Jeśli określony indeks wynosi -1, to znaczy, że wybrana została drukarka domyślna
- Metoda SetPrinter, sprawdza, czy żądana nazwa istnieje na liście właściwości Printers.
procedure TForm1.Button2Click(Sender: TObject);
begin
Printer.SetPrinter(ListBox1.GetSelectedText);
end;
- Zauważ, że istnieje specjalna nazwa dla metody SetPrinter. Jeśli nazwa jest określona jako '*', wybierana jest pierwsza (domyślna) dostępna drukarka.
Każda z metod może zgłosić wyjątek, jeśli określona nazwa lub indeks nie istnieje.
Internals: Klasa pochodna od TPrinter powinna implementować metodę DoSetPrinter() używając parametru aName jako nazwy wybranej drukarki. Metoda nie wymaga specjalnej obsługi nazwy '*', ponieważ jest ostrożna na wyższym poziomie niż TPrinter.
Przygotowywanie rozmiaru papieru
Uwaga: jeśli używasz wielu drukarek (zwłaszcza jeśli używasz specjalistycznych drukarek używających niestandardowego rozmiaru papieru, takiego jak „A4” (europejski) lub „US Letter” (USA)), zawsze chcesz przygotować właściwy rozmiar papieru. Możliwe, że nowo wybrana drukarka nie obsługuje papieru, który był obsługiwany przez poprzednio wybraną drukarkę. Rozmiar papieru nie jest resetowany automatycznie, więc musisz to zrobić samodzielnie.
Istnieją trzy właściwości PaperSize, które można wywołać w celu zresetowania i przygotowania rozmiaru papieru:
- PaperName - zwraca aktualnie wybraną nazwę papieru wybranej drukarki
- DefaultPaperName - zwraca domyślną nazwę papieru dla wybranej drukarki
- SupportedPapers - zwraca listę rozmiarów papieru obsługiwanych przez wybraną drukarkę
Oto przykład kodu, który ponownie odczytuje listę wybranych rozmiarów papierów
procedure TForm1.ListBox1SelectionChange(Sender: TObject; User: boolean);
begin
// zmieniono drukarkę. Ale wybrany rozmiar papieru nadal może być właściwy
Printer.PrinterIndex := ListBox1.ItemIndex;
ListBox2.Clear;
// wywołanie SupportedPapers wymuszające weryfikację rozmiaru papieru
ListBox2.Items.AddStrings(Printer.PaperSize.SupportedPapers);
end;
Oto przykład resetowania rozmiaru papieru przy użyciu właściwości DefaultPaperName.
procedure TForm1.ListBox1SelectionChange(Sender: TObject; User: boolean);
begin
// zmieniono drukarkę. Ale wybrany rozmiar papieru nadal może być właściwy
Printer.PrinterIndex := ListBox1.ItemIndex;
// wywołanie DefaultPaperName wymuszające weryfikację rozmiaru papieru
Printer.PaperSize.DefaultPaperName;
end;
Internals: Pochodna klasa TPrinter powinna poprawnie implementować:
- Metodę DoEnumPapers(). Metoda ta może użyć właściwości PrinterIndex lub PrinterName do określenia aktualnie wybranej drukarki, do której należy wyliczyć rozmiary papieru.
Jeśli metoda wewnętrzna nie zwraca żadnych rozmiarów papieru, klasa TPrinter zastępuje je „domyślnymi” rozmiarami papieru (takimi, które przypuszczalnie obsługiwane są przez większość drukarek). Domyślne rozmiary to np.: Letter, A4, Legal. Jednak dobra implementacja klasy TPrinter powinna wypełniać rozmiary stron.
- Metoda DoGetPaperRect(). Metoda zwraca fizyczny rozmiar papieru określony przez nazwę bieżącej drukarki. Nazwa papieru jest przekazywana przez właściwość aName. Zwracana wartość powinna być wypełniona do struktury TPaperRect.
Należy wypełnić także dwa pola:
- PhysicalRect - rzeczywisty rozmiar całej strony. Rozmiar podany jest w punktach. Oczekuje się, że Left i Top będą zerami.
- WorkRect - prostokąt, w którym drukarka może faktycznie drukować (ze względu na pewne mechaniczne ograniczenia samej drukarki). Prostokąt powinien być zawsze taki sam lub mniejszy niż PhysicalRect. Left i Top są odsunięciami od lewego górnego rogu fizycznego prostokąta. Jednostkami są punkty drukarki.
Liczba punktów zależy od aktualnie wybranego DPI drukarki (ang. dots per inch - liczba punktów obrazu na cal długości). (Zauważ, że istnieją dwa różne DPI dla pomiarów poziomych i pionowych).
Oto tabela przedstawiająca różnicę w rozmiarze A4 w zależności od rozmiaru DPI
DPI | Zwrócony rozmiar w TPaperRect |
72 | 595 x 842 |
300 | 2480 x 3507 |
600 | 4960 x 7014 |
1200 | 9920 x 14028 |
- DoGetDefaultPaperName() - metoda powinna zwrócić nazwę domyślnego rozmiaru papieru, dla aktualnie wybranej drukarki.
- DoGetPaperName - metoda powinna zwrócić nazwę aktualnie wybranego rozmiaru papieru, dla aktualnie wybranej drukarki.
- DoSetPaperName - metoda powinna ustawić rozmiar papieru według nazwy dla wybranej drukarki.
Zobacz także
- Drukowanie
- Dokumentacja do LazReport LazReport może być używany do projektowania raportów opartych na danych, które można drukować, eksportować do formatu PDF itp.
- Poradnik do LazReport