52628.fb2
Программирование графики в .NET Compact Framework опирается на те же базовые приемы, что и работа с графикой для полной версии .NET Framework. Все основные классы для работы с графикой сосредоточены в пространстве имен System.Drawing
. С помощью этих классов можно рисовать всевозможные фигуры, отображать линии, работать с изображениями и даже манипулировать текстом. В качестве своеобразного холста для графических опытов можно использовать поверхность формы или элементов управления. Самым главным классом является класс Graphics
, который предоставляет поверхность и методы для вывода графики. Также широко используются в графике такие классы, как Pen
, Brush
, Color
, Rectangle
, Line
, Image
.
Класс Pen
используется для создания пера, при помощи которого проводятся прямые и кривые линии. В отличие от полной версии .NET Framework, поддерживающей четыре перегруженных версии конструктора Pen
, .NET Compact Framework позволяет создавать перо только с помощью двух конструкторов. При вызове метода Pen(Color)
создается перо указанного цвета. Конструктор Pen(Color, Single)
позволяет создавать перо указанных цвета и ширины. Но второй вариант поддерживается только в .NET Compact Framework 2.0.
В листинге 6.1 приведен пример создания перьев синего и красного цветов. Затем при помощи перьев создаются две линии.
private void Form1_Paint(object sender, PaintEventArgs e) {
Graphics g = e.Graphics;
// Синее перо толщиной 1 пиксел
Pen bluePen = new Pen(Color.Blue);
// Красное перо толщиной 5 пикселов
Pen redFatPen = new Pen(Color.Red, 5);
g.DrawLine(bluePen, 10, 10, 230, 10);
g.DrawLine(redFatPen, 10, 20, 230, 20);
}
Класс Brush
является абстрактным классом для создания кистей, с помощью которых можно рисовать фигуры и текст на графической поверхности. Библиотека .NET Compact Framework поддерживает классы SolidBrush
и TextureBrush
. К сожалению, класс LinearGradientBrush
, позволяющий рисовать красивые фигуры, в настоящее время не поддерживается.
При создании объекта SolidBrush
нужно просто указать цвет, который будет использоваться для отображения фигур. Чтобы сменить цвет кисти, достаточно указать новый цвет в свойстве Color
. В листинге 6.2 приведен код, который позволяет нарисовать зеленый круг и желтый прямоугольник.
private void Form1_Paint(object sender, PaintEventArgs e) {
Graphics g = e.Graphics;
// Создаем кисть зеленого цвета
SolidBrush myBrush = new SolidBrush(Color.Green);
// Рисуем закрашенный круг
g.FillEllipse(myBrush, 10, 30, 30, 30);
// Меняем цвет кисти на желтый
myBrush.Color = Color.Yellow;
// Рисуем закрашенный прямоугольник
g.FillRectangle(myBrush, 50, 30, 50, 25);
}
Класс TextureBrush
позволяет создавать текстурную кисть. Подобная текстурная кисть позволяет не рисовать однородным цветом, а применять текстурное заполнение отображаемых графических примитивов. Использование подобной кисти позволяет добиваться красивых эффектов. В листинге 6.3 приведен пример использования текстурной кисти с использованием изображения, входящего в состав Windows Mobile 2003.
private void Form1_Paint(object sender, PaintEventArgs e) {
Graphics g = e.Graphics;
// выбираем рисунок
Image myImage = new Bitmap(@"\Windows\alerts.bmp");
// создаем текстурную кисть TextureBrush
texture = new TextureBrush(myImage);
// Рисуем эллипс, заполненный рисунком
g.FillEllipse(texture, 10, 60, 120, 120);
g.Dispose();
}
Такие свойства класса TextureBrush, как Transform и WrapMode, не поддерживаются в .NET Compact Framework 2.0.
При создании перьев или кистей применялся класс Color
. Он позволяет задавать цвет либо с помощью предопределенного названия, либо указывая составные части цвета в модели RGB. Например, для создания красного цвета можно использовать код, приведенный в листинге 6.4.
// красный цвет по названию
Color redColor = Color.Red;
// красный цвет из компонентов RGB
Color redColor2 = Color.FromArgb(255, 0, 0);
// Выводим на экран две красные линии
g.DrawLine(new Pen(redColor), 10, 190, 100, 190);
g.DrawLine(new Pen(redColor2), 10, 195, 100, 195);
Класс Font
используется для вывода текста. Как ни странно, вывод текстовой информации тоже является графической операцией, что немного смущает новичков. Из четырнадцати возможных перезагруженных версий конструктора класса в .NET Compact Framework доступно только три. Для создания объекта Font
нужно определить семейство шрифтов, размер символов и стиль начертания. Пример использования шрифта приведен в листинге 6.5.
Font myFont = new Font("Tahoma", 9, FontStyle.Italic);
g.DrawString("Карманный компьютер", myFont, myBrush, 14, 200);
Объект Icon
используется методом DrawIcon
для отображения пиктограмм. Предположим, что необходимо использовать пиктограмму, хранящуюся в ресурсах программы. В таком случае понадобится код, приведенный в листинге 6.6.
Icon myIcon = new Icon(Assembly.GetExecutingAssembly().
GetManifestResourceStream("MyApp.Icon.ico"));
Класс Bitmap
предназначен для работы с растровыми изображениями. Программист может загрузить картинку в объект Bitmap
из потока Stream
, скопировать из существующего объекта Bitmap
или загрузить из файла. Также можно создать новый пустой объект Bitmap
, указав только размеры картинки. Ранее класс Bitmap
уже использовался при создании текстурной кисти. Но при этом применялся родственный объект Image
. В листинге 6.7 приведен новый вариант создания кисти.
// выбираем рисунок
Bitmap myImage = new Bitmap(@"\Windows\alerts.bmp");
// создаем текстурную кисть
TextureBrush texture = new TextureBrush(myImage);
Структура Point
содержит координаты X и Y для указания расположения некоей точки. В библиотеке .NET Compact Framework поддерживается только один конструктор для создания объекта Point
, в котором указываются эти координаты. Структура Point
часто используется в методах DrawPolygon
и FillPolygon
, которые будут рассматриваться позже.
Структура Rectangle
определяет размер и расположение прямоугольника. В мобильной версии используется только один конструктор, определяющий прямоугольник по координатам левого верхнего угла, ширине и высоте, что иллюстрирует код, приведенный в листинге 6.8.
Rectangle myRectangle = new Rectangled(10, 10, 70, 210);
В предыдущих примерах уже были использованы несколько методов для работы с графикой. Но сейчас следует поговорить о них более подробно. Прежде всего нужно помнить, что для работы с графическими методами необходимо сначала создать объект Graphics
. Существует несколько способов получения объекта Graphics
, и они будут рассматриваться достаточно подробно
Метод CreateGraphics
формы или элемента управления позволяет получить объект Graphics
, предоставляющий возможность рисовать на форме или элементе управления. Этот метод демонстрируется в листинге 6.9.
Graphics g = this.CreateGraphics();
Метод FromImage
создает новый объект Graphics
из заданного объекта Image
. При помощи этого метода можно изменять существующее изображение или создавать новое изображение. Причем обработанное изображение можно потом сохранить в графическом файле. Использование метода иллюстрирует код, приведенный в листинге 6.10.
Bitmap bmp = new Bitmap(150, 90);
Graphics g = Graphics.FromImage(bmp);
Метод OnPaint
класса Form
получает в качестве параметра объект PaintEventArgs
. Одним из членов данного объекта является объект Graphics
, связанный с формой. Переопределяя метод OnPaint
класса Form
, можно получить доступ к объекту Graphics
из параметра PaintEventArgs
, после чего можно работать с графикой в клиентской области формы. Вызов этого метода показан в листинге 6.11.
Protected override void OnPaint(PaintEventArgs e) {
Graphics g = e.Graphics;
}
Получив любым из перечисленных способов доступ к объекту Graphics
, программист может рисовать фигуры, линии, кривые, изображения и текст при помощи различных методов. Самые распространенные графические методы будут рассмотрены в этом разделе главы.
Метод DrawImage
рисует заданный объект Image
в указанной позиции экрана. Всего существует четыре перегруженные версии метода. Но в самой простой его версии достаточно указать координаты выводимой картинки, как показано в листинге 6.12.
g.DrawImage(myImage, 10, 10);
С помощью метода DrawImage
можно выводить на экран не все изображение, а только его часть. В этом случае надо указать размеры прямоугольника, который определяет размеры выводимой области картинки, как показано в листинге 6.13. В примере используется перечисление GraphicsUnit.Pixel
, которое позволяет указывать единицы измерения.
Bitmap myBMP = new Bitmap(@"\windows\banner.gif");
Rectangle portion = new Rectangle(1, 1, 150, 25);
g.DrawImage(myBMP, 20, 220, portion, GraphicsUnit.Pixel);
Метод FillRectangle
уже применялся при рассмотрении кистей. Метод DrawRectangle
использует перо вместо кисти, поэтому на экран выводится незакрашенный прямоугольник.
Чтобы нарисовать достаточно сложную фигуру, можно задать массив точек и соединить их прямыми отрезками, после чего можно закрасить получившуюся фигуру. Для этого разработчик может использовать методы DrawPolygon
и FillPolygon
. В листинге 6.14 приведен код, который позволяет нарисовать простой ромб по указанным точкам.
// Нарисуем ромб
// Зададим массив точек
Point[] arrPoint = {
new Point(150, 50),
new Point(200, 100),
new Point(150, 150),
new Point(100, 100),
new Point(150, 50),
};
g.DrawPolygon(bluePen, arrPoint);
Если все рассмотренные ранее методы объединить в одно приложение и затем запустить его, то на экране устройства будет отображено несколько графических образов, как показано на рис. 6.1.
Рис. 6.1. Основные приемы работы с графикой
В данный момент .NET Compact Framework не поддерживает графические методы DrawPiе
и FillPie
, которые позволяли бы рисовать круговые секторы. Но можно создать свою версию этих методов, используя математические вычисления, как показано в листинге 6.15.
///<summary>
///Рисуем закрашенный сектор
///Параметры функции
///g - Объект Graphics
///solidBrush - Кисть для закраски сегмента
///x,y - Координаты центра
///width - Ширина сегмента
///height - Высота сегмента
///startAngle - Значение начального угла
///endAngle - Значение конечного угла
///</summary>
private void FillPie(Graphics g, SolidBrush solidBrush, int x, int y,
int width, int height, double startAngle, double endAngle) {
double[] xAngle = new double[12];
double[] yAngle = new double[12];
double angleIncrement = (endAngle - startAngle) / 10;
double angle = startAngle;
for (int i = 0; i <= 10; i++) {
xAngle[i] = x + (Math.Cos(angle * (Math.PI / 180)) * (width / 2));
yAngle[i] = y + (Math.Sin(angle * (Math.PI / 180)) * (height / 2));
angle += angleIncrement;
}
xAngle[11] = x + (Math.Cos(endAngle * (Math.PI / 180)) * (width / 2));
yAngle[11] = y + (Math.Sin(endAngle * (Math.PI / 180)) * (height / 2));
Point[] anglePoints = {
new Point(x, y),
new Point((int)xAngle[0], (int)yAngle[0]),
new Point((int)xAngle[1], (int)yAngle[1]),
new Point((int)xAngle[2], (int)yAngle[2]),
new Point((int)xAngle[3], (int)yAngle[3]),
new Point((int)xAngle[4], (int)yAngle[4]),
new Point((int)xAngle[5], (int)yAngle[5]),
new Point((int)xAngle[6], (int)yAngle[6]),
new Point((int)xAngle[7], (int)yAngle[7]),
new Point((int)xAngle[8], (int)yAngle[8]),
new Point((int)xAngle[9], (int)yAngle[9]),
new Point((int)xAngle[10], (int)yAngle[10]),
new Point((int)xAngle[11], (int)yAngle[11])
};
g.FillPolygon(solidBrush, anglePoints);
}
///<summary>
/// Рисуем границы сектора
///g - Объект Graphics
///pen - Перо для рисования сегмента
///x,y - Центр сегмента
///width - Ширина сегмента
///height - Высота
///startAngle - Значение начального угла
///endAngle - Значение конечного угла
///</summary>
private void DrawPie(Graphics g, Pen pen, int x, int y,
int width, int height, double startAngle, double endAngle) {
double[] xAngle = new double[12];
double[] yAngle = new double[12];
double angleIncrement = (endAngle - startAngle) / 10;
double angle = startAngle;
for (int i = 0; i <= 10; i++) {
xAngle[i] = x + (Math.Cos(angle * (Math.PI / 180)) * (width /2));
yAngle[i] = y + (Math.Sin(angle * (Math.PI / 180)) * (height / 2));
angle += angleIncrement;
}
xAngle[11] = x + (Math.Cos(endAngle * (Math.PI / 180)) * (width / 2));
yAngle[11] = y + (Math.Sin(endAngle * (Math.PI / 180)) * (height /2));
Point[] anglePoints = {
new Point(x, y),
new Point((int)xAngle[0], (int)yAngle[0]),
new Point((int)xAngle[1], (int)yAngle[1]),
new Point((int)xAngle[2], (int)yAngle[2]),
new Point((int)xAngle[3], (int)yAngle[3]),
new Point((int)xAngle[4], (int)yAngle[4]),
new Point((int)xAngle[5], (int)yAngle[5]),
new Point((int)xAngle[6], (int)yAngle[6]),
new Point((int)xAngle[7], (int)yAngle[7]),
new Point((int)xAngle[8], (int)yAngle[8]),
new Point((int)xAngle[9], (int)yAngle[9]),
new Point((int)xAngle[10], (int)yAngle[10]),
new Point((int)xAngle[11], (int)yAngle[11])
};
g.DrawPolygon(pen, anglePoints);
}
private void Form1_Paint(object sender, PaintEventArgs e) {
// Выводим несколько секторов на экран
DrawPie(e.Graphics, new Pen(Color.Red), 130, 165, 100, 100, 0, 45);
FillPie(e.Graphics, new SolidBrush(Color.Green),
120, 160, 100, 100, 46, 90);
FillPie(e.Graphics, new SolidBrush(Color.Yellow),
120, 160, 100, 100, 91, 120);
FillPie(e.Graphics, new SolidBrush(Color.Blue),
120, 160, 100, 100, 121, 260);
FillPie(e.Graphics, new SolidBrush(Color.Red),
120, 160, 100, 100, 261, 360);
}
Результат работы этой программы показан на рис. 6.2.
Рис. 6.2. Создание секторов
К сожалению, .NET Compact Framework не поддерживает свойство BackgroundImage
, которое создает фоновый рисунок для формы. Но каждый программист может восполнить данный пробел, переопределяя метод OnPaint
.
Нужно создать новый проект и разместить на форме какой-нибудь элемент управления, например кнопку. Кнопка не будет выполнять никаких функций. Она потребуется лишь для демонстрации технологии. Также надо добавить в проект изображение, которое будет использоваться в качестве фона для формы. В нашем примере картинка будет внедрена в программу как ресурс, хотя можно загрузить ее из обычного графического файла. Чтобы все работало так, как запланировано, необходимо переопределить метод OnPaint()
. Новый код метода приведен в листинге 6.16.
protected override void OnPaint(PaintEventArgs e) {
// получим картинку из ресурсов Bitmap
backgroundImage = new Bitmap(Assembly.GetExecutingAssembly().
GetManifestResourceStream("BackgroundImageCS.sochicat.jpg"));
e.Graphics.DrawImage(backgroundImage, this.ClientRectangle,
new Rectangle(0, 0, backgroundImage.Width, backgroundImage.Height),
GraphicsUnit.Pixel);
}
После запуска программы можно будет увидеть, что форма имеет фоновый рисунок, а кнопка расположена поверх фона (рис. 6.3).
Рис. 6.3. Заполнение фона формы своим рисунком
Библиотека .NET Compact Framework 1.0 не поддерживает метод System.Drawing.Image.Clone
, позволяющий создать точную копию картинки. Это ограничение легко обходится с помощью создания собственных методов. Кроме того, можно расширить возможности метода и добавить функциональность, позволяющую копировать часть картинки. Соответствующий код приведен в листинге 6.17.
// Копируем всю картинку
protected Bitmap CopyBitmap(Bitmap source) {
return new Bitmap(source);
}
// Копируем часть картинки
protected Bitmap CopyBitmap(Bitmap source, Rectangle part) {
Bitmap bmp = new Bitmap(part.Width, part.Height);
Graphics g = Graphics.FromImage(bmp);
g.DrawImage(source, 0, 0, part, GraphicsUnit.Pixel);
g.Dispose();
return bmp;
}
private void button1_Click(object sender, EventArgs e) {
Graphics g = CreateGraphics();
Bitmap myBMP = new Bitmap(@"\windows\banner.gif");
// Половина ширины картинки
int left = myBMP.Size.Width / 2;
// Копируем всю картинку Bitmap
clone = CopyBitmap(myBMP);
// копируем левую часть картинки
Bitmap part =
CopyBitmap(myBMP, new Rectangle(0, 0, left, myBMP.Size.Height));
// Выводим три картинки по вертикали:
// источник, копию и копию левой части
int y = 10;
// картинка-источник
g.DrawImage(myBMP, 10, y);
y += myBMP.Height + 10;
// картинка-копия
g.DrawImage(clone, 10, y);
y += clone.Height + 10;
// копия левой части картинки
g.DrawImage(part, 10, y);
y += part.Height + 10;
g.Dispose();
}
private void button2_Click(object sender, EventArgs e) {
Graphics g = CreateGraphics();
Bitmap myBMP = new Bitmap(@"\windows\banner.gif");
g.Clear(Color.White);
int left = myBMP.Size.Width / 2; // Копия картинки
Bitmap clone = (Bitmap)myBMP.Clone();
int y = 10;
g.DrawImage(myBMP, 10, y);
y += myBMP.Height + 10;
g.DrawImage(clone, 10, y);
y += clone.Height + 10;
g.Dispose();
}
В этом примере создаются две перегруженные версии метода CopyImage
. При помощи этого метода можно копировать картинку или ее часть. Для сравнения в примере было показано, как можно скопировать картинку с помощью метода Clone
, доступного в .NET Compact Framework 2.0. Результат работы соответствующего приложения показан на рис. 6.4.
Рис. 6.4. Копирование картинки разными способами
Библиотека .NET Compact Framework позволяет использовать прозрачный цвет, но при этом налагает определенные ограничения на эту возможность. Например, в библиотеке нет такого удобного метода, как MakeTransparent
. Но разработчик может задать прозрачный цвет при помощи метода SetColorKey
класса ImageAttributes
. При этом разрешается использовать один и тот же цвет для минимального и максимального значений цветового ключа в версии метода ImageAttributes.SetColorKey(Color.Color)
.
Вторая перегруженная версия метода ImageAttributes.SetColorKey(Color, Color, ColorAdjustType) в .NET Compact Framework не поддерживается.
Используя прозрачный цвет, можно добиться красивых результатов при наложении картинки на картинку. Этот механизм стоит продемонстрировать на конкретном примере.
Для тестового приложения были подготовлены две картинки. Первое изображение является фотографией моего кота, а во втором хранится небольшая табличка с его именем, сделанная за несколько секунд в стандартной программе Paint. При создании таблички фон был залит красным цветом, а для текстовой строки был выбран другой тон. Затем оба рисунка были добавлены в проект как ресурсы.
Чтобы продемонстрировать рассматриваемый эффект, надо расположить на форме две кнопки. При нажатии первой кнопки табличка будет накладываться на фотографию без изменений. При нажатии на вторую кнопку красный цвет сначала будет определен как прозрачный и только потом произойдет наложение изображений.
Основной код приложения приведен в листинге 6.18.
private void butAddImage_Click(object sender, EventArgs e) {
Graphics g = CreateGraphics();
// получим картинки из ресурсов
Bitmap imgCat = new Bitmap(Assembly.GetExecutingAssembly().
GetManifestResourceStream("TransparentCS.mycat.jpg"));
Bitmap imgName = new Bitmap(Assembly.GetExecutingAssembly().
GetManifestResourceStream("Transparent_CS.catname.bmp"));
g.DrawImage(imgCat, 0, 0,
new Rectangle(0, 0, imgCat.Width, imgCat.Height), GraphicsUnit.Pixel);
g.DrawImage(imgName, 50, 120,
new Rectangle(0, 0, imgName.Width.imgName.Height), GraphicsUnit.Pixel);
g.Dispose();
}
private void butImage2_Click(object sender, EventArgs e) {
Graphics g = CreateGraphics();
// получим картинки из ресурсов
Bitmap imgCat = new BitmapCAssembly.GetExecutingAssembly().
GetManifestResourceStream("Transparent_CS.mycat.jpg"));
Bitmap imgName = new Bitmap(Assembly.GetExecutingAssembly().
GetManifestResourceStream("Transparent_CS.catname.bmp"));
// Очистим экран
g.Clear(Color.White);
// Выводим первую картинку
g.DrawImage(imgCat, 0, 0,
new Rectangle(0, 0, imgCat.Width, imgCat.Height), GraphicsUnit.Pixel);
ImageAttributes attr = new ImageAttributes();
// Устанавливаем красный цвет как прозрачный
attr.SetColorKey(Color.Red, Color.Red);
// Выводим вторую картинку с установленными атрибутами
Rectangle dstRect = new Rectangle(50, 120, imgName.Width, imgName.Height);
g.DrawImage(imgName, dstRect, 0, 0,
imgName.Width, imgName.Height, GraphicsUnit.Pixel.attr);
g.Dispose();
}
He забудьте импортировать пространство имен System.Drawing.Imaging при работе с этим примером.
Если просто наложить одну картинку на другую, то результат будет, мягко говоря, не очень красивым (рис. 6.5).
Рис. 6.5. Неудачный вариант наложения двух картинок
Если же воспользоваться методом SetColorKey
для установки прозрачного цвета, то результат наложения двух изображений будет выглядеть достойно (рис. 6.6).
Рис. 6.6. Наложение картинки с использованием прозрачности
Так как .NET Compact Framework не позволяет создавать округленные прямоугольники встроенными средствами, то необходимо самостоятельно реализовать эту задачу. В этом разделе будет рассматриваться решение, предложенное Алексом Яхниным (Alex Yakhnin) в его блоге blog.opennetcf.org/ayakhnin/. Для достижения заданного эффекта надо нарисовать серию линий, которые соединяют эллипсы, и закрасить внутреннюю область сплошным цветом (рис. 6.7).
Рис. 6.7. Создание прямоугольника со скругленным углами
Соответствующий код приведен в листинге 6.19.
public static void DrawRoundedRectangle(Graphics g, Pen p, Color backColor,
Rectangle rc, Size size) {
Point[] points = new Point[8];
// подготовим точки для фигуры
points[0].X = rc.Left + size.Width / 2;
points[0].Y = rc.Top + 1;
points[1].X = rc.Right - size.Width / 2;
points[1].Y = rc.Top + 1;
points[2].X = rc.Right;
points[2].Y = rc.Top + size.Height / 2;
points[3].X = rc.Right;
points[3].Y = rc.Bottom - size.Height / 2;
points[4].X = rc.Right - size.Width / 2;
points[4].Y = rc.Bottom;
points[5].X = rc.Left + size.Width / 2;
points[5].Y = rc.Bottom;
points[6].X = rc.Left + 1;
points[6].Y = rc.Bottom - size.Height / 2;
points[7].X = rc.Left + 1;
points[7].Y = rc.Top + size.Height / 2;
// приготовим кисть для фона
Brush fillBrush = new SolidBrush(backColor);
// рисуем отрезки и круги для округленного прямоугольника
g.DrawLine(p, rc.Left + size.Width / 2, rc.Top,
rc.Right - size.Width / 2, rc.Top);
g.FillEllipse(fillBrush, rc.Right - size.Width, rc.Top,
size.Width, size.Height);
g.DrawEllipse(p, rc.Right - size.Width, rc.Top, size.Width, size.Height);
g.DrawLine(p, rc.Right, rc.Top + size.Height / 2, rc.Right,
rc.Bottom - size.Height /2);
g.FillEllipse(fillBrush, rc.Right - size.Width, rc.Bottom - size.Height,
size.Width, size.Height);
g.DrawEllipse(p, rc.Right - size.Width, rc.Bottom - size.Height,
size.Width, size.Height);
g.DrawLine(p, rc.Right - size.Width / 2, rc.Bottom,
rc.Left + size.Width / 2, rc.Bottom);
g.FillEllipse(fillBrush, rc.Left, rc.Bottom - size.Height,
size.Width, size.Height);
g.DrawEllipse(p, rc.Left, rc.Bottom - size.Height,
size.Width, size.Height);
g.DrawLine(p, rc.Left, rc.Bottom - size.Height / 2,
rc.Left, rc.Top + size.Height / 2);
g.FillEllipse(fillBrush. rc.Left, rc.Top, size.Width, size.Height);
g.DrawEllipse(p, rc.Left, rc.Top, size.Width. size.Height);
// заполняем прямоугольник, скрывая внутренние эллипсы
g.FillPolygon(fillBrush, points);
// освобождаем ресурсы
fillBrush.Dispose();
}
private void butDrawRoundedRectangle_Click(object sender, EventArgs e) {
Graphics g = CreateGraphics();
Rectangle rc = new Rectangle(10, 10, 200, 50);
DrawRoundedRectangle(g,
new Pen(Color.Black), Color.CadetBlue, rc, new Size(8, 8));
}
Результат работы этого кода показан на рис. 6.8.
Рис. 6.8. Отображение закрашенного прямоугольника со скругленными углами
Если при работе с мобильным устройством необходимо сделать скриншоты, то для реализации замысла необходимо использовать внешние устройства. Конечно, можно просто сфотографировать экран, но настоящий программист будет использовать функции Windows API. В этом разделе главы будет рассматриваться пример копирования определенной области окна, всего рабочего окна программы или любого другого окна. Для демонстрации примера надо разместить на форме список, три кнопки и один таймер. Сам код приведен в листинге 6.20.
[DllImport("coredll.dll", EntryPoint = "GetDesktopWindow")]
public static extern IntPtr GetDesktopWindow();
[DllImport("coredll.dll", EntryPoint = "GetDC")]
public static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("coredll.dll", EntryPoint = "ReleaseDC")]
public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("coredll.dll")]
public static extern int BitBlt(IntPtr hdcDest, int nXDest, int nYDest,
int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, uint dwRop);
const int SRCCOPY = 0x00CC0020;
private void screenshot(string filename, Graphics gx, Rectangle rect) {
Bitmap bmp = new Bitmap(rect.Width, rect.Height);
Graphics g = Graphics.FromImage(bmp);
BitBlt(g.GetHdc(), 0, 0, rect.Width, rect.Height, gx.GetHdc(),
rect.Left, rect.Top, SRCCOPY);
bmp.Save(filename, System.Drawing.Imaging.ImageFormat.Bmp);
bmp.Dispose();
g.Dispose();
}
private void butPartOfWindow_Click(object sender, EventArgs e) {
// Делаем снимок списка
ScreenShot(@"\My Documents\save.bmp", this.CreateGraphics(),
listBox1.Bounds);
}
private void butScreen_Click(object sender, EventArgs e) {
// Делаем снимок экрана
Rectangle rect = new Rectangle(0,0,240,240);
Bitmap bmp = new Bitmap(rect.Width, rect.Height);
Graphics g = Graphics.FromImage(bmp);
IntPtr hwnd = GetDesktopWindow();
IntPtr hdc = GetDC(hwnd);
BitBlt(g.GetHdc(), 0, 0, rect.Width, rect.Height, hdc, rect.Left,
rect.Top, SRCCOPY);
bmp.Save(@"\My Documents\screen.bmp",
System.Drawing.Imaging.ImageFormat.Bmp);
// Освобождаем ресурсы
ReleaseDC(hwnd, hdc);
bmp.Dispose();
g.Dispose();
}
private void timer1_Tick(object sender, EventArgs e) {
// Делаем снимок экрана через 5 секунд
Rectangle rect = new Rectangle(0, 0. 240, 240);
Bitmap bmp = new Bitmap(rect.Width, rect.Height);
Graphics g = Graphics.FromImage(bmp);
IntPtr hwnd = GetDesktopWindow();
IntPtr hdc = GetDC(hwnd);
BitBlt(g.GetHdc(), 0, 0, rect.Width, rect.Height, hdc, rect.Left,
rect.Top, SRCCOPY);
bmp.Save(@"\My Documents\5sec.bmp", System.Drawing.Imaging.ImageFormat.Bmp);
// Освобождаем ресурсы
ReleaseDC(hwnd, hdc);
bmp.Dispose();
g.Dispose();
timer1.Enabled = false;
}
private void but5Sec_Click(object sender, EventArgs e) {
timer1.Enabled = true;
}
Функция ScreenShot
позволяет быстро получить участок экрана и сохранить его в графическом файле. В рассмотренном примере внешний вид списка сохраняется в файле listbox.bmp
. Для этого достаточно было указать имя файла, объект Graphics
и размеры списка ListBox
. Для получения снимка экрана пример пришлось несколько усложнить, добавив вызовы функций GetDesktopWindow
и GetDC
.
Если нужно получить снимок другой программы, то придется воспользоваться таймером. После запуска таймера в распоряжении пользователя будет 5 секунд, чтобы запустить другое приложение. Основная программа будет работать в фоновом режиме и сделает снимок экрана.
Чтобы проверить работу приложения, нужно запустить программу, нажать каждую кнопку, а затем с помощью программы File Explorer найти сохраненные файлы.
Нужно проявлять определенную осторожность при работе с методом Bitmap.Save(). Дело в том, что в Windows Mobile 2003 и более ранних версиях операционных систем библиотека .NET Compact Framework не поддерживает сохранение графических файлов в форматах GIF, JPEG или PNG. Сохранять файлы можно только в формате BMP. Причем во время написания кода редактор не заметит ошибки и позволит запустить программу с неправильным вызовом метода. Однако при вызове метода возникнет исключение NotSupportedException. К счастью, в Windows Mobile 5.0 поддерживаются все четыре графических формата.
В .NET Compact Framework 2.0 появилась ограниченная поддержка метода LockBits
, при помощи которого можно манипулировать массивом пикселов изображения. Перечисление ImageLockMode
в данном методе позволяет использовать значения ReadWrite
, ReadOnly
и WriteOnly
. А перечисление PixelFormat
поддерживает значения, перечисленные в следующем списке:
□ Format16bppRgb555
;
□ Format16bppRgb565
;
□ Format24bppRgb
;
□ Format32bppRgb
.
На сайте MSDN можно найти статью «How to: Use LockBits» с примером, в котором создается картинка и меняется интенсивность синих пикселов с помощью метода LockBits
. В листинге 6.21 приведен пример, который для большей наглядности пришлось немного изменить.
private Bitmap CreateBitmap(int width, int height) {
Bitmap bmp = new Bitmap(@"\Windows\msn.gif");
width = bmp.Size.Width;
height = bmp.Size.Height;
Graphics g = Graphics.FromImage(bmp);
g.Dispose();
return bmp;
}
protected override void OnPaint(PaintEventArgs e) {
Bitmap bmp = CreateBitmap(100, 100);
// Выводим картинку-оригинал
e.Graphics.DrawImage(bmp, 0, 0);
MakeMoreBlue(bmp);
// Рисуем модифицированную картинку ниже исходного изображения
e.Graphics.DrawImage(bmp, 0, 50);
bmp.Dispose();
}
private void MakeMoreBlue(Bitmap bmp) {
// Задаём формат данных о цвете для каждой точки изображения
PixelFormat pxf = PixelFormat.Format24bppRgb;
// Блокируем изображение в памяти
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, pxf);
// Получаем адрес первой строки развертки
IntPtr ptr = bmpData.Scan();
// Массив, содержащий байты изображения
int numBytes = bmp.Width * bmp.Height * 3;
byte[] rgbValues = new byte[numBytes];
// Копируем значения RGB в массив
Marshal.Copy(ptr, rgbValues, 0, numBytes);
// Модифицируем изображение, устанавливая
// синий цвет для каждой точки в картинке
for (int counter = 0; counter < rgbValues.Length; counter += 6)
rgbValues[counter] = 255;
// Копируем значения RGB обратно в изображение
Marshal.Сору(rgbValues, 0, ptr, numBytes);
// Разблокируем биты в памяти
bmp.UnlockBits(bmpData);
}
После запуска приложения на экране будут показаны две копии картинки, причем нижнее изображение будет немного отличаться от верхнего насыщенностью цветов.
Теперь, когда мы ознакомились с графическими методами, настало время написать простейший графический редактор с минимальными возможностями. В этом приложении можно будет рисовать при помощи стилуса линии, а также прямые цветные линии из трех цветов. Процесс рисования узоров на экране КПК при помощи стилуса гораздо больше похож на реальное рисование, чем рисование мышью в стандартных графических редакторах.
Весь код программы сводится к обработке событий мыши MouseDown
, MouseMove
и MouseUp
. В принципе, приемы создания графических эффектов ничем не отличаются от соответствующих приемов, применяемых на обычных персональных компьютерах. Я взял два примера из своей книги «Занимательное программирование на Visual Basic .NET» и перенес код в проект с учетом синтаксиса языка С#, что иллюстрирует листинг 6.22.
private int x_md, y_md;
Pen myPen = new Pen(Color.LightBlue);
private bool bPaint;
Graphics g;
private Pen erasePen;
private Point ptsStart;
private Point ptsPrevious;
private Point ptsCurrent;
private void Form1_MouseDown(object sender, MouseEventArgs e) {
// Начинаем рисование
bPaint = true;
if (mnuLines.Checked) {
ptsStart.X = e.X;
ptsStart.Y = e.Y;
ptsPrevious = ptsStart;
}
if (mnuPaint.Checked) {
// координаты стилуса при нажатии
x_md = e.X;
y_md = e.Y;
}
}
private void Form1_MouseMove(object sender, MouseEventArgs e) {
if (bPaint) {
if (mnuLines.Checked) {
ptsCurrent.X = e.X;
ptsCurrent.Y = e.Y;
g = CreateGraphics();
g.DrawLine(erasePen, ptsStart.X, ptsStart.Y, ptsPrevious.X, ptsPrevious.Y);
g.DrawLine(myPen. ptsStart.X, ptsStart.Y, ptsCurrent.X, ptsCurrent.Y);
ptsPrevious = ptsCurrent;
g.Dispose();
}
if (mnuPaint.Checked) {
g = CreateGraphics();
int x_mm = e.X;
int y_mm = e.Y;
g.DrawLine(myPen, x_md, y_md, x_mm, y_mm);
x_md = x_mm;
y_md = y_mm;
g.Dispose();
}
}
}
private void Form1_MouseUp(object sender, MouseEventArgs e) {
bPaint = false;
}
private void mnuClear_Click(object sender, EventArgs e) {
g = CreateGraphics();
g.Clear(this.BackColor);
g.Dispose();
}
private void Form1_Load(object sender, EventArgs e) {
erasePen = new Pen(this.BackColor);
}
private void mnuPaint_Click(object sender, EventArgs e) {
mnuPaint.Checked = !mnuPaint.Checked;
mnuLines.Checked = !mnuLines.Checked;
}
private void mnuGreenPen_Click(object sender, EventArgs e) {
myPen.Color = Color.Green;
}
private void mnuRedPen_Click(object sender, EventArgs e) {
myPen.Color = Color.Red;
}
На рис. 6.9 показано, как выглядит созданный графический редактор в работе.
Рис. 6.9. Простейший графический редактор
Если нужно ознакомиться с дополнительными материалами по программированию графики, то стоит обратиться к статьям документации MSDN. Например, в статье «How to: Display a Gradient Fill» рассказывается том, как раскрасить элемент градиентной заливкой. Также в документации MSDN имеется статья уже известного нам Алекса Яхнина «Creating a Microsoft .NET Compact Framework-based Animation Control», в которой рассказывается о создании элемента управления, способного воспроизводить анимированное изображение, создавая его на основе серии картинок. Тем, кто планирует писать приложения для устройств под управлением Windows Mobile 5.0, стоит обратить внимание на технологию Windows Mobile DirectX and Direct3D, которая дает дополнительные возможности для работы с графикой.