本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:在C#编程环境中,通过GDI+实现基本图形绘制是GUI开发的重要内容。本项目提供一个基于C#的DOME程序,演示了如何动态获取并显示鼠标位置信息,并使用GDI+绘制矩形、圆形、字符串等图形元素。项目涵盖了Paint事件、鼠标事件处理、双缓冲绘图等关键技术,适合初学者掌握图形绘制与交互的基本方法,并具备良好的功能扩展性,为后续图形界面开发打下基础。
基本图形绘制学习.zip

1. C#图形绘制基础概述

C#作为一门面向对象的编程语言,凭借其强大的类库支持和与Windows平台的深度集成,广泛应用于图形界面开发领域。图形绘制不仅是用户界面设计的重要组成部分,更是实现数据可视化、游戏开发、图像处理等高级功能的基础。

本章将从图形绘制的基本概念入手,介绍C#中图形绘制的整体架构与核心组件,包括绘图模型的基本流程、绘图资源的管理方式以及绘图界面的搭建思路。通过本章学习,读者将建立起对C#图形编程的初步认知,为后续深入掌握GDI+绘图体系打下坚实基础。

2. GDI+绘图体系与核心类详解

GDI+(Graphics Device Interface Plus)是微软为Windows平台提供的一套图形绘制接口,C#作为.NET框架中的主流开发语言,通过System.Drawing命名空间封装了GDI+的底层API,为开发者提供了简洁、高效的图形编程接口。本章将深入讲解GDI+的绘图模型、核心类及其使用方式,帮助开发者掌握图形绘制的底层机制与资源管理技巧。

2.1 GDI+绘图模型与绘图流程

2.1.1 GDI+绘图模型概述

GDI+是一种基于对象的图形设备接口,它将图形绘制抽象为一系列绘图对象的操作。与传统的GDI不同,GDI+引入了面向对象的设计思想,将绘图资源(如画笔、画刷、字体等)封装为类,使得开发者可以更方便地组织和管理图形绘制逻辑。

在GDI+中,绘图模型主要包括以下几个核心概念:

  • 图形上下文(Graphics) :代表绘图表面,是所有绘图操作的起点。
  • 绘图资源(Pen、Brush、Font) :用于控制线条、填充、字体等图形元素的样式。
  • 绘图目标(如窗口、图像、打印机) :图形绘制的最终输出设备。

GDI+的绘图流程可以概括为以下步骤:

graph TD
    A[创建绘图上下文] --> B[加载绘图资源]
    B --> C[执行绘图操作]
    C --> D[释放绘图资源]
    D --> E[完成绘图]

2.1.2 图形上下文(Graphics)对象的作用与生命周期

Graphics 类是GDI+绘图的核心类,它封装了绘图设备的上下文信息,提供了绘制线条、形状、文本、图像等方法。每一个绘图操作都必须在 Graphics 对象的上下文中进行。

