21xrx.com
2025-03-22 14:57:21 Saturday
文章检索 我的文章 写文章
C++编写的绘图软件制作教程
2023-06-23 19:50:02 深夜i     14     0
C++编程 绘图软件 制作教程 图形界面 编程实现

在当今科技发展日新月异的时代,绘图软件成为了一个必不可少的工具。但是我们也很容易遇到一些熟知的问题,例如软件兼容性、软件价格较高等问题。因此,学会编写自己的绘图软件是一件非常有用的事情,而今天我们将通过C++语言为大家提供一份绘图软件的制作教程。

第一步:准备工作

在开始编写绘图软件前,需要我们先准备好相应的开发工具。首先,我们需要安装一个C++编译器,建议使用Code::Blocks或Visual Studio。其次,我们需要安装一个绘图库,例如GDI+。最后,找到一份C++入门教程。

第二步:创建工程

打开C++编译器,选择“新建工程”,然后选择“Win32应用程序”,选择“窗口应用程序”类型,输入工程名,选择存储位置完成创建。然后我们要在工程中添加一个资源文件,用于保存图像。

第三步:编写代码

在代码中,我们需要包含一系列的头文件,例如:

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <gdiplus.h>
#include <CommCtrl.h> // 用于添加滚动条

然后我们需要定义一些常量和变量:

const int WIDTH = 800;
const int HEIGHT = 600;
const int MENU_HEIGHT = 25;
const int DEFAULT_PEN_WIDTH = 5; // 默认线宽度为5
HWND hWnd;
HWND hScroll, vScroll;
HDC hdc;
HDC memHdc;
PAINTSTRUCT ps;
HBITMAP hBitmap = NULL;
Gdiplus::Status status = Gdiplus::Ok;
ULONG_PTR gdiToken;
Gdiplus::Graphics* graphics;
Gdiplus::Pen* pen;
Gdiplus::Point startPoint;
Gdiplus::Point endPoint;
bool isDrawing = false;
int penWidth = DEFAULT_PEN_WIDTH;

接着,在WinMain函数中,我们需要完成应用程序的注册和创建窗口:

// 注册应用程序
WNDCLASSEX wcex;
wcex.cbSize = sizeof (WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon (hInstance, IDI_APPLICATION);
wcex.hCursor = LoadCursor (NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon (wcex.hInstance, IDI_APPLICATION);
if (!RegisterClassEx (&wcex)) {
  MessageBox (NULL, _T("注册应用程序失败"), _T("错误"), MB_ICONERROR);
  return 0;
}
// 创建窗口
hWnd = CreateWindow (szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
           CW_USEDEFAULT, CW_USEDEFAULT, WIDTH, HEIGHT, NULL, NULL, hInstance, NULL);
if (!hWnd) {
  MessageBox (NULL, _T("创建窗口失败"), _T("错误"), MB_ICONERROR);
  return 0;
}
// 显示窗口
ShowWindow (hWnd, nCmdShow);
UpdateWindow (hWnd);

接下来,我们要在WinProc函数中添加所需的功能,例如:

switch (msg) {
case WM_CREATE:
  // 初始化GDI+
  Gdiplus::GdiplusStartupInput gdiplusStartupInput;
  status = Gdiplus::GdiplusStartup (&gdiToken, &gdiplusStartupInput, NULL);
  if (status != Gdiplus::Ok) {
    PostQuitMessage (1);
  }
  // 初始化滚动条,使得画布能够随鼠标移动
  hScroll = CreateWindow (L"SCROLLBAR", NULL, WS_CHILD | WS_VISIBLE | SBS_HORZ,
              0, HEIGHT - MENU_HEIGHT, WIDTH - GetSystemMetrics (SM_CXVSCROLL), MENU_HEIGHT, hWnd,
              (HMENU) 1001, hInstance, NULL);
  vScroll = CreateWindow (L"SCROLLBAR", NULL, WS_CHILD | WS_VISIBLE | SBS_VERT,
              WIDTH - GetSystemMetrics (SM_CXVSCROLL), 0, GetSystemMetrics (SM_CXVSCROLL),
              HEIGHT - MENU_HEIGHT, hWnd, (HMENU) 1002, hInstance, NULL);
  SetScrollRange (hScroll, SB_CTL, 0, WIDTH, FALSE);
  SetScrollRange (vScroll, SB_CTL, 0, HEIGHT, FALSE);
  SetScrollPos (hScroll, SB_CTL, 0, FALSE);
  SetScrollPos (vScroll, SB_CTL, 0, FALSE);
  break;
case WM_COMMAND:
  // 处理菜单选项
  switch (LOWORD (wParam)) {
  case ID_FILE_SAVE:
    SaveBitmap ();
    break;
  case ID_COLOR_RED:
    ChangePenColor (Gdiplus::Color (255, 0, 0));
    break;
  case ID_COLOR_GREEN:
    ChangePenColor (Gdiplus::Color (0, 255, 0));
    break;
  case ID_COLOR_BLUE:
    ChangePenColor (Gdiplus::Color (0, 0, 255));
    break;
  case ID_WIDTH_1:
    ChangePenWidth (1);
    break;
  case ID_WIDTH_3:
    ChangePenWidth (3);
    break;
  case ID_WIDTH_5:
    ChangePenWidth (5);
    break;
  case ID_WIDTH_7:
    ChangePenWidth (7);
    break;
  case ID_WIDTH_9:
    ChangePenWidth (9);
    break;
  case IDM_EXIT:
    Gdiplus::GdiplusShutdown (gdiToken);
    DestroyWindow (hWnd);
    break;
  }
  break;
case WM_SIZE:
  // 调整画布大小以适应窗口
  if (graphics != NULL)
    delete graphics;
  
  if (memHdc != NULL) {
    DeleteObject (memHdc);
  }
  int w, h;
  w = LOWORD (lParam);
  h = HIWORD (lParam);
  hdc = GetDC (hWnd);
  memHdc = CreateCompatibleDC (hdc);
  hBitmap = CreateCompatibleBitmap (hdc, w, h - MENU_HEIGHT);
  SelectObject (memHdc, hBitmap);
  ReleaseDC (hWnd, hdc);
  graphics = new Gdiplus::Graphics (memHdc);
  graphics->SetSmoothingMode (Gdiplus::SmoothingMode::SmoothingModeHighQuality);
  graphics->Clear (Gdiplus::Color (255, 255, 255));
  SetScrollRange (hScroll, SB_CTL, 0, w, FALSE);
  SetScrollRange (vScroll, SB_CTL, 0, h, FALSE);
  break;
case WM_PAINT:
  // 绘制画布
  hdc = BeginPaint (hWnd, &ps);
  graphics->Flush ();
  BitBlt (hdc, 0, 0, WIDTH, HEIGHT - MENU_HEIGHT, memHdc, 0, 0, SRCCOPY);
  EndPaint (hWnd, &ps);
  break;
case WM_LBUTTONDOWN:
  // 处理鼠标按下事件
  startPoint.X = GET_X_LPARAM (lParam);
  startPoint.Y = GET_Y_LPARAM (lParam);
  isDrawing = true;
  break;
case WM_MOUSEMOVE:
  // 处理鼠标移动事件
  if (isDrawing) {
    endPoint.X = GET_X_LPARAM (lParam);
    endPoint.Y = GET_Y_LPARAM (lParam);
    graphics->DrawLine (pen, startPoint, endPoint);
    startPoint.X = endPoint.X;
    startPoint.Y = endPoint.Y;
    InvalidateRect (hWnd, NULL, FALSE);
  }
  break;
case WM_LBUTTONUP:
  // 处理鼠标松开事件
  endPoint.X = GET_X_LPARAM (lParam);
  endPoint.Y = GET_Y_LPARAM (lParam);
  graphics->DrawLine (pen, startPoint, endPoint);
  startPoint.X = 0;
  startPoint.Y = 0;
  endPoint.X = 0;
  endPoint.Y = 0;
  isDrawing = false;
  InvalidateRect (hWnd, NULL, FALSE);
  break;
case WM_VSCROLL:
  // 处理纵向滚动条事件
  switch (LOWORD (wParam)) {
  case SB_TOP:
    SetScrollPos (vScroll, SB_CTL, 0, TRUE);
    break;
  case SB_BOTTOM:
    SetScrollPos (vScroll, SB_CTL, HEIGHT - MENU_HEIGHT, TRUE);
    break;
  case SB_LINEUP:
    SetScrollPos (vScroll, SB_CTL, GetScrollPos (vScroll, SB_CTL) - 10, TRUE);
    break;
  case SB_LINEDOWN:
    SetScrollPos (vScroll, SB_CTL, GetScrollPos (vScroll, SB_CTL) + 10, TRUE);
    break;
  case SB_PAGEUP:
    SetScrollPos (vScroll, SB_CTL, GetScrollPos (vScroll, SB_CTL) - HEIGHT / 2, TRUE);
    break;
  case SB_PAGEDOWN:
    SetScrollPos (vScroll, SB_CTL, GetScrollPos (vScroll, SB_CTL) + HEIGHT / 2, TRUE);
    break;
  case SB_THUMBPOSITION:
  case SB_THUMBTRACK:
    SetScrollPos (vScroll, SB_CTL, HIWORD (wParam), TRUE);
    break;
  }
  ScrollWindowEx (hWnd, 0, GetScrollPos (vScroll, SB_CTL) - HIWORD (wParam), NULL, NULL, NULL, NULL, SW_ERASE | SW_INVALIDATE);
  break;
case WM_HSCROLL:
  // 处理横向滚动条事件
  switch (LOWORD (wParam)) {
  case SB_LEFT:
    SetScrollPos (hScroll, SB_CTL, 0, TRUE);
    break;
  case SB_RIGHT:
    SetScrollPos (hScroll, SB_CTL, WIDTH, TRUE);
    break;
  case SB_LINELEFT:
    SetScrollPos (hScroll, SB_CTL, GetScrollPos (hScroll, SB_CTL) - 10, TRUE);
    break;
  case SB_LINERIGHT:
    SetScrollPos (hScroll, SB_CTL, GetScrollPos (hScroll, SB_CTL) + 10, TRUE);
    break;
  case SB_PAGELEFT:
    SetScrollPos (hScroll, SB_CTL, GetScrollPos (hScroll, SB_CTL) - WIDTH / 2, TRUE);
    break;
  case SB_PAGERIGHT:
    SetScrollPos (hScroll, SB_CTL, GetScrollPos (hScroll, SB_CTL) + WIDTH / 2, TRUE);
    break;
  case SB_THUMBPOSITION:
  case SB_THUMBTRACK:
    SetScrollPos (hScroll, SB_CTL, HIWORD (wParam), TRUE);
    break;
  }
  ScrollWindowEx (hWnd, GetScrollPos (hScroll, SB_CTL) - HIWORD (wParam), 0, NULL, NULL, NULL, NULL, SW_ERASE | SW_INVALIDATE);
  break;
case WM_DESTROY:
  // 窗口销毁消息
  PostQuitMessage (0);
  break;
default:
  return DefWindowProc (hWnd, msg, wParam, lParam);
}
return 0;

最后,我们需要实现一些常用功能,例如更改画笔颜色、更改画笔线宽、保存图像等:

void SaveBitmap () {
  WCHAR filePath[MAX_PATH];
  OPENFILENAME ofn;
  ZeroMemory (&ofn, sizeof (ofn));
  ofn.lStructSize = sizeof (OPENFILENAME);
  ofn.hwndOwner = hWnd;
  ofn.lpstrFilter = L"Bitmap(*.bmp)\0*.bmp\0";
  ofn.lpstrFile = filePath;
  ofn.lpstrDefExt = L"bmp";
  ofn.nMaxFile = MAX_PATH;
  ofn.Flags = OFN_OVERWRITEPROMPT;
  if (GetSaveFileName (&ofn)) {
    CLSID clsid;
    GetEncoderClsid (L"image/bmp", &clsid);
    graphics->Flush ();
    Gdiplus::Bitmap bmp (hBitmap, NULL);
    bmp.Save (filePath, &clsid, NULL);
  }
}
void ChangePenColor (Gdiplus::Color color) {
  delete pen;
  pen = new Gdiplus::Pen (color, penWidth);
}
void ChangePenWidth (int width) {
  penWidth = width;
  delete pen;
  pen = new Gdiplus::Pen (Gdiplus::Color (0, 0, 0), penWidth);
}
BOOL GetEncoderClsid (const WCHAR* format, CLSID* pClsid) {
  UINT num = 0;
  UINT size = 0;
  Gdiplus::ImageCodecInfo* pImageCodecInfo = NULL;
  Gdiplus::GetImageEncodersSize (&num, &size);
  if (size == 0) return FALSE;
  pImageCodecInfo = (Gdiplus::ImageCodecInfo*) (malloc (size));
  if (pImageCodecInfo == NULL) return FALSE;
  Gdiplus::GetImageEncoders (num, size, pImageCodecInfo);
  for (UINT i = 0; i < num; ++i) {
    if (wcscmp (pImageCodecInfo[i].MimeType, format) == 0) {
      *pClsid = pImageCodecInfo[i].Clsid;
      free (pImageCodecInfo);
      return TRUE;
    }
  }
  free (pImageCodecInfo);
  return FALSE;
}

这份代码只是一个简单的绘图软件,但是它能够满足基本的需求。如果你想要实现更多功能,可以添加更多代码逻辑。希望这个C++编写的绘图软件制作教程对你有帮助。

  
  

评论区