netlib.narod.ru< Назад | Оглавление | Далее >

Кое-что о печати

В этой и следующих главах многие программы, демонстрирующие графические возможности, будут похожи на XMarksTheSpot. Большинство из них не такие неуклюжие, как XMarksTheSpot, хотя некоторые, увы, таковы. Но они лишь демонстрируют базовые методики программирования графики, показывая в клиентской области статические изображения, и ничего больше.

Возможность печати этих изображений может быть полезна даже сейчас, когда вы только начинаете изучать С#, чтобы, распечатав на принтере, с гордостью приклеить их на дверцу холодильника. Но, что важнее, печать изображений на принтере при изучении системы программирования графики позволяет лично убедиться в независимости этой системы от устройств.

В книгах по программированию описание вывода на печать обычно стараются загнать в самый конец или вовсе игнорируют эту тематику из-за ее чрезвычайной сложности. Главу 21 я целиком отвел описанию печати, чтобы полностью осветить все разнообразие доступных возможностей и механизмов вывода на печать. Однако наша текущая задача — распечатать одну страницу на стандартно настроенном принтере по умолчанию из приложения Windows Forms, — довольно проста.

Я колебался, стоит ли вообще знакомить читателя с печатью в самом начале. На самом деле единственной причиной моих сомнений была проблема с пользовательским интерфейсом, т.е. как дать пользователю возможность инициировать печать из программы. Как известно, у большинства программ, поддерживающих печать, в меню File (Файл) есть команда Print (Печать). Но время знакомства с меню пока не пришло — этому посвящена глава 14. Я думал реализовать простой интерфейс, использующий клавиатуру, например, через клавишу Print Screen (иногда ее подписывают как PrtScn) или клавиатурную комбинацию Ctrl+P. В конце концов я решил переопределить метод OnClick.

Метод OnClick, реализованный в классе Control, унаследовали все потомки этого класса, включая Form. Каждый раз, когда пользователь щелкает любой из кнопок мыши по клиентской области формы, вызывается метод OnClick — и это все, что я собираюсь сказать об использовании мыши до главы 8!

Чтобы печатать на принтере по умолчанию, нужно прежде создать объект типа PrintDocument — класса, определенного в пространстве имен System.Drawing.Printing.

  PrintDocument prndoc = new PrintDocument();

В главе 21 этому классу будет уделено больше времени, а сейчас я упомяну лишь об одном свойстве, одном событии и одном методе этого класса.

Свойство DocumentName объекта PrintDocument следует установить как текстовую строку. Во время постановки графики в очередь печати текст этой строки идентифицирует задание в диалоговом окне принтера:

  prndoc. DocumentName = "My print job";

Обычно программа, работающая с документом, заносит в нее его имя. В данной главе я буду для этих целей использовать текст заголовка окна программы.

Нужно создать в вашем классе метод, который будет исполнять вызовы на вывод графической информации. Он определяется как делегат PrintPageEventHandler. Этот метод можно назвать PrintDocumentOnPrintPage.

  void PrintDocumentOnPrintPage(object obj, PrintPageEventArgs ppea)
  {
      .
      .
      .
  }

Подключите этот обработчик к событию PrintPage объекта PrintDocument, например, так:

  prndoc.PrintPage += new PrintPageEventHandler(PrintDocumentOnPrintPage);

Именно так были установлены обработчики события Paint в некоторых программах главы 2, а также в программе SysInfoPanel главы 4.

Для начала печати осталось лишь вызвать метод Print объекта PrintDocument:

  prndoc.Print();

Этот метод Print возвращает управление не сразу — некоторое время отображается окно сообщения с именем документа и кнопкой, позволяющей отменить задание печати.

В результате исполнения метода также вызывается обработчик события PrintPage (названный здесь PrintDocumentOnPrintPage). Значением параметра object обработчика PrintDocumentOnPrintPage является ранее созданный объект PrintDocument. У параметра PrintPageEventArgs есть свойства, предоставляющие сведения о принтере, самое важное из которых — Graphics — аналогично одноименному свойству параметра PaintEventArgs за исключением того, что PrintPageEventArgs предоставляет объект Graphics для страницы принтера, а не для клиентской области формы.

Часто метод PrintDocumentOnPrintPage выглядит примерно так:

  void PrintDocumentOnPrintPage(object obj, PrintPageEventArgs ppea)
  {
      Graphics grfx = ppea.Graphics;
      .
      .
      .
  }

Здесь объект Graphics вызывает методы, выводящие графику на страницу принтера.

Если нужно напечатать несколько страниц, следует присвоить свойству HasMorePages объекта PrintPageEventArgs значение true. Но поскольку печатается лишь одна страница, оставим значение свойства по умолчанию — false, и обработчик PrintDocumentOnPrintPage сразу вернет управление.