创建Graphics对象的常见方式:
  1. 从窗体或控件的Paint事件中获取
    csharp private void Form1_Paint(object sender, PaintEventArgs e) { Graphics g = e.Graphics; // 使用g进行绘图操作 }

  2. 从图像对象创建
    csharp Bitmap bitmap = new Bitmap(200, 200); Graphics g = Graphics.FromImage(bitmap);

  3. 从打印机上下文创建(用于打印)

生命周期管理:

Graphics 对象的生命周期通常与绘图事件绑定,例如窗体的Paint事件。开发者 不应手动释放 由PaintEventArgs提供的 Graphics 对象,否则会导致运行时异常。而对于通过 FromImage 等方式创建的 Graphics 对象,必须显式调用 Dispose() 方法进行释放,以避免资源泄漏。

2.1.3 绘图流程的组织与执行

绘图流程的组织通常遵循以下结构:

private void Draw(Graphics g)
{
    // 1. 创建绘图资源
    using (Pen pen = new Pen(Color.Red, 2))
    {
        using (Brush brush = new SolidBrush(Color.Blue))
        {
            // 2. 设置绘图状态
            g.SmoothingMode = SmoothingMode.AntiAlias;

            // 3. 执行绘图操作
            g.FillEllipse(brush, new Rectangle(50, 50, 100, 100));
            g.DrawRectangle(pen, new Rectangle(10, 10, 200, 100));
        }
    }
}
参数说明:
  • Pen :定义线条颜色、宽度、样式等属性。
  • Brush :定义填充颜色或渐变样式。
  • SmoothingMode :抗锯齿模式,提升图形质量。
  • FillEllipse / DrawRectangle :具体的绘图方法。
逻辑分析:
  • using语句 :确保绘图资源在使用完毕后自动释放。
  • 绘图状态设置 :影响后续所有绘图操作的全局设置,如抗锯齿、绘图质量等。
  • 绘图方法调用 :根据需求调用不同的绘制方法。

2.2 核心绘图类的功能与协作

2.2.1 Pen类:线条样式与绘制控制

Pen 类用于控制线条的样式、颜色、宽度等属性。它是绘制图形轮廓的关键类。

示例代码:
using (Pen redPen = new Pen(Color.Red, 3))
{
    redPen.DashStyle = DashStyle.Dash;
    g.DrawLine(redPen, new Point(0, 0), new Point(100, 100));
}
参数说明:
属性名 描述
Color 线条颜色
Width 线条宽度
DashStyle 虚线样式(Solid、Dash、Dot等)
StartCap / EndCap 线条两端的样式(如圆形、方形等)
逻辑分析:
  • 使用 using 语句确保 Pen 对象在绘制完成后被释放。
  • 设置 DashStyle 后,绘制的线条将以虚线形式呈现。
  • DrawLine 方法绘制一条从(0,0)到(100,100)的斜线。

2.2.2 Brush类:颜色填充与渐变样式

Brush 类用于图形的填充操作,常见的子类包括 SolidBrush (纯色填充)、 LinearGradientBrush (线性渐变)和 TextureBrush (纹理填充)。

示例代码:
using (Brush gradientBrush = new LinearGradientBrush(
    new Rectangle(0, 0, 100, 100),
    Color.Red,
    Color.Blue,
    LinearGradientMode.Horizontal))
{
    g.FillRectangle(gradientBrush, new Rectangle(50, 50, 100, 100));
}
参数说明:
构造函数参数 描述
Rectangle 渐变区域
Color1 / Color2 渐变起始和结束颜色
LinearGradientMode 渐变方向(Horizontal、Vertical等)
逻辑分析:
  • 创建了一个从红色到蓝色的水平渐变画刷。
  • 使用 FillRectangle 方法将渐变色填充到指定矩形区域。

2.2.3 Font类:字体样式与文本绘制

Font 类用于定义文本的字体样式、大小和效果。

示例代码:
using (Font font = new Font("Arial", 16, FontStyle.Bold | FontStyle.Italic))
{
    g.DrawString("Hello GDI+", font, Brushes.Black, new PointF(10, 10));
}
参数说明:
构造函数参数 描述
FontFamily 字体名称
Size 字号大小
FontStyle 字体样式(Bold、Italic、Underline等)
逻辑分析:
  • 创建了一个16号加粗斜体的Arial字体。
  • 使用 DrawString 方法在坐标(10,10)处绘制文本。

2.2.4 Graphics类:绘图方法与图形状态管理

Graphics 类不仅提供了绘图方法,还管理图形的绘制状态,如抗锯齿、缩放、旋转等。

示例代码:
g.SmoothingMode = SmoothingMode.AntiAlias;
g.TranslateTransform(100, 100);
g.RotateTransform(45);
g.ScaleTransform(1.5f, 1.5f);

g.DrawEllipse(Pens.Green, new Rectangle(-50, -50, 100, 100));
参数说明:
方法 描述
SmoothingMode 抗锯齿模式
TranslateTransform 平移绘图原点
RotateTransform 旋转绘图坐标系
ScaleTransform 缩放绘图比例
逻辑分析:
  • 设置抗锯齿以提升图形质量。
  • 将绘图原点移动到(100,100),并旋转45度。
  • 缩放绘图比例为1.5倍。
  • 绘制一个绿色的椭圆。

2.3 绘图资源的释放与优化

2.3.1 使用using语句管理绘图资源

绘图资源如 Pen Brush Font 等都是GDI+的非托管资源,必须显式释放。C#中推荐使用 using 语句自动管理资源生命周期。

示例代码:
using (Pen myPen = new Pen(Color.Black))
{
    g.DrawRectangle(myPen, new Rectangle(0, 0, 100, 100));
}
逻辑分析:
  • using 语句确保在代码块结束后自动调用 myPen.Dispose()
  • 避免资源泄漏,提高程序稳定性。

2.3.2 多线程绘图中的资源冲突问题

在多线程环境中,GDI+资源不能跨线程共享。每个线程应独立创建和释放绘图资源。

示例代码:
Thread thread = new Thread(() =>
{
    Bitmap bitmap = new Bitmap(200, 200);
    using (Graphics g = Graphics.FromImage(bitmap))
    {
        using (Pen pen = new Pen(Color.Green))
        {
            g.DrawEllipse(pen, new Rectangle(50, 50, 100, 100));
        }
    }
});
thread.Start();
逻辑分析:
  • 每个线程独立创建 Bitmap Graphics 对象。
  • 避免跨线程访问共享绘图资源,防止资源竞争。

2.3.3 图形资源的缓存与复用策略

为了提升性能,可以对一些常用的绘图资源进行缓存和复用,但需注意资源生命周期和线程安全性。

示例代码:
private static readonly Pen cachedPen = new Pen(Color.Red, 2);

private void DrawCached(Graphics g)
{
    g.DrawRectangle(cachedPen, new Rectangle(10, 10, 100, 100));
}
参数说明:
属性名 描述
cachedPen 静态缓存的Pen对象,避免频繁创建
逻辑分析:
  • 使用静态只读字段缓存常用绘图资源。
  • 提升绘图性能,减少资源分配开销。

本章总结
本章系统讲解了GDI+绘图体系的核心类及其协作方式,包括 Graphics Pen Brush Font 等关键类的使用方法。通过代码示例展示了绘图流程的组织方式、绘图资源的管理策略以及多线程绘图中的注意事项。掌握这些内容,将为后续的图形绘制实践打下坚实基础。

3. Form绘图事件与交互机制

在C#中,图形绘制的触发通常依赖于窗体的绘图事件和用户交互行为。本章将围绕 Form.Paint 事件、鼠标事件以及图形界面的动态更新机制展开讲解。我们将从绘图事件的触发机制开始,逐步深入到用户交互处理以及图形界面的性能优化,最终帮助开发者掌握完整的图形界面交互与绘制流程。

3.1 窗体绘图事件的触发与响应

C# 中的图形绘制主要通过 Form Paint 事件来完成。该事件在窗体需要重绘时自动触发,例如窗体首次加载、窗口大小调整、被其他窗口遮挡后恢复等。理解 Paint 事件的生命周期与调用时机,是掌握图形绘制机制的关键。

3.1.1 Form.Paint事件的生命周期与调用时机

Paint 事件属于 System.Windows.Forms.Control 类的事件之一,其原型为:

public event PaintEventHandler Paint;

当窗体或控件需要绘制时,系统会自动调用该事件。以下是常见的触发 Paint 的场景:

触发条件 描述
窗体首次加载 应用程序启动时,窗体第一次显示,会触发一次完整的绘图流程
窗口大小调整 窗体大小改变时,原有的图形布局可能失效,需要重新绘制
窗口被遮挡后恢复显示 被其他窗口遮挡后恢复显示时,需要重新绘制被覆盖的部分
手动调用 Invalidate() 方法 强制标记控件为“脏区域”,下次绘制时触发 Paint 事件
控件被移动或重绘 控件布局变化时,如拖动、隐藏、显示,也会导致重绘

这些触发机制确保了图形界面在状态变化后能够及时更新。

3.1.2 自定义绘图逻辑的绑定与重绘策略

在实际开发中,我们通常会将自定义绘图逻辑绑定到 Form.Paint 事件中。以下是一个基本的绑定示例:

public partial class MainForm : Form
{
    public MainForm()
    {
        InitializeComponent();
        this.Paint += OnFormPaint;
    }

    private void OnFormPaint(object sender, PaintEventArgs e)
    {
        // 使用 e.Graphics 对象进行绘图
        e.Graphics.DrawString("Hello GDI+", new Font("Arial", 16), Brushes.Red, new PointF(100, 100));
    }
}

在这个例子中:

  • this.Paint += OnFormPaint; :将 OnFormPaint 方法绑定到 Paint 事件。
  • e.Graphics :是 Graphics 类型的对象,用于执行具体的绘图操作。
  • DrawString 方法用于绘制文本,其参数包括字符串内容、字体、画刷、绘制位置。

绘图逻辑优化建议

  1. 避免频繁重绘 :在 Paint 中避免执行复杂计算,仅进行图形绘制。
  2. 逻辑分离 :将绘图数据与绘图逻辑分离,避免在 Paint 中处理业务逻辑。
  3. 缓存绘制结果 :对于静态内容,可以绘制到 Bitmap 中缓存,提高性能。

3.1.3 Invalidate与Update方法的使用场景

Invalidate() Update() 是控制窗体重绘的重要方法:

  • Invalidate() :标记控件为“脏区域”,等待下一次绘制。
  • Update() :强制立即重绘已标记的“脏区域”,但不会触发完整的绘制流程。
  • Refresh() :等价于 Invalidate() + Update() ,即标记并立即重绘整个控件。
使用场景示例:
private void button1_Click(object sender, EventArgs e)
{
    // 修改了图形数据,需要重绘
    this.Invalidate();  // 标记整个窗体为脏区域
    this.Update();      // 立即刷新
}

注意事项

  • 频繁调用 Refresh() 会导致性能下降,应尽量控制调用频率。
  • 如果仅需要局部刷新,可传递一个 Rectangle 参数给 Invalidate() 方法,仅标记特定区域重绘:
this.Invalidate(new Rectangle(50, 50, 100, 100));

3.2 鼠标交互事件的捕获与处理

图形界面不仅需要静态绘制,还需要响应用户的交互操作。C# 提供了丰富的鼠标事件来支持交互行为,包括 MouseMove MouseDown MouseUp 等。

3.2.1 MouseMove事件的绑定与坐标获取

MouseMove 事件用于捕捉鼠标在窗体上移动的坐标信息。以下是一个绑定示例:

public MainForm()
{
    InitializeComponent();
    this.MouseMove += OnMouseMove;
}

private void OnMouseMove(object sender, MouseEventArgs e)
{
    // 获取鼠标当前位置
    int x = e.X;
    int y = e.Y;

    // 显示坐标信息
    Console.WriteLine($"Mouse Position: ({x}, {y})");
}

在这个例子中:

  • e.X e.Y :获取鼠标在控件坐标系中的位置。
  • 通过绑定 MouseMove 事件,可以实现鼠标位置跟踪、实时绘图等功能。

3.2.2 屏幕坐标与窗体坐标的转换方法

鼠标事件返回的坐标通常是相对于当前控件的坐标。如果需要将控件坐标转换为屏幕坐标,或者将屏幕坐标转换为控件坐标,可以使用以下方法:

Point controlPoint = new Point(100, 100);
Point screenPoint = this.PointToScreen(controlPoint); // 控件坐标转屏幕坐标
Point backToControl = this.PointToClient(screenPoint); // 屏幕坐标转回控件坐标

常见转换函数

方法名 功能说明
PointToScreen 将控件坐标转换为屏幕坐标
PointToClient 将屏幕坐标转换为控件坐标
RectangleToScreen 将控件矩形区域转换为屏幕矩形
RectangleToClient 将屏幕矩形区域转换为控件矩形

3.2.3 实时绘制与鼠标位置的联动效果

我们可以结合 MouseMove 事件与 Paint 事件,实现鼠标位置联动绘制的效果。例如,绘制一个跟随鼠标移动的圆:

private Point mouseLocation = new Point(-1, -1);

private void OnMouseMove(object sender, MouseEventArgs e)
{
    mouseLocation = e.Location;
    this.Invalidate();  // 触发重绘
}

private void OnFormPaint(object sender, PaintEventArgs e)
{
    if (mouseLocation != new Point(-1, -1))
    {
        e.Graphics.FillEllipse(Brushes.Blue, mouseLocation.X - 10, mouseLocation.Y - 10, 20, 20);
    }
}

在这个例子中:

  • mouseLocation 存储鼠标位置。
  • 每次鼠标移动时更新位置并调用 Invalidate() ,触发重绘。
  • Paint 中根据鼠标位置绘制一个蓝色圆。

3.3 图形界面的动态更新与性能优化

图形界面的动态更新涉及图形的频繁重绘,若不加以控制,容易导致界面卡顿甚至崩溃。本节将介绍图形刷新机制、控制刷新频率以及双缓冲技术的实现。

3.3.1 动态图形的刷新机制

动态图形通常需要不断重绘,比如动画、实时数据可视化等。刷新机制的核心在于控制 Invalidate() Update() 的调用频率。

以下是一个简单的动态绘图示例,模拟一个旋转的矩形:

private int angle = 0;

private void timer1_Tick(object sender, EventArgs e)
{
    angle += 5;
    this.Invalidate();
}

private void OnFormPaint(object sender, PaintEventArgs e)
{
    e.Graphics.TranslateTransform(this.Width / 2, this.Height / 2); // 平移到中心
    e.Graphics.RotateTransform(angle); // 旋转
    e.Graphics.FillRectangle(Brushes.Green, new Rectangle(-50, -25, 100, 50));
}

在这个例子中:

  • 使用 Timer 控件定时更新角度。
  • 每次定时器触发时调用 Invalidate() ,触发重绘。
  • Paint 中进行旋转和平移操作,实现矩形旋转动画。

3.3.2 控制刷新频率与避免资源浪费

动态绘图时,刷新频率过高会导致 CPU 和 GPU 负载增加。以下是优化建议:

  1. 合理设置 Timer.Interval :避免设置过小的时间间隔,如设置为 10ms 可能导致性能下降。
  2. 局部刷新 :使用 Invalidate(Rectangle) 只刷新变动区域。
  3. 资源释放 :及时释放绘图资源,如 Pen Brush 等对象。

3.3.3 双缓冲技术的原理与实现方式

双缓冲(Double Buffering)是一种常见的图形界面优化技术,用于减少屏幕闪烁。其原理是先将图形绘制到一个内存位图中,再一次性绘制到屏幕上,从而减少重绘次数。

启用双缓冲的方式:
  1. 设置窗体属性
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer, true);
  1. 手动实现双缓冲
private Bitmap bufferBitmap;
private Graphics bufferGraphics;

private void InitializeDoubleBuffer()
{
    bufferBitmap = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);
    bufferGraphics = Graphics.FromImage(bufferBitmap);
}

private void OnFormPaint(object sender, PaintEventArgs e)
{
    // 绘制到内存位图
    bufferGraphics.Clear(this.BackColor);
    bufferGraphics.FillEllipse(Brushes.Red, 50, 50, 100, 100);

    // 一次性绘制到屏幕
    e.Graphics.DrawImage(bufferBitmap, Point.Empty);
}

双缓冲流程图(Mermaid)

graph TD
    A[绘制到内存位图] --> B[清除旧图像]
    B --> C[绘制新图形]
    C --> D[一次性绘制到屏幕]

通过双缓冲技术,可以显著提升图形界面的流畅度,尤其适用于频繁重绘的场景。

4. 基础图形元素的绘制实践

本章将进入具体的图形绘制实践阶段,围绕矩形、圆形、文本等基础图形元素的绘制方法,深入讲解如何通过GDI+类库实现图形输出。我们将从基本的绘制方法入手,逐步介绍图形样式的设计与实现,并最终探讨图形绘制的封装与扩展方式。通过本章的学习,读者将能够掌握C#中常见的图形元素绘制技巧,并具备构建基础图形绘制模块的能力。

4.1 图形元素的绘制方法

在GDI+中,图形元素的绘制主要通过 Graphics 类提供的绘制方法完成。常见的图形元素包括矩形、椭圆、文本等,这些图形都可以通过相应的 DrawXXX 方法进行绘制。本节将分别介绍 DrawRectangle DrawEllipse DrawString 方法的使用方式及其参数意义。

4.1.1 DrawRectangle方法的使用与参数解析

DrawRectangle 方法用于在指定的绘图表面上绘制矩形。该方法是 Graphics 类中非常基础且常用的方法之一。

public void DrawRectangle(Pen pen, Rectangle rect);
  • Pen :用于定义矩形边框的颜色、宽度和样式。
  • Rectangle :表示要绘制的矩形区域,通常由左上角坐标和宽高定义。
示例代码:
private void Form1_Paint(object sender, PaintEventArgs e)
{
    Pen blackPen = new Pen(Color.Black, 2); // 创建黑色边框,线宽为2
    Rectangle rect = new Rectangle(50, 50, 200, 100); // 矩形左上角坐标(50,50),宽200,高100
    e.Graphics.DrawRectangle(blackPen, rect); // 绘制矩形
}
逐行解析:
  • 第1行:创建一个 Pen 对象,颜色为黑色,线条宽度为2像素。
  • 第2行:定义一个 Rectangle 对象,表示矩形的位置和大小。
  • 第3行:调用 Graphics.DrawRectangle 方法,传入 Pen Rectangle 参数进行绘制。

注意 :绘制矩形时,矩形的边框是向外扩展的。例如,一个宽为200像素的矩形,其实际占用的宽度为200 + Pen.Width * 2 / 2(每边扩展Pen.Width/2像素)。

4.1.2 DrawEllipse方法的绘制逻辑与应用场景

DrawEllipse 方法用于绘制椭圆。其参数结构与 DrawRectangle 类似,都是通过 Rectangle 来定义图形的位置和大小。

public void DrawEllipse(Pen pen, Rectangle rect);

该方法会根据传入的矩形区域绘制一个椭圆,其边界与矩形相同。若矩形是正方形,则绘制的是圆形。

示例代码:
private void Form1_Paint(object sender, PaintEventArgs e)
{
    Pen redPen = new Pen(Color.Red, 2);
    Rectangle ellipseRect = new Rectangle(100, 100, 150, 100);
    e.Graphics.DrawEllipse(redPen, ellipseRect);
}
逐行解析:
  • 第1行:创建红色画笔。
  • 第2行:定义一个矩形区域,作为椭圆的边界。
  • 第3行:调用 DrawEllipse 方法绘制椭圆。
应用场景:
  • 图形界面中的圆形按钮、进度环。
  • 数据可视化中的饼图、雷达图等。
  • 游戏开发中的碰撞检测区域(圆形)。

4.1.3 DrawString方法的文本布局与对齐控制

DrawString 方法用于在绘图表面上绘制文本。其功能较为复杂,支持字体、颜色、对齐方式等参数设置。

public void DrawString(string s, Font font, Brush brush, RectangleF layoutRectangle, StringFormat format);
  • s :要绘制的字符串。
  • font :定义文本的字体、大小和样式。
  • brush :定义文本颜色。
  • layoutRectangle :定义文本的布局区域。
  • format :控制文本的对齐方式、换行等格式。
示例代码:
private void Form1_Paint(object sender, PaintEventArgs e)
{
    string text = "Hello, GDI+!";
    Font font = new Font("Arial", 16, FontStyle.Bold);
    Brush blueBrush = new SolidBrush(Color.Blue);
    RectangleF layoutRect = new RectangleF(50, 50, 200, 100);
    StringFormat format = new StringFormat();
    format.Alignment = StringAlignment.Center; // 水平居中
    format.LineAlignment = StringAlignment.Center; // 垂直居中

    e.Graphics.DrawString(text, font, blueBrush, layoutRect, format);
}
逐行解析:
  • 第1行:定义要显示的文本内容。
  • 第2行:创建字体对象,字体为Arial,大小16,加粗。
  • 第3行:创建蓝色画笔。
  • 第4行:定义文本布局区域。
  • 第5~7行:设置文本对齐方式为居中。
  • 第8行:调用 DrawString 方法绘制文本。
对齐方式对照表:
对齐方式 描述
StringAlignment.Near 靠近布局区域边缘
StringAlignment.Center 居中对齐
StringAlignment.Far 远离布局区域边缘

4.2 图形样式的设计与实现

在图形绘制中,除了形状本身,样式(如线条颜色、填充方式、字体风格等)也是决定视觉效果的重要因素。本节将介绍如何通过 Pen Brush Font 对象来控制图形的外观样式。

4.2.1 Pen对象的线条样式与宽度设置

Pen 类用于定义图形的轮廓样式,包括颜色、宽度、虚线样式等。

classDiagram
    class Pen {
        +Color Color
        +float Width
        +DashStyle DashStyle
        +Pen(Color color, float width)
    }
示例代码:
Pen dashedPen = new Pen(Color.Green, 2);
dashedPen.DashStyle = DashStyle.DashDot;
e.Graphics.DrawRectangle(dashedPen, new Rectangle(100, 100, 150, 100));
参数说明:
  • Color :线条颜色。
  • Width :线条宽度,单位为像素。
  • DashStyle :线条样式,如实线、虚线、点划线等。
不同线条样式对比表:
DashStyle 显示效果
Solid 实线
Dash 虚线
Dot 点线
DashDot 点划线

4.2.2 Brush对象的填充方式与颜色渐变

Brush 类用于定义图形的填充样式。常见的 Brush 类型包括 SolidBrush (实色填充)、 LinearGradientBrush (线性渐变)和 TextureBrush (纹理填充)。

示例代码(线性渐变):
Rectangle rect = new Rectangle(50, 50, 200, 100);
LinearGradientBrush lgb = new LinearGradientBrush(rect, Color.Red, Color.Yellow, LinearGradientMode.Horizontal);
e.Graphics.FillRectangle(lgb, rect);
参数说明:
  • rect :填充区域。
  • Color.Red :起始颜色。
  • Color.Yellow :结束颜色。
  • LinearGradientMode.Horizontal :渐变方向为水平。
渐变方向对照表:
渐变方向 描述
Horizontal 水平方向渐变
Vertical 垂直方向渐变
ForwardDiagonal 从左上到右下渐变
BackwardDiagonal 从右上到左下渐变

4.2.3 Font对象的字体大小、样式与抗锯齿设置

Font 类用于定义文本的字体、大小和样式。通过设置抗锯齿属性,可以提升文本的渲染质量。

Font font = new Font("Arial", 14, FontStyle.Italic | FontStyle.Bold, GraphicsUnit.Pixel);
e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
e.Graphics.DrawString("Anti-aliased Text", font, Brushes.Black, new PointF(10, 10));
参数说明:
  • "Arial" :字体名称。
  • 14 :字体大小。
  • FontStyle.Italic | FontStyle.Bold :斜体加粗。
  • GraphicsUnit.Pixel :单位为像素。
  • TextRenderingHint.AntiAlias :启用抗锯齿渲染。
字体样式组合示例:
样式组合 显示效果
FontStyle.Regular 常规
FontStyle.Bold 加粗
FontStyle.Italic 斜体
FontStyle.Underline 下划线
FontStyle.Strikeout 删除线

4.3 图形绘制的扩展与封装

在实际开发中,我们往往需要将图形绘制逻辑封装成可复用的组件或方法,以提高代码的可维护性和扩展性。本节将介绍自定义绘图函数的封装策略、常用图形绘制工具类的设计方法以及图形元素的组合绘制技巧。

4.3.1 自定义绘图函数的封装策略

将常用的图形绘制操作封装为函数,可以提高代码的复用性。例如,我们可以封装一个绘制带边框的矩形函数。

public void DrawFramedRectangle(Graphics g, Color borderColor, Color fillColor, Rectangle rect)
{
    using (Brush fillBrush = new SolidBrush(fillColor))
    {
        g.FillRectangle(fillBrush, rect);
    }
    using (Pen borderPen = new Pen(borderColor))
    {
        g.DrawRectangle(borderPen, rect);
    }
}
调用示例:
DrawFramedRectangle(e.Graphics, Color.Blue, Color.LightBlue, new Rectangle(100, 100, 150, 100));

说明 :该函数接受绘图上下文、边框颜色、填充颜色和矩形区域作为参数,先填充矩形再绘制边框,确保图形的完整性和层次感。

4.3.2 常用图形绘制工具类的设计

为了进一步提高图形绘制的模块化程度,可以设计一个图形绘制工具类 DrawingHelper ,集中管理常用图形的绘制逻辑。

public static class DrawingHelper
{
    public static void DrawRoundedRectangle(Graphics g, Pen pen, Brush brush, Rectangle rect, int cornerRadius)
    {
        using (GraphicsPath path = new GraphicsPath())
        {
            path.AddArc(rect.X, rect.Y, cornerRadius, cornerRadius, 180, 90);
            path.AddArc(rect.X + rect.Width - cornerRadius, rect.Y, cornerRadius, cornerRadius, 270, 90);
            path.AddArc(rect.X + rect.Width - cornerRadius, rect.Y + rect.Height - cornerRadius, cornerRadius, cornerRadius, 0, 90);
            path.AddArc(rect.X, rect.Y + rect.Height - cornerRadius, cornerRadius, cornerRadius, 90, 90);
            path.CloseFigure();

            if (brush != null) g.FillPath(brush, path);
            if (pen != null) g.DrawPath(pen, path);
        }
    }
}
调用示例:
DrawingHelper.DrawRoundedRectangle(e.Graphics, Pens.Red, Brushes.LightPink, new Rectangle(50, 50, 200, 100), 20);

说明 :该工具方法用于绘制带圆角的矩形,通过 GraphicsPath 路径绘制实现,支持填充和边框。

4.3.3 图形元素的组合与复合绘制技巧

在实际开发中,单一图形往往不能满足需求,我们常常需要将多个图形组合使用,以构建更复杂的图形效果。例如,绘制一个包含文本的按钮图形。

public void DrawTextButton(Graphics g, string text, Rectangle rect, Color backColor, Color borderColor, Font font, Color textColor)
{
    using (Brush backBrush = new SolidBrush(backColor))
    {
        g.FillRectangle(backBrush, rect);
    }

    using (Pen borderPen = new Pen(borderColor))
    {
        g.DrawRectangle(borderPen, rect);
    }

    StringFormat format = new StringFormat();
    format.Alignment = StringAlignment.Center;
    format.LineAlignment = StringAlignment.Center;

    using (Brush textBrush = new SolidBrush(textColor))
    {
        g.DrawString(text, font, textBrush, rect, format);
    }
}
调用示例:
DrawTextButton(e.Graphics, "Click Me", new Rectangle(100, 100, 150, 50), Color.LightGray, Color.Gray, new Font("Arial", 12), Color.Black);

说明 :该方法将矩形绘制、边框绘制和文本绘制结合,形成一个按钮样式的图形,适用于界面设计中的控件模拟。

本章系统讲解了C#中基础图形元素的绘制方法、样式控制方式以及图形绘制的封装与扩展策略。通过具体代码示例和参数解析,读者可以掌握矩形、椭圆、文本等常见图形的绘制方式,并能够灵活运用样式设置来增强图形的视觉表现。此外,通过封装图形绘制函数和工具类的设计,进一步提升了代码的可复用性和项目结构的清晰度,为后续更复杂的图形开发打下坚实基础。

5. 综合项目实践与图形功能扩展

在掌握了基础图形绘制方法与事件交互机制后,本章将以一个完整的DOME项目为例,系统性地展示图形绘制功能的整合与扩展方式。

5.1 DOME项目的结构与代码组织

5.1.1 项目模块划分与类结构设计

为了构建一个结构清晰、易于维护的图形绘制项目,我们采用模块化设计。一个典型的DOME项目结构如下:

DomeDrawingApp/
├── Models/
│   ├── Shape.cs
│   ├── RectangleShape.cs
│   └── CircleShape.cs
├── Views/
│   └── DrawingCanvas.cs
├── Controllers/
│   └── DrawingController.cs
├── Services/
│   └── ShapeService.cs
└── Program.cs
  • Models :定义图形对象的基类和具体图形类。
  • Views :实现绘图区域,继承自 Panel Control ,并重写 OnPaint 方法。
  • Controllers :处理用户交互事件,如鼠标点击、拖拽等。
  • Services :封装图形逻辑和数据处理逻辑,实现解耦。

5.1.2 图形绘制逻辑与业务逻辑的分离

为了提高代码的可维护性,我们通过接口和依赖注入实现图形绘制逻辑与业务逻辑的分离。例如:

public interface IShapeRenderer
{
    void Draw(Graphics g);
}

public class RectangleShape : IShapeRenderer
{
    public Rectangle Bounds { get; set; }
    public Pen BorderPen { get; set; }

    public void Draw(Graphics g)
    {
        g.DrawRectangle(BorderPen, Bounds);
    }
}

DrawingCanvas 中调用:

protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint(e);
    foreach (var shape in _shapes)
    {
        shape.Draw(e.Graphics);
    }
}

5.1.3 事件驱动与状态管理的实现方式

我们通过事件订阅机制实现用户交互的响应。例如,鼠标点击选择图形对象:

public class DrawingController
{
    private readonly DrawingCanvas _canvas;
    private List<IShapeRenderer> _shapes;

    public DrawingController(DrawingCanvas canvas, List<IShapeRenderer> shapes)
    {
        _canvas = canvas;
        _shapes = shapes;
        _canvas.MouseDown += OnCanvasMouseDown;
    }

    private void OnCanvasMouseDown(object sender, MouseEventArgs e)
    {
        foreach (var shape in _shapes)
        {
            if (shape is RectangleShape rect && rect.Bounds.Contains(e.Location))
            {
                SelectedShape = shape;
                break;
            }
        }
    }
}

