C#: работаем с графикой и графическими компонентами в приложениях Windows Forms
Для работы с изображениями в библиотеке .NET определён базовый класс System.Drawing.Image
, который предоставляет функциональные возможности для производных классов System.Drawing.Bitmap
(растровая графика) и System.Drawing.MetaFile
(векторная графика). Этот класс содержит методы для создания (и сохранения) объектов типа Image
из указанного файла, потока данных и т.д.
Когда требуется перерисовка элемента управления, происходит событие Paint
, которое, в зависимости от задачи, можно как программировать явно, так и полагаться на его автоматический вызов, происходящий, когда изменилась графическая канва объекта.
Для отрисовки готового файла с изображением, имя которого задано или получено из диалога открытия файла OpenFileDialog
, мы должны создать или получить из аргумента PaintEventArgs
метода Paint
графическую канву типа System.Drawing.Graphics
а затем уже вывести на неё изображение.
Для использования готовых методов обработки изображений (поворот, масштабирование, изменение цвета и т.п.) мы программно создаём объект типа System.Drawing.Bitmap
, копирующий имеющееся изображение, выполняем его обработку, а затем выводим изменённый объект в компоненту, предназначенную для отображения, такую как PictureBox
.
Проект Lab5_1. Выведем выбранный в стандартном диалоге открытия файла рисунок на форму (пункт меню Файл - Открыть) и принудительно перерисуем по пункту меню Правка - Перерисовать. В свойствах диалога открытия файла openFileDialog1
указано Filter = Все файлы|*.*|Рисунки BMP|*.bmp|Рисунки JPEG|*.jpg|Рисунки PNG|*.png
а свойство FileName
равно пустой строке.
В классе формы пропишем объекты "Изображение" и "Имя файла":
private Image Img; private String Name;
Напишем обработчик открытия файла:
//Обработка меню Файл - Открыть openFileDialog1.ShowDialog(); Name = openFileDialog1.FileName.Trim(); if (String.IsNullOrEmpty(Name)) return; try { Img = new Bitmap(Name); //или так: Img = Image.FromFile(Name); } catch (Exception ex) { MessageBox.Show(ex.Message + Environment.NewLine + "(не могу открыть файл)", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); toolStripLabel1.Text = "Файл не выбран"; Img = null; return; } this.ClientSize = new System.Drawing.Size(Img.Width, Img.Height); //Размер формы подгогнали под размер картинки toolStripLabel1.Text = Name; //Имя файла вывели в панель инструментов this.Refresh(); //Потребовать перерисовки!
По событию Paint
формы будет выполняться отрисовка объекта Img
на текущей канве, Y-координата для вставки рисунка учитывает пространство, занимаемое по вертикали компонентами menuStrip1
и toolStripLabel1
:
private void Form1_Paint(object sender, PaintEventArgs e) { if (Img != null) { Point p = new Point(0, menuStrip1.Size.Height+ toolStripLabel1.Size.Height); e.Graphics.DrawImage(Img, p); } }
Объект «Графика», представляющий собой поверхность для рисования, также может быть получен для канвы формы (вот обработчик пункта меню Правка - Перерисовать):
if (Img != null) { Graphics g = this.CreateGraphics(); Point p = new Point(0, menuStrip1.Size.Height + toolStripLabel1.Size.Height); g.DrawImage(Img, p); }
или из загруженного (сгенерированного) изображения, например, см. код для пункта меню Файл - Создать:
Img = new Bitmap(200, 200, System.Drawing.Imaging.PixelFormat.Format24bppRgb); Graphics Gr = Graphics.FromImage(Img); // Теперь становятся доступными методы класса Graphics! Pen pen = new Pen(Color.ForestGreen, 4.0F); Gr.DrawLine(pen, 0, 0, 199, 199); Gr.RotateTransform(180.0F); //поворот на 180 градусов Img.Save("example.jpg", System.Drawing.Imaging.ImageFormat.Jpeg); //сохранение this.Refresh();
Обычно рисунки не отображают на канве формы, а работают с компонентом PictureBox
(вкладка Стандартные), представляющим собой контейнер для размещения рисунка, вот его основные свойства:
Image
- рисунок. Само изображение можно загрузить программно через свойствоImageLocation
, например, в методеLoad
формы:openFileDialog1.ShowDialog(); if (openFileDialog1.FileName != null) this.pictureBox1.ImageLocation = this.openFileDialog1.FileName;
или же присвоить этому свойству объектImage
, как мы делали выше.SizeMode
- режим вывода:Normal
- по левому верхнему углу контейнера с обрезанием,StretchImage
- вписать в компонент,AutoSize
- компонент примет размеры изображения,CenterImage
- центрировать, не меняя размеры (может обрезать рисунок),Zoom
- пропорционально масштабировать по размерам компонента (пространство именPictureBoxSizeMode
).
Для возможности прокрутки загруженного изображения достаточно разместить PictureBox
на элементе Panel
с установленным свойством AutoScroll = true
(и Dock = Fill
, если панель должна занимать всю клиентскую часть окна) и при этом для PictureBox
указать SizeMode = AutoSize
. После этого можно загрузить рисунок кодом вида
Image Img = new Bitmap(openFileDialog1.FileName); pictureBox1.Image = Img;
ErrorImage
- позволяет задать изображение, выводимое при ошибке загрузки;InitialImage
- позволяет задать изображение, выводимое в процессе загрузки.
К другим полезным компонентам можно отнести:
ImageList
(вкладка Компоненты) - список изображений, который можно использовать для "прокрутки" картинок или как список иконок для меню, вкладок и т.п.Timer
(вкладка Компоненты), позволяет обрабатывать периодическое событиеTick
и организовывать смену картинок в реальном времени, частота повторения события в миллисекундах задаётся свойствомInterval
.
Мы используем их в следующей теме.
Скачать пример Lab5_1 в архиве .zip с проектом C# Visual Studio 2019 (12 Кб)
Проект Lab5_2. Основные операции над изображениями. Кроме pictureBox1
, размещённого на Panel
как описано выше, форма включает в себя стандартный диалог открытия файла openFileDialog1
, меню Файл - Открыть (обработчик аналогичен предыдущему примеру) и меню "Правка", откуда мы будем вызывать обработчики загруженного изображения.
5.2.1. Поворот и отражение изображений. В этом примере выведенный в PictureBox
рисунок поворачивается на 180 градусов и выводится обратно в PictureBox
:
if (pictureBox1.Image != null) { Bitmap bitmap1 = new Bitmap(pictureBox1.Image); if (bitmap1 != null) { bitmap1.RotateFlip(RotateFlipType.Rotate180FlipY); pictureBox1.Image = bitmap1; } }
Остальные повороты (отражения) – другие значения параметра RotateFlipType
.
5.2.2. Масштабирование изображения или его части. Код ниже показывает, как можно программно уменьшить загруженное в компоненту PictureBox
изображение в 2 раза:
if (pictureBox1.Image == null) return; Bitmap bitmap1 = new Bitmap (pictureBox1.Image); //взяли рисунок из компоненты Graphics Gr1 = Graphics.FromImage (bitmap1); //получили поверхность рисования из исходного рисунка Bitmap bitmap2 = new Bitmap (bitmap1.Width / 2, bitmap1.Height / 2, Gr1); //сделали вдвое меньший рисунок с тем же разрешением Graphics Gr2 = Graphics.FromImage (bitmap2); //получили поверхность рисования из меньшего рисунка Rectangle compressionRectangle = new Rectangle (0, 0, bitmap1.Width / 2, bitmap1.Height / 2); //определили масштабирующий прямоугольник Gr2.DrawImage (bitmap1, compressionRectangle); //отрисовали на поверхности второго рисунка первый со сжатием Pen MyPen = new Pen (Color.Red); //на измененном рисунке можно что-то подрисовать Gr2.DrawRectangle (MyPen, 0, 0, bitmap2.Width - 1, bitmap2.Height - 1); //например, сделать красную рамку pictureBox1.Image = bitmap2; //назначили второй рисунок компоненте pictureBox1.Size = bitmap2.Size; //поставили размер компоненты по размерам нового рисунка this.ClientSize = pictureBox1.Size; //...и такой же размер клиентской формы
Добавим пункт меню Файл - Сохранить и сохраним изображение:
if (pictureBox1.Image == null) return; Bitmap bitmap1 = new Bitmap (pictureBox1.Image); try { bitmap1.Save (openFileDialog1.FileName); //Сохраняем под именем из диалога открытия файла //Может вызвать исключение, если исходный файл ещё открыт } catch (Exception ex) { MessageBox.Show (ex.Message + "\nНе удалось сохранить файл", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); return; }
Как вариант:
pictureBox1.Image.Save (openFileDialog1.FileName);
К сожалению, этот код может вызвать исключение, если исходный файл ещё открыт. Для "надёжных" операций с файлами при открытии изображений используйте FileStream
, чтобы контролировать весь процесс (перепишем обработчик пункта меню "Открыть"):
openFileDialog1.ShowDialog (); if (openFileDialog1.FileName.Trim () != "" && openFileDialog1.FileName != null) { System.IO.FileStream file; try { file = new System.IO.FileStream (openFileDialog1.FileName, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Inheritable); } catch (Exception ex) { MessageBox.Show (ex.Message + "\nНе удалось открыть файл", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); return; } pictureBox1.Image = Image.FromStream (file); file.Close (); }
5.2.3. Изменение цвета на изображении. На форму добавлен стандартный СolorDialog
и выбранный в нём цвет ставится прозрачным.
if (pictureBox1.Image == null) return; Bitmap bitmap1 = new Bitmap (pictureBox1.Image); if (colorDialog1.ShowDialog () == DialogResult.OK) { bitmap1.MakeTransparent (colorDialog1.Color); pictureBox1.Image = bitmap1; }
5.2.4. Фильтрация всего изображения или его части (по пикселям). В качестве примера уменьшим вдвое интенсивность синего цвета на картинке, избегая пикселей, цвет которых близок к белому (интенсивности красной, зелёной и синей компонент больше значения 250):
if (pictureBox1.Image == null) return; Bitmap bitmap1 = new Bitmap (pictureBox1.Image); for (int x = 0; x < bitmap1.Width; x++) for (int y = 0; y < bitmap1.Height; y++) { Color pixelColor = bitmap1.GetPixel (x, y); if (pixelColor.R > 250 && pixelColor.G > 250 && pixelColor.B > 250) continue; //не фильтруем пиксели, чей цвет близок к белому Color newColor = Color.FromArgb (pixelColor.R, pixelColor.G, pixelColor.B / 2); bitmap1.SetPixel (x, y, newColor); } pictureBox1.Image = bitmap1;
Аналогично можно реализовать любую другую фильтрацию цветов, но из-за «ручного» прохода по пикселям скорость выполнения процесса может быть заметно ниже, чем для пп. 5.2.1-5.2.3. Более быстрый способ преобразования всех цветов рисунка даёт применение фильтрации на основе класса ColorMatrix
. В качестве примера приведём код, преобразующий цветное изображение к оттенкам серого:
if (pictureBox1.Image == null) return; Bitmap bitmap1 = new Bitmap (pictureBox1.Image); Bitmap bitmap2 = new Bitmap (bitmap1.Width, bitmap1.Height); Graphics g = Graphics.FromImage (bitmap2); float [] [] Map = { new float[] {0.30f, 0.30f, 0.30f, 0.00f, 0.00f }, new float[] {0.59f, 0.59f, 0.59f, 0.00f, 0.00f }, new float[] {0.11f, 0.11f, 0.11f, 0.00f, 0.00f }, new float[] {0.00f, 0.00f, 0.00f, 1.00f, 0.00f }, new float[] {0.00f, 0.00f, 0.00f, 0.00f, 1.00f } }; System.Drawing.Imaging.ColorMatrix GrayscaleMatrix = new System.Drawing.Imaging.ColorMatrix (Map); System.Drawing.Imaging.ImageAttributes attributes = new System.Drawing.Imaging.ImageAttributes (); attributes.SetColorMatrix (GrayscaleMatrix); Rectangle rect = new Rectangle (0, 0, bitmap1.Width, bitmap1.Height); g.DrawImage (bitmap1, rect, 0, 0, bitmap1.Width, bitmap1.Height, GraphicsUnit.Pixel, attributes); pictureBox1.Image = bitmap2;
О классе ColorMatrix
можно почитать, например, по ссылке. В нашем фильтре соотношение "весов" красной, зелёной и синей цветовых компонент 0.3 - 0.59 - 0.11 отражает чувствительность человеческого глаза к оттенкам красного, зелёного и синего.
В некоторых случаях фильтровать изображения можно и сменой свойства Image.PixelFormat
, но вариант Format16bppGrayScale
в GDI+ не сработал.
5.2.5. Сохранить рисунок так, как он выглядит на компоненте. Следует понимать, что свойство SizeMode
управляет отображением рисунка в компоненте, при сохранении пропорции рисунка не изменятся от того, что он был выведен, например, при SizeMode=StretchImage
(принудительно растянут по размерам компоненты, возможно, с нарушением пропорций). Тем не менее - а можно ли сохранить рисунок так, как он был выведен в компоненту? Да, можно, например, так:
if (pictureBox1.Image == null) return; pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage; pictureBox1.Dock = DockStyle.Fill; //установили растягивание Bitmap bitmap1 = new Bitmap (pictureBox1.Image); Bitmap bitmap2 = new Bitmap (pictureBox1.Width, pictureBox1.Height); //у 2-го рисунка - размер компоненты Graphics g = Graphics.FromImage (bitmap2); //получили графический контекст из 2-го рисунка g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.Bicubic; //настроили режим интерполяции g.DrawImage (bitmap1, new Rectangle(0,0, bitmap2.Width, bitmap2.Height)); //отрисовали в контекст 2-го рисунка исходный, неискажённый рисунок pictureBox1.Image = bitmap2; //назначили искажённый рисунок компоненте сохранитьToolStripMenuItem_Click (this, e); //вызвали метод сохранения pictureBox1.SizeMode = PictureBoxSizeMode.AutoSize; pictureBox1.Dock = DockStyle.None; //восстановили свойства pictureBox1.Image = bitmap1; //вернули старый рисунок
Убедиться в том, что рисунок был пересохранён в фоновом режиме с размерами, соответствующими клиентской части формы можно, заново открыв его с диска.
5.2.6. Выделить часть рисунка и реализовать обрезку по выделенной области. В класс формы добавим следующие глобальные данные:
Rectangle selRect; //выделенный прямоугольник Point orig; //точка для привязки прямоугольника Pen pen; //перо для отрисовки bool flag; //флажок показывает, находимся ли в режиме выделения части рисунка
Инициализируем их, например, в имеющемся конструкторе формы:
public Form1() { InitializeComponent(); pen = new Pen (Brushes.Blue, 0.8f); //цвет и толщина линии выделения pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash; //штрихи selRect = new Rectangle (0, 0, 0, 0); flag = false; }
Реализация динамического выделения мышью потребует взаимодействия нескольких событий (нажатие кнопки мыши, отпускание кнопки мыши и перемещение мыши). Для удобства используем также динамическое переключение обработчика события Paint
(чтобы рисовать рамку выделения только тогда, когда она нужна – происходит перемещение курсора мыши на pictureBox
при зажатой левой кнопке).
private void pictureBox1_Paint (object sender, PaintEventArgs e) { //Этот обработчик мы создали в конструкторе //Для ситуации, когда выделяем рамку e.Graphics.DrawRectangle (Pens.Black, selRect); } private void Selection_Paint (object sender, PaintEventArgs e) { //Добавили свой обработчик Paint для остальных ситуаций e.Graphics.DrawRectangle (pen, selRect); } private void pictureBox1_MouseDown (object sender, MouseEventArgs e) { //Этот обработчик мы создали в конструкторе //Нажали мышку - включаем наш обработчик и выключаем стандартный pictureBox1.Paint -= new PaintEventHandler (pictureBox1_Paint); pictureBox1.Paint += new PaintEventHandler (Selection_Paint); orig = e.Location; //запомнили, где начало выделения flag = true; } private void pictureBox1_MouseUp (object sender, MouseEventArgs e) { //Этот обработчик мы создали в конструкторе //отжали мышку - всё наоборот pictureBox1.Paint -= new PaintEventHandler (Selection_Paint); pictureBox1.Paint += new PaintEventHandler (pictureBox1_Paint); pictureBox1.Invalidate (); //принудительно перерисовать flag = false; //выйти из режима выделения } private void pictureBox1_MouseMove (object sender, MouseEventArgs e) { //Этот обработчик мы создали в конструкторе if (flag) { //если в режиме выделения selRect = GetSelectionRectangle (orig, e.Location); //запоминаем выделенное if (e.Button == MouseButtons.Left) { pictureBox1.Refresh (); //рефрешим картинку по нажатию левой кнопки } } } private Rectangle GetSelectionRectangle (Point orig, Point location) { //Этот метод пришлось написать, чтобы координаты выделения правильно запоминались //независимо от того, в какую сторону тащим курсор мыши Rectangle rect = new Rectangle (); int dX = location.X - orig.X, dY = location.Y - orig.Y; System.Drawing.Size size = new System.Drawing.Size (Math.Abs (dX), Math.Abs (dY)); //размеры текущего выделения if (dX >= 0 && dY >= 0) rect = new Rectangle (orig, size); else if (dX < 0 && dY > 0) rect = new Rectangle (location.X, orig.Y, size.Width, size.Height); else if (dX > 0 && dY < 0) rect = new Rectangle (orig.X, location.Y, size.Width, size.Height); else rect = new Rectangle (location, size); return rect; }
Теперь, при наличии на изображении выделенной рамки selRect
можно, например, реализовать его обрезание по границам этой рамки:
if (pictureBox1.Image == null) return; if (selRect.Width > 0 && selRect.Height > 0) { Bitmap bitmap1 = new Bitmap (pictureBox1.Image); Bitmap bitmap2 = new Bitmap (selRect.Width, selRect.Height); Graphics g = Graphics.FromImage (bitmap2); g.DrawImage (bitmap1, 0, 0, selRect, GraphicsUnit.Pixel); pictureBox1.Image = bitmap2; selRect = new Rectangle (0, 0, 0, 0); }
Скачать пример Lab5_2 в архиве .zip с проектом C# Visual Studio 2019 (14 Кб)
Проект Lab5_3. Рисование фигур. Показанный выше подход нетрудно применить для рисования геометрических примитивов на канве PictureBox
или формы.
Создадим форму как в предыдущем примере с PictureBox
, расположенным на Label
, у компонент установлены те же свойства.
Рассмотрим варианты рисования линии на канве PictureBox
. При движении мыши с зажатой левой кнопкой наша линия должна динамически обновляться, а при отпускании кнопки - добавляться на существующий рисунок.
Опишем в классе формы необходимые данные:
Point p1, p2; //начало и конец линии Pen pen1; //перо Brush brush1; //кисть Bitmap Img1, Img2; //основная картинка, на которой рисуем и буферная Graphics gr; //графический контекст bool isPressed; //флажок "кнопка мыши зажата"
Для самой формы нам понадобится запрограммировать событие Load
, где мы инициализируем эти объекты, то есть, создадим рисунок размером с клиентскую часть окна формы, назначим его компоненте, создадим перо и выставим в "ложь" флажок:
Img1 = new Bitmap (ClientSize.Width, ClientSize.Height); pictureBox1.Image = Img1; gr = Graphics.FromImage (Img1); pen1 = new Pen (Color.Black); isPressed = false;
Всё остальное запрограммируем в событиях PictureBox
. На нажатие кнопки мыши будем включать флажок и запоминать место клика p1
:
private void pictureBox1_MouseDown (object sender, MouseEventArgs e) { isPressed = true; p1 = e.Location; }
На отпускание кнопки получим координаты второй точки p2
и соединим её с первой, проведя линию на образе Img1
. Заметим, что в реальном коде можно добавлять точки в контейнер, например, в список List
из объектов Point
. Если при этом запоминать, какой именно объект рисовался, можно в нужные моменты просто перерисовывать объекты по списку (что может предотвратить "утечки памяти" при работе приложения), а также удалять или динамически изменять их.
private void pictureBox1_MouseUp (object sender, MouseEventArgs e) { p2 = e.Location; gr = Graphics.FromImage (Img1); gr.DrawLine (pen1, p1, p2); gr.Save (); isPressed = false; pictureBox1.Invalidate (); }
На перемещение мыши обработка будет немного хитрей. Если кнопка не зажата, ничего делать не нужно, а в противном случае будем проводить текущую линию на копии рисунка Img2
, созданной из Img1
, чтобы не получилось "веера" из линий при перемещении мыши с зажатой кнопкой. Img2
всё равно придётся временно назначить рисунком для PictureBox
, чтобы линия была видна в процессе движения мыши.
private void pictureBox1_MouseMove (object sender, MouseEventArgs e) { if (!isPressed) return; //Кнопка не зажата - выйти p2 = e.Location; Img2 = new Bitmap (Img1); pictureBox1.Image = Img2; gr = Graphics.FromImage (Img2); gr.DrawLine (pen1, p1, p2); pictureBox1.Invalidate (); }
В показанном примере все координаты отсчитывались "внутри PictureBox
" и получались непосредственно из аргумента MouseEventArgs
обработчика события. По-другому можно сделать, используя координаты курсора относительно экрана Cursor.Position.X
, Cursor.Position.Y
, а затем вычитая из них координаты верхнего левого угла формы Location.X
, Location.Y
(и, возможно, дополнительные значения, учитывающие занятое другими компонентами пространство на форме):
int x1 = Cursor.Position.X - Location.X, y1 = Cursor.Position.Y - Location.Y;
Расположив на панели инструментов приложения дополнительные кнопки для выбора геометрического примитива, цвета и т. п., мы можем получить приложение графический-редактор (см. прикреплённый проект, где ряд возможностей уже добавлен).
Обратите внимание в коде проекта Lab5_3: при рисовании линии и эллипса координаты второй точки могут быть и "меньше" (ближе к левому верхнему углу холста), чем первой. При рисовании же прямоугольника область экрана должна быть задана, начиная с левого верхнего угла. Метод
GetRectangle
, вызываемый при обработке событийpictureBox1_MouseUp
иpictureBox1_MouseMove
, корректирует эту проблему, как и при выделении прямоугольником в проекте Lab5_2.
Скачать пример Lab5_3 в архиве .zip с проектом C# Visual Studio 2019 (19 Кб)

Простая рисовалка на Windows Forms C#
Проект Lab5_4. Другой контекст. В завершение заметим, что отрисовка, выполняемая в объекте графического контекста, позволяет без каких-либо изменений основного кода "перенести" графический объект на другую канву, например, на экран вместо окна приложения.
Пусть в классе формы имеется метод Draw
, создающий некоторый рисунок:
void Draw (System.Drawing.Graphics g) { //Графический контекст передан в наш метод Pen [] pens = { Pens.Red, Pens.Yellow, Pens.Green }; //Разные перья для рисования int width = ( this.ClientSize.Width - 1 ) / 2, height = ( this.ClientSize.Height - 1 ) / 2; //Половинки ширины и высоты клиентской части окна for (int i = 0; i < 3; i++) //Демо - рисуем перьями g.DrawRectangle (pens [i], i * width, i * height, width, height); Brush [] brushes = { Brushes.Red, Brushes.Yellow, Brushes.Green }; //Разные кисти для выполнения заливки цветом for (int i = 0; i < 3; i++) //Демо - рисуем кистями g.FillEllipse (brushes [i], i * width, i * height, width, height); g.DrawLine (pens [2], 0, 0, width * 2, height * 2); //Рисуем линию пером }
Как и в начале статьи, мы могли бы вызвать его кодом вида
Graphics g = this.CreateGraphics (); Draw (g);
для отображения картинки непосредственно на канве формы.
Теперь выведем рисунок в контексте графического экрана Windows поверх всех окон.
Убедимся, что к файлу формы подключены нужные пространства имён:
using System.Runtime.InteropServices; using System.Drawing;
В классе формы (например, после конструктора) укажем ссылки на нужные методы библиотеки user32.dll
, которые нам потребуются:
[DllImport ("user32.dll")] public static extern IntPtr GetDC (IntPtr hwnd); [DllImport ("user32.dll")] public static extern void ReleaseDC (IntPtr hwnd, IntPtr dc);
В методе рисования (например, по событию Paint
формы) вызовем наш метод с другим контекстом:
private void Form1_Paint (object sender, PaintEventArgs e) { IntPtr desktopPtr = GetDC (IntPtr.Zero); Graphics g = Graphics.FromHdc (desktopPtr); Draw (g); g.Dispose (); ReleaseDC (IntPtr.Zero, desktopPtr); }
Также можно потребовать где-нибудь принудительной перерисовки, например, по клику на форме:
private void Form1_Click (object sender, EventArgs e) { Invalidate (); }
Подключить к проекту внешнюю библиотеку можно и непосредственно, например, для нашего случая:
- в верхнем меню выбрать команду Проект - Добавить существующий элемент...;
- в списке типов файлов выбрать "Исполняемые файлы", показать расположение нужного файла (
c:\Windows\System32\user32.dll
) и нажать "Добавить";- выбрать добавленный файл в Обозревателе решений, в окне "Свойства" указать для него значение "Копировать в выходной каталог" равным "Копировать более новую версию";
- после этого прототипы нужных функций библиотеки можно описать в классе формы с атрибутами
public static extern
и предшествующей директивой[DllImport ("user32.dll")]
, как мы делали выше.
Скачать пример Lab5_4 в архиве .zip с проектом C# Visual Studio 2019 (11 Кб)
Задание по теме может быть, например, таким: реализовать графическое приложение в соответствии с вариантом. Предусмотреть в приложении следующие возможности:
- сохранение полученных графических файлов;
- прокрутка файлов, если они «не помещаются» в окне компоненты
PictureBox
; - возможность построить изображение более, чем в одном масштабе (или масштабировать его программно);
- не менее двух настраиваемых параметров, позволяющих изменить внешний вид объекта (например, для объекта "дом" - количество этажей и количество окон на каждом этаже).
05.04.2023, 19:26 [1780 просмотров]