Создание Windows-приложений на основе Visual C#

Работа с печатью и изображениями

Разбить на страницы
Показывать лекцию целиком
Для работы с данной лекцией используйте примеры.

Тема печати в Windows традиционно относится к одной из самых сложных: наличие огромного количества параметров, свойств и методов делает эту довольно простую вещь сильно запутанной. В этой лекции мы рассмотрим такие несложные задачи, как настройка параметров страницы, предварительный просмотр, печать содержимого элемента RichTextBox, а также преобразование форматов и печать рисунков, помещенных в PictureBox. Элемент PictureBox поддерживает различные режимы просмотра изображений – мы применим их всех и создадим простое приложение, позволяющее просматривать фотографии. Мы также еще раз создадим пользовательский элемент управления с собственными свойствами.

Печать содержимого RichTextBox. Элементы управления PrintDocument, PageSetupDialog, PrintPreviewDialog, PrintDialog

В качестве исходного приложения, в котором будем реализовывать возможность печати, возьмем проект TextEditor, созданный нами во второй лекции. Как мы знаем, максимальный объем элемента TextBox ограничивается 64 Кб, для работы с многостраничными документами этого явно мало. Поэтому удаляем с формы элемент txtBox и на его место помещаем элемент RichTextBox, которому задаем имя rtbText. Свойству Dock устанавливаем значение Fill, кроме того, удаляем значение свойства Text. Изменяем и добавляем следующие пункты главного меню (рис. 6.1):

NameTextShortcut
mnuFile Файл
mnuOpen Открыть CtrlO
mnuSave Сохранить CtrlS
menuItem1 -
mnuPageSetup Параметры страницы
mnuPrintPreview Предварительный просмотр
mnuPrint Печать CtrlP
(рис 6.1) Пункты главного меню

Перетаскиваем на форму из окна ToolBox элементы управления: PrintDocument, PageSetupDialog, PrintPreviewDialog и PrintDialog. Подобно другим элементам диалогов, они отображаются на панели компонент в среде Visual Studio .NET. При печати формируется одна или несколько страниц, за которые отвечает объект PrintDocument. Элементы управления PageSetupDialog, PrintPreviewDialog и PrintDialog представляют собой диалоговые окна параметров страниц, предварительного просмотра и печати соответственно. Настройка свойств этих объектов представляет собой столь гибкую и широкую задачу, решаемую в коде приложения, что, в отличие, например, от элементов OpenFileDialog или SaveFileDialog, имеет смысл создавать их программно, в классе формы:

// PrintDocument printDocument1 = new PrintDocument();
// PageSetupDialog  pageSetupDialog1 = new PageSetupDialog();
// PrintPreviewDialog printPreviewDialog1 = new PrintPreviewDialog();
// PrintDialog printDialog1 = new PrintDialog();

Этот фрагмент полностью аналогичен добавлению на форму элементов. Мы, однако, будем использовать визуальный режим.

Свойству Document элементов управления PageSetupDialog и PrintPreviewDialog устанавливаем значение свойства Name элемента PrintDocumentprintDocument1. Тем самым мы связываем объект printDocument1, отвечающий за формирование страниц печати, с диалоговыми окнами. Устнавливаем следующие свойства элемента PrintDialog:

printDialog1, свойствоЗначениеОписание
AllowSelection True Разрешение на печать выделенного фрагмента документа
AllowSomePages True Разрешение на печать нескольких страниц
PrintDocument printDocument1 Связывание с экземпляром объекта PrintDocument

На информационной панели окна Properties при выборе свойств AllowSelection или AllowSomePages можно заметить сообщение – Enables and disables the Selection/Pages radio button (Включает или отключает доступность радиопереключателей "Выделенный фрагмент/Страницы"). Само по себе определение этих свойств еще не включает соответствующую функциональность — еще одна причина, по которой конфигурировать объекты печати лучше программно:

InitializeComponent();
// pageSetupDialog1.Document = printDocument1;
// printPreviewDialog1.Document = printDocument1;
// printDialog1.Document = printDocument1;
// printDialog1.AllowSomePages = true;
// printDialog1.AllowSelection = true;

Переходим в код формы. Для работы с печатью в библиотеке .NET Framework применяется класс System.Drawing.Printing, поэтому его нужно подключить в самом начале работы:

using System.Drawing.Printing;

В классе формы объявляем следующие переменные:

//Переменная для хранения текста для печати.
//В нее мы будем помещать текст из RichTextBox
string stringPrintText;
//Переменная, определяющая номер страницы, с которой нужно начать печать
int StartPage;
//Переменная, определяющая количество страниц для печати:
int NumPages;
//Переменная, определяющая номер текущей страницы:
int PageNumber;

Следует обратить внимание на переменную stringPrintText — именно она будет отвечать за передачу текста в объект печати. Поскольку мы определили для нее строковый тип, это означает, что мы не сможем вывести на печать рисунки в тексте — как мы знаем, элемент RichTextBox поддерживает их наличие (в этом можно убедиться, скопировав содержимое документа Microsoft Word в наше приложение). Определение полной печати будет более сложным, что, впрочем, не является существенным для понимания самой концепции печати 1За ответом на этот вопрос я отправляю читателя к библиотеке MSDN. Если вы поймете общую идею, то без труда сможете нарастить дополнительные возможности на приложение для печати..

В конструкторе форме определяем диапазон страниц для печати:

public Form1()
  {
      
    InitializeComponent();
    …
    //Определяем номер страницы, с которой следует начать печать
    printDialog1.PrinterSettings.FromPage = 1;
    //Определяем максимальный номер печатаемой страницы.
    printDialog1.PrinterSettings.ToPage = printDialog1.PrinterSettings.MaximumPage;
  }

Максимальное количество страниц в диапазоне — 9999;, очевидно, что его вполне хватит для распечатки любого документа. Основным событием, в обработчике которого практически и будет осуществляться вся печать, будет событие PrintPage элемента printDocument1. Переключаемся в режим дизайна, выделяем элемент и, переключившись в окне Properties на вкладку событий, дважды щелкаем в поле этого события:

private void printDocument1_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
 {
    
 }

Далее все описываемые фрагменты кода будут относиться к этому обработчику.

Класс Graphics, принадлежащий пространству имен System.Drawing, — один из важнейших классов, отвечающих за рисование графики и вывода текста на форме. При создании новой формы пространство имен System.Drawing по умолчанию включается в шаблон. Для формирования страницы печати создаем экземпляр этого класса:

Graphics graph = e.Graphics;
//Создаем объект font, которому устанавливаем 
// шрифт элемента rtbText
Font font = rtbText.Font;
//Получаем значение межстрочного интервала — высоту шрифта 
float HeightFont = font.GetHeight(graph); 
//Создаем экземпляр strfmt класса StringFormat для определения 
//дополнительных параметров форматирования текста.
StringFormat  stringformat = new StringFormat();

Страница представляет собой прямоугольный объект, параметры которого следует определить. Для этого используется класс RectangleF (буква F указывает на использование типа данных с плавающей точкой float ):

//Создаем экземляры  rectanglefFull и rectfText класса RectangleF для 
//определения областей печати и текста. 
RectangleF rectanglefFull, rectfText;
//Создаем переменные для подсчета числа символов и строк.
int NumberSymbols, NumberLines;

Страница, формируемая для печати, состоит из трех областей (рис. 6.2).

(рис 6.2) Области страницы, выводимой на печать

Первая область — полный размер страницы, обычно имеющий названия, например, формат A4 или A3. Информацию об этой области предоставляет свойство PageBounds класса PrintPageEventArgs. Его значением является объект Rectangle, свойства X и У которого равны 0, а свойства Width и Height предоставляют размеры бумаги по умолчанию, выраженные в сотых долях дюйма. Вторая область связана с конструктивными особенностями принтеров — большинство из них используют для печати площадь, меньшую размеров бумаги. Область печати определяется значением свойства VisibleClipBounds класса Graphics, представляемая объектом RectangleF. Свойства Х и У этого объекта имеют значения 0, а ее свойства Width и Height равны горизонтальному и вертикальному размерам области печати страницы. В третьей области помещается текст, она обычно определяется пользователем. В Microsoft Word, например, мы устанавливаем заданные значения полей. По умолчанию, эта область рассчитывается с отступом в один дюйм по периметру страницы 11 дюйм приблизительно равен 2,54 см.. Ее размер возвращается объектом Rectangle свойства MarginBounds.

В любом случае, области формируются объектами Rectangle или RectangleF, конструкторы которых могут иметь следующий вид:

Rectangle (int x, int y, int width, int height);

RectangleF(float x, float y, float width, float height);

Эти конструкторы отличаются только типом данных, формирующих структуру. Координатные оси области, представляющей собой прямоугольник, направлены следующим образом (рис. 6.3):

(рис 6.3) Расположение координатных осей и формирование объекта Rectangle

Вернемся к нашему коду. В качестве области печати устанавливаем объект rectanglefFull:

if (graph.VisibleClipBounds.X<0) rectanglefFull = e.MarginBounds;
else
//Определяем   объект  rectanglefFull
rectanglefFull = new RectangleF(
//Устанавливаем координату  X  
e.MarginBounds.Left — (e.PageBounds.Width — graph.VisibleClipBounds.Width)/2, 
//Устанавливаем координату  Y
.MarginBounds.Top — (e.PageBounds.Height — graph.VisibleClipBounds.Height)/2, 
//Устанавливаем ширину области
e.MarginBounds.Width,
//Устанавливаем высоту области
e.MarginBounds.Height);

Область текста будет представлять собой копию области rectanglefFull c учетом высоты шрифта:

rectanglefText = RectangleF.Inflate(rectanglefFull, 0, -2*HeightFont);
//Определяем число строк
int NumDisplayLines = (int)Math.Floor(rectanglefText.Height/HeightFont);
//Устанавливаем высоту области
rectanglefText.Height = NumDisplayLines*HeightFont;

Элемент RichTextBox обладает свойством WordWrap, при установке значения True которого текст автоматически переносится на новую строку. Однако разбитый на строки текст может выйти за границы предназначенной для него области rectanglefText. Чтобы этого не произошло, мы создали экземпляр stringformat класса StringFormat, для которого теперь устанавливаем значение Trimming:

if (rtbText.WordWrap)
  {
    stringformat.Trimming = StringTrimming.Word;
  }
else
  {
    stringformat.Trimming = StringTrimming.EllipsisCharacter;
    stringformat.FormatFlags |=StringFormatFlags.NoWrap;
  }

Далее код достаточно простой, привожу его с комментариями:

//При печати выбранных страниц переходим к первой стартовой странице
while ((PageNumber<StartPage)(stringPrintText.Length>0))
    {
if(rtbText.WordWrap)
//Измеряем текстовые переменные, 
//формирующие печать,  и возвращаем число символов NumberSymbols
//и число строк NumberLines
graph.MeasureString(stringPrintText, font, rectanglefText.Size, stringformat, out NumberSymbols, out NumberLines);
else
    NumberSymbols = SymbolsInLines(stringPrintText, NumDisplayLines);
    stringPrintText = stringPrintText.Substring(NumberSymbols);
    //Увеличиваем число страниц 
    PageNumber++;
  }
//Если длина строки stringPrintText равняется нулю (нет текста для печати),
// останавливаем печать
    if (stringPrintText.Length==0)
      {
        e.Cancel = true;
        return;
      }
//Выводим (рисуем) текст для печати — stringPrintText, используем для этого шрифт font,
//кисть черного цвета  — Brushes.Black, область печати — rectanglefText,
//передаем строку  дополнительного форматирования stringformat
graph.DrawString(stringPrintText, font, Brushes.Black, rectanglefText, stringformat);
//Получаем текст для следующей страницы
  if (rtbText.WordWrap)
  graph.MeasureString(stringPrintText, font, rectanglefText.Size, stringformat, out NumberSymbols, out NumberLines);
  else
  NumberSymbols = SymbolsInLines(stringPrintText, NumDisplayLines);
  stringPrintText = stringPrintText.Substring(NumberSymbols);
//Очищаем объект stringformat, использованный для формирования полей.
    stringformat = new StringFormat();
//Добавляем  вывод на каждую страницу ее номера
    stringformat.Alignment = StringAlignment.Far;
    graph.DrawString("Страница" + PageNumber, font, Brushes.Black, rectanglefFull, stringformat);
    PageNumber++;
//Cнова проверяем, имеется ли текст для печати и номер страницы, заданной для печати
    e.HasMorePages=(stringPrintText.Length>0)(PageNumber<StartPage+NumPages);
//Для печати из окна предварительного просмотра  снова инициализируем переменные
    if(!e.HasMorePages)
    {
      stringPrintText = rtbText.Text;
      StartPage = 1;
      NumPages = printDialog1.PrinterSettings.MaximumPage;
      PageNumber = 1;
      }

      
    }

Мы закончили рассмотрение метода printDocument1_PrintPage. Достаточно существенным моментом здесь является то, что в коде реализована проверка наличия расстановки переносов в текстовом поле RichTextBox (свойство WordWrap). Метод MeasureString возвращает число выводимых символов. Для учета символов, не попадающих в область rectanglefText, добавляем метод SymbolsInLines:

int SymbolsInLines(string stringPrintText, int NumLines)
  {
    int index = 0;
    for (int i = 0; i< NumLines; i++)
    {
      index = 1+ stringPrintText.IndexOf('\n', index);
      if(index==0)
        return stringPrintText.Length;
    }
    return index;
  }

Создаем обработчики событий пунктов mnuPageSetup и mnuPrintPreview главного меню:

private void mnuPageSetup_Click(object sender, System.EventArgs e)
  {
    //Показываем диалог
    pageSetupDialog1.ShowDialog();
  }
    private void mnuPrintPreview_Click(object sender, System.EventArgs e)
  {
    //Инициализируем переменные
    printDocument1.DocumentName = Text;
    stringPrintText = rtbText.Text;
    StartPage = 1;
    NumPages = printDialog1.PrinterSettings.MaximumPage;
    PageNumber = 1;
    //Показываем диалог
    printPreviewDialog1.ShowDialog();
  }

В обработчике пункта меню mnuPrint определяем диапазон страниц – все страницы, ряд или выделенная область:

private void mnuPrint_Click(object sender, System.EventArgs e)
  {
    printDialog1.AllowSelection = rtbText.SelectionLength >0;
      if(printDialog1.ShowDialog()==DialogResult.OK)
    {
      printDocument1.DocumentName =Text;
      //Определяем диапазон страниц для печати
      switch(printDialog1.PrinterSettings.PrintRange)
      {
        //Выбраны все страницы
        case PrintRange.AllPages:
          stringPrintText = rtbText.Text;
          StartPage = 1;
          NumPages = printDialog1.PrinterSettings.MaximumPage;
          break;
        //Выбрана выделенная область
        case PrintRange.Selection:
          stringPrintText = rtbText.SelectedText;
          StartPage = 1;
          NumPages = printDialog1.PrinterSettings.MaximumPage;
          break;
        //Выбран ряд страниц
        case PrintRange.SomePages:
          stringPrintText = rtbText.Text;
          StartPage = printDialog1.PrinterSettings.FromPage;
          NumPages = printDialog1.PrinterSettings.ToPage - StartPage+1;
          break;
      }
      PageNumber = 1;
      //Вызываем встроенный метод для начала печати
      printDocument1.Print();
    }
    
  }

Запускаем приложение. Изменения, сделанные в диалоговом окне "Параметры страницы" (рис. 6.4), сохраняются, и в окне предварительного просмотра можно видеть подготовленную для печати страницу (рис. 6.5).

(рис 6.5) Настройка параметров страницы(рис 6.4) Окно предварительного просмотра. На каждой странице выводится ее номер

Из окна предварительного просмотра можно сразу перейти к печати. При выборе пункта печати доступен выбор ряда страниц или печать выделенного фрагмента (рис. 6.6).

(рис 6.6) Настройка параметров печати

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

(рис 6.7) Вывод документа в окно виртуального принтера программы pdfFactoryPro

В конце лекции приводится полный листинг приложения TextEditor, а исходный проект имеется на диске, прилагаемом к книге (Code\Glava6\TextEditor).

Работа с изображениями. Программа Picture Viewer

Работа с изображениями — одно из самых увлекательных занятий на компьютере. Не менее интересно создавать графические программы. Во второй лекции мы сталкивались с элементом PictureBox, в который помещали рисунок для окна "О программе". Теперь мы научимся загружать в этот элемент изображения, сохранять их в другом формате и распечатывать. Создайте новое приложение и назовите его PictureViewer. Добавляем на форму меню (рис. 6.8), контекстное меню, OpenFileDialog, SaveFileDialog, PictureBox, устанавливаем следующие свойства элементов управления:

mainMenu1, NameTextShortcut
mnuFile Файл
mnuOpen Открыть CtrlO
mnuSave Сохранить CtrlS
menuItem1
mnuPageSetup Параметры страницы
mnuPrintPreview Предварительный просмотр
mnuPrint Печать CtrlP
mnuExit Exit AltF4
mnuView Вид
mnuResize Подогнать размер
mnuActual Истинный размер
mnuCenterImage По центру
mnuAutoSize Автоматический размер
(рис 6.8) Пункты главного меню программы Picture Viewer
contextMenu1, NameText
mnuResize Подогнать размер
mnuActual Истинный размер
mnuCenterImage По центру
mnuAutoSize Автоматический размер
openFileDialog1, СвойствоЗначение
Title Выбор изображения
saveFileDialog1, СвойствоЗначение
Title Сохранение изображения
pictureBox1, СвойствоЗначение
ContextMenu contextMenu1
Dock Fill
SizeMode StretchImage

Приложение будет отображать в элементе управления PictureBox загруженный рисунок. Добавляем обработчик пункта меню mnuOpen:

private void mnuOpen_Click(object sender, System.EventArgs e)
  {
    OpenFileDialog diag = new OpenFileDialog();
    diag.Filter = "jpg files (*.jpg)|*.jpg|All files (*.*)|*.*" ;
    if (diag.ShowDialog() == DialogResult.OK)
    {
      pictureBox1.Image = new Bitmap(diag.OpenFile());
    }
  }

Несмотря на определение формата jpeg в качестве основного, элемент PictureBox поддерживает также работу с форматами Bitmap (.bmp), Icon (.ico), GIF (.gif), Metafile (.wmf). Сохранения файла практически не отличается от сохранения содержимого текстового поля:

private void mnuSave_Click(object sender, System.EventArgs e)
  {
    SaveFileDialog diag = new SaveFileDialog();
    diag.Filter = "jpg files (*.jpg)|*.jpg|All files (*.*)|*.*" ;
    if(diag.ShowDialog() != DialogResult.OK)
      return;
    // Получаем адрес файла.
    string filename = diag.FileName;
    FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.ReadWrite);
    pictureBox1.Image.Save(fs,  System.Drawing.Imaging.ImageFormat.Jpeg);
    fs.Close();
  }

Обратите внимание на возможность конвертирования форматов — метод Save свойства предоставляет возможность сохранения содержимого PictureBox в различных форматах (рис. 6.9).

(рис 6.9) Второй параметр метода Save принимает название графического формата

Мы выбрали формат Jpeg — при открытии рисунка в другом формате, например, Bmp, и последующем сохранении он будет конвертирован в этот формат.

Элемент управления PictureBox поддерживает несколько вариантов отображения загружаемого рисунка (рис. 6.10).

(рис 6.10) Режимы отображения рисунка элементов PictureBox: подогнать размер (StretchImage), истинный размер (Normal), по центру (CenterImage), автоматический размер (AutoSize)

Мы установили свойству SizeMode значение StretchImage, и поэтому при открывании рисунка его размер автоматически подгоняется под размер PictureBox и, соответственно, формы. Истинный размер выводит рисунок в его действительных пропорциях; при этом на экране помещается только его часть. Конечно, в этом режиме необходима прокрутка рисунка — об этом чуть позже. Режим по центру располагает рисунок в его реальных пропорциях так, чтобы его центр совпадал с центром PictureBox. Последний режим — автоматический размер — подгоняет размеры PictureBox под размер открываемого изображения. На рис. 6.10 он визуально совпадает с режимом StretchImage — дело в том, что мы установили свойству Dock значение Fill и поэтому PictureBox уже имеет максимально большой размер.

В классе формы создадим массив ArrayMenu, в котором будут содержаться значения режимов:

private PictureBoxSizeMode[] ArrayMenu =
  {
    PictureBoxSizeMode.StretchImage,
    PictureBoxSizeMode.Normal, 
    PictureBoxSizeMode.CenterImage,
    PictureBoxSizeMode.AutoSize
  };

Переменная selectedMode будет возвращать выбранное значение:

private int selectedMode = 0;

В обработчиках пунктов меню будем выбирать одно значение из массива:

private void mnuResize_Click(object sender, System.EventArgs e)
{
  if (sender is MenuItem)
  {
    MenuItem menuitem = (MenuItem)sender;
    selectedMode = menuitem.Index;
    pictureBox1.SizeMode = ArrayMenu[0];
    pictureBox1.Invalidate();
    
  }

}
private void mnuActual_Click(object sender, System.EventArgs e)
{
  if (sender is MenuItem)
  {
    MenuItem menuitem = (MenuItem)sender;
    selectedMode = menuitem.Index;
    
    pictureBox1.SizeMode = ArrayMenu[1];
    pictureBox1.Invalidate();
        
  }
}
private void mnuCenterImage_Click(object sender, System.EventArgs e)
{
  if (sender is MenuItem)
  {
    MenuItem menuitem = (MenuItem)sender;
    selectedMode = menuitem.Index;
  
    pictureBox1.SizeMode = ArrayMenu[2];
    pictureBox1.Invalidate();
        
  }
}
private void mnuAutoSize_Click(object sender, System.EventArgs e)
{
  if (sender is MenuItem)
  {
  MenuItem menuitem = (MenuItem)sender;
    selectedMode = menuitem.Index;
  
    pictureBox1.SizeMode = ArrayMenu[3];
    pictureBox1.Invalidate();
        
  }
  
}

В каждом обработчике мы добавили метод Invalidate для перерисовки содержимого pictureBox1 после выбора пункта меню. Удобно, когда выбранный пункт меню помечается галочкой (см. рис. 6.10). Переходим в режим дизайна, выделяем пункт меню Вид (View) и в окне Properties дважды щелкаем в поле события Popup:

private void mnuView_Popup(object sender, System.EventArgs e)
{
  if (sender is MenuItem) 
  {
    bool ImLoad  = (pictureBox1.Image != null);
    foreach (MenuItem menuitem in ((MenuItem)sender).MenuItems)
        {
          menuitem.Enabled = ImLoad;
          menuitem.Checked = (this.selectedMode == menuitem.Index);
        }
  }
    
}

В обработчиках пунктов контекстного меню дублируем код для соответствующих пунктов главного меню:

