DRUKOWANIE

  Drukowanie tekstu i grafiki w C++ Builder jest tym samym co wyświetlanie na ekranie. Wszystkie podstawowe elementy graficzne C++ Buildera bazują na pojęciu płótna. Przy drukowaniu czy to na ekranie monitora czy też w drukarce posługujemy się klasą TCanvas, która dostarcza "czysty arkusz" do rysowania, posiada przy tym większość niezbędnych do tego funkcji. Płótno należące do TForm rysuje na ekranie wewnątrz prostokąta ograniczonego przez formularz, natomiast płótno należące do klasy TPrinter wykonuje rysunek na fizycznym arkuszu papieru drukarki. Wszystkie metody wykorzystywane przy rysowaniu na ekranie można wykorzystać do rysowania na papierze.
    Metody drukowania opierają się na kartezjańskim układzie współrzędnych z początkiem w górnym lewym rogu płótna. Liczba pikseli występujących na płótnie TPrinter zależy od rozdzielczości z jaką drukuje drukarka, i jest ich z reguły dużo więcej niż na płótnie TForm, to znaczy że jeżeli narysujemy jakąś figurę geometryczną na płótnie formularza to na papierze pokaże się ona w tym samym kształcie tylko będzie dużo mniejsza. Różnica w rozmiarze obiektów rysowanych na papierze jest wprost proporcjonalnie mniejsza od tych rysowanych na ekranie monitora.

WAŻNE!
Aby drukować grafikę trzeba włączyć do sekcji include moduł Printers. Pozwoli to używać obiektu TPrinter. Przy czym obiekt TPrinter zawsze zwraca wskaźnik do aktualnie ustawionej drukarki domyślnej.
Moduł Printers najlepiej włączyć do projektu w pliku nagłówkowym np. Unit1.h:

#include <Printers.hpp>

 Drukowanie zawsze rozpoczyna się od metody wywołującej drukarkę Printer()->BeginDoc() i kończy się metodą wyrzucającą bierzącą stronę Printer()->EndDoc(). Tak więc wszystko ca ma być wydrukowane musi zawierać się między tymi metodami.

Menu

 


Drukowanie grafiki.

    Żeby wydrukować obrazek stworzymy prostą funkcję która będzie to zadanie realizowała. W tym celu posłużę się metodą CopyRect wchodzącą w skład klasy TCanvas a więc również zawiera się ona w klasie TPrinter, albowiem ta klasa dziedziczy klasę TCanvas:

// Plik nagłówkowy np. Unit1.h
//---------------------------------------------------------------------------
#ifndef Unit1H
#define Unit1H

