MFC 开发记录

改BUG心得

  1. 指针使用,平时指针直接传参是因为我们修改的是指针指向的区域;如果要修改指针本身,则要加引用

    • 指针是null,在函数中new:加&
    • 类中指针,通过函数get出来,想修改类内指针:不能直接对get出来的指针赋值,需要Set或者将类内变量改为public,直接对类内属性赋值
  2. 对话框相关

    1. 遇到不适当参数:MessageMap中的资源出问题了
    2. 崩了:DodataExchange中的资源出问题了
  3. 如果一个元素需要再该方法之外的地方进行使用,那么一定要使用指针

    我在A中声明一个类变量,将其赋值给一个公共成员,在B中进行使用,但是会发现,得到的内存地址都是正确的,只是无法使用,原因便是A中的函数定义完变量之后,在函数结束后,会将该成员变量释放,导致之后使用会无效,所以需要使用指针,这样就会一直存在于内存,可以随时读取,不过要记得在最后将其释放掉

    void CDlgLayerTree::InitTreeStruct(CString rootName)
    {
    	//AfxSetResourceHandle(((CDrawingFrame*)AfxGetMainWnd())->dllRc);
    	CImageList* m_FileViewImages = new CImageList();
    	bool asd = m_FileViewImages->Create(IDB_FILE_VIEW, 16, 16, RGB(255, 0, 0));
    	int n = m_FileViewImages->GetImageCount();
    	m_layerTree.SetImageList(m_FileViewImages, TVSIL_NORMAL);
    	//AfxSetResourceHandle(((CDrawingFrame*)AfxGetMainWnd())->currnetProRc);
    }
    
    void CDlgLayerTree::InitTreeStruct(CString rootName)
    {
    	//AfxSetResourceHandle(((CDrawingFrame*)AfxGetMainWnd())->dllRc);
    	CImageList m_FileViewImages = new CImageList();
    	bool asd = m_FileViewImages.Create(IDB_FILE_VIEW, 16, 16, RGB(255, 0, 0));
    	int n = m_FileViewImages.GetImageCount();
    	m_layerTree.SetImageList(m_FileViewImages, TVSIL_NORMAL);
    	//AfxSetResourceHandle(((CDrawingFrame*)AfxGetMainWnd())->currnetProRc);
    }
    

    以上两种写法看似没区别,但是实际差距很大

    void CRHTree::DrawItemImage(CDC* pDC, CRect rCell, HTREEITEM hItem, BOOL bSelected)
    // Draws an item's image from the tree's list
    {
    	CImageList* pIL = this->GetImageList(TVSIL_NORMAL);
    	int n = pIL->GetImageCount();
    }
    

    按照第一种写法,则Get出来的imagelist就是充实的,n是有值的;

    如果按照第二种写法,看似没影响,Get出来的地址也是相同的,根本找不到问题,但是n却是0,表明该list根本不存在,已经被释放掉了

    所以这个bug十分隐晦,如果经验不足很容易掉进坑里,尤其是这种错误有时候不会报错,也不会影响程序运行,根本无法定位

    • 2023/11/16 第二次踩坑,原因是读取图片数据用的是临时变量,但是传递的是指针;如果使用指针经常会忘掉,那不如直接对象赋值,这样会拷贝一份,不会释放数据
  4. 资源文件要对应,多个项目尤其是设计资源文件转化的时候,如果资源调用与预期设计不符,那一定是那个地方资源文件切换有问题,一点点定位就好**(着重找资源文件使用的函数)**

    FindResource();
    LockResource();
    
    • 找相应的接收变量,看变量的赋值过程
    • 找赋值最初是如何调用的资源ID
    • 根据ID确定该使用哪个rc句柄,进行切换
    HINSTANCE save_hInstance = AfxGetResourceHandle();
    HINSTANCE   hDll = LoadLibrary(_T("rc.dll"));
    
    AfxSetResourceHandle(dllRc);
    
    HINSTANCE currnetProRc = save_hInstance;
    HINSTANCE dllRc = hDll;
    
    AfxSetResourceHandle(currnetProRc);
    
  5. 控件无法显示完全:onInitDialog没有调用父类方法

    BOOL CDrawing::OnInitDialog()
    {
    	CDialog::OnInitDialog();
    }
    
  6. OnInitDialog 不支持的参数

    1. rc文件中的资源和DoDataExchange中的资源出现了链接问题
    2. rc Dll 没有编译或者编译的位置不对【x64环境的dll编译到了32】
  7. 无法解析的外部符号

    1. class _DACSDATA_EXT_CLASS ClassObj: public CDacsObj
  8. 区分

    		for (auto polygon : polygonList)
            {
                polygon.x = 1;
            }
    		for (int polI = 0; polI < polygonList.size(); polI++)
            {
                polygonList[i].x = 1;
            }
    

    两种有什么区别:第一种会保护原数据,第二种会污染原数据,等价起来应写为

    for (int polI = 0; polI < polygonList.size(); polI++)
            {
                auto polygon = polygonList[i];
        		polygon.x = 1;
            }
    

    这是个很容易忽视的小地方,容易想当然,浪费了好几个小时

  9. 找坑

    				int hidI = 0;
    				for(auto hidePol : hidePolygonList)
    				{
    					hidI++;
    					for (int ptI = 0; ptI < hidePol.points.size(); ptI++)
    					{
    						Hge::Vec2f tempVec;
    						tempVec.y() = (hidePol.points[ptI].y() + 1) * pViewReport->height() / 2;
    						tempVec.x() = (hidePol.points[ptI].x() + 1) * pViewReport->width() / 2;
    						tempVec.y() = pViewReport->height() - tempVec.y();
    
    						hidePol.points[ptI] = tempVec;
    					}
    
    					if (polygonOverAllIdx[polI] < hidepolygonOverAllIdx[hidI] || insideH == true) break;
    					if (polygonOverAllIdx[polI] < hidepolygonValuable[hidI] || hidepolygonValuable[hidI] == -1)
    					{
    						insideH = pointInPolygon(pos,
    							hidePol.points,
    							polygonBoxMax[polI],
    							polygonBoxMin[polI],
    							hidePol.wvp,
    							pViewReport->width(),
    							pViewReport->height()
    						);
    						if (insideH == true)
    						{
    							//break;
    						}
    					}
    

    找到了吗,坑在于hidI在后面使用了,但是在进入循环的时候直接被更新掉,所以对不起来,就会有bug,浪费一个小时

  10. 是否保存修改

AfxFormatString1(prompt, AFX_IDP_ASK_TO_SAVE, name);

GDI Drawing

首先要明白,GDIGDI+不是一个东西,GDI是C++最基础的画图工具,实现比较简单,GDI+是相对复杂的画图体系,与OpenGL以及DirectX类似

分别讲一下GDIGDI+画图的步骤

GDI

  • 代码

    void CMyView::DrawWithGDI(CDC* pDC, CPoint point)
    {
        HDC pHDC = pDC->GetSafeHdc();
        HPEN pen;
    
        pen = CreatePen(PS_DOT, penWidth, penColor);
        SelectObject(pHDC, pen);
    
        MoveToEx(pHDC, point.x, point.y, NULL);
        LineTo(pHDC, lastMousePos.x, lastMousePos.y);
    }
    
  • 画图的时候会需要一个绘图上下文HDC:标榜的是设备环境,可以理解为往哪里画

    • CDCMFC特有的上下文信息,可以通过GetDC获取,或者通过消息函数自带的参数,而通过GetSafeHdc可以获取到HDC
  • GDI画图需要的画笔为HPEN,当然还有其他类型比如Brush等等,使用之前需要先创建

    参数

    • pen画出线的类型
    • 粗细
    • 颜色(COLORREF
  • SelectObject:为特定的HDC选取所使用的obj,这里选取的是pen,表示之后画图等操作使用的是pen

    • 如果之后需要切换操作,比如换成笔刷,需要重新选择obj
    • 后续的操作不会以pen作为参数,而是以HDC作为参数,需要注意,所以使用之前需要先选择obj
  • MoveToEX:将操作移动到特定位置

  • LineTo:画一条线

GDI画线的时候用的是先移动画笔,然后落笔,而GDI+中可以直接定位两点连线

GDI Plus

  • demo代码

    #include <gdiplus.h>
    {
        Gdiplus::GdiplusStartupInput gdiplusStartupInput;
        ULONG_PTR gdiplusToken;
        // Initialize GDI+.
        GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
    
         HDC hdc;
         Gdiplus::Graphics* graphics;
         Gdiplus::Pen* pen;
    
         hdc = pDC->GetSafeHdc();
         graphics = new  Gdiplus::Graphics(hdc);
         pen = new  Gdiplus::Pen(Gdiplus::Color(GetRValue(penColor), GetGValue(penColor), GetBValue(penColor)), penWidth);
    
         pen->SetStartCap(Gdiplus::LineCapRound);
         pen->SetEndCap(Gdiplus::LineCapRound);
    
         graphics->DrawLine(pen, lastMousePos.x, lastMousePos.y, point.x, point.y);
    
         delete graphics;
         delete pen;
    }
    
  • 使用GDI+之前,首先需要初始化GDI+环境

    •     Gdiplus::GdiplusStartupInput gdiplusStartupInput;
          ULONG_PTR gdiplusToken;
          // Initialize GDI+.
          GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
      
    • 具体干啥不太明白,但是加上就行了

  • GDI类似,也需要一个Pen,区别是GDI+用的是Graphics进行操作

  • Graphics创建的时候将hdc作为参数,保证绘图的窗口能显示

  • Pen:正常创建

  • pen->SetStartCap(Gdiplus::LineCapRound);:设置开始和结束的类型

    • GDI中,默认划线图元是一个小圆点,这样画出来的线就是丰满的
    • 但是GDI+中,默认是一条垂直的线,这样画的时候,就会次次毛毛的,看着不好看
    • 所以需要换成点,其实参数中还有其他数值,比如方块,三角形等等,效果都不如圆点好,因此这里换成圆点
  • 正常的DrawLine,其他的绘图方法在MS的官方文档中有,写的还是很详细的,直接套用就可以

GDI+相对于GDI中很牛的一个改变,就是GDI+可以存储路径,并且进行读取

其实路径存取有很多方法,最笨的就是将所有画出来的点都存到vec中,但是这样会很吃内存,而且效率会比较低

目前没想到什么好方法,而GDI+自带的方法目前也只是存取显示可以正常,关闭再打开显示的时候总会有一点小毛病,看看之后如果不行就得换到OpenGL

文件存取

  • Metafile metafile(L"SampleMetafile.emf", hdc); 
    {
       Graphics graphics(&metafile);
       Pen greenPen(Color(255, 0, 255, 0));
       SolidBrush solidBrush(Color(255, 0, 0, 255));
    
       // Add a rectangle and an ellipse to the metafile.
       graphics.DrawRectangle(&greenPen, Rect(50, 10, 25, 75));
       graphics.DrawEllipse(&greenPen, Rect(100, 10, 25, 75));
    
       // Add an ellipse (drawn with antialiasing) to the metafile.
       graphics.SetSmoothingMode(SmoothingModeHighQuality);
       graphics.DrawEllipse(&greenPen, Rect(150, 10, 25, 75));
    
       // Add some text (drawn with antialiasing) to the metafile.
       FontFamily fontFamily(L"Arial");
       Font font(&fontFamily, 24, FontStyleRegular, UnitPixel);
       
       graphics.SetTextRenderingHint(TextRenderingHintAntiAlias);
       graphics.RotateTransform(30.0f);
       graphics.DrawString(L"Smooth Text", 11, &font, 
          PointF(50.0f, 50.0f), &solidBrush);
    } // End of recording metafile.
    
    // Play back the metafile.
    Graphics playbackGraphics(hdc);
    playbackGraphics.DrawImage(&metafile, 200, 100);
    
  • 代码是在官方文档中抄的,没什么注意点挺好理解的

    • 想要实现存储的目的,一定要给文件一个结束信号,比如官方文档中,将graphics定义到大括号内部,在大括号结束的时候,graphics的生命周期默认结束,此时会识别到Recording结束,这样才能正常存储
    • 如果不加大括号,那可以用指针,在画完的时候手动delete或者release,总之要明确告诉文件,Recording的行为结束了
    • 否则存的就是空的
  • 有几个问题没解决,很烦

    • 文件存储完毕,关掉程序再打开,读取会显示不出来

      可能与DrawImage的位置有关系?

    • 有时候能读取出来,但是读取出来的只是一道小杠杠,画了,但是画的不对

    • 边画边存,比如这个demo,画的图形是正常的,但是用Line,定位之前和现在的pos画出来的就很奇怪,会缩在一团,而且有时候会突然蹦出好长一条线,尽可能填满之后,发现缩在了view中间的一块方形区域,很纳闷

字符转换

在MFC(Microsoft Foundation Classes)中,CString 类提供了两种不同的字符集编码版本:CStringA(ANSI 字符集)和 CStringW(Unicode 字符集)。函数 CT2A 是用于将 CString 转换为 ANSI 字符集的 C 字符串(const char*)的宏。

如果你使用 CT2A(cstring.GetBuffer(0)) 进行转换,它的工作原理是将 CString 转换为 const char*,然后再构造一个 std::string。这个方法使用了 CT2A 宏来执行 ANSI 字符集的转换。

但是,cstring.GetBuffer(0) 返回的是 TCHAR* 指针,它的类型可能是 char*wchar_t*,具体取决于你的项目的字符集设置。如果项目使用 Unicode 字符集,GetBuffer(0) 返回的是 wchar_t*,而不是 char*。因此,直接将其赋给 std::string 可能导致编译器错误,因为 std::string 需要 char* 类型。

序列化反序列化

  1. cstring不可直接序列化反序列化

  2. string不可直接序列化反序列化

  3. 序列化和反序列化的数据是可以直接转为const char*的数据,而cstring和string都不符合,需要转换一下

    1. cstring.getbuffer
    2. string.c_str()
    3. &string[0]
  4. 序列化总是失败的原因:在对class序列化的时候,每次都直接关掉了;如果是一系列的序列化过程,需要保持文件的打开状态,不能直接close

  5. 尝试一下保存class创建时需要的信息,尝试重建

  6. 结构体中有cstring时,不能整体序列化,会将cstring置为空

控件

RibbonBar

  1. 快速启动工具栏

    GetQuickAccessCommands

    右键->添加到快速启动工具栏

    m_wndRibbonBar.SetQuickAccessCommands(MainlstCommands);

    {

     MainlstCommands.AddTail(ID_FILE_OPEN);
     MainlstCommands.AddTail(ID_OPTION);
     MainlstCommands.AddTail(ID_APP_ABOUT);
    

    }

    m_wndRibbonBar.SetQuickAccessCommands(MainlstCommands);

控件位置获取

		// 获取鼠标的屏幕坐标
		CPoint ptClient;
		::GetCursorPos(&ptClient);
		m_ProjectList.ScreenToClient(&ptClient);	// 转为控件的局部坐标

		// 获取列表控件的客户区矩形大小
		CRect rectList;
		m_ProjectList.GetClientRect(&rectList);

		// 检查点击位置是否在列表控件的客户区域内
		if (rectList.PtInRect(ptClient))
        {}

这样写是没问题的

坐标函数前面加上控价是可以对于控件进行操作的;

但是之前在子对话框的时候使用这种思路出了问题,猜测可能是和框架以及父对话框设置有关系,还需继续考察

对话框

  1. 对话框居中显示 onInitDialog()

    	CRect rect;
    	GetWindowRect(&rect);
    
    	int nX = ::GetSystemMetrics(SM_CXFULLSCREEN);
    	int nY = ::GetSystemMetrics(SM_CYFULLSCREEN);
    
    	int nW = rect.Width();
    	int nH = rect.Height();
    
    	MoveWindow((nX - nW) / 2, (nY - nH) / 2, nW, nH, TRUE); //显示居中
    
  2. 对话框相对位置定位

    	CRect rect;
    	GetDlgItem(IDC_BUTTON_PANEL_CON)->GetWindowRect(&rect);
    	ScreenToClient(&rect);
    
    	switch (CurSel) {
    	case CONNECT_COM:
    	{
    		m_pnow->connectType = CONNECT_COM;
    
    		m_dlgPort = new CDlgCommunicateSetting();
    
    		m_dlgPort->Create(IDD_DLG_COMMUNICATE_SETTING_COM, this);
    		m_dlgPort->InitComboCtrl(m_pnow);
    		m_dlgPort->SetWindowPos(NULL, rect.left, rect.top, 0, 0, SWP_NOSIZE);
    
    		m_dlgPort->ShowWindow(SW_SHOW);
    
    		break;
    
    	}
    	case CONNECT_BLE:
    	{
    		m_pnow->connectType = CONNECT_BLE;
    
    		m_dlgBle = new CDlgCommunicateSettingBle();
    
    		m_dlgBle->Create(IDD_DLG_COMMUNICATE_SETTING_BLE, this);
    		m_dlgBle->SetWindowPos(NULL, rect.left, rect.top, 0, 0, SWP_NOSIZE);
    
    		m_dlgBle->ShowWindow(SW_SHOW);
    
    		break;
    	}
    	}// switch
    

响应函数

BEGIN_MESSAGE_MAP(CDlg, CDialogEx)
END_MESSAGE_MAP()
  1. 任意右键响应、通知

    ​ ON_NOTIFY(NM_RCLICK, IDC_LIST_PRPJECTLIST, &CPointCloudManagerMFCDlg::OnProjectListRbtnUp)

  2. 按钮、菜单响应

    ​ ON_COMMAND(IDC_LIST_PRPJECTLIST, &CPointCloudManagerMFCDlg::OnAddProject)

  3. 独立响应

    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_WM_SIZE()
    

ListCtrl

  1. 初始化

    	CWnd* pWnd = GetDlgItem(IDC_LIST_PRPJECTLIST);
    	CRect rect;
    	m_ProjectList.GetClientRect(&rect);
    	int width = rect.Width();
    
    	CFont font;
    	font.CreatePointFont(120, _T("黑体"));  // 120 corresponds to 12 points
    	
    	long dwStyle = m_ProjectList.GetExtendedStyle();
    	dwStyle |= LVS_EX_FULLROWSELECT;		//如果您想要在 List Control 中启用完整行选择,您可以使用 LVS_EX_FULLROWSELECT 扩展样式。这个样式允许用户单击任何一行的任意位置来选择整行。
    	//dwStyle |= LVS_EX_GRIDLINES;
    
    	m_ProjectList.SetExtendedStyle(dwStyle);
    	m_ProjectList.SetFont(&font);
    
  2. 添加元素

    m_ProjectList.InsertColumn(0, _T("项目列表"), 0, width);
    m_ProjectList.InsertItem(0, L"项目1");
    
    // 假设你有一个 CListCtrl 控件的成员变量 m_listCtrl
    // 控件的风格需要设置为 LVS_REPORT,两列的标识符分别为 0 和 1
    
    // 在 OnInitDialog 或其他适当的位置初始化列表控件和列
    m_listCtrl.InsertColumn(0, _T("Column 1"), LVCFMT_LEFT, 100);
    m_listCtrl.InsertColumn(1, _T("Column 2"), LVCFMT_LEFT, 100);
    
    // 插入一行数据
    int nIndex = m_listCtrl.InsertItem(0, _T("Item 1"));
    
    // 设置第一列的文本
    m_listCtrl.SetItemText(nIndex, 0, _T("Data for Column 1"));		// 实际会和上一句重复,这里是为了整体
    
    // 设置第二列的文本
    m_listCtrl.SetItemText(nIndex, 1, _T("Data for Column 2"));
    
  3. 添加完元素可能会出现奇怪的布局,那是listctrl设置不对,在vs中,listCtrl的视图属性,进行调整即可

  4. 如何在对话框中,给单独控件加鼠标响应

    ON_NOTIFY(NM_RCLICK, IDC_LIST_PRPJECTLIST, &CPointCloudManagerMFCDlg::OnProjectListRbtnUp)

    鼠标响应写法,可以看一下ON_NOTIFY需要的函数类型,如果对不上则会报错

  5. List,主动检测元素

    ​ 以上一条右键响应为例子,检测是否存在条目

    void CPointCloudManagerMFCDlg::OnProjectListRbtnUp(NMHDR* pNMHDR, LRESULT* pResult)
    {
    	CPoint pt;
    
    	GetCursorPos(&pt);
    	m_ProjectList.ScreenToClient(&pt);
    	
    	int item = m_ProjectList.HitTest(pt);
    	
    	if (item == -1)
    	{
    		AfxMessageBox(L"空白区域");
    	
    	}
    	else
    	{
    		AfxMessageBox(L"第 item 条");  // 0开始
    	}
    
    }
    
  6. 分组

    如何添加分组

    	// 按顺序添加分组
    	LVGROUP group1, group2;
    	ZeroMemory(&group2, sizeof(LVGROUP));
    	group2.cbSize = sizeof(LVGROUP);
    	group2.mask = LVGF_HEADER | LVGF_GROUPID;
    	group2.pszHeader = L"分割点云";
    	group2.iGroupId = 1;
    	m_listCtrl.InsertGroup(0, &group2);
    	ZeroMemory(&group1, sizeof(LVGROUP));
    	group1.cbSize = sizeof(LVGROUP);
    	group1.mask = LVGF_HEADER | LVGF_GROUPID;
    	group1.pszHeader = L"原始点云";
    	group1.iGroupId = 0;
    	m_listCtrl.InsertGroup(0, &group1);
    

    如何对分组添加元素

    int nItem = m_listCtrl.GetItemCount();
    			LVITEM lvItem;
    			ZeroMemory(&lvItem, sizeof(LVITEM));
    			lvItem.mask = LVIF_TEXT | LVIF_GROUPID | LVIF_IMAGE;
    			lvItem.iImage = 0;
    			lvItem.iItem = nItem;
    			lvItem.iGroupId = 0;
    			lvItem.pszText = const_cast<LPTSTR>(static_cast<LPCTSTR>(strLastPart));
    			m_listCtrl.InsertItem(&lvItem);
    
    			CString* pStrPath = new CString(subdirectory);
    			m_listCtrl.SetItemData(nItem, (DWORD_PTR)pStrPath);
    
  7. 获取选中行

    	CListCtrl* list = (CListCtrl*)_Ctrl;
    
        CString str;
        POSITION pos = list->GetFirstSelectedItemPosition(); //pos选中的首行位置
        if (pos == NULL)
        {
            return;
        }
    
        while (pos)   //如果你选择多行
        {
            int nIdx = -1;
            nIdx = list->GetNextSelectedItem(pos);
    
            if (nIdx >= 0)
            {
    			str = list->GetItemText(nIdx - preN, 0);
                AfxMessageBox(str);
            }
        }
    

    这里注意,如果是多选删除的话,删除条目时候的下标是选之前的下标,但是每次删除之后,其实列表是直接变化了的

        std::vector<int> indList;
        while (pos)   //如果你选择多行
        {
            int nIdx = -1;
            nIdx = list->GetNextSelectedItem(pos);
    
            if (nIdx >= 0 && nIdx < list->GetItemCount())
            {
                indList.push_back(nIdx);
            }
        }
    
        int preN = 0;
        for (int ind : indList)
        {
            list->DeleteItem(ind - preN ++);		// 消除影响
            // 也不能放到pos里后面,因为删除项也会影响pos,所以需要将下标全都获取完,再进行删除
        }
    
  8. 获取选中组

    int GetGroupIDForItem(CListCtrl& listCtrl, int nIndex) {
    	// 确保索引在有效范围内
    	if (nIndex >= 0 && nIndex < listCtrl.GetItemCount()) {
    		LVITEM lvItem;
    		ZeroMemory(&lvItem, sizeof(LVITEM));
    		lvItem.iItem = nIndex;
    		lvItem.mask = LVIF_GROUPID;
    		if (listCtrl.GetItem(&lvItem)) {
    			// 返回项的组 ID
    			return lvItem.iGroupId;
    		}
    	}
    	// 如果未找到项或者无法获取组信息,则返回 -1 表示失败
    	return -1;
    }
    

    listCtrl.GetItem(&lvItem)有点类似索引搜索,而lvItem.mask = LVIF_GROUPID表示只对组这个属性感兴趣

  9. 复选框

    直接看代码吧

    // 启用 List Control 的复选框功能
    m_listCtrl.SetExtendedStyle(m_listCtrl.GetExtendedStyle() | LVS_EX_CHECKBOXES);
    
    // 在 List Control 中添加两列
    m_listCtrl.InsertColumn(0, _T("Name"), LVCFMT_LEFT, 200); // 添加第一列
    m_listCtrl.InsertColumn(1, _T("Checkbox"), LVCFMT_LEFT, 100); // 添加复选框列
    
    // 向 List Control 中添加几行数据,并设置复选框初始状态为 true
    for (int i = 0; i < 5; i++)
    {
        CString strName;
        strName.Format(_T("Item %d"), i + 1);
    
        int nIndex = m_listCtrl.InsertItem(i, strName);
        m_listCtrl.SetCheck(nIndex, TRUE); // 将复选框默认设置为选中状态
    
        // 将复选框显示在第二列
        m_listCtrl.SetItemText(nIndex, 1, _T("")); // 设置第二列的文本为空
    }
    
  10. 重绘表头颜色 Gitee仓库 - MFC相关资料

绘制消息

NM_CUSTOMDRAW 是 Windows 消息中的一种通知消息,用于自定义绘制(Custom Draw)控件的外观。在 MFC 中,它通常与 CListCtrlCTreeCtrlCListCtrl 等控件结合使用,用于自定义绘制列表项、树节点等元素。

当你在 CListCtrl 或者其他控件中启用了自定义绘制功能后,当控件需要进行绘制时,会发送 NM_CUSTOMDRAW 消息给父窗口(即控件的所有者窗口),然后父窗口可以根据需要进行绘制操作。

注意:listCtrlEx中重绘的只能是列表项,如果想重绘表头,需要自定义HeaderCtrl,并对list的Header进行重赋值

要将自定义的表头控件赋值给当前列表控件,你需要做两件事:

  1. 创建自定义的表头控件类(例如 CMyHeaderCtrl),并在其中实现你自定义的绘制逻辑。
  2. 在你的列表控件类中,使用自定义的表头控件类,并在 PreSubclassWindow 函数中将其与列表控件的表头控件关联起来。

菜单

  1. 菜单弹出来只有一条缝

    CreatePopupMenu 用于创建弹出菜单,而 CreateMenu 用于创建常规菜单。这两个函数的使用取决于你的应用程序的具体需求和用户交互设计。

    所以右键的时候应该是有Popupmenu,否则出来的就只是一条缝

    	CMenu menu1;
    
    	menu1.CreatePopupMenu();     //动态创建弹出式菜单对象
    
    	menu1.AppendMenu(MF_STRING, ID_ADD_PROJECT, L"菜单项1");
    
    	menu1.AppendMenu(MF_STRING, ID_DELETE_PROJECT, L"菜单项2");
    
    	menu1.InsertMenu(2, MF_BYPOSITION | MF_POPUP | MF_STRING,
    
    		(UINT)menuMain.m_hMenu, "子菜单"); //添加子菜单
    
    	CPoint pt;
    
    	GetCursorPos(&pt);
    
    	menu1.TrackPopupMenu(TPM_RIGHTBUTTON, pt.x, pt.y, this);
    
    	menu1.DestroyMenu();
    

Text

  1. 获取内容

    m_EProjName.GetWindowText(pName); 直接获取即可,输入内容这部分是自动执行的

Combobox

  1. 添加内容

            m_combo.AddString(_T("小明"));
            m_combo.AddString(_T("小红"));
            m_combo.AddString(_T("小兰"));
    		
    		// 设置默认选择项
            m_combo.SetCurSel(0);
    

    注意,AddString会自动排序,如果不希望排序

    m_CobOpenMode.InsertString(0, L"Modeqweqweqwe");
    m_CobOpenMode.InsertString(1, L"Mode2");
    m_CobOpenMode.InsertString(2, L"Mode3");
    
    m_CobOpenMode.SetCurSel(0);
    
  2. 获取内容

    // 在需要获取 ComboBox 内容的地方使用如下代码:
    int nIndex = m_combo.GetCurSel(); // 获取当前选中项的索引
    if (nIndex != CB_ERR) {
        CString strText;
        m_combo.GetLBText(nIndex, strText); // 获取当前选中项的文本内容
        // 在这里使用 strText,它包含了当前选中项的文本内容
    }
    
  3. 点击不展开

打开exe

  1. 简单demo

    void CMainWindow::OnOpenExternalApp()
    {
        // 要打开的外部应用程序的路径
        CString strAppPath = _T("C:\\Path\\To\\YourApp.exe");
    
        // 传入的参数
        CString strArguments = _T("/your_argument_here");
    
        // 使用ShellExecute启动外部应用程序
        ShellExecute(NULL, _T("open"), strAppPath, strArguments, NULL, SW_SHOWNORMAL);
    }
    

选择文件、文件夹

  1. 选择单个文件

    void CDlgImportPointCloud::OnSelectPath()
    {
    	CFileDialog dlg(TRUE, _T("*.*"), _T("points"), OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
    		_g(_T("xyz File(*.xyz)|*.xyz|"
    			"pts File(*.pts)|*.pts|"
    			"ptx File(*.ptx)|*.ptx|"
    			"las File(*.las)|*.las|"
    			"laz File(*.laz)|*.laz|"
    			"stl File(*.stl)|*.stl|"
    			"asc File(*.asc)|*.asc|"
    			"txt File(*.txt)|*.txt|")));//FALSE表示为“另存为”对话框,否则为“打开”对话框
    	if (dlg.DoModal() == IDOK)
    	{
    		CString strFile = dlg.GetPathName();
    		CString strTitle = dlg.GetFileTitle();
    		m_FilePath.SetWindowText(strFile);
    		m_FileName.SetWindowText(strTitle);
    	}
    }
    
  2. 选择目录

    	WCHAR szPath[MAX_PATH];     //存放选择的目录路径 
        CString str;
    
        ZeroMemory(szPath, sizeof(szPath));
    
        BROWSEINFO bi;
        bi.hwndOwner = m_hWnd;
        bi.pidlRoot = NULL;
        bi.pszDisplayName = szPath;
        bi.lpszTitle = L"请选择处理结果存储路径";
        bi.ulFlags = 0;
        bi.lpfn = NULL;
        bi.lParam = 0;
        bi.iImage = 0;
        //弹出选择目录对话框
        LPITEMIDLIST lp = SHBrowseForFolder(&bi);
    
        if (lp && SHGetPathFromIDList(lp, szPath))
        {
            str.Format(L"%s", szPath);
            SetDlgItemText(IDC_EDIT2, str);
    
            AfxMessageBox(str);
        }
        else
        {
            AfxMessageBox(L"wrong");
        }
    

遍历文件、文件夹

  1. 遍历当前目录下的文件夹,并判断文件夹中是否有json文件

    #include <iostream>
    #include <filesystem>
    #include <vector>
    
    namespace fs = std::tr2::sys;
    
    bool hasJsonFile(const fs::path& folderPath) {
        for (const auto& entry : fs::directory_iterator(folderPath)) {
            if (fs::is_regular_file(entry.status()) && entry.path().extension() == ".json") {
                return true; // 发现JSON文件
            }
        }
        return false; // 没有找到JSON文件
    }
    
    int main() {
        std::string currentPath = ".";
    
        try {
            for (const auto& entry : fs::directory_iterator(currentPath)) {
                if (fs::is_directory(entry.status())) {
                    std::cout << "Folder: " << entry.path().string() << std::endl;
    
                    if (hasJsonFile(entry.path())) {
                        std::cout << "  - Contains JSON file(s)" << std::endl;
                    } else {
                        std::cout << "  - No JSON file found" << std::endl;
                    }
                }
            }
        } catch (const std::exception& e) {
            std::cerr << "Error: " << e.what() << std::endl;
        }
    
        return 0;
    }
    

获取当前路径

	// 获取可执行文件路径
    TCHAR buffer[MAX_PATH];
    ::GetModuleFileName(NULL, buffer, MAX_PATH);

 	// 从路径中提取出所在文件夹
    PathRemoveFileSpec(buffer);

    // 将路径存储在CString对象中
    CString currentPath(buffer);

	std::string dpPath = CW2A(currentPath) + "\\File.dp";

	std::ifstream ifs(dpPath);

重绘以及原理

  1. 重绘的具体操作,以为dlg为例,就是重写onPaint函数,可以简单的实现背景的替换或者添加花纹等等,像圆角和透明度等操作需要在外部实现

    一个简单的小demo

    void CDlgLayerTree::OnPaint()
    {
    	CPaintDC dc(this); // 用于绘制的设备上下文
    
    	// 获取对话框客户区的矩形区域
    	CRect rect;
    	GetClientRect(&rect);
    
    	// 创建一个内存设备上下文
    	CDC memDC;
    	memDC.CreateCompatibleDC(&dc);
    
    	// 创建一个位图与内存设备上下文相关联
    	CBitmap bitmap;
    	bitmap.CreateCompatibleBitmap(&dc, rect.Width(), rect.Height());
    	memDC.SelectObject(&bitmap);
    
    	// 在内存设备上下文中绘制背景(示例中使用红色背景,你可以根据需要自定义)
    	memDC.FillSolidRect(rect, RGB(200, 255, 255));
    
    	// 在内存设备上下文中绘制聊天内容(示例中绘制一行文本,你可以根据需要自定义)
    	CString strMessage = _T("Hello, World!"); // 这里可以是你的聊天内容
    	memDC.SetTextColor(RGB(255, 255, 255)); // 设置文本颜色为白色
    	memDC.SetBkMode(TRANSPARENT); // 设置背景透明
    	memDC.DrawText(strMessage, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); // 绘制文本
    
    	// 将内存设备上下文的内容绘制到窗口设备上下文中
    	dc.BitBlt(0, 0, rect.Width(), rect.Height(), &memDC, 0, 0, SRCCOPY);
    
    	CDialog::OnPaint();
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值