private void cmnuResize_Click(object sender, System.EventArgs e)
  {
    Вставляем скопированный код для  mnuResize_Click
  }

private void cmnuActual_Click(object sender, System.EventArgs e)
  {
    Вставляем скопированный код для  mnuActual _Click
  }

private void cmnuCenterImage_Click(object sender, System.EventArgs e)
  {
    Вставляем скопированный код для  mnuCenterImage _Click
  }

private void cmnuAutoSize_Click(object sender, System.EventArgs e)
  {
    Вставляем скопированный код для  mnuAutoSize _Click
  }

Запускаем приложение. Оно должно походить на приложение, изображенное на рис. 6.10. Добавим теперь к нашему приложению строку состояния, на которую будет выводиться размер загруженного изображения и его адрес. Из окна ToolBox перетаскиваем на форму элемент StatusBar, щелкаем в поле его свойства Panels и в открывшемся окне StatusBarPanel Collection Edito r добавляем панели sbFile и sbSize (рис. 6.11 и рис. 6.12).

(рис 6.12) Панель sbFile. Свойству Width панели установлено значение 500 пикселей(рис 6.11) Панель sbSize

Свойству ShowPanels элемента statusBar1 устанавливаем значение True. В обработчике пункта меню "Открыть" — код для вывода на панель sbFile адреса файла, а на панель sbSize — его размер:

private void mnuOpen_Click(object sender, System.EventArgs e)
  {
    …
    
    //Строка для вывода   во время загрузки изображения
    sbFile.Text  = "Загрузка " + diag.FileName;
    //Строка для вывода после загрузки изображения    
sbFile.Text  = "Изображение " + diag.FileName;
//Вывод ширины (Width) и высоты (Height) изображения
  sbSize.Text= String.Format("{0:#} x {1:#}", pictureBox1.Image.Width, pictureBox1.Image.Height);
  }

Запускаем и тестируем приложение (рис. 6.13).

(рис 6.13) Строка состояния приложения Picture Viewer

Печать содержимого PictureBox

Теперь займемся печатью изображений, помещенных в PictureBox. Добавляем на форму элементы PrintDocument, PageSetupDialog, PrintPreviewDialog и PrintDialog. На этот раз мы не будем настраивать визуально свойства этих элементов. Создаем обработчиков пунктов меню, в которых просто вызываем диалоговые окна:

private void mnuPageSetup_Click(object sender, System.EventArgs e)
  {
    PageSetupDialog diag = new PageSetupDialog();
    diag.Document = printDocument1;
    diag.ShowDialog();
  }
  private void mnuPreview_Click(object sender, System.EventArgs e)
  {
    PrintPreviewDialog diag = new PrintPreviewDialog();
    diag.Document = printDocument1;
    diag.ShowDialog();
  }
  private void mnuPrint_Click(object sender, System.EventArgs e)
  {
    PrintDialog diag = new PrintDialog();
    diag.Document = printDocument1;
    if (diag.ShowDialog() == DialogResult.OK)
    {
      printDocument1.Print();
    }
    
  }

Обратите внимание на свойство диалоговых окон Document — мы программно устанавливаем значение printDocument1. Переключаемся в режим дизайна, выделяем объект printDocument1 и в окне Properties дважды щелкаем в поле события PrintPage:

private void printDocument1_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
    {
}

Дальнейший код будет относиться к этому обработчику. Проверяем наличие изображения в pictureBox1:

if (pictureBox1.Image == null)
  {
    e.Cancel = true;
    return;
  }

Определяем печатную область страницы:

float leftMargin = e.MarginBounds.Left;
float rightMargin = e.MarginBounds.Right;
float topMargin = e.MarginBounds.Top;
float bottomMargin= e.MarginBounds.Bottom;
float printableWidth = e.MarginBounds.Width;
float printableHeight = e.MarginBounds.Height;

Cоздаем экземпляр graph класса Graphics:

Graphics graph = e.Graphics;

В документе, формируемом для печати, не содержится текста — ведь мы имеем дело с изображением. Но интересно будет выводить на печать строку, указывающую, к примеру, размер изображения. Для этого создадим экземпляр font класса Font:

Font font= new Font("Comic Sans MS", 16); 
//Определяем высоту шрифта
float fontHeight = font.GetHeight(graph);
//Определяем размер пробелов 
float spaceWidth = graph.MeasureString(" ", font).Width;

Определяем область, в которую будет вписываться изображение; размер наибольшей стороны изображения составляет 90% от кратчайшей стороны листа:

float imageLength;
float Xposition = leftMargin;
float Yposition = topMargin + fontHeight;
if (printableWidth < printableHeight)
  {
    imageLength = printableWidth * 90/100;
    Yposition += imageLength;
  }
  else
  {
    imageLength = printableHeight * 90/100;
    Xposition += imageLength + spaceWidth;
  }

Выводим изображение в области rectImage:

Rectangle rectImage= new Rectangle((int)leftMargin + 1, 
(int)topMargin + 1, (int)imageLength,(int)imageLength);
graph.DrawImage(pictureBox1.Image,(int)leftMargin + 1,  
(int)topMargin + 1, (int)imageLength,(int)imageLength);

Определяем область rectText и выводим в нее строку с указанием размера файла (используется метод PrintTextString ):

RectangleF rectText = new RectangleF(Xposition, Yposition, rightMargin -
- Xposition,  bottomMargin - Yposition);
PrintText (graph, font,"Размер изображения: ", Convert.ToString
(pictureBox1.Image.Size), ref rectText);

}

На этом код метода printDocument1_PrintPage заканчивается. Для вывода изображения все готово. Остается только написать метод PrintText, который будет выводить текстовую строку. Привожу код этого метода с комментариями:

protected void PrintText( Graphics graph,   Font font,  string name,  string text,  ref RectangleF rectText)
  {
    // Определяем размеры печатной области для текста:
    float leftMargin = rectText.Left;
    float rightMargin = rectText.Right;
    float topMargin = rectText.Top;
    float bottomMargin = rectText.Bottom;
    //Определяем высоту текста и координаты, где он будет выводиться:
    float fontHeight = font.GetHeight(graph);
    float Xposition = rectText.Left;
    float Yposition = topMargin + fontHeight;
    //Определяем ширину текста и размер пробелов
    float spaceWidth = graph.MeasureString(" ",   font).Width;
    float nameWidth  = graph.MeasureString(name, font).Width;
    graph.DrawString(name, font,
    Brushes.Black, new PointF(Xposition, Yposition));
    leftMargin += nameWidth + spaceWidth;
    Xposition = leftMargin;
    // Формируем несколько строк для текста  в случае,
    // если он не будет умещаться на одной строке
    string[] words
      = text.Split(" \r\t\n\0".ToCharArray());
    foreach (string word in words)
    {
      float wordWidth = graph.MeasureString(
        word, font).Width;
      if (wordWidth == 0.0)
        continue;
      if (Xposition + wordWidth > rightMargin)
      {
        // Начало с новой строки
        Xposition = leftMargin;
        Yposition += fontHeight;
        if (Yposition > bottomMargin)
        {            
          break;
        }
      }
      graph.DrawString(word, font,Brushes.Black,  new PointF(Xposition, Yposition));
      Xposition += wordWidth;
    }
    // Исключаем область, на которую был выведен текст, из области печати 
    //для избежания наложения текста и рисунка
    rectText.Y = Yposition;
    rectText.Height = bottomMargin - Yposition;
  }

Запускаем приложение. При выборе книжной ориентации страницы рисунок вписывается с сохранением своих пропорций (рис. 6.14), затем выводится на печать (рис. 6.15).

(рис 6.15) Предварительный просмотр приложения Picture Viewer (рис 6.14) Вывод рисунка в окно виртуального принтера программы pdfFactoryPro

В конце лекции приводится полный листинг приложения Picture Viewer, а исходный проект имеется на диске, прилагаемом к книге (Code\Glava6\ Picture Viewer).

Рисование в Windows-формах

Библиотека .NET Framework предоставляет чрезвычайно большой набор классов для реализации рисования в Windows-формах. Кривые Безье, текстурные кисти и перья, трансформация изображения, всевозможные виды градиентной заливки, слои, — практически все атрибуты крупного графического пакета могут быть воссозданы на одном из языков .NET Framework. Для рассмотрения этих методов и свойств потребовалась бы отдельная книга, поэтому мы ограничимся простым приложением, позволяющим рисовать линиями различного цвета и толщины. Создайте новое приложение и назовите его EasyPaint. Устанавливаем следующие значения свойств формы:

Form1, форма, свойствоЗначение
BackColor ActiveCaptionText
Icon Code\Glava6\EasyPaint\Icon\rc_bitmap.ico
Text EasyPaint

Добавляем на форму элементы управления MainMenu и ColorDialog. Создаем следующие пункты меню:

NameText
mnuFormat Формат
mnuColor Цвет
mnuWidth Толщина линии

Как обычно, рисовать мы будем при удержании и перемещении левой кнопки мыши. В классе формы добавляем объекты:

//Переменная, отвечающая за включения режима рисования.
//Значение true — режим включен
bool DrawMode = false;
//Создаем массив точек, из которых будут формироваться линии
ArrayList points = new ArrayList();

Для рисования линий и кривых в библиотеки .NET Framework имеется класс Pen, экземпляр которого нам и нужно создать:

Pen mainPen;

В графических программах принято в рабочей области изменять курсор — пользователю удобнее рисовать, когда он имеет вид карандаша. Можно просто установить значение из перечисления свойства Cursor формы (рис. 6.16), но как быть, если нужен вид, не входящий в это перечисление?

(рис 6.16) Свойство Cursor формы

Самый простой способ добавить произвольный курсор к приложению — это поместить изображение курсора (в данном случае, Pencil.cur) в папку bin\Debug приложения и в конструкторе формы указать этот курсор в качестве основного:

public Form1()
  {
    InitializeComponent();
    this.Cursor = new Cursor("Pencil.cur");
    mainPen = new Pen(this.ForeColor);
  }

Во второй строчке мы также устанавливаем цвет созданного пера mainPen. Переключаемся в режим дизайна, в окне Properties в событиях формы дважды щелкаем в поле событий MouseDown, MouseMove, MouseUp:

//Событие возникает при нажатии на кнопку мыши.
  private void Form1_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
  {
    //Если нажата левая кнопка мыши
    if(e.Button==MouseButtons.Left)
    {
      //Включаем режим рисования
      DrawMode = true;
      //Удаляем все элементы из массива points
      points.Clear();
    }
  
  }
//Событие возникает при перемещении мыши
  private void Form1_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
  {
    //Если включен режим рисования
    if(DrawMode)
    {
      //Создаем экземпляр pt класса Point,
      //представляющий собой точку с координатами мыши в данный момент
      Point pt = new Point(e.X, e.Y);
      //Добавляем созданную точку в массив points
      points.Add(pt);
      if (points.Count<=1) return;
      //Создаем новый массив pts, содержащий число  точек в массиве points
      Point [] pts = new Point [ points.Count];
      //Преобразуем массив points в массив pts, начиная с первого элемента
      points.CopyTo(pts, 0);
      //Создаем экземпляр graph класса Graphics для рисования
      Graphics graph = this.CreateGraphics();
      //Вызываем метод DrawCurve для рисования кривой, которому 
      //передаем перо mainPen и массив pts
      graph.DrawCurve(mainPen, pts);
    
    }
  
  }
//Событие возникает при отпускании кнопки мыши
  private void Form1_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
  {
    //Отключаем режим рисования.
    DrawMode = false;
  }

Логика работы предельно проста — при нажатии левой кнопки мыши включается режим рисования; при ее перемещении формируется массив точек с текущими координатами мыши, массив изображений точек pts затем передается методу DrawCurve, который и рисует линию. При отпускании кнопки режим рисования отключается.

В обработчике пункта меню Color при выборе цвета будет создаваться новое перо:

private void mnuColor_Click(object sender, System.EventArgs e)
    {
      if(colorDialog1.ShowDialog()==DialogResult.OK)
      {
        mainPen = new Pen(colorDialog1.Color);
      }
    }

Запускаем приложение. Вызывая диалоговое окно выбора цвета, можно рисовать разными цветами на форме (рис. 6.17).

(рис 6.17) Приложение EasyPaint. Курсор имеет точно такой же вид, как и в редакторе Microsoft Paint

Диалоговое окно выбора ширины линии. Элемент управления TrackBar

Займемся теперь линией для рисования — создадим собственное диалоговое окно, позволяющее устанавливать ее ширину. В окне Solution Explorer щелкаем правой кнопкой на имени проекта и в появившемся контекстном меню выбираем Add/Add Windows Form… . Называем новую форму LineWidth.cs, из окна Toolbox перетаскиваем на нее две кнопки, надпись, панель, элементы NumericUpDown , TrackBar (рис. 6.18) и устанавливаем следующие значения свойств:

button1, свойствоЗначение
Name btnOK
Location 32; 224
Text OK
button2, свойствоЗначение
Name btnCancel
Location 136; 224
Text Отмена
LineWidth, форма, свойствоЗначение
Name LineWidth
AcceptButton BtnOK
CancelButton BtnCancel
FormBorderStyle FixedDialog
MaximizeBox False
MinimizeBox False
Size 254; 286
Text Толщина линии
label1, свойствоЗначение
Location 8; 136
Text Толщина
numericUpDown1, свойствоЗначение
DecimalPlaces 1
Increment 0,1
Location 120; 136
Minimum 1
Value 1
trackBar1, свойствоЗначение
DecimalPlaces 1
Location 16; 168
Maximum 100
Minimum 1
Size 224; 45
TickFrequency 5
Value 5
(рис 6.18) Форма LineWidth в режиме дизайна

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

private void DrawLine()
    {
      //Создаем переменную, которая равна ординате точки, 
      //делящий элемент panel1 по высоте пополам
      int y = panel1.Bottom/2;
      //Создаем точку point1 лежащей на левой границе панели, c ординатой y
      Point point1 = new Point(panel1.Left,y);
      //Создаем точку point2 лежащей на правой  границе панели, c ординатой y
      Point point2 = new Point(panel1.Right,y);
      //Создаем экземпляр graph класса Graphics для рисования
      Graphics graph = panel1.CreateGraphics();
      //Определяем толщину линии, равную значению в элементе numericUpDown1
      float f = Decimal.ToSingle(numericUpDown1.Value);
      //Создаем экземпляр pen для рисования линии 
      Pen pen = new Pen (Color. Blue, f);
      //Соединяем точки point1 и point2
      graph.DrawLine(pen, point1, point2);
    }

Создаем свойство PenWidth, которое будет возвращать значение толщины для пера главной формы:

public float PenWidth
    {
      get
      {
        float f = Decimal.ToSingle(numericUpDown1.Value);
        return f;
      }
    }

При вызове диалогового окна на панели сразу будет появляться эскиз линии по умолчанию, для этого создаем обработчик события Paint формы, в котором вызываем метод DrawLine:

private void LineWidth_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
    {
      DrawLine();
    }

Связываем перемещение ползунка элемента trackBar1 со значением в поле элемента numericUpDown1, для чего в обработчике события Scroll вводим переменную val, связывающую свойства Value этих элементов:

private void trackBar1_Scroll(object sender, System.EventArgs e)
    {
      int val = trackBar1.Value;
      numericUpDown1.Value = val;
    }

Аналогично, для связывания значения в поле элемента numericUpDown1 с перемещением ползунка в обработчике события ValueChanged вводим переменную val2:

private void numericUpDown1_ValueChanged(object sender, System.EventArgs e)
    {
      int val2 = (int)numericUpDown1.Value;
      trackBar1.Value = val2;
      DrawLine();
    }

В этом обработчике мы также вызываем метод DrawLine для вывода эскиза линии на форме. Последнее, что нам осталось сделать, — добавить обработчик в пункте меню "Толщина линии" главной формы:

private void mnuWidth_Click(object sender, System.EventArgs e)
    {
      LineWidth diag = new LineWidth();
      if (diag.ShowDialog(this)== DialogResult.OK)
      {
        mainPen = new Pen(mainPen.Color, diag.PenWidth);
      }
    }

Запускаем приложение. Теперь можно рисовать не только разными цветами, но и разной толщиной пера (рис. 6.19).

(рис 6.19) Приложение EasyPaint и его диалоговое окно "Толщина линии"

На диске, прилагаемом к книге, вы найдете приложение EasyPaint (Code\Glava6\ EasyPaint).

Автоматическое преобразование размера и прокручивание изображения

При создании приложения Picture Viewer нашей задачей была печать содержимого PictureBox, и мы не слишком обращали внимание на внешний вид изображения, помещаемого в него. Между тем, единственным способом просмотра изображения полностью был выбор режима "Подогнать размер" (StretchImage), который приводил к довольно существенным искажениям (рис. 6.20).

(рис 6.20) Помещение рисунка в PictureBox без сохранения его пропорций (наверху, приложение Picture Viewer) и с сохранением (внизу)</p>

Кроме того, при выборе режима "Истинный размер" (Normal) на форме не появлялись полосы прокрутки, позволяющие просматривать изображение по частям (рис. 6.21).

(рис 6.21) При выходе изображения за границы формы необходимы полосы прокрутки

Конечно, вписанное изображение можно было просматривать, изменяя размер самой формы, но этот способ вряд ли будет устраивать пользователей. Займемся разработкой приложения, обладающего более удобным интерфейсом. Далее я снова местами буду пользоваться транслитом и называть режим S croll Prokrutkoy: дело в том, что мы научимся создавать собственные свойства пользовательского элемента управления, и мне хочется, чтобы они бросались в глаза. Запускаем Visual Studio .NET, создаем новый проект, выбираем шаблон Windows Control Library и называем его PictureElement (рис. 6.22):

(рис 6.22) Создание проекта пользовательского элемента управления

В окне Properties появившейся формы в поле свойства Name вводим NamePictureElement. В окне Solution Explorer изменяем название UserControl1.cs на SolPictureElement.cs. Добавляем на форму элемент управления PictureBox — его размеры будут достаточно маленькими, но это не важно, в дальнейшем мы изменим его размер так, как будет нужно (рис. 6.23).

(рис 6.23) Элемент управления PictureElement в режиме дизайна

Свойству SizeMode элемента управления PictureBox устанавливаем значение StretchImage. Переходим в код формы, в пространстве имен PictureElement создаем перечисление RejimProsmotra:

public enum RejimProsmotra
  {
    Prokrutka,
    PodgonRazmera
  }

В классе формы создаем переменную prosmotrperemen, которая будет определять режим просмотра:

private RejimProsmotra prosmotrperemen;

Создаем свойство Izobrajenie, с помощью которого можно будет помещать изображение в PictureBox элемента:

public Image Izobrajenie
    {
      get{return this.pictureBox1.Image;}
      set
      {
        this.pictureBox1.Image = value;
//Вызываем метод для установки режима просмотра
        this.UstanovkaRejima();
        
      }
    }

Создаем свойство UserPropRejimProsmotra, в котором переменной prosmotrperemen устанавливается значение выбранного режима просмотра:

public RejimProsmotra UserPropRejimProsmotra
    {
      get{return this.prosmotrperemen;}
      set
      {            
        this.prosmotrperemen = value;        
        this.AutoScroll = (this.prosmotrperemen == RejimProsmotra.Prokrutka );    
        //Вызываем метод для установки режима просмотра
        this.UstanovkaRejima();        
      }
    }

В методе PodgonRazmera определяются различные возможные случаи отношений размеров изображения, PictureBox и самого элемента:

private void PodgonRazmera()
    {
      //Определяем переменную ProporciiElementa, которая равна 
      //отношению ширины элемента к его высоте
      float ProporciiElementa = (float)this.Width/this.Height;
      //Определяем переменную ProporciiIzobrajeniya, которая равна 
      //отношению ширины загруженного изображения к его высоте
float ProporciiIzobrajeniya = (float)this.pictureBox1.Image.Width/this.pictureBox1.Image.Height;
      //Если ширина элемента  больше или равна ширине изображения 
      //и высота больше или равна высоте изображения, устанавливаем значения ширины 
      //и высоты pictureBox1 равными ширине и высоте изображения
if ( this.Width >= this.pictureBox1.Image.Width  this.Height >= this.pictureBox1.Image.Height )
      {        
        this.pictureBox1.Width = this.pictureBox1.Image.Width;
        this.pictureBox1.Height = this.pictureBox1.Image.Height;        
      }
      //Иначе, если ширина элемента  больше ширины изображения 
      //и высота меньше высоты  изображения, устанавливаем значение высоты 
      // pictureBox1, равное  высоте  элемента, а  значение ширины — равное значению 
      //ширины изображения 
else if( this.Width > this.pictureBox1.Image.Width  this.Height < this.pictureBox1.Image.Height)
      {
        this.pictureBox1.Height = this.Height;
        this.pictureBox1.Width = (int)(this.Height * ProporciiIzobrajeniya);      
      }
      //Иначе, если ширина элемента  меньше  ширины изображения 
      //и высота больше  высоты  изображения, устанавливаем значение ширины 
      // pictureBox1, равное  ширине  элемента, а  значение высоты — равное значению 
      //высоты изображения 
else if( this.Width < this.pictureBox1.Image.Width  this.Height > this.pictureBox1.Image.Height)
      {        
        this.pictureBox1.Width = this.Width;
        this.pictureBox1.Height = (int)(this.Width / ProporciiIzobrajeniya);                  
      }
      //Иначе, если ширина элемента  меньше  ширины изображения 
      //и высота меньше  высоты  изображения
else if ( this.Width < this.pictureBox1.Image.Width  this.Height < this.pictureBox1.Image.Height )
      {
        //если ширина элемента больше или равна его высоте
        if (this.Width >= this.Height )
        {
      //Если ширина изображения больше или равна его высоте и переменная 
      //ProporciiIzobrajeniya больше или равна переменной ProporciiElementa,
      //устанавливаем значение ширины pictureBox1, равное значению ширины элемента,
      //а значение высоты — равное значению высоты изображения
if ( this.pictureBox1.Image.Width >= this.pictureBox1.Image.Height  ProporciiIzobrajeniya >= ProporciiElementa )
          {
            this.pictureBox1.Width = this.Width;
            this.pictureBox1.Height = (int)(this.Width / ProporciiIzobrajeniya);
          }
      //Иначе, устанавливаем значение высоты  pictureBox1, равное значению
      //высоты элемента, а значение ширины — равное значению ширины изображения
          else
          {              
            this.pictureBox1.Height = this.Height;
this.pictureBox1.Width = (int)(this.Height * ProporciiIzobrajeniya);
          }          
        }
      //Иначе
        else
        {
      //Если ширина изображения меньше  его высоты и переменная 
      //ProporciiIzobrajeniya меньше переменной ProporciiElementa,
      //устанавливаем значение высоты  pictureBox1, равное значению высоты элемента,
      //а значение ширины — равное значению ширины изображения
if ( this.pictureBox1.Image.Width < this.pictureBox1.Image.Height  ProporciiIzobrajeniya < ProporciiElementa )
          {            
            this.pictureBox1.Height = this.Height;
this.pictureBox1.Width = (int)(this.Height * ProporciiIzobrajeniya);
          }
      //Иначе, устанавливаем значение ширины  pictureBox1, равное значению
      //ширины  элемента, а значение высоты — равное значению высоты изображения
          else 
          {                          
            this.pictureBox1.Width = this.Width;
            this.pictureBox1.Height = (int)(this.Width / ProporciiIzobrajeniya);            
          }
        }
      }    
      //Вызываем метод для размещения изображения в центре
      PomeshenieIzobrajeniyavCenter();
    }