//---------------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <Printers.hpp>
// ważne! ten moduł trzeba włączyć do projektu.
//---------------------------------------------------------------------------
class TForm1 : public TForm
{


// Plik źródłowy np. Unit1.cpp
//--------------------------------

void __fastcall Drukuj(Graphics::TBitmap *Bmp)
{
 Printer()->Orientation = poLandscape;  // ustawienie drukowania w poziomie.

 Printer()->BeginDoc();

 int x = Printer()->PageWidth;  // długość strony.
 int y = Printer()->PageHeight; // wysokość strony.

 Printer()->Canvas->CopyRect(Rect(10, 10, x, y), Bmp->Canvas, Rect(0, 0, Bmp->Width, Bmp->Height));

 Printer()->EndDoc();
}
//--------------------------------

Przedstawiona tutaj przeze mnie funkcja CopyRect po prostu kopiuje obrazek w formacie *.bmp przekazany jako argument funkcji Drukuj, na papier drukarki. Oczywiście to jeszcze niczego nie drukuje, żeby to zrobić trzeba najpierw wczytać jakiś plik a następnie wywołać funkcję Drukuj. W tym celu posłużę się zdarzeniem OnClick dla przycisku Button1:

// Plik źródłowy np. Unit1.cpp
//--------------------------------

void __fastcall TForm1::Button1Click(TObject* Sender)
{
 Graphics::TBitmap *BMP = new Graphics::TBitmap();
 BMP->LoadFromFile("nazwa_pliku.bmp");

 Drukuj(BMP);

 delete BMP;
}
//--------------------------------

Oczywiście to spowoduje rozciągnięcie  obrazka na cały papier więc obraz zostanie zniekształcony. Żeby uniknąć zniekształcenia obrazka trzeba nieco zmodyfikować funkcję Drukuj, tak żeby obrazek został maksymalnie rozciągnięty na papier i jednocześnie zachował proporcje długości do szerokości:

// Plik źródłowy np. Unit1.cpp
//--------------------------------

void __fastcall Drukuj(Graphics::TBitmap *Bmp)
{
 Printer()->Orientation = poLandscape;

 Printer()->BeginDoc();

 int x = Printer()->PageWidth; // długość strony.
 int y = Printer()->PageHeight; // wysokość strony.

 int width;
 int height;

 if((x / Bmp->Width) < (y / Bmp->Height))
 {
  width = Bmp->Width * ((x / Bmp->Width) + 0.5);
  height = Bmp->Height * ((x / Bmp->Width) + 0.5);
 }
 else
 {
  height = Bmp->Height * ((y / Bmp->Height) + 0.5);
  width = Bmp->Width * ((y / Bmp->Height) + 0.5);
 }

 Printer()->Canvas->CopyRect(Rect(10, 10, width, height), Bmp->Canvas, Rect(0, 0, Bmp->Width, Bmp->Height));

 Printer()->EndDoc();
}
//--------------------------------

Na żółto zaznaczyłem metodę Printer()->Orientation a służy ona do ustawiania kierunku drukowania, możliwe są dwa kierunki:

...powrót do menu. 

Drukowanie tekstu.

Drukowanie tekstu z wykorzystaniem metody Canvas jest bardzo podobne do drukowania grafiki (patrz: drukowanie grafiki), więc nie będę wszystkiego od początku opisywał. W celu wydrukowania tekstu na papierze posłużymy się funkcją TextOut:

// Plik źródłowy np. Unit1.cpp
//--------------------------------

void __fastcall Drukuj(Graphics::TBitmap *Bmp)
{
 Printer()->BeginDoc();

 Printer()->Canvas->TextOut(100, 200, "Przykładowy tekst.");

 Printer()->EndDoc();
}
//--------------------------------

Jak widać instrukcja drukowania zostaje wywołana po zainicjowaniu drukarki metodą BeginDoc. Funkcja TextOut pobiera trzy argumenty z których pierwszy określa odległość drukowanego tekstu od lewego brzegu, a drugi od górnego brzegu kartki, natomiast trzeci parametr do drukowany tekst. Oczywiście nie można zrobić znacznie więcej, a więc zmienić np. rozmiar, krój i kolor czcionki dla drukowanego tekstu:

// Plik źródłowy np. Unit1.cpp
//--------------------------------

void __fastcall Drukuj(Graphics::TBitmap *Bmp)
{
 Printer()->BeginDoc();

 Printer()->Canvas->Font->Size = 12;
 Printer()->Canvas->Font->Name = "Courier New";
 Printer()->Canvas->Font->Color = clRed;
 Printer()->Canvas->Font->Style = Printer()->Canvas->Font->Style << fsBold;
 Printer()->Canvas->TextOut(100, 200, "Przykładowy tekst.");

 Printer()->EndDoc();
}
//--------------------------------

...powrót do menu. 

Drukowanie zawartości obiektu Memo.
Poprawiono wszystkie błędy.

    W celu wydrukowania zawartości obiektu Memo (klasa TMemo), napiszę funkcję, która będzie pobierała tylko dwa argumenty, a mianowicie odwołanie do obiektu typu TMemo, którego zawartość ma być wydrukowana oraz odwołanie do obiektu typu TPrintDialog. Funkcja jest bardzo prosta, będzie drukowała całą zawartość Memo, a jeżeli wszystko nie zmieści się na jednej stronie, to zostanie to automatycznie przeniesione na stronę drugo itd... W procesie drukowania zostanie wykorzystany komponent PrintDialog1, jako obiekt inicjujący oraz pozwalający na anulowanie drukowania.

// Plik nagłówkowy np. Unit1.h
//---------------------------------------------------------------------------
#ifndef Unit1H
#define Unit1H

//---------------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <Printers.hpp>
// ważne! ten moduł trzeba włączyć do projektu.
//---------------------------------------------------------------------------
private:
        void __fastcall
PrintMemo(TMemo *Memo, TPrintDialog *PrintDialog);
        void __fastcall PrintPage(int page);
        TStringList *pages;
        int currentpage;


// Plik źródłowy np. Unit1.cpp
//--------------------------------

void __fastcall TForm1::PrintMemo(TMemo *Memo, TPrintDialog *PrintDialog)
{
 int trips;
 int dups;
 int firstpg;
 int lastpg;
 bool needsnewpg = false;

 pages->Text = Memo->Text;

 if(!PrintDialog->Execute())
  return; //jeżeli użytkownik nie naciśnie OK, drukowanie zostanie anulowane.

 if(!PrintDialog->Collate)
 {
  trips = 1;
  dups  = PrintDialog->Copies;
 }
 else
 {
  trips = PrintDialog->Copies;
  dups  = 1;
 }

 if(PrintDialog->PrintRange == prAllPages)
 {
  firstpg = PrintDialog->MinPage; //Minimalna ilość drukowanych stron.
  lastpg  = PrintDialog->MaxPage; //Maksymalna ilość drukowanych stron.
 }
 else
 {
  firstpg = PrintDialog->FromPage; //Numer strony od której rozpocznie się drukowanie.
  lastpg  = PrintDialog->ToPage;   //Numer strony na której skończy się drukowanie
 }

 Printer()->BeginDoc(); //Inicjowanie pracy drukarki.

 Printer()->Title = "Cyfrowy Baron"; //Tytuł wydruku.

 for(int x = 0; x < trips; x++)             //dla każdego obiegu
  for(int y = firstpg - 1; y < lastpg; y++) //dla każdej strony
   for(int z = 0; z < dups; z++)            //dla każdej otwartej strony
   {
    if(needsnewpg)
    Printer()->NewPage();

    PrintPage(y); //drukuj tą stronę.
    needsnewpg = true; //dalsze strony potrzebują przerw.
   }

 Printer()->EndDoc();
}
//--------------------------------
void __fastcall TForm1::PrintPage(int page)
{
 int pageline = 0;

 Printer()->Canvas->TextOut(20, (10 + Printer()->Canvas->TextHeight("Jakiś tekst")) * pageline,
 "Jeszcze jeden tekst" + IntToStr(page + 1));
 pageline += 2;

 for(__int8 line = 0; line < pages->Count; line++)
 {
  __int8 h = (10 + Printer()->Canvas->TextHeight("dowolny tekst"));
  Printer()->Canvas->TextOut(20, h * pageline, pages->Strings[line].c_str());
  pageline++;
  if(pageline *h >= Printer()->PageHeight)
  {
   pageline = 0;
   Printer()->NewPage();
  }
 }
}
//--------------------------------
void __fastcall TForm1::FormShow(TObject *Sender)
{
 currentpage = 0;
 pages = new TStringList;
}
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 PrintMemo(Memo1, PrintDialog1);
}

