C#

УЧЕБНЫЕ МАТЕРИАЛЫ ПО ПРОГРАММИРОВАНИЮ, КРЕКИНГУ, HTML, CSS, ОФИСНЫМ ПРИЛОЖЕНИЯМ

[Главная страница] [C#] [Контакты]

Графика GDI+


Отображение статической графики

ЗАДАЧА
Нарисовать на форме собственные элементы, исключив их искажение при минимизации или сокрытии формы.

РЕШЕНИЕ
Поместите весь код рисования в обработчик события Control.Paint или Form. Paint.

ОБСУЖДЕНИЕ
При сокрытии части формы ОС Windows автоматически объявляет всю ее графическую информацию недействительной. При последующем отображении формы Windows генерирует событие Paint, приказывая форме перерисовать себя. Таким образом, для правильного обновления окна весь собственный код рисования всегда следует помещать в обработчик события Paint. Чтобы жизнь программистов была еще легче, в обработчик события Paint всегда передается параметр PaintEventArgs. Он ссылается на объект Graphics, представляющий поверхность рисования элемента управления или формы. Методы объекта Graphics позволяют отображать текст, фигуры или рисунки.

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

private void Form1_Paint(object sender, PaintEventArgs e)
{
    Rectangle rectangle = new Rectangle(5, 5, this.ClientRectangle.Width - 10, this.ClientRectangle.Height - 10);

    // Рисование границы прямоугольника.
    Pen drawingPen = new Pen(Color.Blue, 2);
    e.Graphics.DrawRectangle(drawingPen, rectangle);

    // Градиентное закрашивание прямоугольника
    LinearGradientBrush drawingBrush=new LinearGradientBrush(rectangle, Color.Blue,Color.Gold,45);
    e.Graphics.FillRectangle(drawingBrush, rectangle);
}

р

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

private void Form1_Resize(object sender, EventArgs e)
{
    // Этот вызов объявляет форму недействительной
    // и приказывает Windows перерисовать ее.
    this.Invalidate();
}

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

Отображение динамической графики

ЗАДАЧА
Отобразить на форме комбинацию элементов, за которыми можно было бы следить с целью последующей перерисовки.

РЕШЕНИЕ
Поместите весь код рисования в обработчик события Form_Paint. При необходимости обновления формы вызывайте метод Form.Invalidate.

ОБСУЖДЕНИЕ
Во многих приложениях рисование выполняется в ответ на другие действия, такие как щелчок кнопки или самой поверхности формы. Например, это приложение рисует небольшой квадрат в любом месте формы, когда пользователь щелкает кнопку мыши:

о

В данном случае у вас есть два варианта.

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

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

// В наборе ArrayList хранятся нарисованные пользователем фигуры.
private ArrayList points = new ArrayList();

private void Form1_Paint(object sender, PaintEventArgs e)
{
    Pen drawingPen = new Pen(Color.Blue, 2);

    // Отображение всех нарисованных на данный момент фигур.
            
    foreach (Rectangle p in points)
    {
        e.Graphics.DrawRectangle(drawingPen, p);
    }
    toolStripStatusLabel1.Text = " " + points.Count.ToString() + " Points";
}

private void Form1_MouseDown(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left)
    {
        // Определение новой фигуры.
        Rectangle p = new Rectangle(e.X, e.Y, 20, 20);

        // Сохранение фигуры для последующих обновлений.
        points.Add(p);

        // Обновление части формы, в которой будет нарисована новая
        // фигура, недействительной. Windows вызовет ваш обработчик
        // события Paint и обновит только этй область.
        this.Invalidate(Rectangle.Inflate(p, 3, 3));
    }
}

При объявлении части формы недействительной будет выполнен весь ваш код рисования. Однако Windows обновит только ту часть формы, что вы указали при вызове метода Invalidate. Это значит, что мерцание экрана будет сведено к минимуму, но, если ваш обработчик события Paint очень сложен или требует длительного времени, рисование может замедлиться. В этом случае логику рисования и обновления следовало бы разделить:

// В наборе ArrayList хранятся нарисованные пользователем фигуры.
private ArrayList points = new ArrayList();

public void DrawShape(Graphics g, Rectangle shape)
{
    // Действительное рисование фигуры с использованием
    // предоставленного объекта Graphics
    Pen drawingPen = new Pen(Color.Blue, 2);
    g.DrawRectangle(drawingPen, shape);
}

private void Form1_Paint(object sender, PaintEventArgs e)
{
    // Отображение всех нарисованных на данный момент фигур.
    foreach (Rectangle p in points)
    {
        DrawShape(e.Graphics, p);
    }
}

private void Form1_MouseDown(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left)
    {
        // Определение новой фигуры.
        Rectangle p = new Rectangle(e.X, e.Y, 20, 20);

        // Сохранение фигуры для последующих обновлений.
        points.Add(p);

        // Отображение фигуры. Вы должны явно создать
        // для формы поверхность рисования GDI+
        Graphics g = this.CreateGraphics();
        DrawShape(g, p);
        g.Dispose();
        toolStripStatusLabel1.Text = " " + points.Count.ToString() + " Points";
    }
}

Проверка попадания координат щелчка в нужную область

ЗАДАЧА
Узнать, попадают ли координаты щелчка внутрь фигуры.

РЕШЕНИЕ
Проверьте координаты точки щелчка методом Rectangle.Contains или GraphicsPath.IsVisible.

ОБСУЖДЕНИЕ
Если вы включаете в программу собственные графические элементы, поддерживающие взаимодействие с пользователем, вам понадобится определять, когда указатель мыши находится внутри или вне конкретной области. Решить эту задачу помогают два метода .NET Framework. Во-первых, это метод Rectangle.Contains, который принимает точку и возвращает True, если точка относится к внутренней области прямоугольника.

Например, в предыдущую программу можно было бы добавить код проверяющий, попадает ли точка щелчка правой кнопкой мыши в один из квадратов:

private void Form1_MouseDown(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left)
    {
        // (Код рисования опущен).
    }
    else if (e.Button == MouseButtons.Right)
    {
        // Попадают ли координаты щелчка в область прямоугольника?
        Boolean inside = false;
        foreach (Rectangle rect in points)
            if (rect.Contains(e.X, e.Y))
                inside = true;

        if (inside)
            MessageBox.Show("Точка лежит внутри квадрата.");
        else
            MessageBox.Show("Точка не лежит внутри квадрата.");
    }
}

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

Второй подход заключается в использовании класса GraphicsPath из пространства имен System.Drawing.Drawing2D. Это полезно, если нужно проверить, содержится ли точка в непрямоугольной области. Сначала надо создать новый объект GraphicsPath и добавить в него фигуру (или несколько фигур). Затем вы можете вызывать метод IsVisible, передавая в него точку щелчка. Вот, например, код, создающий объект GraphicsPath, который содержит окружность и квадрат.

private GraphicsPath path = new GraphicsPath();

public Form1()
{
    InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e)
{
    path.AddEllipse(60, 60, 100, 100);
    path.AddRectangle(new Rectangle(10, 10, 50, 50));
}

Код рисования отображает GraphicsPath и закрашивает его внутреннюю область:

private void Form1_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

    Pen pen = new Pen(Color.Green, 4);
    e.Graphics.DrawPath(pen, path);
    e.Graphics.FillPath(Brushes.Yellow, path);
}

Теперь, вызвав метод IsVisible, можно узнать, щелкнул ли пользователь внутри окружности или квадрата:

private void Form1_MouseDown(object sender, MouseEventArgs e)
{
    if (path.IsVisible(e.X,e.Y))
    {
        MessageBox.Show("Вы щелкнули внутри GraphicsPath");
    }
}
 о

 

 

Используемая литература:

  1. Занимательное программирование на Bisual Basic.NET: Климов А.П.
  2. Программирование для Microsoft Windows на C#: Чарльз Петцольд.
  3. Рецепты программирования на Visual Basic.NET: Мэтью Макдональд.