В методе Prokrutka размеры PictureBox устанавливаются равными размерам загруженного изображения:

private void Prokrutka()
    {
    //Устанавливаем значение ширины pictureBox1, равное значению ширины изображения
      this.pictureBox1.Width = this.pictureBox1.Image.Width;
    //Устанавливаем значение высоты pictureBox1, равное значению высоты изображения
      this.pictureBox1.Height = this.pictureBox1.Image.Height;
    //Вызываем метод для установки режима просмотра
      this.PomeshenieIzobrajeniyavCenter();
    }

В методе UstanovkaRejima проверяется значение переменной prosmotrperemen и вызывается соответствующий метод:

private void UstanovkaRejima()
    {
      //если pictureBox1 не содержит изображения, возвращаемся назад
      if ( this.pictureBox1.Image == null )
        return;
      //Если значение переменной prosmotrperemen равно PodgonRazmera,
      //вызываем метод PodgonRazmera
      if ( this.prosmotrperemen == RejimProsmotra.PodgonRazmera )
        this.PodgonRazmera();
      //Иначе вызываем метод Prokrutka
      else
      {
        
        this.Prokrutka();  
        this.AutoScroll = true;
      
      }
    }

В методе PomeshenieIzobrajeniyavCenter определяется положение элемента pictureBox1 и, соответственно, положение изображения:

private void PomeshenieIzobrajeniyavCenter()
    {
      //Переменная top равна разнице между высотой элемента 
      //и высотой pictureBox1, деленной пополам
      int top = (int)((this.Height - this.pictureBox1.Height)/2.0);
      //Переменная left равна разнице между шириной  элемента 
      //и шириной pictureBox1, деленной пополам
      int left = (int)((this.Width - this.pictureBox1.Width)/2.0);
      if ( top < 0 )
        top = 0;
      if ( left > 0 )
        left = 0;
      this.pictureBox1.Top = top;
      this.pictureBox1.Left = left;    
    }

Переключаемся в режим дизайна, выделяем форму, в окне Properties переключаемся на события и дважды щелкаем в поле Resize и Load:

private void NamePictureElement_Resize(object sender, System.EventArgs e)
    {
    //При изменении размеров элемента вызывается метод для установки режима просмотра:
    this.UstanovkaRejima();
    }

private void NamePictureElement_Load(object sender, System.EventArgs e)
    {
      //при загрузке элемента устанавливаем нулевые ширину и высоту pictureBox1
      this.pictureBox1.Width = 0;
      this.pictureBox1.Height = 0;
      //Вызываем метод для установки режима просмотра
      this.UstanovkaRejima();
    }

Все! Компилируем проект, используя сочетания клавиш Ctrl+Shift+B, и закрываем его. Создаем новое Windows-приложение, которое называем AutoScroll_and_Constrain. В свойстве Text формы можно ввести надпись "Сохранение пропорций и прокрутка". Перетаскиваем на форму OpenFileDialog, а главное меню скопируем из приложения Picture Viewer, затем оставим следующие пункты:

mainMenu1, NameTextShortcut
mnuFile Файл
mnuOpen Открыть CtrlO
mnuView Вид
mnuResize Подогнать размер
mnuActual Истинный размер

Теперь нам нужно добавить созданный пользовательский элемент управления. В окне Toolbox щелкаем правой кнопкой на закладке Windows Forms и выбираем пункт Add/Remove Items… . В окне Customize Toolbox на вкладке .NET Framework Components нажимаем кнопку Browse и выбираем файл PictureElement.dll из папки bin/Debug проекта PictureElement. В результате в окне компонент появляется элемент NamePictureElement, принадлежащий пространству имен PictureElement (рис. 6.24).

(рис 6.24) Элемент NamePictureElement

Закрываем окно Customize Toolbox и перетаскиваем на форму появившейся элемент управления NamePictureElement. Обратите внимание на название этого элемента — не случайно при работе с проектом PictureElement мы дали проекту, форме и объекту в Solution Explorer три разных названия. На вкладке Toolbox элемент управления будет называться так же, как и форма пользовательского элемента в режиме дизайна (рис. 6.25).

(рис 6.25) Свойство Name формы будет названием пользовательского элемента управления

В окне Properties элемента namePictureElement1 появились два новых свойства, которые мы создали сами (рис. 6.26)!

(рис 6.26) Слева приведены фрагменты кода пользовательского элемента управления, на основании которых и были созданы свойства в окне Properties

Устанавливаем свойству Dock значение Fill, а свойству UserPropRejimProsmotra — значение PodgonRazmera. В обработчике пункта меню "Открыть" свойству Izobrajenie элемента загружаемое изображение:

private void mnuOpen_Click(object sender, System.EventArgs e)
{
  this.openFileDialog1.ShowDialog();
  string path = this.openFileDialog1.FileName;
  this.namePictureElement1.Izobrajenie = Image.FromFile(path);
}

В обработчиках пунктов меню изменения режима просмотра последовательно устанавливаем два значения свойства UserPropRejimProsmotra:

private void mnuResize_Click(object sender, System.EventArgs e)
  {
   this.namePictureElement1.UserPropRejimProsmotra = PictureElement.RejimProsmotra.PodgonRazmera;
  }

   private void mnuActual_Click(object sender, System.EventArgs e)
  {
   this.namePictureElement1.UserPropRejimProsmotra = PictureElement.RejimProsmotra.Prokrutka;
  }

Строка кода в обработчике mnuResize_Click скопирована из области Windows Form Designer generated code:

// 
// namePictureElement1
// 
this.namePictureElement1.Dock = System.Windows.Forms.DockStyle.Fill;
this.namePictureElement1.Izobrajenie = null;
…
this.namePictureElement1.UserPropRejimProsmotra = PictureElement.RejimProsmotra.PodgonRazmera;

Запускаем приложение. Два режима просмотра позволяют просматривать уменьшенное изображение с сохранением пропорций или его действительную часть с прокруткой (рис. 6.27).

(рис 6.27) Готовое приложение AutoScroll_and_Constrain

На диске, прилагаемом к книге, вы найдете приложения PictureElement и AutoScroll_and_Constrain (Code\Glava6\PictureElement и AutoScroll_and_Constrain ).

Полный листинг приложения TextEditor

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.IO;
using System.Drawing.Printing;

namespace TextEditor
{
  
  public class Form1 : System.Windows.Forms.Form
  {
    private System.Windows.Forms.OpenFileDialog openFileDialog1;
    private System.Windows.Forms.SaveFileDialog saveFileDialog1;
    private System.Windows.Forms.RichTextBox rtbText;
    private System.Windows.Forms.MainMenu mainMenu1;
    private System.Windows.Forms.MenuItem mnuFile;
    private System.Windows.Forms.MenuItem mnuOpen;
    private System.Windows.Forms.MenuItem mnuSave;
    private System.Windows.Forms.MenuItem menuItem1;
    private System.Windows.Forms.MenuItem mnuPageSetup;
    private System.Windows.Forms.MenuItem mnuPrintPreview;
    private System.Windows.Forms.MenuItem mnuPrint;
    private System.Windows.Forms.PageSetupDialog pageSetupDialog1;
    private System.Drawing.Printing.PrintDocument printDocument1;
    private System.Windows.Forms.PrintDialog printDialog1;
    private System.Windows.Forms.PrintPreviewDialog printPreviewDialog1;
    //Переменная для хранения текста для печати.
    //В нее мы будем помещать текст из RichTextBox
    string stringPrintText;
    //Переменная, определяющая номер страницы, с которой нужно начать печать
    int StartPage;
    //Переменная, определяющая количество страниц для печати:
    int NumPages;
    //Переменная, определяющая номер текущей страницы:
    int PageNumber;

    //    PrintDocument printDocument1 = new PrintDocument();
    //    PageSetupDialog  pageSetupDialog1 = new PageSetupDialog();
    //    PrintPreviewDialog printPreviewDialog1 = new PrintPreviewDialog();
    //    PrintDialog printDialog1 = new PrintDialog();

    /// <summary>
    /// Required designer variable.
    /// </summary>
    private System.ComponentModel.Container components = null;

    public Form1()
    {
      
      InitializeComponent();
      //      pageSetupDialog1.Document = printDocument1;
      //      printPreviewDialog1.Document = printDocument1;
      //      printDialog1.Document = printDocument1;
      //      printDialog1.AllowSomePages = true;
      //      printDialog1.AllowSelection = true;
      //
      //Определяем номер страницы, с которой следует начать печать
      printDialog1.PrinterSettings.FromPage = 1;
      //Определяем максимальный номер печатаемой страницы.
      printDialog1.PrinterSettings.ToPage = printDialog1.PrinterSettings.MaximumPage;

    
    }

  
    protected override void Dispose( bool disposing )
    {
      if( disposing )
      {
        if (components != null) 
        {
          components.Dispose();
        }
      }
      base.Dispose( disposing );
    }

    #region Windows Form Designer generated code
    /// <summary>
    /// Required method for Designer support — do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
      System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(Form1));
      this.openFileDialog1 = new System.Windows.Forms.OpenFileDialog();
      this.saveFileDialog1 = new System.Windows.Forms.SaveFileDialog();
      this.rtbText = new System.Windows.Forms.RichTextBox();
      this.mainMenu1 = new System.Windows.Forms.MainMenu();
      this.mnuFile = new System.Windows.Forms.MenuItem();
      this.mnuOpen = new System.Windows.Forms.MenuItem();
      this.mnuSave = new System.Windows.Forms.MenuItem();
      this.menuItem1 = new System.Windows.Forms.MenuItem();
      this.mnuPageSetup = new System.Windows.Forms.MenuItem();
      this.mnuPrintPreview = new System.Windows.Forms.MenuItem();
      this.mnuPrint = new System.Windows.Forms.MenuItem();
      this.pageSetupDialog1 = new System.Windows.Forms.PageSetupDialog();
      this.printDocument1 = new System.Drawing.Printing.PrintDocument();
      this.printDialog1 = new System.Windows.Forms.PrintDialog();
      this.printPreviewDialog1 = new System.Windows.Forms.PrintPreviewDialog();
      this.SuspendLayout();
      // 
      // openFileDialog1
      // 
      this.openFileDialog1.FileName = "Текстовые файлы";
      this.openFileDialog1.Filter = "Text Files (*.txt)|*.txt|All Files(*.*)|*.*  ";
      // 
      // saveFileDialog1
      // 
      this.saveFileDialog1.FileName = "Текстовые файлы";
      this.saveFileDialog1.Filter = "Text Files (*.txt)|*.txt|All Files(*.*)|*.*  ";
      // 
      // rtbText
      // 
      this.rtbText.Dock = System.Windows.Forms.DockStyle.Fill;
      this.rtbText.Location = new System.Drawing.Point(0, 0);
      this.rtbText.Name = "rtbText";
      this.rtbText.Size = new System.Drawing.Size(292, 266);
      this.rtbText.TabIndex = 1;
      this.rtbText.Text = "";
      // 
      // mainMenu1
      // 
      this.mainMenu1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
                                            this.mnuFile});
      // 
      // mnuFile
      // 
      this.mnuFile.Index = 0;
      this.mnuFile.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
                                          this.mnuOpen,
                                          this.mnuSave,
                                          this.menuItem1,
                                          this.mnuPageSetup,
                                          this.mnuPrintPreview,
                                          this.mnuPrint});
      this.mnuFile.Text = "Файл";
      // 
      // mnuOpen
      // 
      this.mnuOpen.Index = 0;
      this.mnuOpen.Shortcut = System.Windows.Forms.Shortcut.CtrlO;
      this.mnuOpen.Text = "Открыть";
      this.mnuOpen.Click += new System.EventHandler(this.mnuOpen_Click);
      // 
      // mnuSave
      // 
      this.mnuSave.Index = 1;
      this.mnuSave.Shortcut = System.Windows.Forms.Shortcut.CtrlS;
      this.mnuSave.Text = "Сохранить";
      // 
      // menuItem1
      // 
      this.menuItem1.Index = 2;
      this.menuItem1.Text = "-";
      // 
      // mnuPageSetup
      // 
      this.mnuPageSetup.Index = 3;
      this.mnuPageSetup.Text = "Параметры страницы";
      this.mnuPageSetup.Click += new System.EventHandler(this.mnuPageSetup_Click);
      // 
      // mnuPrintPreview
      // 
      this.mnuPrintPreview.Index = 4;
      this.mnuPrintPreview.Text = "Предварительный просмотр";
      this.mnuPrintPreview.Click += new System.EventHandler(this.mnuPrintPreview_Click);
      // 
      // mnuPrint
      // 
      this.mnuPrint.Index = 5;
      this.mnuPrint.Shortcut = System.Windows.Forms.Shortcut.CtrlP;
      this.mnuPrint.Text = "Печать";
      this.mnuPrint.Click += new System.EventHandler(this.mnuPrint_Click);
      // 
      // pageSetupDialog1
      // 
      this.pageSetupDialog1.Document = this.printDocument1;
      // 
      // printDocument1
      // 
      this.printDocument1.PrintPage += new System.Drawing.Printing.PrintPageEventHandler(this.printDocument1_PrintPage);
      // 
      // printDialog1
      // 
      this.printDialog1.AllowSelection = true;
      this.printDialog1.AllowSomePages = true;
      this.printDialog1.Document = this.printDocument1;
      // 
      // printPreviewDialog1
      // 
      this.printPreviewDialog1.AutoScrollMargin = new System.Drawing.Size(0, 0);
      this.printPreviewDialog1.AutoScrollMinSize = new System.Drawing.Size(0, 0);
      this.printPreviewDialog1.ClientSize = new System.Drawing.Size(400, 300);
      this.printPreviewDialog1.Document = this.printDocument1;
      this.printPreviewDialog1.Enabled = true;
      this.printPreviewDialog1.Icon = ((System.Drawing.Icon)(resources.GetObject("printPreviewDialog1.Icon")));
      this.printPreviewDialog1.Location = new System.Drawing.Point(17, 54);
      this.printPreviewDialog1.MinimumSize = new System.Drawing.Size(375, 250);
      this.printPreviewDialog1.Name = "printPreviewDialog1";
      this.printPreviewDialog1.TransparencyKey = System.Drawing.Color.Empty;
      this.printPreviewDialog1.Visible = false;
      // 
      // Form1
      // 
      this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
      this.ClientSize = new System.Drawing.Size(292, 266);
      this.Controls.Add(this.rtbText);
      this.Menu = this.mainMenu1;
      this.Name = "Form1";
      this.Text = "TextEditor";
      this.ResumeLayout(false);

    }
    #endregion

    
    [STAThread]
    static void Main() 
    {
      Application.Run(new Form1());
    }

    private void mnuOpen_Click(object sender, System.EventArgs e)
    {
      //Показываем диалог выбора файла
      openFileDialog1.ShowDialog() ;
      // Переменной fileName присваиваем имя открываемого файла
      string fileName = openFileDialog1.FileName;
      //Создаем поток fs и открываем файл для чтения.
      FileStream filestream= File.Open(fileName, FileMode.Open, FileAccess.Read);
      //Проверяем,  открыт ли поток,  и если открыт, выполняем условие
      if(filestream != null)
      {
        //Создаем объект streamreader и связываем его с потоком filestream
        StreamReader streamreader = new StreamReader(filestream /*System.Text.Encoding.Unicode*/);
        //Считываем весь файл и записываем его в TextBox
        rtbText.Text = streamreader.ReadToEnd();
        //Закрываем поток.
        filestream.Close();
      }
    }

    private void mnuSave_Click(object sender, System.EventArgs e)
    {
      //Показываем диалог выбора файла
      saveFileDialog1.ShowDialog();
      // В качестве имени сохраняемого файла устанавливаем переменную fileName
      string fileName=saveFileDialog1.FileName;
      //Создаем поток fs и открываем файл для записи.
      FileStream filestream = File.Open(fileName, FileMode.Create, FileAccess.Write);
        //Проверяем,  открыт ли поток,  и если открыт, выполняем условие
      if(filestream != null)
      {
        //Создаем объект streamwriter и связываем его с потоком filestream
        StreamWriter streamwriter = new StreamWriter(filestream);
        //Записываем данные из TextBox в файл
        streamwriter.Write(rtbText.Text);
        //Переносим данные из потока в файл
        streamwriter.Flush();
        //Закрываем поток
        filestream.Close();
      }
    }

    private void mnuPageSetup_Click(object sender, System.EventArgs e)
    {
      //Показываем диалог
      pageSetupDialog1.ShowDialog();
    }

    private void mnuPrintPreview_Click(object sender, System.EventArgs e)
    {
      //Инициализируем переменные
      printDocument1.DocumentName = Text;
      stringPrintText = rtbText.Text;
      StartPage = 1;
      NumPages = printDialog1.PrinterSettings.MaximumPage;
      PageNumber = 1;
      //Показываем диалог
      printPreviewDialog1.ShowDialog();
    }

    private void mnuPrint_Click(object sender, System.EventArgs e)
    {
      printDialog1.AllowSelection = rtbText.SelectionLength >0;

      if(printDialog1.ShowDialog()==DialogResult.OK)
      {
        printDocument1.DocumentName =Text;
        //Определяем диапазон страниц для печати
        switch(printDialog1.PrinterSettings.PrintRange)
        {
            //Выбраны все страницы
          case PrintRange.AllPages:
            stringPrintText = rtbText.Text;
            StartPage = 1;
            NumPages = printDialog1.PrinterSettings.MaximumPage;
            break;
            //Выбрана выделенная область
          case PrintRange.Selection:
            stringPrintText = rtbText.SelectedText;
            StartPage = 1;
            NumPages = printDialog1.PrinterSettings.MaximumPage;
            break;
            //Выбран ряд страниц
          case PrintRange.SomePages:
            stringPrintText = rtbText.Text;
            StartPage = printDialog1.PrinterSettings.FromPage;
            NumPages = printDialog1.PrinterSettings.ToPage - StartPage+1;
            break;
        }
        PageNumber = 1;
        //Вызываем встроенный метод для начала печати
        printDocument1.Print();
      }
      
    }
    private void printDocument1_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
    {
      //Создаем экземпляр graph класса Graphics
      Graphics graph = e.Graphics;
      //Создаем объект font, которому устанавливаем 
      // шрифт элемента rtbText
      Font font = rtbText.Font;
      //Получаем значение межстрочного интервала — высоту шрифта Т1, 134
      float HeightFont = font.GetHeight(graph);
      //Создаем экземпляр stringformat класса StringFormat для определения 
      //дополнительных параметров форматирования текста.
      StringFormat  stringformat = new StringFormat();
      //Создаем экземляры  rectanglefFull и rectanglefText класса RectangleF для 
      //определния областей печати и текста. Т1, 104
      RectangleF rectanglefFull, rectanglefText;
      //Создаем переменные для подсчета числа символов и строк.
      int NumberSymbols, NumberLines;
      //В качестве области печати устанавливаем объект rectanglefFull
      if (graph.VisibleClipBounds.X<0) rectanglefFull = e.MarginBounds;
      else
        //Определяем   объект  rectanglefFull
        rectanglefFull = new RectangleF(
          //Устанавливаем координату  X  
          e.MarginBounds.Left - (e.PageBounds.Width - graph.VisibleClipBounds.Width)/2, 
          //Устанавливаем координату  Y
          e.MarginBounds.Top - (e.PageBounds.Height - graph.VisibleClipBounds.Height)/2, 
          //Устанавливаем ширину области
          e.MarginBounds.Width,
          //Устанавливаем высоту области
          e.MarginBounds.Height);
      rectanglefText = RectangleF.Inflate(rectanglefFull, 0, -2*HeightFont);
      //Определяем число строк
      int NumDisplayLines = (int)Math.Floor(rectanglefText.Height/HeightFont);
      //Устанавливаем высоту области
      rectanglefText.Height = NumDisplayLines*HeightFont;
    
      if (rtbText.WordWrap)
      {
        stringformat.Trimming = StringTrimming.Word;
      }
      else
      {
        stringformat.Trimming = StringTrimming.EllipsisCharacter;
        stringformat.FormatFlags |=StringFormatFlags.NoWrap;
      }
      //При печати выбранных страниц переходим к первой стартовой странице
      while ((PageNumber<StartPage)(stringPrintText.Length>0))
      {
        if(rtbText.WordWrap)
          //Измеряем текстовые переменные, 
          //формирующие печать,  и возвращаем число символов NumberSymbols
          //и число строк NumberLines
          graph.MeasureString(stringPrintText, font, rectanglefText.Size, stringformat, out NumberSymbols, out NumberLines);
        else
          NumberSymbols = SymbolsInLines(stringPrintText, NumDisplayLines);
        stringPrintText = stringPrintText.Substring(NumberSymbols);
        //Увеличиваем число страниц 
        PageNumber++;
      }
      //Если длина строки stringPrintText равняется нулю (нет текста для печати),
      // останавливаем печать
      if (stringPrintText.Length==0)
      {
        e.Cancel = true;
        return;
      }
      //Выводим (рисуем) текст для печати — stringPrintText, используем для этого шрифт font,
      //кисть черного цвета  — Brushes.Black, область печати — rectanglefText,
      //передаем строку  дополнительного форматирования stringformat
      graph.DrawString(stringPrintText, font, Brushes.Black, rectanglefText, stringformat);
      //Получаем текст для следующей страницы
      if (rtbText.WordWrap)
        graph.MeasureString(stringPrintText, font, rectanglefText.Size, stringformat, out NumberSymbols, out NumberLines);
      else
        NumberSymbols = SymbolsInLines(stringPrintText, NumDisplayLines);
      stringPrintText = stringPrintText.Substring(NumberSymbols);
      //Очищаем объект stringformat, использованный для формирования полей.
      stringformat = new StringFormat();
      //Добавляем  вывод на каждую страницу ее номера
      stringformat.Alignment = StringAlignment.Far;
      graph.DrawString("Страница " + PageNumber, font, Brushes.Black, rectanglefFull, stringformat);
      PageNumber++;
      //Cнова проверяем, имеется ли текст для печати и номер страницы, заданной для печати
      e.HasMorePages=(stringPrintText.Length>0)(PageNumber<StartPage+NumPages);
      //Для печати из окна предварительного просмотра  снова инициализируем переменные
      if(!e.HasMorePages)
      {
        stringPrintText = rtbText.Text;
        StartPage = 1;
        NumPages = printDialog1.PrinterSettings.MaximumPage;
        PageNumber = 1;
      }

      
    }
    

    int SymbolsInLines(string stringPrintText, int NumLines)
    {
      int index = 0;
      for (int i = 0; i< NumLines; i++)
      {
        index = 1+ stringPrintText.IndexOf('\n', index);
        if(index==0)
          return stringPrintText.Length;
      }
      return index;
    }

    

    }
  }