...powrót do menu. 

Drukowanie tekstu obróconego o zadany kąt.

Próbowaliście kiedyś wydrukować na kartce papieru tekst obrócony o jakiś kąt, jest to oczywiście możliwe i nie wymaga wiele kodu, wystarczy posłużyć się strukturą LOGFONT:

// Plik źródłowy np. Unit1.cpp
//--------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
 Printer()->BeginDoc();
 
 LOGFONT LogFont;
 GetObject(Printer()->Canvas->Font->Handle, sizeof(LOGFONT), &LogFont);
 LogFont.lfEscapement = 10 * 90; // kąt obrotu.
 LogFont.lfOrientation = 10 * 90;
 Printer()->Canvas->Font->Handle = CreateFontIndirect(&LogFont);
 Printer()->Canvas->TextOut(300, 200, "PRZYKŁADOWY TEKST");
 LogFont.lfEscapement = 0; // przywrócenie właściwego kąta tekstu.
 
 Printer()->Canvas->Font->Handle = CreateFontIndirect(&LogFont);
 Printer()->EndDoc();
}
//--------------------------------

A to jeszcze jeden sposób, z ustawianiem czcionki i przeźroczystego tła dla tekstu:

// Plik źródłowy np. Unit1.cpp
//--------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
 Printer()->BeginDoc();
 LOGFONT lf; // Windows native font structure

 Printer()->Canvas->Brush->Style = bsClear; // ustawianie przeźroczystego tła pod czcionką.
 ZeroMemory(&lf, sizeof(LOGFONT));

 lf.lfHeight = 20;
 lf.lfEscapement = 10 * 45; // kąt obrotu.
 lf.lfOrientation = 10 * 45;
 lf.lfCharSet = DEFAULT_CHARSET; // ustawienie skryptu czcionki
 strcpy(lf.lfFaceName, "Tahoma"); // wybór czcionki

 Printer()->Canvas->Font->Handle = CreateFontIndirect(&lf);

 Printer()->Canvas->TextOut(10, 100, "Przykładowy Tekst");
 Printer()->EndDoc();
}
//--------------------------------

Nie jestem pewien, ale przedstawiony sposób drukowania może się nie udać z czcionką TruType.
Można stosować wartości w zakresie od -1799 do 1799.

...powrót do menu. 

Jak zmienić domyślną drukarkę Windows?

Oto prosty kod na zmianę domyślnej drukarki w systemie Windows:

// Plik źródłowy np. Unit1.cpp
//--------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
  char WinIniFileName[128];
  TRegIniFile *rIni = new TRegIniFile(*WinIniFileName);
  char S[64];
  GetWindowsDirectory(WinIniFileName, sizeof(WinIniFileName));
  StrCat(WinIniFileName, "\\win.ini");
  rIni->CreateKey(WinIniFileName);
  rIni->WriteString("windows","device","HP LaserJet Series II,HPPCL,LPT1:");
  rIni->Free();
  StrCopy(S, "windows");
  SendMessage(HWND_BROADCAST, WM_WININICHANGE, 0, &S);
}
//--------------------------------

Nigdy nie przetestowałem tej porady. Przedstawiony kod znalazłem w sieci i został on stworzony dla środowiska C++ Builder 3.x. Jak widać w poradzie zastosowano klasę TRegIniFile jest to odpowiednik klasy TIniFile z biblioteki Inifiles.hpp, poza tym kod opiera się na dodaniu odpowiedniego wpisu do pliki win.ini i wysłaniu komunikatu WM_WININICHANGE do systemu, ale nie wiem czy to zadziała.

...powrót do menu.