自定义菜单显示文字
在上一节课学习了绘制直方图,这节课学习了自定义菜单。其中包括在原有菜单上添加选项并实现内容,以及创建新的菜单,通过菜单能对显示的文字改变
1.显示文字
在上一节课中,通过DrawText函数显示文字,这里我们也先编写显示文字的代码,其中简单了解CFont类。CFont类封装了一个Windows图形设备接口(GDI)字体,并为操作字体提供了成员函数。初始化函数较复杂,可用CreatePointFont函数来简单创建字样,指定其字体和字号。
函数 | 功能 |
---|---|
f.CreatePointFont(字号,字体) | 为CFont类对象f快捷创建对应字号和字体的字样 |
CDC *pDC->SelectObject(&f) | 选择当前环境的字体为f |
pDC->SetTextColor(颜色) | 设置字体颜色 |
在本例中,我们先在View类添加CString的数据成员,等等在客户区中显示。并且创建一个COLORREF类型的数组作数据成员,用于保存多个RGB颜色的值。在后面通过菜单转变字体颜色。
View类头文件中添加:
public:
CString str;
COLORREF colors[3];
int color_flag;
CFont font;
View类Cpp文件中更改或添加:
//构造函数
CMENUAView::CMENUAView() noexcept:str("萌萌哒")
{
colors[0] = RGB(254, 67, 101);
colors[1] = RGB(102, 204, 255);
colors[2] = RGB(35, 235, 185);
color_flag = 0;
font.CreatePointFont(400, _T("微软雅黑"));
// TODO: 在此处添加构造代码
}
void CMENUAView::OnDraw(CDC* pDC)
{
CMENUADoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
CRect rect;
GetClientRect(rect); //指定对应的文本位置
pDC->SelectObject(&font); //选择字体
pDC->SetTextColor(colors[color_flag]); //选择字体颜色
pDC->DrawText(str, rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
// TODO: 在此处为本机数据添加绘制代码
}
另外,CFont类对象的CreatePointFont函数只能使用一次像初始化一样,如果没有在构造函数中使用,而是放在OnDraw函数中使用,每当调整窗口时便会报错。
因为CreatePointFont重复调用,如果真的要放在OnDraw函数中,在函数末尾对象调用DeleteObject就可以了。
2.在主菜单中添加自己的选项
2.1添加选项
接下来,我们在资源视图中找到menu,进入里面的MAINFRAME。
在【此处键入】处输入自己的选项就可以了。对应选项中改变选项的ID方便等等完善代码,如果改变ID后记得先保存文件,否则Cpp文件对应ID无法检测。
添加好后完善相关代码。点击对应选项右键,添加事件处理程序。类列表选View类,消息类型是COMMAND类型。
2.2添加"显示"选项的代码
我们先完善显示选项的代码。在View类中添加bool类型的show_flag,当为1的时候才在OnDraw函数中调用DrawText函数。并且在"添加"选项的事件处理程序中当flag为1是就置零,否则置1。
函数 | 功能 |
---|---|
Invalidate() | 刷新窗口,重新调用OnDraw函数 |
void CMENUAView::OnDraw(CDC* pDC)
{
CMENUADoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
CRect rect;
GetClientRect(rect);
pDC->SelectObject(&font);
pDC->SetTextColor(colors[color_flag]);
if(show_flag)
pDC->DrawText(str, rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
// TODO: 在此处为本机数据添加绘制代码
}
void CMENUAView::OnShow()
{
if (show_flag)
show_flag = 0;
else show_flag = 1;
Invalidate(); //刷新窗口
// TODO: 在此添加命令处理程序代码
}
2.3添加"颜色更改"选项的代码
“颜色更改”选项的代码添加我们不通过上面方式添加,先在头文件中声明 afx_msg void Color_choose(UINT id);
afx_msg代表该函数是消息响应函数,参数中是无符号整型数获取颜色更改后面的三个选项ID。然后切至CPP文件中,ON_COMMAND(ID_SHOW, &CMENUAView::OnShow)
这串代码是上面添加单个选项的消息响应函数添加的。我们在MESSAGE_MAP处添加以下代码。
ON_COMMAND_RANGE(ID_RED,ID_GREEN,&CMENUAView::Color_choose)
这样建立多个选项和函数间的关联。这样多个选项就只能选择一个。参数中第一个为第一项的ID,第二个为最后一项的ID,第三个为函数入口。接下来把函数完善就好。
void CMENUAView::Color_choose(UINT id)
{
color_flag = id - ID_RED;
Invalidate();
}
这里因为红色到绿色三个选项都是依次建立的,它们的ID相差1,用获取的ID减初始的红色选项的ID就可以得出其选的是哪个,然后存储到作为之前COLORREF类型数组的下标变量color_flag中。重新编译后,之前灰色的选项都能够选了。
3.程序中为当前选项标记
有了选项后,可能在选项前加一些记号表示当前该选项的状态会让人更明白自己选了什么。我们在回到资源视图中,先在"显示"的选项处添加事件处理程序,和之前一样,但类型是UPDATE_COMMAND_UI类型。这时候生成的函数参数是CCmdUI类类型的指针。
详细参考:https://baike.baidu.com/item/CCmdUI/6849835?fr=aladdin
这里我们调用一下函数便可
函数 | 功能 |
---|---|
SetCheck | 设置选中状态,在用户接口对象前面打√或不打 |
SetRadio | 设置选中状态,在对象前面用圆点选取,用于单选钮组 |
成员m_nID | 选中用户接口对象的ID |
void CMENUAView::OnUpdateShow(CCmdUI* pCmdUI)
{
pCmdUI->SetCheck(show_flag); //show_flag为1则选取
// TODO: 在此添加命令更新用户界面处理程序代码
}
"颜色更改"的函数我们与COMMAND类型的事件处理程序添加方法类似。
头文件中
afx_msg void OnUpdateColor(CCmdUI* pCmdUI);
CPP中
MESSAGE_MAP处
ON_UPDATE_COMMAND_UI_RANGE(ID_RED,ID_GREEN,&CMENUAView::OnUpdateColor)
void CMENUAView::OnUpdateColor(CCmdUI* pCmdUI)
{
pCmdUI->SetRadio(color_flag == (pCmdUI->m_nID - ID_RED));
}
4.新建自己的菜单
我们再新建一个自己的菜单,在客户区中右键也能改变颜色或显不显示文字。
4.1新建菜单
在资源视图中在Menu文件夹处右键然后点击添加资源。资源是Menu类。
在新建的菜单中,填好自己的选项,我们这里与上面添加的一样。
4.2弹出菜单操作
接下来由于代码的实现和主菜单的一样,我们可以直接更改自己新建菜单对应选项的ID(就是改回之前的ID),因为在MESSAGE_MAP中的ON_COMMAND已经将对应ID与函数绑定起来。我们先在View中添加CMenu对象,然后在构造函数加载我们新建的菜单资源。然后给View类添加鼠标右键的函数。完善代码
函数 | 功能 |
---|---|
a.LoadMenuW(ID) | CMenu对象a加载对应ID的菜单资源 |
a.GetSubMenu(i) | 返回a的第i个子菜单 |
a.TrackPopupMenu(格式,x,y,this) | 在客户区的x,y中以格式弹出菜单 |
ClientToScreen(&point) | 将客户区的点坐标转化为基于屏幕的坐标 |
部分格式如下:
详细参考:https://baike.baidu.com/item/TrackPopupMenu/6373582?fr=aladdin
格式 | 意义 |
---|---|
TPM_CENTERALIGN | 按参数x指定的坐标水平居中放置快捷菜单。 |
TPM_LEFTALIGN | 菜单的左边界与由参数X指定的坐标对齐。 |
TPM_RIGHTALIGN | 菜单的右边界与由参数X指定的坐标对齐。 |
TPM_BOTTOMALIGN | 菜单的下边界与由参数y指定的坐标对齐。 |
TPM_TOPALIGN | 菜单的上边界与由参数y指定的坐标对齐。 |
TPM_VCENTERALIGN | 按参数y指定的坐标垂直居中放置快捷菜单 |
View头文件中添加:
CMenu menu;
Viewcpp文件中添加:
构造函数添加:
menu.LoadMenuW(IDR_MENU1);
void CMENUAView::OnRButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
CMenu* mm = menu.GetSubMenu(0);
ClientToScreen(&point);
mm->TrackPopupMenu(TPM_LEFTALIGN, point.x, point.y, this);
CView::OnRButtonDown(nFlags, point);
}
子菜单的意思拿下面菜单为例,就是操作下面的【显示,颜色变换】构成m.GetSubMenu(0)拿到的第一个子菜单,如果里面参数填1,而你恰好有第二个子菜单,如测试下面还有选项的话,拿到的就是测试下面的菜单。如果没有制作,不会报错,但右键时会退出程序。
如果直接拿载入新建菜单的CMenu对象来弹出会如何?m.TrackPopupMenu(…)
可以看到【操作】【测试】这些选项并不会出现,但其子菜单只要鼠标移动到适当位置是会出现的。
另外我们这里看到标志的函数在新建的菜单并没有用,原因是弹出的菜单不支持UPDATE_COMMAND_UI,这里需要另外的函数来实现标记菜单状况。
函数 | 功能 |
---|---|
a.CheckMenuItem(ID,格式) | CMenu对象a,设置ID选中状态,打√或不打 |
a.CheckMenuRadioItem(ID1,ID2,ID3,格式) | a,设置从ID1到ID2单选钮组的选中状态,给ID3打圆点 |
例子中用到的格式:
格式 | 意义 |
---|---|
MF_CHECKED | 选中 |
MF_UNCHECKED | 未选中 |
MF_BYCOMMAND | 表示以ID值标识菜单项 |
void CMENUAView::OnRButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
CMenu* mm = menu.GetSubMenu(0);
UINT check_flag = show_flag ? MF_CHECKED : MF_UNCHECKED;
mm->CheckMenuItem(ID_SHOW,MF_BYCOMMAND| check_flag);
mm->CheckMenuRadioItem(ID_RED, ID_GREEN, ID_RED + color_flag,MF_BYCOMMAND); //为菜单标记状态
ClientToScreen(&point);
mm->TrackPopupMenu(TPM_LEFTALIGN, point.x, point.y, this);
CView::OnRButtonDown(nFlags, point);
}
最后弹出来的菜单也会有状态标记了。