通过这种方式,可以实现图形的选中、拖动、删除等交互操作。

5.2 图形功能的扩展实践

5.2.1 图形元素的拖拽与移动实现

实现图形拖拽功能的关键在于记录鼠标按下时的坐标,并在鼠标移动时更新图形位置:

private Point _startPoint;
private IShapeRenderer _selectedShape;

private void OnCanvasMouseDown(object sender, MouseEventArgs e)
{
    // 查找被选中的图形
    foreach (var shape in _shapes)
    {
        if (shape is RectangleShape rect && rect.Bounds.Contains(e.Location))
        {
            _selectedShape = shape;
            _startPoint = e.Location;
            break;
        }
    }
}

private void OnCanvasMouseMove(object sender, MouseEventArgs e)
{
    if (_selectedShape != null)
    {
        int dx = e.X - _startPoint.X;
        int dy = e.Y - _startPoint.Y;
        if (dx != 0 || dy != 0)
        {
            if (_selectedShape is RectangleShape rect)
            {
                rect.Bounds = new Rectangle(rect.Bounds.X + dx, rect.Bounds.Y + dy, rect.Bounds.Width, rect.Bounds.Height);
            }
            _startPoint = e.Location;
            _canvas.Invalidate(); // 触发重绘
        }
    }
}

private void OnCanvasMouseUp(object sender, MouseEventArgs e)
{
    _selectedShape = null;
}

5.2.2 图形对象的选择与高亮机制

可以通过设置一个选中状态来实现图形的高亮显示。例如:

public class RectangleShape : IShapeRenderer
{
    public Rectangle Bounds { get; set; }
    public Pen BorderPen { get; set; }
    public bool IsSelected { get; set; }

    public void Draw(Graphics g)
    {
        g.DrawRectangle(BorderPen, Bounds);
        if (IsSelected)
        {
            using (var highlightPen = new Pen(Color.Red, 2))
            {
                highlightPen.DashStyle = DashStyle.Dot;
                g.DrawRectangle(highlightPen, Bounds);
            }
        }
    }
}

在控制器中设置 IsSelected = true 即可触发高亮效果。

5.2.3 图形绘制与数据绑定的结合应用

我们可以通过绑定图形对象的属性,实现图形状态与数据模型的同步。例如:

public class ShapeViewModel : INotifyPropertyChanged
{
    private Rectangle _bounds;
    public Rectangle Bounds
    {
        get => _bounds;
        set
        {
            _bounds = value;
            OnPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string name = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }
}

然后在绘图类中监听属性变化并更新图形:

viewModel.PropertyChanged += (sender, args) =>
{
    if (args.PropertyName == nameof(viewModel.Bounds))
    {
        rectangleShape.Bounds = viewModel.Bounds;
        canvas.Invalidate();
    }
};

5.3 图形系统的可维护性与可扩展性优化

5.3.1 图形绘制组件的模块化设计

将图形绘制组件封装为独立的类库,便于复用和测试。例如,可以将图形绘制部分封装为一个 DrawingLibrary.dll ,供多个项目引用。

5.3.2 使用接口与抽象类提升扩展性

通过定义 IShapeRenderer 接口和 ShapeBase 抽象类,可以轻松扩展新的图形类型:

public abstract class ShapeBase : IShapeRenderer
{
    public abstract void Draw(Graphics g);
}

public class CircleShape : ShapeBase
{
    public Rectangle Bounds { get; set; }
    public Pen BorderPen { get; set; }

    public override void Draw(Graphics g)
    {
        g.DrawEllipse(BorderPen, Bounds);
    }
}

这样,新增图形类型只需继承 ShapeBase 并实现 Draw 方法即可。

5.3.3 图形配置与样式管理的外部化处理

可以将图形样式(如颜色、线条粗细、字体等)保存在配置文件中,实现运行时动态加载:

{
  "Shapes": {
    "Rectangle": {
      "Color": "Blue",
      "LineWidth": 2
    },
    "Circle": {
      "Color": "Red",
      "LineWidth": 3
    }
  }
}

在运行时读取配置并创建图形对象:

var config = JsonConvert.DeserializeObject<ShapeConfig>(File.ReadAllText("config.json"));
var rectPen = new Pen(Color.FromName(config.Shapes.Rectangle.Color), config.Shapes.Rectangle.LineWidth);

通过这种方式,可以灵活调整图形样式,无需重新编译程序。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:在C#编程环境中,通过GDI+实现基本图形绘制是GUI开发的重要内容。本项目提供一个基于C#的DOME程序,演示了如何动态获取并显示鼠标位置信息,并使用GDI+绘制矩形、圆形、字符串等图形元素。项目涵盖了Paint事件、鼠标事件处理、双缓冲绘图等关键技术,适合初学者掌握图形绘制与交互的基本方法,并具备良好的功能扩展性,为后续图形界面开发打下基础。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

更多推荐