Полный листинг приложения PictureViewer

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.IO;
using System.Drawing.Printing;

namespace PictureViewer
{
  /// <summary>
  /// Summary description for Form1.
  /// </summary>
  public class Form1 : System.Windows.Forms.Form
  {
    private System.Windows.Forms.MainMenu mainMenu1;
    private System.Windows.Forms.MenuItem mnuFile;
    private System.Windows.Forms.MenuItem mnuOpen;
    private System.Windows.Forms.MenuItem mnuSave;
    private System.Windows.Forms.MenuItem menuItem1;
    private System.Windows.Forms.MenuItem mnuPageSetup;
    private System.Windows.Forms.MenuItem mnuPreview;
    private System.Windows.Forms.MenuItem mnuPrint;
    private System.Windows.Forms.MenuItem menuItem5;
    private System.Windows.Forms.MenuItem mnuExit;
    private System.Windows.Forms.MenuItem mnuView;
    private System.Windows.Forms.MenuItem mnuResize;
    private System.Windows.Forms.OpenFileDialog openFileDialog1;
    private System.Windows.Forms.SaveFileDialog saveFileDialog1;
    private System.Windows.Forms.ContextMenu contextMenu1;
    private System.Windows.Forms.PictureBox pictureBox1;
    private System.Windows.Forms.MenuItem mnuActual;
    private System.Windows.Forms.MenuItem mnuCenterImage;
    private System.Windows.Forms.MenuItem mnuAutoSize;
    private System.Windows.Forms.MenuItem cmnuResize;
    private System.Windows.Forms.MenuItem cmnuActual;
    private System.Windows.Forms.MenuItem cmnuCenterImage;
    private System.Windows.Forms.MenuItem cmnuAutoSize;
    private System.Windows.Forms.StatusBar statusBar1;
    private System.Windows.Forms.StatusBarPanel sbFile;
    private System.Windows.Forms.StatusBarPanel sbSize;
    private System.Windows.Forms.PrintDialog printDialog1;
    private System.Windows.Forms.PrintPreviewDialog printPreviewDialog1;
    private System.Windows.Forms.PageSetupDialog pageSetupDialog1;
    private System.Drawing.Printing.PrintDocument printDocument1;
    
    private System.ComponentModel.Container components = null;

    public Form1()
    {
      InitializeComponent();
    }
  
  
    protected override void Dispose( bool disposing )
    {
      if( disposing )
      {
        if (components != null) 
        {
          components.Dispose();
        }
      }
      base.Dispose( disposing );
    }

    #region Windows Form Designer generated code
    /// <summary>
    /// Required method for Designer support — do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
      System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(Form1));
      this.mainMenu1 = new System.Windows.Forms.MainMenu();
      this.mnuFile = new System.Windows.Forms.MenuItem();
      this.mnuOpen = new System.Windows.Forms.MenuItem();
      this.mnuSave = new System.Windows.Forms.MenuItem();
      this.menuItem1 = new System.Windows.Forms.MenuItem();
      this.mnuPageSetup = new System.Windows.Forms.MenuItem();
      this.mnuPreview = new System.Windows.Forms.MenuItem();
      this.mnuPrint = new System.Windows.Forms.MenuItem();
      this.menuItem5 = new System.Windows.Forms.MenuItem();
      this.mnuExit = new System.Windows.Forms.MenuItem();
      this.mnuView = new System.Windows.Forms.MenuItem();
      this.mnuResize = new System.Windows.Forms.MenuItem();
      this.mnuActual = new System.Windows.Forms.MenuItem();
      this.mnuCenterImage = new System.Windows.Forms.MenuItem();
      this.mnuAutoSize = new System.Windows.Forms.MenuItem();
      this.openFileDialog1 = new System.Windows.Forms.OpenFileDialog();
      this.saveFileDialog1 = new System.Windows.Forms.SaveFileDialog();
      this.contextMenu1 = new System.Windows.Forms.ContextMenu();
      this.cmnuResize = new System.Windows.Forms.MenuItem();
      this.cmnuActual = new System.Windows.Forms.MenuItem();
      this.cmnuCenterImage = new System.Windows.Forms.MenuItem();
      this.cmnuAutoSize = new System.Windows.Forms.MenuItem();
      this.pictureBox1 = new System.Windows.Forms.PictureBox();
      this.statusBar1 = new System.Windows.Forms.StatusBar();
      this.sbFile = new System.Windows.Forms.StatusBarPanel();
      this.sbSize = new System.Windows.Forms.StatusBarPanel();
      this.printDialog1 = new System.Windows.Forms.PrintDialog();
      this.printPreviewDialog1 = new System.Windows.Forms.PrintPreviewDialog();
      this.pageSetupDialog1 = new System.Windows.Forms.PageSetupDialog();
      this.printDocument1 = new System.Drawing.Printing.PrintDocument();
      ((System.ComponentModel.ISupportInitialize)(this.sbFile)).BeginInit();
      ((System.ComponentModel.ISupportInitialize)(this.sbSize)).BeginInit();
      this.SuspendLayout();
      // 
      // mainMenu1
      // 
      this.mainMenu1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
                                            this.mnuFile,
                                            this.mnuView});
      // 
      // mnuFile
      // 
      this.mnuFile.Index = 0;
      this.mnuFile.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
                                          this.mnuOpen,
                                          this.mnuSave,
                                          this.menuItem1,
                                          this.mnuPageSetup,
                                          this.mnuPreview,
                                          this.mnuPrint,
                                          this.menuItem5,
                                          this.mnuExit});
      this.mnuFile.Text = "Файл";
      // 
      // mnuOpen
      // 
      this.mnuOpen.Index = 0;
      this.mnuOpen.Shortcut = System.Windows.Forms.Shortcut.CtrlO;
      this.mnuOpen.Text = "Открыть";
      this.mnuOpen.Click += new System.EventHandler(this.mnuOpen_Click);
      // 
      // mnuSave
      // 
      this.mnuSave.Index = 1;
      this.mnuSave.Shortcut = System.Windows.Forms.Shortcut.CtrlS;
      this.mnuSave.Text = "Сохранить";
      this.mnuSave.Click += new System.EventHandler(this.mnuSave_Click);
      // 
      // menuItem1
      // 
      this.menuItem1.Index = 2;
      this.menuItem1.Text = "-";
      // 
      // mnuPageSetup
      // 
      this.mnuPageSetup.Index = 3;
      this.mnuPageSetup.Text = "Page Setup";
      this.mnuPageSetup.Click += new System.EventHandler(this.mnuPageSetup_Click);
      // 
      // mnuPreview
      // 
      this.mnuPreview.Index = 4;
      this.mnuPreview.Text = "Print Preview";
      this.mnuPreview.Click += new System.EventHandler(this.mnuPreview_Click);
      // 
      // mnuPrint
      // 
      this.mnuPrint.Index = 5;
      this.mnuPrint.Shortcut = System.Windows.Forms.Shortcut.CtrlP;
      this.mnuPrint.Text = "Print";
      this.mnuPrint.Click += new System.EventHandler(this.mnuPrint_Click);
      // 
      // menuItem5
      // 
      this.menuItem5.Index = 6;
      this.menuItem5.Text = "-";
      // 
      // mnuExit
      // 
      this.mnuExit.Index = 7;
      this.mnuExit.Shortcut = System.Windows.Forms.Shortcut.AltF4;
      this.mnuExit.Text = "Выход";
      this.mnuExit.Click += new System.EventHandler(this.mnuExit_Click);
      // 
      // mnuView
      // 
      this.mnuView.Index = 1;
      this.mnuView.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
                                          this.mnuResize,
                                          this.mnuActual,
                                          this.mnuCenterImage,
                                          this.mnuAutoSize});
      this.mnuView.Text = "Вид";
      this.mnuView.Popup += new System.EventHandler(this.mnuView_Popup);
      // 
      // mnuResize
      // 
      this.mnuResize.Index = 0;
      this.mnuResize.Text = "Подогнать размер";
      this.mnuResize.Click += new System.EventHandler(this.mnuResize_Click);
      // 
      // mnuActual
      // 
      this.mnuActual.Index = 1;
      this.mnuActual.Text = "Истинный размер";
      this.mnuActual.Click += new System.EventHandler(this.mnuActual_Click);
      // 
      // mnuCenterImage
      // 
      this.mnuCenterImage.Index = 2;
      this.mnuCenterImage.Text = "По центру";
      this.mnuCenterImage.Click += new System.EventHandler(this.mnuCenterImage_Click);
      // 
      // mnuAutoSize
      // 
      this.mnuAutoSize.Index = 3;
      this.mnuAutoSize.Text = "Автоматический размер";
      this.mnuAutoSize.Click += new System.EventHandler(this.mnuAutoSize_Click);
      // 
      // openFileDialog1
      // 
      this.openFileDialog1.Title = "Выбор изображения";
      // 
      // saveFileDialog1
      // 
      this.saveFileDialog1.Title = "Сохранение изображения";
      // 
      // contextMenu1
      // 
      this.contextMenu1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
                                             this.cmnuResize,
                                             this.cmnuActual,
                                             this.cmnuCenterImage,
                                             this.cmnuAutoSize});
      // 
      // cmnuResize
      // 
      this.cmnuResize.Index = 0;
      this.cmnuResize.Text = "Подогнать размер";
      this.cmnuResize.Click += new System.EventHandler(this.cmnuResize_Click);
      // 
      // cmnuActual
      // 
      this.cmnuActual.Index = 1;
      this.cmnuActual.Text = "Истинный размер";
      this.cmnuActual.Click += new System.EventHandler(this.cmnuActual_Click);
      // 
      // cmnuCenterImage
      // 
      this.cmnuCenterImage.Index = 2;
      this.cmnuCenterImage.Text = "По центру";
      this.cmnuCenterImage.Click += new System.EventHandler(this.cmnuCenterImage_Click);
      // 
      // cmnuAutoSize
      // 
      this.cmnuAutoSize.Index = 3;
      this.cmnuAutoSize.Text = "Автоматический размер";
      this.cmnuAutoSize.Click += new System.EventHandler(this.cmnuAutoSize_Click);
      // 
      // pictureBox1
      // 
      this.pictureBox1.ContextMenu = this.contextMenu1;
      this.pictureBox1.Dock = System.Windows.Forms.DockStyle.Fill;
      this.pictureBox1.Location = new System.Drawing.Point(0, 0);
      this.pictureBox1.Name = "pictureBox1";
      this.pictureBox1.Size = new System.Drawing.Size(616, 266);
      this.pictureBox1.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage;
      this.pictureBox1.TabIndex = 0;
      this.pictureBox1.TabStop = false;
      // 
      // statusBar1
      // 
      this.statusBar1.Location = new System.Drawing.Point(0, 244);
      this.statusBar1.Name = "statusBar1";
      this.statusBar1.Panels.AddRange(new System.Windows.Forms.StatusBarPanel[] {
                                              this.sbFile,
                                              this.sbSize});
      this.statusBar1.ShowPanels = true;
      this.statusBar1.Size = new System.Drawing.Size(616, 22);
      this.statusBar1.TabIndex = 3;
      // 
      // sbFile
      // 
      this.sbFile.Width = 500;
      // 
      // printPreviewDialog1
      // 
      this.printPreviewDialog1.AutoScrollMargin = new System.Drawing.Size(0, 0);
      this.printPreviewDialog1.AutoScrollMinSize = new System.Drawing.Size(0, 0);
      this.printPreviewDialog1.ClientSize = new System.Drawing.Size(400, 300);
      this.printPreviewDialog1.Enabled = true;
      this.printPreviewDialog1.Icon = ((System.Drawing.Icon)(resources.GetObject("printPreviewDialog1.Icon")));
      this.printPreviewDialog1.Location = new System.Drawing.Point(151, 59);
      this.printPreviewDialog1.MinimumSize = new System.Drawing.Size(375, 250);
      this.printPreviewDialog1.Name = "printPreviewDialog1";
      this.printPreviewDialog1.TransparencyKey = System.Drawing.Color.Empty;
      this.printPreviewDialog1.Visible = false;
      // 
      // printDocument1
      // 
      this.printDocument1.PrintPage += new System.Drawing.Printing.PrintPageEventHandler(this.printDocument1_PrintPage);
      // 
      // Form1
      // 
      this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
      this.ClientSize = new System.Drawing.Size(616, 266);
      this.ContextMenu = this.contextMenu1;
      this.Controls.Add(this.statusBar1);
      this.Controls.Add(this.pictureBox1);
      this.Menu = this.mainMenu1;
      this.Name = "Form1";
      this.Text = "Picture Viewer";
      ((System.ComponentModel.ISupportInitialize)(this.sbFile)).EndInit();
      ((System.ComponentModel.ISupportInitialize)(this.sbSize)).EndInit();
      this.ResumeLayout(false);

    }
    #endregion

    private PictureBoxSizeMode[] ArrayMenu =
    {
      PictureBoxSizeMode.StretchImage,
      PictureBoxSizeMode.Normal, 
      PictureBoxSizeMode.CenterImage,
      PictureBoxSizeMode.AutoSize
    };
    private int selectedMode = 0;
    [STAThread]
    static void Main() 
    {
      Application.Run(new Form1());
    }

    private void mnuOpen_Click(object sender, System.EventArgs e)
    {
      OpenFileDialog diag = new OpenFileDialog();
      diag.Filter = "jpg files (*.jpg)|*.jpg|All files (*.*)|*.*" ;
      if (diag.ShowDialog() == DialogResult.OK)
      {
        pictureBox1.Image = new Bitmap(diag.OpenFile());
      }
    
      //Строка для вывода   во время загрузки изображения
      sbFile.Text  = "Загрузка " + diag.FileName;
      //Строка для вывода после загрузки изображения
      sbFile.Text  = "Изображение " + diag.FileName;
      //Вывод ширины (Width) и высоты (Height) изображения
      sbSize.Text= String.Format("{0:#} x {1:#}",  pictureBox1.Image.Width, pictureBox1.Image.Height);
    }

    private void mnuSave_Click(object sender, System.EventArgs e)
    {
      SaveFileDialog diag = new SaveFileDialog();
      diag.Filter = "jpg files (*.jpg)|*.jpg|All files (*.*)|*.*" ;
      if(diag.ShowDialog() != DialogResult.OK)
        return;
      // Получаем адрес файла.
      string filename = diag.FileName;
      FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.ReadWrite);
      pictureBox1.Image.Save(fs,  System.Drawing.Imaging.ImageFormat.Jpeg);
      fs.Close();
    }

    private void mnuExit_Click(object sender, System.EventArgs e)
    {
      this.Close();
    }

    private void mnuResize_Click(object sender, System.EventArgs e)
    {
      if (sender is MenuItem)
      {
        MenuItem menuitem = (MenuItem)sender;
        selectedMode = menuitem.Index;
        pictureBox1.SizeMode = ArrayMenu[0];
        pictureBox1.Invalidate();
        
      }
    
    }

    private void mnuActual_Click(object sender, System.EventArgs e)
    {
      if (sender is MenuItem)
      {
        MenuItem menuitem = (MenuItem)sender;
        selectedMode = menuitem.Index;
      
        pictureBox1.SizeMode = ArrayMenu[1];
        pictureBox1.Invalidate();
        
      }
    }

    private void mnuView_Popup(object sender, System.EventArgs e)
    {
      if (sender is MenuItem) 
      {
        bool ImLoad  = (pictureBox1.Image != null);
        foreach (MenuItem menuitem in ((MenuItem)sender).MenuItems)
              {
                    menuitem.Enabled = ImLoad;
                    menuitem.Checked = (this.selectedMode == menuitem.Index);
              }
      }
      
    }

    private void mnuCenterImage_Click(object sender, System.EventArgs e)
    {
      if (sender is MenuItem)
      {
        MenuItem menuitem = (MenuItem)sender;
        selectedMode = menuitem.Index;
      
        pictureBox1.SizeMode = ArrayMenu[2];
        pictureBox1.Invalidate();
        
      }
    }

    private void mnuAutoSize_Click(object sender, System.EventArgs e)
    {
      if (sender is MenuItem)
      {
        MenuItem menuitem = (MenuItem)sender;
        selectedMode = menuitem.Index;
      
        pictureBox1.SizeMode = ArrayMenu[3];
        pictureBox1.Invalidate();
        
      }
    
    }

    private void cmnuResize_Click(object sender, System.EventArgs e)
    {
      if (sender is MenuItem)
      {
        MenuItem menuitem = (MenuItem)sender;
        selectedMode = menuitem.Index;
        pictureBox1.SizeMode = ArrayMenu[0];
        pictureBox1.Invalidate();
        
      }
      
      
    }

    private void cmnuActual_Click(object sender, System.EventArgs e)
    {
      if (sender is MenuItem)
      {
        MenuItem menuitem = (MenuItem)sender;
        selectedMode = menuitem.Index;
        pictureBox1.SizeMode = ArrayMenu[1];
        pictureBox1.Invalidate();
        
      }
    
    }

    private void cmnuCenterImage_Click(object sender, System.EventArgs e)
    {
      if (sender is MenuItem)
      {
        MenuItem menuitem = (MenuItem)sender;
        selectedMode = menuitem.Index;
        pictureBox1.SizeMode = ArrayMenu[2];
        pictureBox1.Invalidate();
        
      }
      
    }

    private void cmnuAutoSize_Click(object sender, System.EventArgs e)
    {
      if (sender is MenuItem)
      {
        MenuItem menuitem = (MenuItem)sender;
        selectedMode = menuitem.Index;
        pictureBox1.SizeMode = ArrayMenu[3];
        pictureBox1.Invalidate();
        
      }
      
    }

    private void mnuPageSetup_Click(object sender, System.EventArgs e)
    {
      PageSetupDialog diag
        = new PageSetupDialog();
      diag.Document = printDocument1;
      diag.ShowDialog();
    }

    private void mnuPreview_Click(object sender, System.EventArgs e)
    {
      PrintPreviewDialog diag
        = new PrintPreviewDialog();
      diag.Document = printDocument1;
      diag.ShowDialog();
    }

    private void mnuPrint_Click(object sender, System.EventArgs e)
    {
      PrintDialog diag = new PrintDialog();
      diag.Document = printDocument1;
      if (diag.ShowDialog() == DialogResult.OK)
      {
        printDocument1.Print();
      }
      
    }

    private void printDocument1_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
    {
      //Если в pictureBox1 не загружено изображение, возвращаемся назад
      if (pictureBox1.Image == null)
      {
        e.Cancel = true;
        return;
      }
      //Определяем печатную область страницы
      float leftMargin = e.MarginBounds.Left;
      float rightMargin = e.MarginBounds.Right;
      float topMargin = e.MarginBounds.Top;
      float bottomMargin= e.MarginBounds.Bottom;
      float printableWidth = e.MarginBounds.Width;
      float printableHeight = e.MarginBounds.Height;
      //Cоздаем экземпляр graph класса Graphics
      Graphics graph = e.Graphics;
      //Создаем экземпляр font  класса Font
      Font font= new Font("Comic Sans MS", 16);
      //Определяем высоту шрифта
      float fontHeight = font.GetHeight(graph);
      //Определяем размер пробелов 
      float spaceWidth = graph.MeasureString(" ", font).Width;
      //Определяем область, в которую будет вписываться изображение, 
      //размер наибольшей стороны изображения составляет 90% 
      //от кратчайшей стороны листа
      float imageLength;
      float Xposition = leftMargin;
      float Yposition = topMargin + fontHeight;
      if (printableWidth < printableHeight)
      {
        imageLength = printableWidth * 90/100;
        Yposition += imageLength;
      }
      else
      {
        imageLength = printableHeight * 90/100;
        Xposition += imageLength + spaceWidth;
      }
      // Выводим изображение в области rectImage
      Rectangle rectImage  = new Rectangle((int)leftMargin + 1,  (int)topMargin + 1,(int)imageLength,  (int)imageLength);
      graph.DrawImage(pictureBox1.Image,(int)leftMargin + 1,  (int)topMargin + 1, (int)imageLength,(int)imageLength);
      // Определяем область rectText и выводим в нее строку с размером файла
      RectangleF rectText  = new RectangleF(Xposition, Yposition, rightMargin - Xposition,  bottomMargin - Yposition);
      PrintText(graph, font,"Размер изображения: ", Convert.ToString(pictureBox1.Image.Size),   ref rectText);
    }
    protected void PrintText( Graphics graph,   Font font,  string name, string text,  ref RectangleF rectText)
    {
      // Определяем размеры печатной области для текста:
      float leftMargin = rectText.Left;
      float rightMargin = rectText.Right;
      float topMargin = rectText.Top;
      float bottomMargin = rectText.Bottom;
      //Определяем высоту текста и координаты, где он будет выводиться:
      float fontHeight = font.GetHeight(graph);
      float Xposition = rectText.Left;
      float Yposition = topMargin + fontHeight;
      //Определяем ширину текста и размер пробелов
      float spaceWidth = graph.MeasureString(" ",   font).Width;
      float nameWidth  = graph.MeasureString(name, font).Width;
      graph.DrawString(name, font,
      Brushes.Black, new PointF(Xposition, Yposition));
      leftMargin += nameWidth + spaceWidth;
      Xposition = leftMargin;
      // Формируем несколько строк для текста  в случае,
      // если он не будет умещаться на одной строке
      string[] words
        = text.Split(" \r\t\n\0".ToCharArray());
      foreach (string word in words)
      {
        float wordWidth = graph.MeasureString(
          word, font).Width;
        if (wordWidth == 0.0)
          continue;
        if (Xposition + wordWidth > rightMargin)
        {
          // Начало с новой строки
          Xposition = leftMargin;
          Yposition += fontHeight;
          if (Yposition > bottomMargin)
          {            
            break;
          }
        }
        graph.DrawString(word, font,Brushes.Black,  new PointF(Xposition, Yposition));
        Xposition += wordWidth;
      }
      // Исключаем область, на которую был выведен текст, из области печати 
      //для избежания наложения текста и рисунка
      rectText.Y = Yposition;
      rectText.Height = bottomMargin - Yposition;
  
    }
  }
}
Страницы:
Для работы с данной лекцией используйте примеры.

Тема печати в Windows традиционно относится к одной из самых сложных: наличие огромного количества параметров, свойств и методов делает эту довольно простую вещь сильно запутанной. В этой лекции мы рассмотрим такие несложные задачи, как настройка параметров страницы, предварительный просмотр, печать содержимого элемента RichTextBox, а также преобразование форматов и печать рисунков, помещенных в PictureBox. Элемент PictureBox поддерживает различные режимы просмотра изображений – мы применим их всех и создадим простое приложение, позволяющее просматривать фотографии. Мы также еще раз создадим пользовательский элемент управления с собственными свойствами.

Печать содержимого RichTextBox. Элементы управления PrintDocument, PageSetupDialog, PrintPreviewDialog, PrintDialog

В качестве исходного приложения, в котором будем реализовывать возможность печати, возьмем проект TextEditor, созданный нами во второй лекции. Как мы знаем, максимальный объем элемента TextBox ограничивается 64 Кб, для работы с многостраничными документами этого явно мало. Поэтому удаляем с формы элемент txtBox и на его место помещаем элемент RichTextBox, которому задаем имя rtbText. Свойству Dock устанавливаем значение Fill, кроме того, удаляем значение свойства Text. Изменяем и добавляем следующие пункты главного меню (рис. 6.1):

NameTextShortcut
mnuFile Файл
mnuOpen Открыть CtrlO
mnuSave Сохранить CtrlS
menuItem1 -
mnuPageSetup Параметры страницы
mnuPrintPreview Предварительный просмотр
mnuPrint Печать CtrlP
(рис 6.1) Пункты главного меню

Перетаскиваем на форму из окна ToolBox элементы управления: PrintDocument, PageSetupDialog, PrintPreviewDialog и PrintDialog. Подобно другим элементам диалогов, они отображаются на панели компонент в среде Visual Studio .NET. При печати формируется одна или несколько страниц, за которые отвечает объект PrintDocument. Элементы управления PageSetupDialog, PrintPreviewDialog и PrintDialog представляют собой диалоговые окна параметров страниц, предварительного просмотра и печати соответственно. Настройка свойств этих объектов представляет собой столь гибкую и широкую задачу, решаемую в коде приложения, что, в отличие, например, от элементов OpenFileDialog или SaveFileDialog, имеет смысл создавать их программно, в классе формы:

// PrintDocument printDocument1 = new PrintDocument();
// PageSetupDialog  pageSetupDialog1 = new PageSetupDialog();
// PrintPreviewDialog printPreviewDialog1 = new PrintPreviewDialog();
// PrintDialog printDialog1 = new PrintDialog();

Этот фрагмент полностью аналогичен добавлению на форму элементов. Мы, однако, будем использовать визуальный режим.

Свойству Document элементов управления PageSetupDialog и PrintPreviewDialog устанавливаем значение свойства Name элемента PrintDocumentprintDocument1. Тем самым мы связываем объект printDocument1, отвечающий за формирование страниц печати, с диалоговыми окнами. Устнавливаем следующие свойства элемента PrintDialog:

printDialog1, свойствоЗначениеОписание
AllowSelection True Разрешение на печать выделенного фрагмента документа
AllowSomePages True Разрешение на печать нескольких страниц
PrintDocument printDocument1 Связывание с экземпляром объекта PrintDocument

На информационной панели окна Properties при выборе свойств AllowSelection или AllowSomePages можно заметить сообщение – Enables and disables the Selection/Pages radio button (Включает или отключает доступность радиопереключателей "Выделенный фрагмент/Страницы"). Само по себе определение этих свойств еще не включает соответствующую функциональность — еще одна причина, по которой конфигурировать объекты печати лучше программно:

InitializeComponent();
// pageSetupDialog1.Document = printDocument1;
// printPreviewDialog1.Document = printDocument1;
// printDialog1.Document = printDocument1;
// printDialog1.AllowSomePages = true;
// printDialog1.AllowSelection = true;

Переходим в код формы. Для работы с печатью в библиотеке .NET Framework применяется класс System.Drawing.Printing, поэтому его нужно подключить в самом начале работы:

using System.Drawing.Printing;

В классе формы объявляем следующие переменные:

//Переменная для хранения текста для печати.
//В нее мы будем помещать текст из RichTextBox
string stringPrintText;
//Переменная, определяющая номер страницы, с которой нужно начать печать
int StartPage;
//Переменная, определяющая количество страниц для печати:
int NumPages;
//Переменная, определяющая номер текущей страницы:
int PageNumber;

Следует обратить внимание на переменную stringPrintText — именно она будет отвечать за передачу текста в объект печати. Поскольку мы определили для нее строковый тип, это означает, что мы не сможем вывести на печать рисунки в тексте — как мы знаем, элемент RichTextBox поддерживает их наличие (в этом можно убедиться, скопировав содержимое документа Microsoft Word в наше приложение). Определение полной печати будет более сложным, что, впрочем, не является существенным для понимания самой концепции печати 1За ответом на этот вопрос я отправляю читателя к библиотеке MSDN. Если вы поймете общую идею, то без труда сможете нарастить дополнительные возможности на приложение для печати..

В конструкторе форме определяем диапазон страниц для печати:

public Form1()
  {
      
    InitializeComponent();
    …
    //Определяем номер страницы, с которой следует начать печать
    printDialog1.PrinterSettings.FromPage = 1;
    //Определяем максимальный номер печатаемой страницы.
    printDialog1.PrinterSettings.ToPage = printDialog1.PrinterSettings.MaximumPage;
  }

Максимальное количество страниц в диапазоне — 9999;, очевидно, что его вполне хватит для распечатки любого документа. Основным событием, в обработчике которого практически и будет осуществляться вся печать, будет событие PrintPage элемента printDocument1. Переключаемся в режим дизайна, выделяем элемент и, переключившись в окне Properties на вкладку событий, дважды щелкаем в поле этого события:

private void printDocument1_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
 {
    
 }

Далее все описываемые фрагменты кода будут относиться к этому обработчику.

Класс Graphics, принадлежащий пространству имен System.Drawing, — один из важнейших классов, отвечающих за рисование графики и вывода текста на форме. При создании новой формы пространство имен System.Drawing по умолчанию включается в шаблон. Для формирования страницы печати создаем экземпляр этого класса:

Graphics graph = e.Graphics;
//Создаем объект font, которому устанавливаем 
// шрифт элемента rtbText
Font font = rtbText.Font;
//Получаем значение межстрочного интервала — высоту шрифта 
float HeightFont = font.GetHeight(graph); 
//Создаем экземпляр strfmt класса StringFormat для определения 
//дополнительных параметров форматирования текста.
StringFormat  stringformat = new StringFormat();

Страница представляет собой прямоугольный объект, параметры которого следует определить. Для этого используется класс RectangleF (буква F указывает на использование типа данных с плавающей точкой float ):

//Создаем экземляры  rectanglefFull и rectfText класса RectangleF для 
//определения областей печати и текста. 
RectangleF rectanglefFull, rectfText;
//Создаем переменные для подсчета числа символов и строк.
int NumberSymbols, NumberLines;

Страница, формируемая для печати, состоит из трех областей (рис. 6.2).

(рис 6.2) Области страницы, выводимой на печать

Первая область — полный размер страницы, обычно имеющий названия, например, формат A4 или A3. Информацию об этой области предоставляет свойство PageBounds класса PrintPageEventArgs. Его значением является объект Rectangle, свойства X и У которого равны 0, а свойства Width и Height предоставляют размеры бумаги по умолчанию, выраженные в сотых долях дюйма. Вторая область связана с конструктивными особенностями принтеров — большинство из них используют для печати площадь, меньшую размеров бумаги. Область печати определяется значением свойства VisibleClipBounds класса Graphics, представляемая объектом RectangleF. Свойства Х и У этого объекта имеют значения 0, а ее свойства Width и Height равны горизонтальному и вертикальному размерам области печати страницы. В третьей области помещается текст, она обычно определяется пользователем. В Microsoft Word, например, мы устанавливаем заданные значения полей. По умолчанию, эта область рассчитывается с отступом в один дюйм по периметру страницы 11 дюйм приблизительно равен 2,54 см.. Ее размер возвращается объектом Rectangle свойства MarginBounds.

В любом случае, области формируются объектами Rectangle или RectangleF, конструкторы которых могут иметь следующий вид:

Rectangle (int x, int y, int width, int height);

RectangleF(float x, float y, float width, float height);

Эти конструкторы отличаются только типом данных, формирующих структуру. Координатные оси области, представляющей собой прямоугольник, направлены следующим образом (рис. 6.3):

(рис 6.3) Расположение координатных осей и формирование объекта Rectangle

Вернемся к нашему коду. В качестве области печати устанавливаем объект rectanglefFull:

if (graph.VisibleClipBounds.X<0) rectanglefFull = e.MarginBounds;
else
//Определяем   объект  rectanglefFull
rectanglefFull = new RectangleF(
//Устанавливаем координату  X  
e.MarginBounds.Left — (e.PageBounds.Width — graph.VisibleClipBounds.Width)/2, 
//Устанавливаем координату  Y
.MarginBounds.Top — (e.PageBounds.Height — graph.VisibleClipBounds.Height)/2, 
//Устанавливаем ширину области
e.MarginBounds.Width,
//Устанавливаем высоту области
e.MarginBounds.Height);

Область текста будет представлять собой копию области rectanglefFull c учетом высоты шрифта:

rectanglefText = RectangleF.Inflate(rectanglefFull, 0, -2*HeightFont);
//Определяем число строк
int NumDisplayLines = (int)Math.Floor(rectanglefText.Height/HeightFont);
//Устанавливаем высоту области
rectanglefText.Height = NumDisplayLines*HeightFont;

Элемент RichTextBox обладает свойством WordWrap, при установке значения True которого текст автоматически переносится на новую строку. Однако разбитый на строки текст может выйти за границы предназначенной для него области rectanglefText. Чтобы этого не произошло, мы создали экземпляр stringformat класса StringFormat, для которого теперь устанавливаем значение Trimming:

if (rtbText.WordWrap)
  {
    stringformat.Trimming = StringTrimming.Word;
  }
else
  {
    stringformat.Trimming = StringTrimming.EllipsisCharacter;
    stringformat.FormatFlags |=StringFormatFlags.NoWrap;
  }

Далее код достаточно простой, привожу его с комментариями:

//При печати выбранных страниц переходим к первой стартовой странице
while ((PageNumber<StartPage)(stringPrintText.Length>0))
    {
if(rtbText.WordWrap)
//Измеряем текстовые переменные, 
//формирующие печать,  и возвращаем число символов NumberSymbols
//и число строк NumberLines
graph.MeasureString(stringPrintText, font, rectanglefText.Size, stringformat, out NumberSymbols, out NumberLines);
else
    NumberSymbols = SymbolsInLines(stringPrintText, NumDisplayLines);
    stringPrintText = stringPrintText.Substring(NumberSymbols);
    //Увеличиваем число страниц 
    PageNumber++;
  }
//Если длина строки stringPrintText равняется нулю (нет текста для печати),
// останавливаем печать
    if (stringPrintText.Length==0)
      {
        e.Cancel = true;
        return;
      }
//Выводим (рисуем) текст для печати — stringPrintText, используем для этого шрифт font,
//кисть черного цвета  — Brushes.Black, область печати — rectanglefText,
//передаем строку  дополнительного форматирования stringformat
graph.DrawString(stringPrintText, font, Brushes.Black, rectanglefText, stringformat);
//Получаем текст для следующей страницы
  if (rtbText.WordWrap)
  graph.MeasureString(stringPrintText, font, rectanglefText.Size, stringformat, out NumberSymbols, out NumberLines);
  else
  NumberSymbols = SymbolsInLines(stringPrintText, NumDisplayLines);
  stringPrintText = stringPrintText.Substring(NumberSymbols);
//Очищаем объект stringformat, использованный для формирования полей.
    stringformat = new StringFormat();
//Добавляем  вывод на каждую страницу ее номера
    stringformat.Alignment = StringAlignment.Far;
    graph.DrawString("Страница" + PageNumber, font, Brushes.Black, rectanglefFull, stringformat);
    PageNumber++;
//Cнова проверяем, имеется ли текст для печати и номер страницы, заданной для печати
    e.HasMorePages=(stringPrintText.Length>0)(PageNumber<StartPage+NumPages);
//Для печати из окна предварительного просмотра  снова инициализируем переменные
    if(!e.HasMorePages)
    {
      stringPrintText = rtbText.Text;
      StartPage = 1;
      NumPages = printDialog1.PrinterSettings.MaximumPage;
      PageNumber = 1;
      }

      
    }

Мы закончили рассмотрение метода printDocument1_PrintPage. Достаточно существенным моментом здесь является то, что в коде реализована проверка наличия расстановки переносов в текстовом поле RichTextBox (свойство WordWrap). Метод MeasureString возвращает число выводимых символов. Для учета символов, не попадающих в область rectanglefText, добавляем метод SymbolsInLines:

int SymbolsInLines(string stringPrintText, int NumLines)
  {
    int index = 0;
    for (int i = 0; i< NumLines; i++)
    {
      index = 1+ stringPrintText.IndexOf('\n', index);
      if(index==0)
        return stringPrintText.Length;
    }
    return index;
  }

Создаем обработчики событий пунктов mnuPageSetup и mnuPrintPreview главного меню:

private void mnuPageSetup_Click(object sender, System.EventArgs e)
  {
    //Показываем диалог
    pageSetupDialog1.ShowDialog();
  }
    private void mnuPrintPreview_Click(object sender, System.EventArgs e)
  {
    //Инициализируем переменные
    printDocument1.DocumentName = Text;
    stringPrintText = rtbText.Text;
    StartPage = 1;
    NumPages = printDialog1.PrinterSettings.MaximumPage;
    PageNumber = 1;
    //Показываем диалог
    printPreviewDialog1.ShowDialog();
  }

В обработчике пункта меню mnuPrint определяем диапазон страниц – все страницы, ряд или выделенная область:

private void mnuPrint_Click(object sender, System.EventArgs e)
  {
    printDialog1.AllowSelection = rtbText.SelectionLength >0;
      if(printDialog1.ShowDialog()==DialogResult.OK)
    {
      printDocument1.DocumentName =Text;
      //Определяем диапазон страниц для печати
      switch(printDialog1.PrinterSettings.PrintRange)
      {
        //Выбраны все страницы
        case PrintRange.AllPages:
          stringPrintText = rtbText.Text;
          StartPage = 1;
          NumPages = printDialog1.PrinterSettings.MaximumPage;
          break;
        //Выбрана выделенная область
        case PrintRange.Selection:
          stringPrintText = rtbText.SelectedText;
          StartPage = 1;
          NumPages = printDialog1.PrinterSettings.MaximumPage;
          break;
        //Выбран ряд страниц
        case PrintRange.SomePages:
          stringPrintText = rtbText.Text;
          StartPage = printDialog1.PrinterSettings.FromPage;
          NumPages = printDialog1.PrinterSettings.ToPage - StartPage+1;
          break;
      }
      PageNumber = 1;
      //Вызываем встроенный метод для начала печати
      printDocument1.Print();
    }
    
  }

Запускаем приложение. Изменения, сделанные в диалоговом окне "Параметры страницы" (рис. 6.4), сохраняются, и в окне предварительного просмотра можно видеть подготовленную для печати страницу (рис. 6.5).

(рис 6.5) Настройка параметров страницы(рис 6.4) Окно предварительного просмотра. На каждой странице выводится ее номер

Из окна предварительного просмотра можно сразу перейти к печати. При выборе пункта печати доступен выбор ряда страниц или печать выделенного фрагмента (рис. 6.6).

(рис 6.6) Настройка параметров печати

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

(рис 6.7) Вывод документа в окно виртуального принтера программы pdfFactoryPro

В конце лекции приводится полный листинг приложения TextEditor, а исходный проект имеется на диске, прилагаемом к книге (Code\Glava6\TextEditor).

Работа с изображениями. Программа Picture Viewer

Работа с изображениями — одно из самых увлекательных занятий на компьютере. Не менее интересно создавать графические программы. Во второй лекции мы сталкивались с элементом PictureBox, в который помещали рисунок для окна "О программе". Теперь мы научимся загружать в этот элемент изображения, сохранять их в другом формате и распечатывать. Создайте новое приложение и назовите его PictureViewer. Добавляем на форму меню (рис. 6.8), контекстное меню, OpenFileDialog, SaveFileDialog, PictureBox, устанавливаем следующие свойства элементов управления:

mainMenu1, NameTextShortcut
mnuFile Файл
mnuOpen Открыть CtrlO
mnuSave Сохранить CtrlS
menuItem1
mnuPageSetup Параметры страницы
mnuPrintPreview Предварительный просмотр
mnuPrint Печать CtrlP
mnuExit Exit AltF4
mnuView Вид
mnuResize Подогнать размер
mnuActual Истинный размер
mnuCenterImage По центру
mnuAutoSize Автоматический размер
(рис 6.8) Пункты главного меню программы Picture Viewer
contextMenu1, NameText
mnuResize Подогнать размер
mnuActual Истинный размер
mnuCenterImage По центру
mnuAutoSize Автоматический размер
openFileDialog1, СвойствоЗначение
Title Выбор изображения
saveFileDialog1, СвойствоЗначение
Title Сохранение изображения
pictureBox1, СвойствоЗначение
ContextMenu contextMenu1
Dock Fill
SizeMode StretchImage

Приложение будет отображать в элементе управления PictureBox загруженный рисунок. Добавляем обработчик пункта меню mnuOpen:

private void mnuOpen_Click(object sender, System.EventArgs e)
  {
    OpenFileDialog diag = new OpenFileDialog();
    diag.Filter = "jpg files (*.jpg)|*.jpg|All files (*.*)|*.*" ;
    if (diag.ShowDialog() == DialogResult.OK)
    {
      pictureBox1.Image = new Bitmap(diag.OpenFile());
    }
  }

Несмотря на определение формата jpeg в качестве основного, элемент PictureBox поддерживает также работу с форматами Bitmap (.bmp), Icon (.ico), GIF (.gif), Metafile (.wmf). Сохранения файла практически не отличается от сохранения содержимого текстового поля:

private void mnuSave_Click(object sender, System.EventArgs e)
  {
    SaveFileDialog diag = new SaveFileDialog();
    diag.Filter = "jpg files (*.jpg)|*.jpg|All files (*.*)|*.*" ;
    if(diag.ShowDialog() != DialogResult.OK)
      return;
    // Получаем адрес файла.
    string filename = diag.FileName;
    FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.ReadWrite);
    pictureBox1.Image.Save(fs,  System.Drawing.Imaging.ImageFormat.Jpeg);
    fs.Close();
  }

Обратите внимание на возможность конвертирования форматов — метод Save свойства предоставляет возможность сохранения содержимого PictureBox в различных форматах (рис. 6.9).

(рис 6.9) Второй параметр метода Save принимает название графического формата

Мы выбрали формат Jpeg — при открытии рисунка в другом формате, например, Bmp, и последующем сохранении он будет конвертирован в этот формат.

Элемент управления PictureBox поддерживает несколько вариантов отображения загружаемого рисунка (рис. 6.10).

(рис 6.10) Режимы отображения рисунка элементов PictureBox: подогнать размер (StretchImage), истинный размер (Normal), по центру (CenterImage), автоматический размер (AutoSize)

Мы установили свойству SizeMode значение StretchImage, и поэтому при открывании рисунка его размер автоматически подгоняется под размер PictureBox и, соответственно, формы. Истинный размер выводит рисунок в его действительных пропорциях; при этом на экране помещается только его часть. Конечно, в этом режиме необходима прокрутка рисунка — об этом чуть позже. Режим по центру располагает рисунок в его реальных пропорциях так, чтобы его центр совпадал с центром PictureBox. Последний режим — автоматический размер — подгоняет размеры PictureBox под размер открываемого изображения. На рис. 6.10 он визуально совпадает с режимом StretchImage — дело в том, что мы установили свойству Dock значение Fill и поэтому PictureBox уже имеет максимально большой размер.

В классе формы создадим массив ArrayMenu, в котором будут содержаться значения режимов:

private PictureBoxSizeMode[] ArrayMenu =
  {
    PictureBoxSizeMode.StretchImage,
    PictureBoxSizeMode.Normal, 
    PictureBoxSizeMode.CenterImage,
    PictureBoxSizeMode.AutoSize
  };

Переменная selectedMode будет возвращать выбранное значение:

private int selectedMode = 0;

В обработчиках пунктов меню будем выбирать одно значение из массива:

private void mnuResize_Click(object sender, System.EventArgs e)
{
  if (sender is MenuItem)
  {
    MenuItem menuitem = (MenuItem)sender;
    selectedMode = menuitem.Index;
    pictureBox1.SizeMode = ArrayMenu[0];
    pictureBox1.Invalidate();
    
  }

}
private void mnuActual_Click(object sender, System.EventArgs e)
{
  if (sender is MenuItem)
  {
    MenuItem menuitem = (MenuItem)sender;
    selectedMode = menuitem.Index;
    
    pictureBox1.SizeMode = ArrayMenu[1];
    pictureBox1.Invalidate();
        
  }
}
private void mnuCenterImage_Click(object sender, System.EventArgs e)
{
  if (sender is MenuItem)
  {
    MenuItem menuitem = (MenuItem)sender;
    selectedMode = menuitem.Index;
  
    pictureBox1.SizeMode = ArrayMenu[2];
    pictureBox1.Invalidate();
        
  }
}
private void mnuAutoSize_Click(object sender, System.EventArgs e)
{
  if (sender is MenuItem)
  {
  MenuItem menuitem = (MenuItem)sender;
    selectedMode = menuitem.Index;
  
    pictureBox1.SizeMode = ArrayMenu[3];
    pictureBox1.Invalidate();
        
  }
  
}

В каждом обработчике мы добавили метод Invalidate для перерисовки содержимого pictureBox1 после выбора пункта меню. Удобно, когда выбранный пункт меню помечается галочкой (см. рис. 6.10). Переходим в режим дизайна, выделяем пункт меню Вид (View) и в окне Properties дважды щелкаем в поле события Popup:

private void mnuView_Popup(object sender, System.EventArgs e)
{
  if (sender is MenuItem) 
  {
    bool ImLoad  = (pictureBox1.Image != null);
    foreach (MenuItem menuitem in ((MenuItem)sender).MenuItems)
        {
          menuitem.Enabled = ImLoad;
          menuitem.Checked = (this.selectedMode == menuitem.Index);
        }
  }
    
}

В обработчиках пунктов контекстного меню дублируем код для соответствующих пунктов главного меню:

private void cmnuResize_Click(object sender, System.EventArgs e)
  {
    Вставляем скопированный код для  mnuResize_Click
  }

private void cmnuActual_Click(object sender, System.EventArgs e)
  {
    Вставляем скопированный код для  mnuActual _Click
  }

private void cmnuCenterImage_Click(object sender, System.EventArgs e)
  {
    Вставляем скопированный код для  mnuCenterImage _Click
  }

private void cmnuAutoSize_Click(object sender, System.EventArgs e)
  {
    Вставляем скопированный код для  mnuAutoSize _Click
  }

Запускаем приложение. Оно должно походить на приложение, изображенное на рис. 6.10. Добавим теперь к нашему приложению строку состояния, на которую будет выводиться размер загруженного изображения и его адрес. Из окна ToolBox перетаскиваем на форму элемент StatusBar, щелкаем в поле его свойства Panels и в открывшемся окне StatusBarPanel Collection Edito r добавляем панели sbFile и sbSize (рис. 6.11 и рис. 6.12).

(рис 6.12) Панель sbFile. Свойству Width панели установлено значение 500 пикселей(рис 6.11) Панель sbSize

Свойству ShowPanels элемента statusBar1 устанавливаем значение True. В обработчике пункта меню "Открыть" — код для вывода на панель sbFile адреса файла, а на панель sbSize — его размер:

private void mnuOpen_Click(object sender, System.EventArgs e)
  {
    …
    
    //Строка для вывода   во время загрузки изображения
    sbFile.Text  = "Загрузка " + diag.FileName;
    //Строка для вывода после загрузки изображения    
sbFile.Text  = "Изображение " + diag.FileName;
//Вывод ширины (Width) и высоты (Height) изображения
  sbSize.Text= String.Format("{0:#} x {1:#}", pictureBox1.Image.Width, pictureBox1.Image.Height);
  }

Запускаем и тестируем приложение (рис. 6.13).

(рис 6.13) Строка состояния приложения Picture Viewer

Печать содержимого PictureBox

Теперь займемся печатью изображений, помещенных в PictureBox. Добавляем на форму элементы PrintDocument, PageSetupDialog, PrintPreviewDialog и PrintDialog. На этот раз мы не будем настраивать визуально свойства этих элементов. Создаем обработчиков пунктов меню, в которых просто вызываем диалоговые окна:

private void mnuPageSetup_Click(object sender, System.EventArgs e)
  {
    PageSetupDialog diag = new PageSetupDialog();
    diag.Document = printDocument1;
    diag.ShowDialog();
  }
  private void mnuPreview_Click(object sender, System.EventArgs e)
  {
    PrintPreviewDialog diag = new PrintPreviewDialog();
    diag.Document = printDocument1;
    diag.ShowDialog();
  }
  private void mnuPrint_Click(object sender, System.EventArgs e)
  {
    PrintDialog diag = new PrintDialog();
    diag.Document = printDocument1;
    if (diag.ShowDialog() == DialogResult.OK)
    {
      printDocument1.Print();
    }
    
  }

Обратите внимание на свойство диалоговых окон Document — мы программно устанавливаем значение printDocument1. Переключаемся в режим дизайна, выделяем объект printDocument1 и в окне Properties дважды щелкаем в поле события PrintPage:

private void printDocument1_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
    {
}

Дальнейший код будет относиться к этому обработчику. Проверяем наличие изображения в pictureBox1:

if (pictureBox1.Image == null)
  {
    e.Cancel = true;
    return;
  }

Определяем печатную область страницы:

float leftMargin = e.MarginBounds.Left;
float rightMargin = e.MarginBounds.Right;
float topMargin = e.MarginBounds.Top;
float bottomMargin= e.MarginBounds.Bottom;
float printableWidth = e.MarginBounds.Width;
float printableHeight = e.MarginBounds.Height;

Cоздаем экземпляр graph класса Graphics:

Graphics graph = e.Graphics;

В документе, формируемом для печати, не содержится текста — ведь мы имеем дело с изображением. Но интересно будет выводить на печать строку, указывающую, к примеру, размер изображения. Для этого создадим экземпляр font класса Font:

Font font= new Font("Comic Sans MS", 16); 
//Определяем высоту шрифта
float fontHeight = font.GetHeight(graph);
//Определяем размер пробелов 
float spaceWidth = graph.MeasureString(" ", font).Width;

Определяем область, в которую будет вписываться изображение; размер наибольшей стороны изображения составляет 90% от кратчайшей стороны листа:

float imageLength;
float Xposition = leftMargin;
float Yposition = topMargin + fontHeight;
if (printableWidth < printableHeight)
  {
    imageLength = printableWidth * 90/100;
    Yposition += imageLength;
  }
  else
  {
    imageLength = printableHeight * 90/100;
    Xposition += imageLength + spaceWidth;
  }

Выводим изображение в области rectImage:

Rectangle rectImage= new Rectangle((int)leftMargin + 1, 
(int)topMargin + 1, (int)imageLength,(int)imageLength);
graph.DrawImage(pictureBox1.Image,(int)leftMargin + 1,  
(int)topMargin + 1, (int)imageLength,(int)imageLength);

Определяем область rectText и выводим в нее строку с указанием размера файла (используется метод PrintTextString ):

RectangleF rectText = new RectangleF(Xposition, Yposition, rightMargin -
- Xposition,  bottomMargin - Yposition);
PrintText (graph, font,"Размер изображения: ", Convert.ToString
(pictureBox1.Image.Size), ref rectText);

}

На этом код метода printDocument1_PrintPage заканчивается. Для вывода изображения все готово. Остается только написать метод PrintText, который будет выводить текстовую строку. Привожу код этого метода с комментариями:

protected void PrintText( Graphics graph,   Font font,  string name,  string text,  ref RectangleF rectText)
  {
    // Определяем размеры печатной области для текста:
    float leftMargin = rectText.Left;
    float rightMargin = rectText.Right;
    float topMargin = rectText.Top;
    float bottomMargin = rectText.Bottom;
    //Определяем высоту текста и координаты, где он будет выводиться:
    float fontHeight = font.GetHeight(graph);
    float Xposition = rectText.Left;
    float Yposition = topMargin + fontHeight;
    //Определяем ширину текста и размер пробелов
    float spaceWidth = graph.MeasureString(" ",   font).Width;
    float nameWidth  = graph.MeasureString(name, font).Width;
    graph.DrawString(name, font,
    Brushes.Black, new PointF(Xposition, Yposition));
    leftMargin += nameWidth + spaceWidth;
    Xposition = leftMargin;
    // Формируем несколько строк для текста  в случае,
    // если он не будет умещаться на одной строке
    string[] words
      = text.Split(" \r\t\n\0".ToCharArray());
    foreach (string word in words)
    {
      float wordWidth = graph.MeasureString(
        word, font).Width;
      if (wordWidth == 0.0)
        continue;
      if (Xposition + wordWidth > rightMargin)
      {
        // Начало с новой строки
        Xposition = leftMargin;
        Yposition += fontHeight;
        if (Yposition > bottomMargin)
        {            
          break;
        }
      }
      graph.DrawString(word, font,Brushes.Black,  new PointF(Xposition, Yposition));
      Xposition += wordWidth;
    }
    // Исключаем область, на которую был выведен текст, из области печати 
    //для избежания наложения текста и рисунка
    rectText.Y = Yposition;
    rectText.Height = bottomMargin - Yposition;
  }

Запускаем приложение. При выборе книжной ориентации страницы рисунок вписывается с сохранением своих пропорций (рис. 6.14), затем выводится на печать (рис. 6.15).

(рис 6.15) Предварительный просмотр приложения Picture Viewer (рис 6.14) Вывод рисунка в окно виртуального принтера программы pdfFactoryPro

В конце лекции приводится полный листинг приложения Picture Viewer, а исходный проект имеется на диске, прилагаемом к книге (Code\Glava6\ Picture Viewer).

Рисование в Windows-формах

Библиотека .NET Framework предоставляет чрезвычайно большой набор классов для реализации рисования в Windows-формах. Кривые Безье, текстурные кисти и перья, трансформация изображения, всевозможные виды градиентной заливки, слои, — практически все атрибуты крупного графического пакета могут быть воссозданы на одном из языков .NET Framework. Для рассмотрения этих методов и свойств потребовалась бы отдельная книга, поэтому мы ограничимся простым приложением, позволяющим рисовать линиями различного цвета и толщины. Создайте новое приложение и назовите его EasyPaint. Устанавливаем следующие значения свойств формы:

Form1, форма, свойствоЗначение
BackColor ActiveCaptionText
Icon Code\Glava6\EasyPaint\Icon\rc_bitmap.ico
Text EasyPaint

Добавляем на форму элементы управления MainMenu и ColorDialog. Создаем следующие пункты меню:

NameText
mnuFormat Формат
mnuColor Цвет
mnuWidth Толщина линии

Как обычно, рисовать мы будем при удержании и перемещении левой кнопки мыши. В классе формы добавляем объекты:

//Переменная, отвечающая за включения режима рисования.
//Значение true — режим включен
bool DrawMode = false;
//Создаем массив точек, из которых будут формироваться линии
ArrayList points = new ArrayList();

Для рисования линий и кривых в библиотеки .NET Framework имеется класс Pen, экземпляр которого нам и нужно создать:

Pen mainPen;

В графических программах принято в рабочей области изменять курсор — пользователю удобнее рисовать, когда он имеет вид карандаша. Можно просто установить значение из перечисления свойства Cursor формы (рис. 6.16), но как быть, если нужен вид, не входящий в это перечисление?

(рис 6.16) Свойство Cursor формы

Самый простой способ добавить произвольный курсор к приложению — это поместить изображение курсора (в данном случае, Pencil.cur) в папку bin\Debug приложения и в конструкторе формы указать этот курсор в качестве основного:

public Form1()
  {
    InitializeComponent();
    this.Cursor = new Cursor("Pencil.cur");
    mainPen = new Pen(this.ForeColor);
  }

Во второй строчке мы также устанавливаем цвет созданного пера mainPen. Переключаемся в режим дизайна, в окне Properties в событиях формы дважды щелкаем в поле событий MouseDown, MouseMove, MouseUp:

//Событие возникает при нажатии на кнопку мыши.
  private void Form1_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
  {
    //Если нажата левая кнопка мыши
    if(e.Button==MouseButtons.Left)
    {
      //Включаем режим рисования
      DrawMode = true;
      //Удаляем все элементы из массива points
      points.Clear();
    }
  
  }
//Событие возникает при перемещении мыши
  private void Form1_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
  {
    //Если включен режим рисования
    if(DrawMode)
    {
      //Создаем экземпляр pt класса Point,
      //представляющий собой точку с координатами мыши в данный момент
      Point pt = new Point(e.X, e.Y);
      //Добавляем созданную точку в массив points
      points.Add(pt);
      if (points.Count<=1) return;
      //Создаем новый массив pts, содержащий число  точек в массиве points
      Point [] pts = new Point [ points.Count];
      //Преобразуем массив points в массив pts, начиная с первого элемента
      points.CopyTo(pts, 0);
      //Создаем экземпляр graph класса Graphics для рисования
      Graphics graph = this.CreateGraphics();
      //Вызываем метод DrawCurve для рисования кривой, которому 
      //передаем перо mainPen и массив pts
      graph.DrawCurve(mainPen, pts);
    
    }
  
  }
//Событие возникает при отпускании кнопки мыши
  private void Form1_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
  {
    //Отключаем режим рисования.
    DrawMode = false;
  }

Логика работы предельно проста — при нажатии левой кнопки мыши включается режим рисования; при ее перемещении формируется массив точек с текущими координатами мыши, массив изображений точек pts затем передается методу DrawCurve, который и рисует линию. При отпускании кнопки режим рисования отключается.

В обработчике пункта меню Color при выборе цвета будет создаваться новое перо:

private void mnuColor_Click(object sender, System.EventArgs e)
    {
      if(colorDialog1.ShowDialog()==DialogResult.OK)
      {
        mainPen = new Pen(colorDialog1.Color);
      }
    }

Запускаем приложение. Вызывая диалоговое окно выбора цвета, можно рисовать разными цветами на форме (рис. 6.17).

(рис 6.17) Приложение EasyPaint. Курсор имеет точно такой же вид, как и в редакторе Microsoft Paint

Диалоговое окно выбора ширины линии. Элемент управления TrackBar

Займемся теперь линией для рисования — создадим собственное диалоговое окно, позволяющее устанавливать ее ширину. В окне Solution Explorer щелкаем правой кнопкой на имени проекта и в появившемся контекстном меню выбираем Add/Add Windows Form… . Называем новую форму LineWidth.cs, из окна Toolbox перетаскиваем на нее две кнопки, надпись, панель, элементы NumericUpDown , TrackBar (рис. 6.18) и устанавливаем следующие значения свойств:

button1, свойствоЗначение
Name btnOK
Location 32; 224
Text OK
button2, свойствоЗначение
Name btnCancel
Location 136; 224
Text Отмена
LineWidth, форма, свойствоЗначение
Name LineWidth
AcceptButton BtnOK
CancelButton BtnCancel
FormBorderStyle FixedDialog
MaximizeBox False
MinimizeBox False
Size 254; 286
Text Толщина линии
label1, свойствоЗначение
Location 8; 136
Text Толщина
numericUpDown1, свойствоЗначение
DecimalPlaces 1
Increment 0,1
Location 120; 136
Minimum 1
Value 1
trackBar1, свойствоЗначение
DecimalPlaces 1
Location 16; 168
Maximum 100
Minimum 1
Size 224; 45
TickFrequency 5
Value 5
(рис 6.18) Форма LineWidth в режиме дизайна

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

private void DrawLine()
    {
      //Создаем переменную, которая равна ординате точки, 
      //делящий элемент panel1 по высоте пополам
      int y = panel1.Bottom/2;
      //Создаем точку point1 лежащей на левой границе панели, c ординатой y
      Point point1 = new Point(panel1.Left,y);
      //Создаем точку point2 лежащей на правой  границе панели, c ординатой y
      Point point2 = new Point(panel1.Right,y);
      //Создаем экземпляр graph класса Graphics для рисования
      Graphics graph = panel1.CreateGraphics();
      //Определяем толщину линии, равную значению в элементе numericUpDown1
      float f = Decimal.ToSingle(numericUpDown1.Value);
      //Создаем экземпляр pen для рисования линии 
      Pen pen = new Pen (Color. Blue, f);
      //Соединяем точки point1 и point2
      graph.DrawLine(pen, point1, point2);
    }

Создаем свойство PenWidth, которое будет возвращать значение толщины для пера главной формы:

public float PenWidth
    {
      get
      {
        float f = Decimal.ToSingle(numericUpDown1.Value);
        return f;
      }
    }

При вызове диалогового окна на панели сразу будет появляться эскиз линии по умолчанию, для этого создаем обработчик события Paint формы, в котором вызываем метод DrawLine:

private void LineWidth_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
    {
      DrawLine();
    }

Связываем перемещение ползунка элемента trackBar1 со значением в поле элемента numericUpDown1, для чего в обработчике события Scroll вводим переменную val, связывающую свойства Value этих элементов:

private void trackBar1_Scroll(object sender, System.EventArgs e)
    {
      int val = trackBar1.Value;
      numericUpDown1.Value = val;
    }

Аналогично, для связывания значения в поле элемента numericUpDown1 с перемещением ползунка в обработчике события ValueChanged вводим переменную val2:

private void numericUpDown1_ValueChanged(object sender, System.EventArgs e)
    {
      int val2 = (int)numericUpDown1.Value;
      trackBar1.Value = val2;
      DrawLine();
    }

В этом обработчике мы также вызываем метод DrawLine для вывода эскиза линии на форме. Последнее, что нам осталось сделать, — добавить обработчик в пункте меню "Толщина линии" главной формы:

private void mnuWidth_Click(object sender, System.EventArgs e)
    {
      LineWidth diag = new LineWidth();
      if (diag.ShowDialog(this)== DialogResult.OK)
      {
        mainPen = new Pen(mainPen.Color, diag.PenWidth);
      }
    }

Запускаем приложение. Теперь можно рисовать не только разными цветами, но и разной толщиной пера (рис. 6.19).

(рис 6.19) Приложение EasyPaint и его диалоговое окно "Толщина линии"

На диске, прилагаемом к книге, вы найдете приложение EasyPaint (Code\Glava6\ EasyPaint).

Автоматическое преобразование размера и прокручивание изображения

При создании приложения Picture Viewer нашей задачей была печать содержимого PictureBox, и мы не слишком обращали внимание на внешний вид изображения, помещаемого в него. Между тем, единственным способом просмотра изображения полностью был выбор режима "Подогнать размер" (StretchImage), который приводил к довольно существенным искажениям (рис. 6.20).

(рис 6.20) Помещение рисунка в PictureBox без сохранения его пропорций (наверху, приложение Picture Viewer) и с сохранением (внизу)</p>

Кроме того, при выборе режима "Истинный размер" (Normal) на форме не появлялись полосы прокрутки, позволяющие просматривать изображение по частям (рис. 6.21).

(рис 6.21) При выходе изображения за границы формы необходимы полосы прокрутки

Конечно, вписанное изображение можно было просматривать, изменяя размер самой формы, но этот способ вряд ли будет устраивать пользователей. Займемся разработкой приложения, обладающего более удобным интерфейсом. Далее я снова местами буду пользоваться транслитом и называть режим S croll Prokrutkoy: дело в том, что мы научимся создавать собственные свойства пользовательского элемента управления, и мне хочется, чтобы они бросались в глаза. Запускаем Visual Studio .NET, создаем новый проект, выбираем шаблон Windows Control Library и называем его PictureElement (рис. 6.22):

(рис 6.22) Создание проекта пользовательского элемента управления

В окне Properties появившейся формы в поле свойства Name вводим NamePictureElement. В окне Solution Explorer изменяем название UserControl1.cs на SolPictureElement.cs. Добавляем на форму элемент управления PictureBox — его размеры будут достаточно маленькими, но это не важно, в дальнейшем мы изменим его размер так, как будет нужно (рис. 6.23).

(рис 6.23) Элемент управления PictureElement в режиме дизайна

Свойству SizeMode элемента управления PictureBox устанавливаем значение StretchImage. Переходим в код формы, в пространстве имен PictureElement создаем перечисление RejimProsmotra:

public enum RejimProsmotra
  {
    Prokrutka,
    PodgonRazmera
  }

В классе формы создаем переменную prosmotrperemen, которая будет определять режим просмотра:

private RejimProsmotra prosmotrperemen;

Создаем свойство Izobrajenie, с помощью которого можно будет помещать изображение в PictureBox элемента:

public Image Izobrajenie
    {
      get{return this.pictureBox1.Image;}
      set
      {
        this.pictureBox1.Image = value;
//Вызываем метод для установки режима просмотра
        this.UstanovkaRejima();
        
      }
    }

Создаем свойство UserPropRejimProsmotra, в котором переменной prosmotrperemen устанавливается значение выбранного режима просмотра:

public RejimProsmotra UserPropRejimProsmotra
    {
      get{return this.prosmotrperemen;}
      set
      {            
        this.prosmotrperemen = value;        
        this.AutoScroll = (this.prosmotrperemen == RejimProsmotra.Prokrutka );    
        //Вызываем метод для установки режима просмотра
        this.UstanovkaRejima();        
      }
    }

В методе PodgonRazmera определяются различные возможные случаи отношений размеров изображения, PictureBox и самого элемента:

private void PodgonRazmera()
    {
      //Определяем переменную ProporciiElementa, которая равна 
      //отношению ширины элемента к его высоте
      float ProporciiElementa = (float)this.Width/this.Height;
      //Определяем переменную ProporciiIzobrajeniya, которая равна 
      //отношению ширины загруженного изображения к его высоте
float ProporciiIzobrajeniya = (float)this.pictureBox1.Image.Width/this.pictureBox1.Image.Height;
      //Если ширина элемента  больше или равна ширине изображения 
      //и высота больше или равна высоте изображения, устанавливаем значения ширины 
      //и высоты pictureBox1 равными ширине и высоте изображения
if ( this.Width >= this.pictureBox1.Image.Width  this.Height >= this.pictureBox1.Image.Height )
      {        
        this.pictureBox1.Width = this.pictureBox1.Image.Width;
        this.pictureBox1.Height = this.pictureBox1.Image.Height;        
      }
      //Иначе, если ширина элемента  больше ширины изображения 
      //и высота меньше высоты  изображения, устанавливаем значение высоты 
      // pictureBox1, равное  высоте  элемента, а  значение ширины — равное значению 
      //ширины изображения 
else if( this.Width > this.pictureBox1.Image.Width  this.Height < this.pictureBox1.Image.Height)
      {
        this.pictureBox1.Height = this.Height;
        this.pictureBox1.Width = (int)(this.Height * ProporciiIzobrajeniya);      
      }
      //Иначе, если ширина элемента  меньше  ширины изображения 
      //и высота больше  высоты  изображения, устанавливаем значение ширины 
      // pictureBox1, равное  ширине  элемента, а  значение высоты — равное значению 
      //высоты изображения 
else if( this.Width < this.pictureBox1.Image.Width  this.Height > this.pictureBox1.Image.Height)
      {        
        this.pictureBox1.Width = this.Width;
        this.pictureBox1.Height = (int)(this.Width / ProporciiIzobrajeniya);                  
      }
      //Иначе, если ширина элемента  меньше  ширины изображения 
      //и высота меньше  высоты  изображения
else if ( this.Width < this.pictureBox1.Image.Width  this.Height < this.pictureBox1.Image.Height )
      {
        //если ширина элемента больше или равна его высоте
        if (this.Width >= this.Height )
        {
      //Если ширина изображения больше или равна его высоте и переменная 
      //ProporciiIzobrajeniya больше или равна переменной ProporciiElementa,
      //устанавливаем значение ширины pictureBox1, равное значению ширины элемента,
      //а значение высоты — равное значению высоты изображения
if ( this.pictureBox1.Image.Width >= this.pictureBox1.Image.Height  ProporciiIzobrajeniya >= ProporciiElementa )
          {
            this.pictureBox1.Width = this.Width;
            this.pictureBox1.Height = (int)(this.Width / ProporciiIzobrajeniya);
          }
      //Иначе, устанавливаем значение высоты  pictureBox1, равное значению
      //высоты элемента, а значение ширины — равное значению ширины изображения
          else
          {              
            this.pictureBox1.Height = this.Height;
this.pictureBox1.Width = (int)(this.Height * ProporciiIzobrajeniya);
          }          
        }
      //Иначе
        else
        {
      //Если ширина изображения меньше  его высоты и переменная 
      //ProporciiIzobrajeniya меньше переменной ProporciiElementa,
      //устанавливаем значение высоты  pictureBox1, равное значению высоты элемента,
      //а значение ширины — равное значению ширины изображения
if ( this.pictureBox1.Image.Width < this.pictureBox1.Image.Height  ProporciiIzobrajeniya < ProporciiElementa )
          {            
            this.pictureBox1.Height = this.Height;
this.pictureBox1.Width = (int)(this.Height * ProporciiIzobrajeniya);
          }
      //Иначе, устанавливаем значение ширины  pictureBox1, равное значению
      //ширины  элемента, а значение высоты — равное значению высоты изображения
          else 
          {                          
            this.pictureBox1.Width = this.Width;
            this.pictureBox1.Height = (int)(this.Width / ProporciiIzobrajeniya);            
          }
        }
      }    
      //Вызываем метод для размещения изображения в центре
      PomeshenieIzobrajeniyavCenter();
    }