Если свойству HasMorePages объекта PrintPageEventArgs присвоено значение false, то после возврата управления обработчиком PrintDocumentOnPrintPage исходно вызванный метод Print объекта PrintDocument также возвращает управление. По завершении задания печати завершается и сама программа, передача графической информации на принтер уже не наша проблема, как и застрявшая бумага, пустые картриджи, грязь от лишнего тонера и плохие кабели.

К машине может быть подключено несколько принтеров. Показанный выше подход использует принтер по умолчанию. Задать принтер по умолчанию можно, вызвав на панели управления или из меню Start | Settings (Пуск | Настройка) диалоговое окно Printers (Принтеры) и выбрав из меню File (Файл) соответствующую команду.

Как известно, свойство ClientSize формы предоставляет размеры ее клиентской области, выраженные в пикселах. Для создания графического шедевра в пределах клиентской области этих сведений достаточно, но использовать аналогичное свойство для страницы принтера в силу некоторых причин проблематично.

Страница принтера определяется тремя разными областями. Во-первых, полным размером страницы. Эту информацию предоставляет свойство PageBounds класса PrintPageEventArgs. Его значением является структура Rectangle, чьи свойства X и Y равны 0, а свойства Width и Height предоставляют размеры бумаги по умолчанию, выраженные в сотых долях дюйма. Например, для листа размером 8,5 на 11 дюймов значения свойств Width и Heigh объекта PageBounds будут равны 850 и 1 100. Если в принтере по умолчанию задана альбомная, а не книжная ориентация страницы, то они будут равны 1 100 и 850 соответственно.

Во-вторых, есть область печати страницы (printable area). Ее размер обычно очень близок к полному размеру страницы, кроме полей, недоступных печатающей головке (или аналогичному узлу) принтера. Ширина верхнего, нижнего, левого и правого полей может быть разной. Значением свойства VisibleClipBounds класса Graphics является структура RectangleF, предоставляющая размер области печати страницы. Свойства X и Y этой структуры устанавливаются в 0, а ее свойства Width и Height равны горизонтальному и вертикальному размерам области печати страницы, выраженным в тех же единицах, что будут использованы и для рисования на странице принтера.

Размер третьей области рассчитывается с учетом 1-дюймового отступа по периметру страницы и представляет собой границы, в пределах которых предпочитает печатать пользователь. Эта информация возвращается в виде структуры Rectangle свойством MarginBounds объекта PrintPageEventArgs.

В главе 21 мы разберем эти вопросы подробнее, а сейчас лучше всего без лишних слов применить свойство VisibleClipBounds класса Graphics. Объект Graphics, полученный из объекта PrintPageEventArgs, согласован с этим свойством, т.е. точка с координатами (0, 0) соответствует верхнему левому углу области печать страницы.

Конечно же, все, на что я так настойчиво обращал ваше внимание относительно использования видимых цветов на экране, неверно для принтера. Лучший цвет для принтера — Color.Black, лучшее перо — Pens.Black, а лучшая кисть — Brushes.Black. Это подходит для всех, кроме чудаков, непременно желающих печатать на черной бумаге.

Вот программа, которая выводит в клиентской области приглашение «Click to print» и начинает печать после щелчка по этому приглашению.

HelloPrinter.cs

  //---------------------------------------------
  // HelloPrinter.cs (C) 2001 by Charles Petzold
  //---------------------------------------------
  using System;
  using System.Drawing;
  using System.Drawing.Printing;
  using System.Windows.Forms;

  class HelloPrinter: Form
  {
      public static void Main()
      {
          Application.Run(new HelloPrinter());
      }
      public HelloPrinter()
      {
          Text = "Hello Printer!";
          BackColor = SystemColors.Window;
          ForeColor = SystemColors.WindowText;
      }
      protected override void OnPaint(PaintEventArgs pea)
      {
          Graphics     grfx   = pea.Graphics;
          StringFormat strfmt = new StringFormat();

          strfmt.Alignment = strfmt.LineAlignment = StringAlignment.Center;

          grfx.DrawString("Click to print", Font, new SolidBrush(ForeColor),
                          ClientRectangle, strfmt);
      }
      protected override void OnClick(EventArgs ea)
      {
          PrintDocument prndoc = new PrintDocument();

          prndoc.DocumentName = Text;
          prndoc.PrintPage +=
              new PrintPageEventHandler(PrintDocumentOnPrintPage);
          prndoc.Print();
      }
      void PrintDocumentOnPrintPage(object obj, PrintPageEventArgs ppea)
      {
          Graphics grfx = ppea.Graphics;

          grfx.DrawString(Text, Font, Brushes.Black, 0, 0);

          SizeF sizef = grfx.MeasureString(Text, Font);

          grfx.DrawLine(Pens.Black, sizef.ToPointF(), 
                                    grfx.VisibleClipBounds.Size.ToPointF());
      }
  }

Заметьте, что я использовал свойство Text формы как для имени печатаемого документа, так и в качестве аргумента DrawString и MeasureString в методе PrintDocumentOnPrintPage. Программа выводит текст «Hello Printer!» в верхнем левом углу области печати страницы, а затем рисует линию от правого нижнего угла текстовой строки до правого нижнего угла области печати. Этого примера должно быть достаточно, чтобы убедиться, что свойство VisibleClipBounds предоставляет сведения, согласованные с объектом Graphics.

