Using the printer/pl

From Lazarus wiki
Jump to navigationJump to search

Deutsch (de) English (en) español (es) 日本語 (ja) polski (pl) 中文(中国大陆) (zh_CN)

Korzystanie z drukarki

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.

Light bulb  Uwaga: Obecnie drukowanie w WinCE (przy użyciu pakietu Printers4Lazarus) nie jest obsługiwane.

Podstawowe kroki

Aby móc korzystać z drukarek, musisz wykonać następujące czynności:

  1. Dodaj pakiet Printer4Lazarus do wymagań projektu.
  2. Dodaj moduł Printers do sekcji uses w swoim module.
  3. 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:

  1. 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.
  2. Kliknij przycisk Dodaj, czyli przycisk ze znakiem plus u góry okna.
  3. Otwórz stronę Nowy wymóg.
  4. Z listy Nazwa pakietu wybierz Printer4Lazarus.
  5. Kliknij OK.
  6. 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.

Light bulb  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).

Note-icon.png

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:
    1. 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.
    2. 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.
    3. 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.
    4. 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