В методе Prokrutka размеры PictureBox устанавливаются равными размерам загруженного изображения:

private void Prokrutka()
    {
    //Устанавливаем значение ширины pictureBox1, равное значению ширины изображения
      this.pictureBox1.Width = this.pictureBox1.Image.Width;
    //Устанавливаем значение высоты pictureBox1, равное значению высоты изображения
      this.pictureBox1.Height = this.pictureBox1.Image.Height;
    //Вызываем метод для установки режима просмотра
      this.PomeshenieIzobrajeniyavCenter();
    }

В методе UstanovkaRejima проверяется значение переменной prosmotrperemen и вызывается соответствующий метод:

private void UstanovkaRejima()
    {
      //если pictureBox1 не содержит изображения, возвращаемся назад
      if ( this.pictureBox1.Image == null )
        return;
      //Если значение переменной prosmotrperemen равно PodgonRazmera,
      //вызываем метод PodgonRazmera
      if ( this.prosmotrperemen == RejimProsmotra.PodgonRazmera )
        this.PodgonRazmera();
      //Иначе вызываем метод Prokrutka
      else
      {
        
        this.Prokrutka();  
        this.AutoScroll = true;
      
      }
    }

В методе PomeshenieIzobrajeniyavCenter определяется положение элемента pictureBox1 и, соответственно, положение изображения:

private void PomeshenieIzobrajeniyavCenter()
    {
      //Переменная top равна разнице между высотой элемента 
      //и высотой pictureBox1, деленной пополам
      int top = (int)((this.Height - this.pictureBox1.Height)/2.0);
      //Переменная left равна разнице между шириной  элемента 
      //и шириной pictureBox1, деленной пополам
      int left = (int)((this.Width - this.pictureBox1.Width)/2.0);
      if ( top < 0 )
        top = 0;
      if ( left > 0 )
        left = 0;
      this.pictureBox1.Top = top;
      this.pictureBox1.Left = left;    
    }

Переключаемся в режим дизайна, выделяем форму, в окне Properties переключаемся на события и дважды щелкаем в поле Resize и Load:

private void NamePictureElement_Resize(object sender, System.EventArgs e)
    {
    //При изменении размеров элемента вызывается метод для установки режима просмотра:
    this.UstanovkaRejima();
    }

private void NamePictureElement_Load(object sender, System.EventArgs e)
    {
      //при загрузке элемента устанавливаем нулевые ширину и высоту pictureBox1
      this.pictureBox1.Width = 0;
      this.pictureBox1.Height = 0;
      //Вызываем метод для установки режима просмотра
      this.UstanovkaRejima();
    }

Все! Компилируем проект, используя сочетания клавиш Ctrl+Shift+B, и закрываем его. Создаем новое Windows-приложение, которое называем AutoScroll_and_Constrain. В свойстве Text формы можно ввести надпись "Сохранение пропорций и прокрутка". Перетаскиваем на форму OpenFileDialog, а главное меню скопируем из приложения Picture Viewer, затем оставим следующие пункты:

mainMenu1, NameTextShortcut
mnuFile Файл
mnuOpen Открыть CtrlO
mnuView Вид
mnuResize Подогнать размер
mnuActual Истинный размер

Теперь нам нужно добавить созданный пользовательский элемент управления. В окне Toolbox щелкаем правой кнопкой на закладке Windows Forms и выбираем пункт Add/Remove Items… . В окне Customize Toolbox на вкладке .NET Framework Components нажимаем кнопку Browse и выбираем файл PictureElement.dll из папки bin/Debug проекта PictureElement. В результате в окне компонент появляется элемент NamePictureElement, принадлежащий пространству имен PictureElement (рис. 6.24).

(рис 6.24) Элемент NamePictureElement

Закрываем окно Customize Toolbox и перетаскиваем на форму появившейся элемент управления NamePictureElement. Обратите внимание на название этого элемента — не случайно при работе с проектом PictureElement мы дали проекту, форме и объекту в Solution Explorer три разных названия. На вкладке Toolbox элемент управления будет называться так же, как и форма пользовательского элемента в режиме дизайна (рис. 6.25).

(рис 6.25) Свойство Name формы будет названием пользовательского элемента управления

В окне Properties элемента namePictureElement1 появились два новых свойства, которые мы создали сами (рис. 6.26)!

(рис 6.26) Слева приведены фрагменты кода пользовательского элемента управления, на основании которых и были созданы свойства в окне Properties

Устанавливаем свойству Dock значение Fill, а свойству UserPropRejimProsmotra — значение PodgonRazmera. В обработчике пункта меню "Открыть" свойству Izobrajenie элемента загружаемое изображение:

private void mnuOpen_Click(object sender, System.EventArgs e)
{
  this.openFileDialog1.ShowDialog();
  string path = this.openFileDialog1.FileName;
  this.namePictureElement1.Izobrajenie = Image.FromFile(path);
}

В обработчиках пунктов меню изменения режима просмотра последовательно устанавливаем два значения свойства UserPropRejimProsmotra:

private void mnuResize_Click(object sender, System.EventArgs e)
  {
   this.namePictureElement1.UserPropRejimProsmotra = PictureElement.RejimProsmotra.PodgonRazmera;
  }

   private void mnuActual_Click(object sender, System.EventArgs e)
  {
   this.namePictureElement1.UserPropRejimProsmotra = PictureElement.RejimProsmotra.Prokrutka;
  }

Строка кода в обработчике mnuResize_Click скопирована из области Windows Form Designer generated code:

// 
// namePictureElement1
// 
this.namePictureElement1.Dock = System.Windows.Forms.DockStyle.Fill;
this.namePictureElement1.Izobrajenie = null;
…
this.namePictureElement1.UserPropRejimProsmotra = PictureElement.RejimProsmotra.PodgonRazmera;

Запускаем приложение. Два режима просмотра позволяют просматривать уменьшенное изображение с сохранением пропорций или его действительную часть с прокруткой (рис. 6.27).

(рис 6.27) Готовое приложение AutoScroll_and_Constrain

На диске, прилагаемом к книге, вы найдете приложения PictureElement и AutoScroll_and_Constrain (Code\Glava6\PictureElement и AutoScroll_and_Constrain ).

Полный листинг приложения TextEditor

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.IO;
using System.Drawing.Printing;

namespace TextEditor
{
  
  public class Form1 : System.Windows.Forms.Form
  {
    private System.Windows.Forms.OpenFileDialog openFileDialog1;
    private System.Windows.Forms.SaveFileDialog saveFileDialog1;
    private System.Windows.Forms.RichTextBox rtbText;
    private System.Windows.Forms.MainMenu mainMenu1;
    private System.Windows.Forms.MenuItem mnuFile;
    private System.Windows.Forms.MenuItem mnuOpen;
    private System.Windows.Forms.MenuItem mnuSave;
    private System.Windows.Forms.MenuItem menuItem1;
    private System.Windows.Forms.MenuItem mnuPageSetup;
    private System.Windows.Forms.MenuItem mnuPrintPreview;
    private System.Windows.Forms.MenuItem mnuPrint;
    private System.Windows.Forms.PageSetupDialog pageSetupDialog1;
    private System.Drawing.Printing.PrintDocument printDocument1;
    private System.Windows.Forms.PrintDialog printDialog1;
    private System.Windows.Forms.PrintPreviewDialog printPreviewDialog1;
    //Переменная для хранения текста для печати.
    //В нее мы будем помещать текст из RichTextBox
    string stringPrintText;
    //Переменная, определяющая номер страницы, с которой нужно начать печать
    int StartPage;
    //Переменная, определяющая количество страниц для печати:
    int NumPages;
    //Переменная, определяющая номер текущей страницы:
    int PageNumber;

    //    PrintDocument printDocument1 = new PrintDocument();
    //    PageSetupDialog  pageSetupDialog1 = new PageSetupDialog();
    //    PrintPreviewDialog printPreviewDialog1 = new PrintPreviewDialog();
    //    PrintDialog printDialog1 = new PrintDialog();

    /// <summary>
    /// Required designer variable.
    /// </summary>
    private System.ComponentModel.Container components = null;

    public Form1()
    {
      
      InitializeComponent();
      //      pageSetupDialog1.Document = printDocument1;
      //      printPreviewDialog1.Document = printDocument1;
      //      printDialog1.Document = printDocument1;
      //      printDialog1.AllowSomePages = true;
      //      printDialog1.AllowSelection = true;
      //
      //Определяем номер страницы, с которой следует начать печать
      printDialog1.PrinterSettings.FromPage = 1;
      //Определяем максимальный номер печатаемой страницы.
      printDialog1.PrinterSettings.ToPage = printDialog1.PrinterSettings.MaximumPage;

    
    }

  
    protected override void Dispose( bool disposing )
    {
      if( disposing )
      {
        if (components != null) 
        {
          components.Dispose();
        }
      }
      base.Dispose( disposing );
    }

    #region Windows Form Designer generated code
    /// <summary>
    /// Required method for Designer support — do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
      System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(Form1));
      this.openFileDialog1 = new System.Windows.Forms.OpenFileDialog();
      this.saveFileDialog1 = new System.Windows.Forms.SaveFileDialog();
      this.rtbText = new System.Windows.Forms.RichTextBox();
      this.mainMenu1 = new System.Windows.Forms.MainMenu();
      this.mnuFile = new System.Windows.Forms.MenuItem();
      this.mnuOpen = new System.Windows.Forms.MenuItem();
      this.mnuSave = new System.Windows.Forms.MenuItem();
      this.menuItem1 = new System.Windows.Forms.MenuItem();
      this.mnuPageSetup = new System.Windows.Forms.MenuItem();
      this.mnuPrintPreview = new System.Windows.Forms.MenuItem();
      this.mnuPrint = new System.Windows.Forms.MenuItem();
      this.pageSetupDialog1 = new System.Windows.Forms.PageSetupDialog();
      this.printDocument1 = new System.Drawing.Printing.PrintDocument();
      this.printDialog1 = new System.Windows.Forms.PrintDialog();
      this.printPreviewDialog1 = new System.Windows.Forms.PrintPreviewDialog();
      this.SuspendLayout();
      // 
      // openFileDialog1
      // 
      this.openFileDialog1.FileName = "Текстовые файлы";
      this.openFileDialog1.Filter = "Text Files (*.txt)|*.txt|All Files(*.*)|*.*  ";
      // 
      // saveFileDialog1
      // 
      this.saveFileDialog1.FileName = "Текстовые файлы";
      this.saveFileDialog1.Filter = "Text Files (*.txt)|*.txt|All Files(*.*)|*.*  ";
      // 
      // rtbText
      // 
      this.rtbText.Dock = System.Windows.Forms.DockStyle.Fill;
      this.rtbText.Location = new System.Drawing.Point(0, 0);
      this.rtbText.Name = "rtbText";
      this.rtbText.Size = new System.Drawing.Size(292, 266);
      this.rtbText.TabIndex = 1;
      this.rtbText.Text = "";
      // 
      // mainMenu1
      // 
      this.mainMenu1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
                                            this.mnuFile});
      // 
      // mnuFile
      // 
      this.mnuFile.Index = 0;
      this.mnuFile.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
                                          this.mnuOpen,
                                          this.mnuSave,
                                          this.menuItem1,
                                          this.mnuPageSetup,
                                          this.mnuPrintPreview,
                                          this.mnuPrint});
      this.mnuFile.Text = "Файл";
      // 
      // mnuOpen
      // 
      this.mnuOpen.Index = 0;
      this.mnuOpen.Shortcut = System.Windows.Forms.Shortcut.CtrlO;
      this.mnuOpen.Text = "Открыть";
      this.mnuOpen.Click += new System.EventHandler(this.mnuOpen_Click);
      // 
      // mnuSave
      // 
      this.mnuSave.Index = 1;
      this.mnuSave.Shortcut = System.Windows.Forms.Shortcut.CtrlS;
      this.mnuSave.Text = "Сохранить";
      // 
      // menuItem1
      // 
      this.menuItem1.Index = 2;
      this.menuItem1.Text = "-";
      // 
      // mnuPageSetup
      // 
      this.mnuPageSetup.Index = 3;
      this.mnuPageSetup.Text = "Параметры страницы";
      this.mnuPageSetup.Click += new System.EventHandler(this.mnuPageSetup_Click);
      // 
      // mnuPrintPreview
      // 
      this.mnuPrintPreview.Index = 4;
      this.mnuPrintPreview.Text = "Предварительный просмотр";
      this.mnuPrintPreview.Click += new System.EventHandler(this.mnuPrintPreview_Click);
      // 
      // mnuPrint
      // 
      this.mnuPrint.Index = 5;
      this.mnuPrint.Shortcut = System.Windows.Forms.Shortcut.CtrlP;
      this.mnuPrint.Text = "Печать";
      this.mnuPrint.Click += new System.EventHandler(this.mnuPrint_Click);
      // 
      // pageSetupDialog1
      // 
      this.pageSetupDialog1.Document = this.printDocument1;
      // 
      // printDocument1
      // 
      this.printDocument1.PrintPage += new System.Drawing.Printing.PrintPageEventHandler(this.printDocument1_PrintPage);
      // 
      // printDialog1
      // 
      this.printDialog1.AllowSelection = true;
      this.printDialog1.AllowSomePages = true;
      this.printDialog1.Document = this.printDocument1;
      // 
      // printPreviewDialog1
      // 
      this.printPreviewDialog1.AutoScrollMargin = new System.Drawing.Size(0, 0);
      this.printPreviewDialog1.AutoScrollMinSize = new System.Drawing.Size(0, 0);
      this.printPreviewDialog1.ClientSize = new System.Drawing.Size(400, 300);
      this.printPreviewDialog1.Document = this.printDocument1;
      this.printPreviewDialog1.Enabled = true;
      this.printPreviewDialog1.Icon = ((System.Drawing.Icon)(resources.GetObject("printPreviewDialog1.Icon")));
      this.printPreviewDialog1.Location = new System.Drawing.Point(17, 54);
      this.printPreviewDialog1.MinimumSize = new System.Drawing.Size(375, 250);
      this.printPreviewDialog1.Name = "printPreviewDialog1";
      this.printPreviewDialog1.TransparencyKey = System.Drawing.Color.Empty;
      this.printPreviewDialog1.Visible = false;
      // 
      // Form1
      // 
      this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
      this.ClientSize = new System.Drawing.Size(292, 266);
      this.Controls.Add(this.rtbText);
      this.Menu = this.mainMenu1;
      this.Name = "Form1";
      this.Text = "TextEditor";
      this.ResumeLayout(false);

    }
    #endregion

    
    [STAThread]
    static void Main() 
    {
      Application.Run(new Form1());
    }

    private void mnuOpen_Click(object sender, System.EventArgs e)
    {
      //Показываем диалог выбора файла
      openFileDialog1.ShowDialog() ;
      // Переменной fileName присваиваем имя открываемого файла
      string fileName = openFileDialog1.FileName;
      //Создаем поток fs и открываем файл для чтения.
      FileStream filestream= File.Open(fileName, FileMode.Open, FileAccess.Read);
      //Проверяем,  открыт ли поток,  и если открыт, выполняем условие
      if(filestream != null)
      {
        //Создаем объект streamreader и связываем его с потоком filestream
        StreamReader streamreader = new StreamReader(filestream /*System.Text.Encoding.Unicode*/);
        //Считываем весь файл и записываем его в TextBox
        rtbText.Text = streamreader.ReadToEnd();
        //Закрываем поток.
        filestream.Close();
      }
    }

    private void mnuSave_Click(object sender, System.EventArgs e)
    {
      //Показываем диалог выбора файла
      saveFileDialog1.ShowDialog();
      // В качестве имени сохраняемого файла устанавливаем переменную fileName
      string fileName=saveFileDialog1.FileName;
      //Создаем поток fs и открываем файл для записи.
      FileStream filestream = File.Open(fileName, FileMode.Create, FileAccess.Write);
        //Проверяем,  открыт ли поток,  и если открыт, выполняем условие
      if(filestream != null)
      {
        //Создаем объект streamwriter и связываем его с потоком filestream
        StreamWriter streamwriter = new StreamWriter(filestream);
        //Записываем данные из TextBox в файл
        streamwriter.Write(rtbText.Text);
        //Переносим данные из потока в файл
        streamwriter.Flush();
        //Закрываем поток
        filestream.Close();
      }
    }

    private void mnuPageSetup_Click(object sender, System.EventArgs e)
    {
      //Показываем диалог
      pageSetupDialog1.ShowDialog();
    }

    private void mnuPrintPreview_Click(object sender, System.EventArgs e)
    {
      //Инициализируем переменные
      printDocument1.DocumentName = Text;
      stringPrintText = rtbText.Text;
      StartPage = 1;
      NumPages = printDialog1.PrinterSettings.MaximumPage;
      PageNumber = 1;
      //Показываем диалог
      printPreviewDialog1.ShowDialog();
    }

    private void mnuPrint_Click(object sender, System.EventArgs e)
    {
      printDialog1.AllowSelection = rtbText.SelectionLength >0;

      if(printDialog1.ShowDialog()==DialogResult.OK)
      {
        printDocument1.DocumentName =Text;
        //Определяем диапазон страниц для печати
        switch(printDialog1.PrinterSettings.PrintRange)
        {
            //Выбраны все страницы
          case PrintRange.AllPages:
            stringPrintText = rtbText.Text;
            StartPage = 1;
            NumPages = printDialog1.PrinterSettings.MaximumPage;
            break;
            //Выбрана выделенная область
          case PrintRange.Selection:
            stringPrintText = rtbText.SelectedText;
            StartPage = 1;
            NumPages = printDialog1.PrinterSettings.MaximumPage;
            break;
            //Выбран ряд страниц
          case PrintRange.SomePages:
            stringPrintText = rtbText.Text;
            StartPage = printDialog1.PrinterSettings.FromPage;
            NumPages = printDialog1.PrinterSettings.ToPage - StartPage+1;
            break;
        }
        PageNumber = 1;
        //Вызываем встроенный метод для начала печати
        printDocument1.Print();
      }
      
    }
    private void printDocument1_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
    {
      //Создаем экземпляр graph класса Graphics
      Graphics graph = e.Graphics;
      //Создаем объект font, которому устанавливаем 
      // шрифт элемента rtbText
      Font font = rtbText.Font;
      //Получаем значение межстрочного интервала — высоту шрифта Т1, 134
      float HeightFont = font.GetHeight(graph);
      //Создаем экземпляр stringformat класса StringFormat для определения 
      //дополнительных параметров форматирования текста.
      StringFormat  stringformat = new StringFormat();
      //Создаем экземляры  rectanglefFull и rectanglefText класса RectangleF для 
      //определния областей печати и текста. Т1, 104
      RectangleF rectanglefFull, rectanglefText;
      //Создаем переменные для подсчета числа символов и строк.
      int NumberSymbols, NumberLines;
      //В качестве области печати устанавливаем объект rectanglefFull
      if (graph.VisibleClipBounds.X<0) rectanglefFull = e.MarginBounds;
      else
        //Определяем   объект  rectanglefFull
        rectanglefFull = new RectangleF(
          //Устанавливаем координату  X  
          e.MarginBounds.Left - (e.PageBounds.Width - graph.VisibleClipBounds.Width)/2, 
          //Устанавливаем координату  Y
          e.MarginBounds.Top - (e.PageBounds.Height - graph.VisibleClipBounds.Height)/2, 
          //Устанавливаем ширину области
          e.MarginBounds.Width,
          //Устанавливаем высоту области
          e.MarginBounds.Height);
      rectanglefText = RectangleF.Inflate(rectanglefFull, 0, -2*HeightFont);
      //Определяем число строк
      int NumDisplayLines = (int)Math.Floor(rectanglefText.Height/HeightFont);
      //Устанавливаем высоту области
      rectanglefText.Height = NumDisplayLines*HeightFont;
    
      if (rtbText.WordWrap)
      {
        stringformat.Trimming = StringTrimming.Word;
      }
      else
      {
        stringformat.Trimming = StringTrimming.EllipsisCharacter;
        stringformat.FormatFlags |=StringFormatFlags.NoWrap;
      }
      //При печати выбранных страниц переходим к первой стартовой странице
      while ((PageNumber<StartPage)(stringPrintText.Length>0))
      {
        if(rtbText.WordWrap)
          //Измеряем текстовые переменные, 
          //формирующие печать,  и возвращаем число символов NumberSymbols
          //и число строк NumberLines
          graph.MeasureString(stringPrintText, font, rectanglefText.Size, stringformat, out NumberSymbols, out NumberLines);
        else
          NumberSymbols = SymbolsInLines(stringPrintText, NumDisplayLines);
        stringPrintText = stringPrintText.Substring(NumberSymbols);
        //Увеличиваем число страниц 
        PageNumber++;
      }
      //Если длина строки stringPrintText равняется нулю (нет текста для печати),
      // останавливаем печать
      if (stringPrintText.Length==0)
      {
        e.Cancel = true;
        return;
      }
      //Выводим (рисуем) текст для печати — stringPrintText, используем для этого шрифт font,
      //кисть черного цвета  — Brushes.Black, область печати — rectanglefText,
      //передаем строку  дополнительного форматирования stringformat
      graph.DrawString(stringPrintText, font, Brushes.Black, rectanglefText, stringformat);
      //Получаем текст для следующей страницы
      if (rtbText.WordWrap)
        graph.MeasureString(stringPrintText, font, rectanglefText.Size, stringformat, out NumberSymbols, out NumberLines);
      else
        NumberSymbols = SymbolsInLines(stringPrintText, NumDisplayLines);
      stringPrintText = stringPrintText.Substring(NumberSymbols);
      //Очищаем объект stringformat, использованный для формирования полей.
      stringformat = new StringFormat();
      //Добавляем  вывод на каждую страницу ее номера
      stringformat.Alignment = StringAlignment.Far;
      graph.DrawString("Страница " + PageNumber, font, Brushes.Black, rectanglefFull, stringformat);
      PageNumber++;
      //Cнова проверяем, имеется ли текст для печати и номер страницы, заданной для печати
      e.HasMorePages=(stringPrintText.Length>0)(PageNumber<StartPage+NumPages);
      //Для печати из окна предварительного просмотра  снова инициализируем переменные
      if(!e.HasMorePages)
      {
        stringPrintText = rtbText.Text;
        StartPage = 1;
        NumPages = printDialog1.PrinterSettings.MaximumPage;
        PageNumber = 1;
      }

      
    }
    

    int SymbolsInLines(string stringPrintText, int NumLines)
    {
      int index = 0;
      for (int i = 0; i< NumLines; i++)
      {
        index = 1+ stringPrintText.IndexOf('\n', index);
        if(index==0)
          return stringPrintText.Length;
      }
      return index;
    }

    

    }
  }