Слышу, как смеются мои читатели, поскольку я легкомысленно использовал свойство Font формы при вызове методов DrawString и MeasureString, не думая, что у принтера может быть разрешение в 300, 600, 720, 1200, 1400 и даже 2400 или 2880 точек на дюйм. Шрифт, доступный через свойство Font, выбран системой как подходящий для экрана, разрешение которого скорее всего не превышает 100 точек на дюйм. В результате отпечатанный на принтере текст может выглядеть просто мизерным.

Ладно, продолжим: попробуйте исполнить программу. Принтер печатает текст вполне приличного размера — 8 пунктов. Кроме того, обратите внимание, что толщина диагональной линии явно больше 1 пиксела. Линии толщиной в 1 пиксел едва ли будут различимы на современных принтерах, печатающих с высоким разрешением, вместо этого Windows Forms рисует прекрасно видимую линию достаточной толщины. Причина столь радостного результата пусть пока останется тайной. Она раскроется в главах 7 и 9.

А теперь напишем программу, которая выводит в клиентской области формы и печатает на принтере одну и ту же информацию. Нет, мы не будем копировать код из OnPaint в метод PrintDocumentOnPrintPage. Давайте покажем, что мы кое-что смыслим в программировании и поместим код для вывода графической информации в отдельный метод DoPage, вызываемый как обработчиком OnPaint, так и PrintDocumentOnPrintPage. Вот вариация программы XMarksTheSpot, которая работает именно так.

PrintableForm.cs

  //----------------------------------------------
  // PrintableForm.cs (C) 2001 by Charles Petzold
  //----------------------------------------------
  using System;
  using System.Drawing;
  using System.Drawing.Printing;
  using System.Windows.Forms;

  class PrintableForm: Form
  {
      public static void Main()
      {
          Application.Run(new PrintableForm());
      }
      public PrintableForm()
      {
          Text = "Printable Form";
          BackColor = SystemColors.Window;
          ForeColor = SystemColors.WindowText;
          ResizeRedraw = true;
      }
      protected override void OnPaint(PaintEventArgs pea)
      {
          DoPage(pea.Graphics, ForeColor, 
                 ClientSize.Width, ClientSize.Height);
      }
      protected override void OnClick(EventArgs ea)
      {
          PrintDocument prndoc = new PrintDocument();

          prndoc.DocumentName = Text;
          prndoc.PrintPage +=
                new PrintPageEventHandler(PrintDocumentOnPrintPage);
          prndoc.Print();
      }
      void PrintDocumentOnPrintPage(object obj, PrintPageEventArgs ppea)
      {
          Graphics grfx  = ppea.Graphics;
          SizeF    sizef = grfx.VisibleClipBounds.Size;

          DoPage(grfx, Color.Black, (int)sizef.Width, (int)sizef.Height);
      }
      protected virtual void DoPage(Graphics grfx, Color clr, int cx, int cy)
      {
          Pen pen = new Pen(clr);

          grfx.DrawLine(pen, 0,      0, cx - 1, cy - 1);
          grfx.DrawLine(pen, cx - 1, 0, 0,      cy - 1);
      }
  }

Метод DoPage, код которого расположен в конце листинга, выводит графическую информацию. Его аргументы — объект Graphics, цвет, подходящий для устройства, ширина и высота области вывода. DoPage вызывают два других метода: OnPaint и PrintDocumentOnPrintPage. При вызове из метода OnPaint трем последним аргументам метода DoPage присваиваются значения свойства ForeColor, а также ширины и высоты клиентской области формы соответственно. В случае вызова из PrintDocumentOnPrintPage аргументами становятся Color.Black, ширина и высота VisibleClipBounds.

Два последних аргумента метода DoPage я назвал сх и су. Буква с — сокращенное слово count (число), а х и у — это обычно имена координат. Таким образом, сх и су можно интерпретировать как «число точек по оси координат» или как ширину и высоту.

Занятно, что в случае объекта Graphics для клиентской области значения свойства VisibleClipBounds равны ширине и высоте именно этой области. Я мог бы просто передать аргументы сх и су методу DoPage, после чего использовать VisibleClipBounds для вывода как на экран, так и на принтер. Но мне нравится, когда значения ширины и высоты заранее доступны в виде обычных переменных, особенно в контексте текущей задачи. Заметьте: я сделал метод DoPage защищенным (protected) и виртуальным (virtual), следовательно, его можно переопределить. Если понадобится написать программу, выводящую лишь один экран, заполненный графикой, можно породить нужные классы из PrintabteForm, а не из Form, получив, таким образом, в своей программе встроенную поддержку печати.

Именно так я и буду поступать практически во всех программах-примерах из этой и других глав, посвященных программированию графики.


netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz