MFC中如何画带实心箭头的直线

本文介绍了一种在软件中绘制带有箭头的直线的方法。通过确定两点并利用旋转和平移等几何变换,实现箭头的精确绘制。文章还提供了具体的代码实现及图形填充的详细步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

工作中遇到话流程图的项目,需要画带箭头的直线,经过摸索,解决;思路如下:

(1) 两个点(p1,p2)确定一个直线,以直线的一个端点(假设p2)为原点,设定一个角度

(2)以P2为原点得到向量P2P1(P),向量P旋转theta角得到向量P1,向量P旋转-theta角得到向量P2

(3)伸缩向量至制定长度,平移变量到直线的末端

(4)现在已经有3个点了,画线就可

具体代码如下:

void CworkflowDlg::DrawLine(CPoint p1, CPoint p2)
{
        CClientDC dc(this);//获取客户窗口DC
    CPen pen,pen1,*oldpen;
    int PenLineWidth=2;//为了根据线条宽度设置箭头的大小
    pen.CreatePen(PS_SOLID, PenLineWidth, RGB(0, 0, 0));
    pen1.CreatePen(PS_SOLID, PenLineWidth, RGB(0, 0, 0));
    oldpen=dc.SelectObject(&pen);
    
    double theta=3.1415926/15*PenLineWidth;//转换为弧度
    double Px,Py,P1x,P1y,P2x,P2y;
    //以P2为原点得到向量P2P1(P)
    Px=p1.x-p2.x;
    Py=p1.y-p2.y;
    //向量P旋转theta角得到向量P1
    P1x=Px*cos(theta)-Py*sin(theta);
    P1y=Px*sin(theta)+Py*cos(theta);
    //向量P旋转-theta角得到向量P2
    P2x=Px*cos(-theta)-Py*sin(-theta);
    P2y=Px*sin(-theta)+Py*cos(-theta);
    //伸缩向量至制定长度
    double x1,x2;
    int length=10;
    x1=sqrt(P1x*P1x+P1y*P1y);
    P1x=P1x*length/x1;
    P1y=P1y*length/x1;
    x2=sqrt(P2x*P2x+P2y*P2y);
    P2x=P2x*length/x2;
    P2y=P2y*length/x2;
    //平移变量到直线的末端
    P1x=P1x+p2.x;
    P1y=P1y+p2.y;
    P2x=P2x+p2.x;
    P2y=P2y+p2.y;

    

    dc.MoveTo(p1.x,p1.y);
    dc.LineTo(p2.x,p2.y);
    dc.SelectObject(&pen1);
    dc.MoveTo(p2.x,p2.y);
    dc.LineTo(P1x,P1y);
    dc.MoveTo(p2.x,p2.y);
    dc.LineTo(P2x,P2y);

    dc.MoveTo(P1x,P1y);
    dc.LineTo(P2x,P2y);

    CPoint ptVertex[3];

    ptVertex[0].x = p2.x;
    ptVertex[0].y = p2.y;
    ptVertex[1].x = P1x;
    ptVertex[1].y = P1y;
    ptVertex[2].x = P2x;
    ptVertex[2].y = P2y;
        //填充三角形区域
    CBrush br(RGB(40,130,170));  
    CRgn rgn; 
    rgn.CreatePolygonRgn(ptVertex,3,ALTERNATE);
    dc.FillRgn(&rgn, &br);  

    dc.SelectObject(oldpen);

    br.DeleteObject();  
    rgn.DeleteObject();
}  

这里面用到CreatePolyonRgn这个函数,具体用法如下:

 

 

BOOL CRgn::CreatePolygonRgn(LPPOINT lpPoints, int nCount, int nMode);
[说明]
创建一个由一系列点围成的区域。windows在需要时自动将最后点与第一点相连以封闭多边形
[参数表]
lpPoint -------- POINTAPI,nCount个POINTAPI结构中的第一个POINTAPI结构
nCount --------- Long,多边形的点数
nPolyFillMode -- Long,描述多边形填充模式。可为ALTERNATE 或 WINDING常数。nPolyFillMode在默认情 况下为ALTERNATE;
模式ALTERNATE:其从封闭区域中的一个点向无穷远处水平画一条射线,只有当该射线穿越奇数条边框线时,封闭区域才被填充,如为偶数,则不填充该区域;
模式WINDING:方法一样,如为奇数,填充该区域;如为偶数则要根据边框线的方向来判断:如果穿过的边框线在不同方向的边框线数目相等,则不填充,如不等,则填充。
[返回值]
Long,执行成功为创建的区域句柄,失败则为0

转载于:https://www.cnblogs.com/ChinacloudTech/p/6207109.html