Полный листинг приложения PictureViewer

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.IO;
using System.Drawing.Printing;

namespace PictureViewer
{
  /// <summary>
  /// Summary description for Form1.
  /// </summary>
  public class Form1 : System.Windows.Forms.Form
  {
    private System.Windows.Forms.MainMenu mainMenu1;
    private System.Windows.Forms.MenuItem mnuFile;
    private System.Windows.Forms.MenuItem mnuOpen;
    private System.Windows.Forms.MenuItem mnuSave;
    private System.Windows.Forms.MenuItem menuItem1;
    private System.Windows.Forms.MenuItem mnuPageSetup;
    private System.Windows.Forms.MenuItem mnuPreview;
    private System.Windows.Forms.MenuItem mnuPrint;
    private System.Windows.Forms.MenuItem menuItem5;
    private System.Windows.Forms.MenuItem mnuExit;
    private System.Windows.Forms.MenuItem mnuView;
    private System.Windows.Forms.MenuItem mnuResize;
    private System.Windows.Forms.OpenFileDialog openFileDialog1;
    private System.Windows.Forms.SaveFileDialog saveFileDialog1;
    private System.Windows.Forms.ContextMenu contextMenu1;
    private System.Windows.Forms.PictureBox pictureBox1;
    private System.Windows.Forms.MenuItem mnuActual;
    private System.Windows.Forms.MenuItem mnuCenterImage;
    private System.Windows.Forms.MenuItem mnuAutoSize;
    private System.Windows.Forms.MenuItem cmnuResize;
    private System.Windows.Forms.MenuItem cmnuActual;
    private System.Windows.Forms.MenuItem cmnuCenterImage;
    private System.Windows.Forms.MenuItem cmnuAutoSize;
    private System.Windows.Forms.StatusBar statusBar1;
    private System.Windows.Forms.StatusBarPanel sbFile;
    private System.Windows.Forms.StatusBarPanel sbSize;
    private System.Windows.Forms.PrintDialog printDialog1;
    private System.Windows.Forms.PrintPreviewDialog printPreviewDialog1;
    private System.Windows.Forms.PageSetupDialog pageSetupDialog1;
    private System.Drawing.Printing.PrintDocument printDocument1;
    
    private System.ComponentModel.Container components = null;

    public Form1()
    {
      InitializeComponent();
    }
  
  
    protected override void Dispose( bool disposing )
    {
      if( disposing )
      {
        if (components != null) 
        {
          components.Dispose();
        }
      }
      base.Dispose( disposing );
    }

    #region Windows Form Designer generated code
    /// <summary>
    /// Required method for Designer support — do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
      System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(Form1));
      this.mainMenu1 = new System.Windows.Forms.MainMenu();
      this.mnuFile = new System.Windows.Forms.MenuItem();
      this.mnuOpen = new System.Windows.Forms.MenuItem();
      this.mnuSave = new System.Windows.Forms.MenuItem();
      this.menuItem1 = new System.Windows.Forms.MenuItem();
      this.mnuPageSetup = new System.Windows.Forms.MenuItem();
      this.mnuPreview = new System.Windows.Forms.MenuItem();
      this.mnuPrint = new System.Windows.Forms.MenuItem();
      this.menuItem5 = new System.Windows.Forms.MenuItem();
      this.mnuExit = new System.Windows.Forms.MenuItem();
      this.mnuView = new System.Windows.Forms.MenuItem();
      this.mnuResize = new System.Windows.Forms.MenuItem();
      this.mnuActual = new System.Windows.Forms.MenuItem();
      this.mnuCenterImage = new System.Windows.Forms.MenuItem();
      this.mnuAutoSize = new System.Windows.Forms.MenuItem();
      this.openFileDialog1 = new System.Windows.Forms.OpenFileDialog();
      this.saveFileDialog1 = new System.Windows.Forms.SaveFileDialog();
      this.contextMenu1 = new System.Windows.Forms.ContextMenu();
      this.cmnuResize = new System.Windows.Forms.MenuItem();
      this.cmnuActual = new System.Windows.Forms.MenuItem();
      this.cmnuCenterImage = new System.Windows.Forms.MenuItem();
      this.cmnuAutoSize = new System.Windows.Forms.MenuItem();
      this.pictureBox1 = new System.Windows.Forms.PictureBox();
      this.statusBar1 = new System.Windows.Forms.StatusBar();
      this.sbFile = new System.Windows.Forms.StatusBarPanel();
      this.sbSize = new System.Windows.Forms.StatusBarPanel();
      this.printDialog1 = new System.Windows.Forms.PrintDialog();
      this.printPreviewDialog1 = new System.Windows.Forms.PrintPreviewDialog();
      this.pageSetupDialog1 = new System.Windows.Forms.PageSetupDialog();
      this.printDocument1 = new System.Drawing.Printing.PrintDocument();
      ((System.ComponentModel.ISupportInitialize)(this.sbFile)).BeginInit();
      ((System.ComponentModel.ISupportInitialize)(this.sbSize)).BeginInit();
      this.SuspendLayout();
      // 
      // mainMenu1
      // 
      this.mainMenu1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
                                            this.mnuFile,
                                            this.mnuView});
      // 
      // mnuFile
      // 
      this.mnuFile.Index = 0;
      this.mnuFile.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
                                          this.mnuOpen,
                                          this.mnuSave,
                                          this.menuItem1,
                                          this.mnuPageSetup,
                                          this.mnuPreview,
                                          this.mnuPrint,
                                          this.menuItem5,
                                          this.mnuExit});
      this.mnuFile.Text = "Файл";
      // 
      // mnuOpen
      // 
      this.mnuOpen.Index = 0;
      this.mnuOpen.Shortcut = System.Windows.Forms.Shortcut.CtrlO;
      this.mnuOpen.Text = "Открыть";
      this.mnuOpen.Click += new System.EventHandler(this.mnuOpen_Click);
      // 
      // mnuSave
      // 
      this.mnuSave.Index = 1;
      this.mnuSave.Shortcut = System.Windows.Forms.Shortcut.CtrlS;
      this.mnuSave.Text = "Сохранить";
      this.mnuSave.Click += new System.EventHandler(this.mnuSave_Click);
      // 
      // menuItem1
      // 
      this.menuItem1.Index = 2;
      this.menuItem1.Text = "-";
      // 
      // mnuPageSetup
      // 
      this.mnuPageSetup.Index = 3;
      this.mnuPageSetup.Text = "Page Setup";
      this.mnuPageSetup.Click += new System.EventHandler(this.mnuPageSetup_Click);
      // 
      // mnuPreview
      // 
      this.mnuPreview.Index = 4;
      this.mnuPreview.Text = "Print Preview";
      this.mnuPreview.Click += new System.EventHandler(this.mnuPreview_Click);
      // 
      // mnuPrint
      // 
      this.mnuPrint.Index = 5;
      this.mnuPrint.Shortcut = System.Windows.Forms.Shortcut.CtrlP;
      this.mnuPrint.Text = "Print";
      this.mnuPrint.Click += new System.EventHandler(this.mnuPrint_Click);
      // 
      // menuItem5
      // 
      this.menuItem5.Index = 6;
      this.menuItem5.Text = "-";
      // 
      // mnuExit
      // 
      this.mnuExit.Index = 7;
      this.mnuExit.Shortcut = System.Windows.Forms.Shortcut.AltF4;
      this.mnuExit.Text = "Выход";
      this.mnuExit.Click += new System.EventHandler(this.mnuExit_Click);
      // 
      // mnuView
      // 
      this.mnuView.Index = 1;
      this.mnuView.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
                                          this.mnuResize,
                                          this.mnuActual,
                                          this.mnuCenterImage,
                                          this.mnuAutoSize});
      this.mnuView.Text = "Вид";
      this.mnuView.Popup += new System.EventHandler(this.mnuView_Popup);
      // 
      // mnuResize
      // 
      this.mnuResize.Index = 0;
      this.mnuResize.Text = "Подогнать размер";
      this.mnuResize.Click += new System.EventHandler(this.mnuResize_Click);
      // 
      // mnuActual
      // 
      this.mnuActual.Index = 1;
      this.mnuActual.Text = "Истинный размер";
      this.mnuActual.Click += new System.EventHandler(this.mnuActual_Click);
      // 
      // mnuCenterImage
      // 
      this.mnuCenterImage.Index = 2;
      this.mnuCenterImage.Text = "По центру";
      this.mnuCenterImage.Click += new System.EventHandler(this.mnuCenterImage_Click);
      // 
      // mnuAutoSize
      // 
      this.mnuAutoSize.Index = 3;
      this.mnuAutoSize.Text = "Автоматический размер";
      this.mnuAutoSize.Click += new System.EventHandler(this.mnuAutoSize_Click);
      // 
      // openFileDialog1
      // 
      this.openFileDialog1.Title = "Выбор изображения";
      // 
      // saveFileDialog1
      // 
      this.saveFileDialog1.Title = "Сохранение изображения";
      // 
      // contextMenu1
      // 
      this.contextMenu1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
                                             this.cmnuResize,
                                             this.cmnuActual,
                                             this.cmnuCenterImage,
                                             this.cmnuAutoSize});
      // 
      // cmnuResize
      // 
      this.cmnuResize.Index = 0;
      this.cmnuResize.Text = "Подогнать размер";
      this.cmnuResize.Click += new System.EventHandler(this.cmnuResize_Click);
      // 
      // cmnuActual
      // 
      this.cmnuActual.Index = 1;
      this.cmnuActual.Text = "Истинный размер";
      this.cmnuActual.Click += new System.EventHandler(this.cmnuActual_Click);
      // 
      // cmnuCenterImage
      // 
      this.cmnuCenterImage.Index = 2;
      this.cmnuCenterImage.Text = "По центру";
      this.cmnuCenterImage.Click += new System.EventHandler(this.cmnuCenterImage_Click);
      // 
      // cmnuAutoSize
      // 
      this.cmnuAutoSize.Index = 3;
      this.cmnuAutoSize.Text = "Автоматический размер";
      this.cmnuAutoSize.Click += new System.EventHandler(this.cmnuAutoSize_Click);
      // 
      // pictureBox1
      // 
      this.pictureBox1.ContextMenu = this.contextMenu1;
      this.pictureBox1.Dock = System.Windows.Forms.DockStyle.Fill;
      this.pictureBox1.Location = new System.Drawing.Point(0, 0);
      this.pictureBox1.Name = "pictureBox1";
      this.pictureBox1.Size = new System.Drawing.Size(616, 266);
      this.pictureBox1.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage;
      this.pictureBox1.TabIndex = 0;
      this.pictureBox1.TabStop = false;
      // 
      // statusBar1
      // 
      this.statusBar1.Location = new System.Drawing.Point(0, 244);
      this.statusBar1.Name = "statusBar1";
      this.statusBar1.Panels.AddRange(new System.Windows.Forms.StatusBarPanel[] {
                                              this.sbFile,
                                              this.sbSize});
      this.statusBar1.ShowPanels = true;
      this.statusBar1.Size = new System.Drawing.Size(616, 22);
      this.statusBar1.TabIndex = 3;
      // 
      // sbFile
      // 
      this.sbFile.Width = 500;
      // 
      // printPreviewDialog1
      // 
      this.printPreviewDialog1.AutoScrollMargin = new System.Drawing.Size(0, 0);
      this.printPreviewDialog1.AutoScrollMinSize = new System.Drawing.Size(0, 0);
      this.printPreviewDialog1.ClientSize = new System.Drawing.Size(400, 300);
      this.printPreviewDialog1.Enabled = true;
      this.printPreviewDialog1.Icon = ((System.Drawing.Icon)(resources.GetObject("printPreviewDialog1.Icon")));
      this.printPreviewDialog1.Location = new System.Drawing.Point(151, 59);
      this.printPreviewDialog1.MinimumSize = new System.Drawing.Size(375, 250);
      this.printPreviewDialog1.Name = "printPreviewDialog1";
      this.printPreviewDialog1.TransparencyKey = System.Drawing.Color.Empty;
      this.printPreviewDialog1.Visible = false;
      // 
      // printDocument1
      // 
      this.printDocument1.PrintPage += new System.Drawing.Printing.PrintPageEventHandler(this.printDocument1_PrintPage);
      // 
      // Form1
      // 
      this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
      this.ClientSize = new System.Drawing.Size(616, 266);
      this.ContextMenu = this.contextMenu1;
      this.Controls.Add(this.statusBar1);
      this.Controls.Add(this.pictureBox1);
      this.Menu = this.mainMenu1;
      this.Name = "Form1";
      this.Text = "Picture Viewer";
      ((System.ComponentModel.ISupportInitialize)(this.sbFile)).EndInit();
      ((System.ComponentModel.ISupportInitialize)(this.sbSize)).EndInit();
      this.ResumeLayout(false);

    }
    #endregion

    private PictureBoxSizeMode[] ArrayMenu =
    {
      PictureBoxSizeMode.StretchImage,
      PictureBoxSizeMode.Normal, 
      PictureBoxSizeMode.CenterImage,
      PictureBoxSizeMode.AutoSize
    };
    private int selectedMode = 0;
    [STAThread]
    static void Main() 
    {
      Application.Run(new Form1());
    }

    private void mnuOpen_Click(object sender, System.EventArgs e)
    {
      OpenFileDialog diag = new OpenFileDialog();
      diag.Filter = "jpg files (*.jpg)|*.jpg|All files (*.*)|*.*" ;
      if (diag.ShowDialog() == DialogResult.OK)
      {
        pictureBox1.Image = new Bitmap(diag.OpenFile());
      }
    
      //Строка для вывода   во время загрузки изображения
      sbFile.Text  = "Загрузка " + diag.FileName;
      //Строка для вывода после загрузки изображения
      sbFile.Text  = "Изображение " + diag.FileName;
      //Вывод ширины (Width) и высоты (Height) изображения
      sbSize.Text= String.Format("{0:#} x {1:#}",  pictureBox1.Image.Width, pictureBox1.Image.Height);
    }

    private void mnuSave_Click(object sender, System.EventArgs e)
    {
      SaveFileDialog diag = new SaveFileDialog();
      diag.Filter = "jpg files (*.jpg)|*.jpg|All files (*.*)|*.*" ;
      if(diag.ShowDialog() != DialogResult.OK)
        return;
      // Получаем адрес файла.
      string filename = diag.FileName;
      FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.ReadWrite);
      pictureBox1.Image.Save(fs,  System.Drawing.Imaging.ImageFormat.Jpeg);
      fs.Close();
    }

    private void mnuExit_Click(object sender, System.EventArgs e)
    {
      this.Close();
    }

    private void mnuResize_Click(object sender, System.EventArgs e)
    {
      if (sender is MenuItem)
      {
        MenuItem menuitem = (MenuItem)sender;
        selectedMode = menuitem.Index;
        pictureBox1.SizeMode = ArrayMenu[0];
        pictureBox1.Invalidate();
        
      }
    
    }

    private void mnuActual_Click(object sender, System.EventArgs e)
    {
      if (sender is MenuItem)
      {
        MenuItem menuitem = (MenuItem)sender;
        selectedMode = menuitem.Index;
      
        pictureBox1.SizeMode = ArrayMenu[1];
        pictureBox1.Invalidate();
        
      }
    }

    private void mnuView_Popup(object sender, System.EventArgs e)
    {
      if (sender is MenuItem) 
      {
        bool ImLoad  = (pictureBox1.Image != null);
        foreach (MenuItem menuitem in ((MenuItem)sender).MenuItems)
              {
                    menuitem.Enabled = ImLoad;
                    menuitem.Checked = (this.selectedMode == menuitem.Index);
              }
      }
      
    }

    private void mnuCenterImage_Click(object sender, System.EventArgs e)
    {
      if (sender is MenuItem)
      {
        MenuItem menuitem = (MenuItem)sender;
        selectedMode = menuitem.Index;
      
        pictureBox1.SizeMode = ArrayMenu[2];
        pictureBox1.Invalidate();
        
      }
    }

    private void mnuAutoSize_Click(object sender, System.EventArgs e)
    {
      if (sender is MenuItem)
      {
        MenuItem menuitem = (MenuItem)sender;
        selectedMode = menuitem.Index;
      
        pictureBox1.SizeMode = ArrayMenu[3];
        pictureBox1.Invalidate();
        
      }
    
    }

    private void cmnuResize_Click(object sender, System.EventArgs e)
    {
      if (sender is MenuItem)
      {
        MenuItem menuitem = (MenuItem)sender;
        selectedMode = menuitem.Index;
        pictureBox1.SizeMode = ArrayMenu[0];
        pictureBox1.Invalidate();
        
      }
      
      
    }

    private void cmnuActual_Click(object sender, System.EventArgs e)
    {
      if (sender is MenuItem)
      {
        MenuItem menuitem = (MenuItem)sender;
        selectedMode = menuitem.Index;
        pictureBox1.SizeMode = ArrayMenu[1];
        pictureBox1.Invalidate();
        
      }
    
    }

    private void cmnuCenterImage_Click(object sender, System.EventArgs e)
    {
      if (sender is MenuItem)
      {
        MenuItem menuitem = (MenuItem)sender;
        selectedMode = menuitem.Index;
        pictureBox1.SizeMode = ArrayMenu[2];
        pictureBox1.Invalidate();
        
      }
      
    }

    private void cmnuAutoSize_Click(object sender, System.EventArgs e)
    {
      if (sender is MenuItem)
      {
        MenuItem menuitem = (MenuItem)sender;
        selectedMode = menuitem.Index;
        pictureBox1.SizeMode = ArrayMenu[3];
        pictureBox1.Invalidate();
        
      }
      
    }

    private void mnuPageSetup_Click(object sender, System.EventArgs e)
    {
      PageSetupDialog diag
        = new PageSetupDialog();
      diag.Document = printDocument1;
      diag.ShowDialog();
    }

    private void mnuPreview_Click(object sender, System.EventArgs e)
    {
      PrintPreviewDialog diag
        = new PrintPreviewDialog();
      diag.Document = printDocument1;
      diag.ShowDialog();
    }

    private void mnuPrint_Click(object sender, System.EventArgs e)
    {
      PrintDialog diag = new PrintDialog();
      diag.Document = printDocument1;
      if (diag.ShowDialog() == DialogResult.OK)
      {
        printDocument1.Print();
      }
      
    }

    private void printDocument1_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
    {
      //Если в pictureBox1 не загружено изображение, возвращаемся назад
      if (pictureBox1.Image == null)
      {
        e.Cancel = true;
        return;
      }
      //Определяем печатную область страницы
      float leftMargin = e.MarginBounds.Left;
      float rightMargin = e.MarginBounds.Right;
      float topMargin = e.MarginBounds.Top;
      float bottomMargin= e.MarginBounds.Bottom;
      float printableWidth = e.MarginBounds.Width;
      float printableHeight = e.MarginBounds.Height;
      //Cоздаем экземпляр graph класса Graphics
      Graphics graph = e.Graphics;
      //Создаем экземпляр font  класса Font
      Font font= new Font("Comic Sans MS", 16);
      //Определяем высоту шрифта
      float fontHeight = font.GetHeight(graph);
      //Определяем размер пробелов 
      float spaceWidth = graph.MeasureString(" ", font).Width;
      //Определяем область, в которую будет вписываться изображение, 
      //размер наибольшей стороны изображения составляет 90% 
      //от кратчайшей стороны листа
      float imageLength;
      float Xposition = leftMargin;
      float Yposition = topMargin + fontHeight;
      if (printableWidth < printableHeight)
      {
        imageLength = printableWidth * 90/100;
        Yposition += imageLength;
      }
      else
      {
        imageLength = printableHeight * 90/100;
        Xposition += imageLength + spaceWidth;
      }
      // Выводим изображение в области rectImage
      Rectangle rectImage  = new Rectangle((int)leftMargin + 1,  (int)topMargin + 1,(int)imageLength,  (int)imageLength);
      graph.DrawImage(pictureBox1.Image,(int)leftMargin + 1,  (int)topMargin + 1, (int)imageLength,(int)imageLength);
      // Определяем область rectText и выводим в нее строку с размером файла
      RectangleF rectText  = new RectangleF(Xposition, Yposition, rightMargin - Xposition,  bottomMargin - Yposition);
      PrintText(graph, font,"Размер изображения: ", Convert.ToString(pictureBox1.Image.Size),   ref rectText);
    }
    protected void PrintText( Graphics graph,   Font font,  string name, string text,  ref RectangleF rectText)
    {
      // Определяем размеры печатной области для текста:
      float leftMargin = rectText.Left;
      float rightMargin = rectText.Right;
      float topMargin = rectText.Top;
      float bottomMargin = rectText.Bottom;
      //Определяем высоту текста и координаты, где он будет выводиться:
      float fontHeight = font.GetHeight(graph);
      float Xposition = rectText.Left;
      float Yposition = topMargin + fontHeight;
      //Определяем ширину текста и размер пробелов
      float spaceWidth = graph.MeasureString(" ",   font).Width;
      float nameWidth  = graph.MeasureString(name, font).Width;
      graph.DrawString(name, font,
      Brushes.Black, new PointF(Xposition, Yposition));
      leftMargin += nameWidth + spaceWidth;
      Xposition = leftMargin;
      // Формируем несколько строк для текста  в случае,
      // если он не будет умещаться на одной строке
      string[] words
        = text.Split(" \r\t\n\0".ToCharArray());
      foreach (string word in words)
      {
        float wordWidth = graph.MeasureString(
          word, font).Width;
        if (wordWidth == 0.0)
          continue;
        if (Xposition + wordWidth > rightMargin)
        {
          // Начало с новой строки
          Xposition = leftMargin;
          Yposition += fontHeight;
          if (Yposition > bottomMargin)
          {            
            break;
          }
        }
        graph.DrawString(word, font,Brushes.Black,  new PointF(Xposition, Yposition));
        Xposition += wordWidth;
      }
      // Исключаем область, на которую был выведен текст, из области печати 
      //для избежания наложения текста и рисунка
      rectText.Y = Yposition;
      rectText.Height = bottomMargin - Yposition;
  
    }
  }
}
Вернуться к учебному плану