#include <Windows.h> #include <string> #include <vector> #include <sstream> // 定义控件ID #define ID_EDIT_AX 101 #define ID_EDIT_AY 102 #define ID_EDIT_BX 103 #define ID_EDIT_BY 104 #define ID_BUTTON_DRAW 105 #define ID_BUTTON_CLEAR 106 // 存储点坐标的结构体 struct Point { float x = 0.0f; float y = 0.0f; bool valid = false; }; // 全局变量存储点A和B Point g_pointA; Point g_pointB; // 窗口过程函数 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &ps); // 获取客户区尺寸 RECT rect; GetClientRect(hWnd, &rect); int width = rect.right - rect.left; int height = rect.bottom - rect.top; // 设置坐标系原点在左下角 int originX = 50; // 左边距 int originY = height - 50; // 下边距 // 创建白色笔用于坐标轴 HPEN hAxisPen = CreatePen(PS_SOLID, 2, RGB(255, 255, 255)); HPEN hOldPen = (HPEN)SelectObject(hdc, hAxisPen); // 绘制X轴(箭头) MoveToEx(hdc, originX, originY, NULL); LineTo(hdc, width - 20, originY); // X轴主线段 // X轴箭头 LineTo(hdc, width - 30, originY - 5); MoveToEx(hdc, width - 20, originY, NULL); LineTo(hdc, width - 30, originY + 5); // 绘制Y轴(箭头) MoveToEx(hdc, originX, originY, NULL); LineTo(hdc, originX, 20); // Y轴主线段 // Y轴箭头 LineTo(hdc, originX - 5, 30); MoveToEx(hdc, originX, 20, NULL); LineTo(hdc, originX + 5, 30); // 创建虚线笔用于网格线 HPEN hGridPen = CreatePen(PS_DOT, 1, RGB(100, 100, 100)); SelectObject(hdc, hGridPen); // 绘制网格线(每50像素一条) for (int x = originX + 50; x < width - 20; x += 50) { MoveToEx(hdc, x, originY, NULL); LineTo(hdc, x, 20); } for (int y = originY - 50; y > 20; y -= 50) { MoveToEx(hdc, originX, y, NULL); LineTo(hdc, width - 20, y); } // 恢复原始笔 SelectObject(hdc, hOldPen); DeleteObject(hAxisPen); DeleteObject(hGridPen); // 设置文本颜色为白色 SetTextColor(hdc, RGB(255, 255, 255)); SetBkColor(hdc, RGB(0, 0, 0)); // 黑色背景 // 绘制刻度标记 for (int i = 1; i <= 10; i++) { int xPos = originX + i * 50; int yPos = originY - i * 50; // X轴刻度 MoveToEx(hdc, xPos, originY - 5, NULL); LineTo(hdc, xPos, originY + 5); // Y轴刻度 MoveToEx(hdc, originX - 5, yPos, NULL); LineTo(hdc, originX + 5, yPos); // 绘制刻度值 std::wstring label = std::to_wstring(i); TextOut(hdc, xPos - 5, originY + 10, label.c_str(), label.length()); TextOut(hdc, originX - 25, yPos - 8, label.c_str(), label.length()); } // 绘制坐标轴标签 TextOut(hdc, width - 15, originY - 15, L"X", 1); TextOut(hdc, originX - 15, 15, L"Y", 1); TextOut(hdc, originX - 10, originY + 10, L"0", 1); // 如果点A和点B有效,绘制它们并连接 if (g_pointA.valid && g_pointB.valid) { // 转换为屏幕坐标 int screenAx = originX + static_cast<int>(g_pointA.x * 50); int screenAy = originY - static_cast<int>(g_pointA.y * 50); int screenBx = originX + static_cast<int>(g_pointB.x * 50); int screenBy = originY - static_cast<int>(g_pointB.y * 50); // 创建红色笔用于直线 HPEN hLinePen = CreatePen(PS_SOLID, 2, RGB(255, 0, 0)); SelectObject(hdc, hLinePen); // 绘制直线 MoveToEx(hdc, screenAx, screenAy, NULL); LineTo(hdc, screenBx, screenBy); // 创建红色实心笔用于点 HBRUSH hRedBrush = CreateSolidBrush(RGB(255, 0, 0)); HBRUSH hOldBrush = (HBRUSH)SelectObject(hdc, hRedBrush); // 绘制点A Ellipse(hdc, screenAx - 5, screenAy - 5, screenAx + 5, screenAy + 5); TextOut(hdc, screenAx + 10, screenAy - 10, L"A", 1); // 绘制点B Ellipse(hdc, screenBx - 5, screenBy - 5, screenBx + 5, screenBy + 5); TextOut(hdc, screenBx + 10, screenBy - 10, L"B", 1); // 计算并显示距离 float distance = sqrtf(powf(g_pointB.x - g_pointA.x, 2) + powf(g_pointB.y - g_pointA.y, 2)); std::wstring distText = L"距离: " + std::to_wstring(distance).substr(0, 4); TextOut(hdc, width - 150, 30, distText.c_str(), distText.length()); // 恢复并删除对象 SelectObject(hdc, hOldBrush); DeleteObject(hLinePen); DeleteObject(hRedBrush); } EndPaint(hWnd, &ps); break; } case WM_CREATE: { // 创建输入框和标签 CreateWindow(L"STATIC", L"A点 X:", WS_CHILD | WS_VISIBLE | SS_CENTER, 10, 10, 50, 20, hWnd, NULL, NULL, NULL); CreateWindow(L"EDIT", L"", WS_CHILD | WS_VISIBLE | WS_BORDER | ES_NUMBER, 65, 10, 50, 20, hWnd, (HMENU)ID_EDIT_AX, NULL, NULL); CreateWindow(L"STATIC", L"Y:", WS_CHILD | WS_VISIBLE | SS_CENTER, 120, 10, 20, 20, hWnd, NULL, NULL, NULL); CreateWindow(L"EDIT", L"", WS_CHILD | WS_VISIBLE | WS_BORDER | ES_NUMBER, 145, 10, 50, 20, hWnd, (HMENU)ID_EDIT_AY, NULL, NULL); CreateWindow(L"STATIC", L"B点 X:", WS_CHILD | WS_VISIBLE | SS_CENTER, 10, 40, 50, 20, hWnd, NULL, NULL, NULL); CreateWindow(L"EDIT", L"", WS_CHILD | WS_VISIBLE | WS_BORDER | ES_NUMBER, 65, 40, 50, 20, hWnd, (HMENU)ID_EDIT_BX, NULL, NULL); CreateWindow(L"STATIC", L"Y:", WS_CHILD | WS_VISIBLE | SS_CENTER, 120, 40, 20, 20, hWnd, NULL, NULL, NULL); CreateWindow(L"EDIT", L"", WS_CHILD | WS_VISIBLE | WS_BORDER | ES_NUMBER, 145, 40, 50, 20, hWnd, (HMENU)ID_EDIT_BY, NULL, NULL); // 创建按钮 CreateWindow(L"BUTTON", L"绘制", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 210, 10, 80, 25, hWnd, (HMENU)ID_BUTTON_DRAW, NULL, NULL); CreateWindow(L"BUTTON", L"清除", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 210, 40, 80, 25, hWnd, (HMENU)ID_BUTTON_CLEAR, NULL, NULL); // 设置默认值 SetDlgItemText(hWnd, ID_EDIT_AX, L"2"); SetDlgItemText(hWnd, ID_EDIT_AY, L"3"); SetDlgItemText(hWnd, ID_EDIT_BX, L"7"); SetDlgItemText(hWnd, ID_EDIT_BY, L"8"); break; } case WM_COMMAND: { int wmId = LOWORD(wParam); if (wmId == ID_BUTTON_DRAW) { // 获取输入的坐标值 wchar_t text[32]; GetDlgItemText(hWnd, ID_EDIT_AX, text, 32); g_pointA.x = _wtof(text); GetDlgItemText(hWnd, ID_EDIT_AY, text, 32); g_pointA.y = _wtof(text); GetDlgItemText(hWnd, ID_EDIT_BX, text, 32); g_pointB.x = _wtof(text); GetDlgItemText(hWnd, ID_EDIT_BY, text, 32); g_pointB.y = _wtof(text); // 验证坐标是否在有效范围内(0-10) g_pointA.valid = (g_pointA.x >= 0 && g_pointA.x <= 10 && g_pointA.y >= 0 && g_pointA.y <= 10); g_pointB.valid = (g_pointB.x >= 0 && g_pointB.x <= 10 && g_pointB.y >= 0 && g_pointB.y <= 10); // 重绘窗口 InvalidateRect(hWnd, NULL, TRUE); } else if (wmId == ID_BUTTON_CLEAR) { // 清除点 g_pointA.valid = false; g_pointB.valid = false; // 重绘窗口 InvalidateRect(hWnd, NULL, TRUE); } break; } case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } // 应用程序入口点 int WINAPI wWinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { // 1. 注册窗口类 const wchar_t CLASS_NAME[] = L"CartesianWindowClass"; WNDCLASSEXW wcex = { sizeof(WNDCLASSEX) }; wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.hInstance = hInstance; wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); wcex.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); // 黑色背景 wcex.lpszClassName = CLASS_NAME; if (!RegisterClassExW(&wcex)) { MessageBox(NULL, L"窗口类注册失败!", L"错误", MB_ICONERROR); return 1; } // 2. 计算窗口尺寸(包含边框和标题栏) int clientWidth = 600; int clientHeight = 600; RECT rect = { 0, 0, clientWidth, clientHeight }; AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE); int windowWidth = rect.right - rect.left; int windowHeight = rect.bottom - rect.top; // 3. 计算居中位置 int screenWidth = GetSystemMetrics(SM_CXSCREEN); int screenHeight = GetSystemMetrics(SM_CYSCREEN); int posX = (screenWidth - windowWidth) / 2; int posY = (screenHeight - windowHeight) / 2; // 4. 创建窗口 HWND hWnd = CreateWindowExW( 0, // 无扩展样式 CLASS_NAME, L"笛卡尔坐标系 - 绘制点A和点B", WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX, // 禁用调整大小和最大化 posX, posY, // 位置 windowWidth, // 宽度 windowHeight, // 高度 nullptr, nullptr, hInstance, nullptr); if (!hWnd) { MessageBox(NULL, L"窗口创建失败!", L"错误", MB_ICONERROR); return 1; } // 5. 显示窗口 ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); // 6. 消息循环 MSG msg; while (GetMessage(&msg, nullptr, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int)msg.wParam; } 将改代码修改为C语言能运行的代码,功能不变
最新发布
07-23
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值