CEdit重新审视
一个从没想过的问题今天终于出现了!我自己都感到以外!呵呵,什么问题呢?可能你也没想过哦。即使想过,你解答了没有?
是一个聊天的程序,类似于QQ的界面。简化一点说把。(注意是简化)一个基于对话框的的程序,只有2个编辑框m_edit1,m_edit2, 还有一个是按钮。现在我要做的就是在 m_edit2中输入,然后点击按钮,内容就跑到了m_edit1中。并且每次点击后,上次 m_edit1的内容还会留下来,当然是聊天嘛,每次发送的内容要在m_edit1的最后显示。呵呵,听起来很简单哦
注意我最后一句话。如果你采用连加的话,像这样:
m_edit1.GetWindowText(m_str1);
m_edit2.GetWindowText(m_str2);
m_str1+=m_str2;
m_edit1.SetWindowText(m_str1);
内容是出现了,但是光标还在第一个位置,这样如果m_edit1是一个多行的程序,有垂直滚动条,那么就麻烦了,前面我已经说了,要求是 “每次发送的内容要在m_edit1的最后显示”,也就是要求滚动条要移动到最后,按照上面那种方法,滚动条都在上面。
怎么办哪?没有关于滚动条的函数啊。其实不是滚动条的原因,原因是光标。光标还在第一个位置啊。明白了,我们要把光标移动到最后。
于是上msdn了。找“CEdit”,“class members“,这么多函数,还不知哪个是移动光标的函数呢。一个一个的试。posfromchar,SetCaretPos ,......都不行啊,晕了。。。
正绝望呢,突然想起了久违的setsel和GetSel 我们都知道CEdit是用来选中编辑框中的内容的,它们会不会有用呢?管他呢,先 全部选中再说。
m_edit1.SetFocus();
m_edit1.SetSel(0,-1);
啊!选中了!不,我不是惊喜这个(不然我会被看贴的砍死的)。除了选中之余,光标也移动到了最后!呵呵,成功了!
再想想,还不行,差一点。这样岂不是我每次要全部选中?不好。既然选中是移动光标的话,那我干脆一不做二不休,函数照用,但是我不选,呵呵,这样写:
m_edit.SetFocus();
m_edit1.SetSel(m_str1.GetLength(),m_str1.GetLength());
搞定了!
高兴之余,我又想了一下这个问题。SetSel可以确定光标位置,那么GetSel不就可以知道光标的位置吗?试试:(另外一个程序)
DWORD curPos = m_edit.GetSel();
CString sCurStr ;
m_edit.GetWindowText(sCurStr);
sCurStr.Insert(LOWORD(curPos), str);
m_edit.SetWindowText(sCurStr);
这样的话,随便定义光标在一个位置,都可以插入东西了,呵呵。
感慨就是函数的功能还真多!看来要慢慢理解!
控件的实时拖动
由于一个小项目的需要,必须做控件的拖曳。以前还真没有想过这种问题。先来看看有没有处理消息,比如drag什么的,很遗憾没有。再来看看有没有函数,好像也没有,郁闷。没办法了。只好用绝招,自己做一个派生类把,下面我就以编辑框为例,简单说明一下拖曳的过程。
1. 从CEdit派生一个类为CmyEdit
2. 添加左键处理函数:
void Cmyedit::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
pp=point; // pp是Cmyedit的成员变量
SetTimer(5,10,NULL); // 引发定时器
CEdit::OnLButtonDown(nFlags, point);
}
void Cmyedit::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
KillTimer(5); // 关闭定时器
CEdit::OnLButtonUp(nFlags, point);
}
3. 添加定时消息处理:
void Cmyedit::OnTimer(UINT nIDEvent)
{
// TODO: Add your message handler code here and/or call default
if(nIDEvent==5){
//获得鼠标当前位置,转换为父窗口内的坐标
CPoint cursor_Pos;
GetCursorPos(&cursor_Pos);
GetParent()->ScreenToClient(&cursor_Pos);
//鼠标原来单击位置pp,转换为所在父窗口内的坐标
CPoint edit_Pos=pp;
ClientToScreen(&edit_Pos);
GetParent()->ScreenToClient(&edit_Pos);
// 获得控件的大小,并且转换到父窗口的坐标,主要是为了获得左上角的坐标,方便下面的movewindow调用
CRect edit_rect;
GetWindowRect(edit_rect);
GetParent()->ScreenToClient(&edit_rect);
// 利用向量相等的性质求得鼠标移动后的新左上角坐标
int x=edit_rect.left+(cursor_Pos.x-edit_Pos.x);
int y=edit_rect.top+(cursor_Pos.y-edit_Pos.y);
MoveWindow(x,y,edit_rect.Width(),edit_rect.Height());
}
CEdit::OnTimer(nIDEvent);
}
4.改变鼠标形状
BOOL Cmyedit::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
// TODO: Add your message handler code here and/or call default
SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZEALL));
return TRUE;
}
这样就结束了。可以到处用了。符合拖动的要求了。
抽屉菜单的简单实现
看到 QQ的抽屉,也想自己做一个,于是做了个最简单的,界面如图所示
这是一个基于对话框的程序,有3个按钮,有4个对话框,1个是主窗口,还有3个是子窗口,因此需要3个对话框的资源风格为: “下层,对话框架“
下面是具体设计过程:
1.给3个子窗口(对话框)分别派生一个对话框类,名为Cmydlg1,Cmydlg2,Cmydlg3;
2.在 Cmydlg (主窗口) 中添加成员:
Cmydlg1 *dlg1;
Cmydlg2 *dlg2;
Cmydlg3 *dlg3;
int m_left; // 按钮的左边坐标
int m_right; // 按钮的右边坐标
int m_buttonHeight;// 按钮的高度
int m_buttonWidth; // 按钮的宽度
int m_dlgHeight; // 子对话框的高度,而宽度与按钮应该相同
CButton m_button[3]; // 3个按钮,下面会动态创建
3.初始化
BOOL CMyDlg::OnInitDialog()
{
CDialog::OnInitDialog();
……….
dlg1=new Cmydlg1;
dlg2=new Cmydlg2;
dlg3=new Cmydlg3;
// 创建3个按钮,注意它的位置
for(int i=0;i<3;i++)
{
m_button[i].Create("hehe",WS_CHILD|WS_VISIBLE, \
CRect(m_left,m_buttonHeight*i,m_right,m_buttonHeight* (i+1)),this,IDC_BUTTON+i);
}
// 创建非模态窗口(即充当子窗口)
dlg1->Create(IDD_DIALOG1,this);
dlg2->Create(IDD_DIALOG2,this);
dlg3->Create(IDD_DIALOG3,this);
// 首先移动3个子窗口的位置,以后都不用再移动了,只需要显示或者不显示
dlg1->MoveWindow(m_left,m_buttonHeight,m_buttonWidth,m_dlgHeight);
dlg2->MoveWindow(m_left,m_buttonHeight*2,m_buttonWidth,m_dlgHeight);
dlg3->MoveWindow(m_left,m_buttonHeight*3,m_buttonWidth,m_dlgHeight);
// 成员函数
show(0);
return TRUE; // return TRUE unless you set the focus to a control
}
4.显示函数
void CMyDlg::show(int index)
{
dlg1->ShowWindow(! (index-0)); // 判断是显示哪个窗体
dlg2->ShowWindow(! (index-1));
dlg3->ShowWindow(! (index-2));
// 显示窗体以后就要移动相应按钮的位置,处理一下几何位置关系,不细说了
for(int i=0;i<=index;i++)
{
m_button[i].MoveWindow( m_left, m_buttonHeight*i,m_buttonWidth,
m_buttonHeight );
}
for(i=index+1;i<3;i++)
{
m_button[i].MoveWindow( m_left,m_buttonHeight*i+m_dlgHeight,m_buttonWidth,m_buttonHeight );
}
}
5.添加按钮的消息映射
.h :
// Generated message map functions
//{{AFX_MSG(CMyDlg)
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
//}}AFX_MSG
afx_msg void OnClick(UINT nID);
.cpp:
BEGIN_MESSAGE_MAP(CMyDlg, CDialog)
//{{AFX_MSG_MAP(CMyDlg)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
//}}AFX_MSG_MAP
ON_COMMAND_RANGE(IDC_BUTTON+0,IDC_BUTTON+2,OnClick)
END_MESSAGE_MAP()
6. 消息响应
void CMyDlg::OnClick(UINT nID)
{
int index=nID-IDC_BUTTON;
show(index);
}
7.画边框
void CMyDlg::OnPaint()
{
。。。。。。。。。
CPaintDC dc(this);
CBrush brush(RGB(255,0,0));
dc.FrameRect(CRect(m_left-1,-1,m_right+1,m_buttonHeight*3+m_dlgHeight+1),&brush);
CDialog::OnPaint();
}
}
完成!值得干一杯了!当然你还可以优化一下界面!
轻松画背景
在用MFC编程的时候,尤其是设置界面的时候,大家都会觉得VC的控件属性设置还比不上VB.连个颜色都设置不了.但也正是这样,MFC提供给我们的自由也就越多,我们可以随意派生自己的控件类,想怎么做就怎么做.VB不行.
但是派生一个自己的控件类,然后去自绘,也不是容易的事情.在不断的探索中,我发现了一个简单的设置背景的方法,就是直接利用PICTURE控件.把它设置成容纳位图,然后就可以充当背景了.
比如有一个static控件.现在再绘制一个,PICTURE控件,放在static上面.这个时候,图像是会遮盖住static控件的.我们可以重新设置一下Tab序,让PICTURE控件排在前面即可.下面看看我的作品把:
CListCtrl学习笔记(1)---基础篇
背景:CListCtrl 在小型数据库中应用的还是比较多的.因此掌握它是一个很重要的技能.最近在做老师布置的一个项目,用到它,开始的时候不知所措.后来上网找了一些资料,再加上自己的探索,终于对它有一个比较全面的认识.下面我简单讲讲它的一些用法,以此和大家共享一下.
1. 基本风格设置
(1)函数: ModifyStyle( )
(2)重要参数: LVS_ICON // 大图标
LVS_SMALLICON // 小图标
LVS_LIST // 列表
LVS_REPORT // 报表
(3)说明:
用的比较多的是最后的报表视图.因为它可以有多列,正好代表数据库中的多个属性.所以下面的用法都是针对这种风格的.当然这些风格也可以在控件的属性中设置
(4) 注意
我们知道在窗口各种各样的风格之间,有时是可以用” |”,表示属性叠加.如:
WS_CHILD | WS_VISIBLE;
但是上面的四种风格是不可能放在一起的.所以不要用到 “ |”操作符.
也正是因此,防止用户出错, 微软干脆把LVS_ICON, LVS_SMALLICON, LVS_LIST, LVS_REPORT这些都不设置成位标志,因此不能叠加.同时还设置了一个多余的掩码:
LVS_TYPEMASK.它是用来屏蔽的.
(5) 举例:
如果我们要判断一个CListCtrl的风格:
DWORD dwStyle = m_listctrl.GetStyle( );
// 判断是否大图标样式
If ( dwStyle & LVS_ICON )
…….
这种写法是错误的.正确的写法涉及到掩码:
DWORD dwStyle = m_listctrl.GetStyle( ) & LVS_TYPEMASK;
If ( dwStyle = = LVS_ICON)
…….
同理,我们在改变风格时,应该这样写:
ModifyStyle( LVS_TYPEMASK, LVS_ICON);
2. 扩展风格设置
(1) 函数 : SetExtendedStyle( ) GetExtendedStyle ( )
(2) 重要参数:
LVS_EX_FULLROWSELECT //选中某行使整行高亮(只适用与报表风格)
LVS_EX_GRIDLINES //网格线(只适用与报表风格)
LVS_EX_CHECKBOXES //设置checkbox状态
(3)举例
DWORD dwStyle = GetExtendedStyle();
dwStyle |= LVS_EX_FULLROWSELECT;
dwStyle |= LVS_EX_GRIDLINES;
SetExtendedStyle(dwStyle);
3.其他风格设置:
函数: SetTextColor ( ) // 设置文字颜色
SetBkColor ( ) // 设置边框颜色
SetTextBkColor ( ) // 设置文字背景颜色
//下面直接举例说明
4.图标设置
可以给大图标风格和小图标风格设置图标:
HICON icon=AfxGetApp()->LoadIcon(IDI_ICON1);
m_icon.Add(icon);
m_listctrl.SetImageList(&m_icon,LVSIL_SMALL);//小图标
m_listctrl.SetImageList(&m_icon,LVSIL_NORMAL)// 大图标
5插入一列
m_listctrl.InsertColumn(0,”哈哈”,LVCFMT_LEFT,80);
其中: 0是索引项,”哈哈”是列标题,LVCFMT_LEFT是显示方式(靠左),80表示列的宽
6插入一行
m_listctrl.InsertItem(0, “123”,0);// 插入为第一行第一列的内容,最后一个0是图标的索引
m_listctrl.SetItemText( 0,1, “123”) ; // 设置第一行第2列的内容
m_ listctrl.SetItemText(0,2, “123”) ; // 设置第一行第3列的内容
7得到所有的行数
m_listctrl.GetItemCount( ) ;
8.得到所有的列数
m_listctrl.GetHeaderCtrl().GetItemCount( ) ;
9.得到被单击的项的行列号
void Cmylist::OnClick(NMHDR* pNMHDR, LRESULT* pResult) //单击消息
{
NM_LISTVIEW *info=(NM_LISTVIEW*)pNMHDR;
selectedIndex=info->iItem; // 行号
selectedsub=info->iSubItem; // 列号
}
(Cmylist为CListCtrl的派生类,以selectedIndex和selectedsub为接口,方便使用)
10.得到被单击的列头索引号
void Cmylist::OnColumnclick(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
selectedField=pNMListView->iSubItem;//列头号
}
同上,也是以selectedField为接口,方便用户调用
11.选中或者取消一行
plistctrl->SetItemState(index,LVIS_SELECTED,LVIS_SELECTED);//选中一行
plistctrl->SetItemState(index,0,LVIS_SELECTED);//取消一行
DWORD style=plistctrl->GetItemState(index,LVIS_SELECTED);//获得选中信息
12.获得被选中的多行
POSITION p=m_listctrl.GetFirstSelectedItemPosition();// 得到第一次选中的位置
while(p)
{
int index=m_listctrl.GetNextSelectedItem(p);
…
}
13.删除一行或者一列
m_listctrl.DeleteItem( index );
m_listctrl.DeleteColumn(index);
14.删除多行或者多列
注意要从后面开始删起,比如,如果我想删除第一行和第二行,应该:
m_listctrl.DeleteItem(1);
m_listctrl.DeleteItem(0);
而不能:
m_listctrl.DeleteItem(0);
m_listctrl.DeleteItem(1);
因为每次删除一行或者一列,后面的索引号都要变化,所以从后面开始删除就没关系.
如果用for循环,应该递减:
for(int k=m_listctrl.GetItemCount( );k>=-1;k++)
m_listctrl.DeleteItem(k);
这就是删除所有行,当然也可以用CListCtrl::DeleteAllItems
15 根据索引号得到某一列的信息
HDITEM hdi;
TCHAR lpBuffer[256];
hdi.mask = HDI_TEXT;
hdi.pszText = lpBuffer;
hdi.cchTextMax = 256;
plistctrl->GetHeaderCtrl()->GetItem(index, &hdi);
CString str=hdi.pszText;
CListCtrl学习笔记(2)---中级篇(1)
专题1: 如何使CListCtrl完全可编辑?
1. 背景 : 我们知道如果CListCtrl是报表样式,那么CListCtrl所提供的编辑功能只局限于第一列.也就是说只有第一列可编辑.这样显然无法满足一般数据库的要求.我们想要每个子项都能编辑.
2. 思路 : CEdit是一个很好的可控制编辑控件.如何把CEdit和我们的CListCtrl联系起来?一种很好的想法是------一般我们如果想编辑某一项,那么就应该去双击.双击以后就让CEdit在那里显示,当然要把大小调整和子项表格一样.如果CEdit失去了焦点,表示修改完毕,那么立即更改子项的数据,同时让CEdit隐藏.因为每次只能编辑一项,所以只需要一个CEdit就够了.
3. 方法:
(1) 首先从CListCtrl派生一个类,其他已经有的变量或者函数设置我已经介绍,如果不清楚的读者,可以去参考”基础篇”.
(2) 有一点可以肯定,我们必须响应双击事件:
void Cmylist::OnLButtonDblClk(UINT nFlags, CPoint point)
{
int index;//行号
int colnum;//列号
GetWindowRect(r);//稍后说明
GetParent()->ScreenToClient(r);//稍后说明
if((index=HitTestEx(point,&colnum))!=-1)
EditSubItem(index,colnum);
CListCtrl::OnLButtonDblClk(nFlags, point);
}
其中HitTestEx是用来求出双击点所在的行列号,如果行号不为-1,那么就调用函数EditSubItem. 这个函数会根据行列号求出该子项具体坐标,方便CEdit调整位置.
(3) 如何求出行列号?行号是很好求出来的 ,但是列号就不是很简单了,必须详细判断.
int Cmylist::HitTestEx(CPoint &point, int *pcolumn)
{
int columnNum=0;//获取页面内首行索引号,不一定是0,要考虑滚动条的情况
int row=GetTopIndex(); // GetCountPerPage()获取在页面内行的总数
int bottom=row+this->GetCountPerPage();// 防止超出范围
if(bottom>this->GetItemCount())
bottom=GetItemCount(); //获取列的总数
int ncolumncount=this->GetHeaderCtrl()->GetItemCount();//可以肯定双击点肯定在页面内,因此从页面首行索引号开始判断
for(;row<=bottom;++row)
{
CRect rect;//求出行的rect
GetItemRect(row,&rect,LVIR_BOUNDS);//点是否在行的矩形内
if(rect.PtInRect(point))//如果点在行的矩形内,求出点在哪一列
for(columnNum=0;columnNum<ncolumncount;columnNum++)
{
int colwidth=this->GetColumnWidth(columnNum);//求出列的宽度
if(point.x>=rect.left&&point.x<=(rect.left+colwidth))
{
*pcolumn=columnNum;
return row;
}
rect.left+=colwidth;
}
}
return -1;
}
当然上面那种方法有点复杂,是完全从头开始判断.其实我们可以先利用CListCtrl提供的函数求出行号,再求列号,这样稍微简单点
int Cmylist::HitTestEx(CPoint &point, int *pcolumn)
{
int columnNum=0;
int row=HitTest(point);//求出行号
int ncolumncount=this->GetHeaderCtrl()->GetItemCount();
(2007.1.3更新)
LVHITTESTINFO Info;
Info.pt=point;
this->SubItemHitTest(&Info);
*pcolumn=Info.iSubItem;
if(*pcolumn>=0&&*pcolumn<ncolumncount)
return row;
else
return -1;
/* int ncolumncount=this->GetHeaderCtrl()->GetItemCount();
Rect rect;
GetItemRect(row,&rect,LVIR_BOUNDS);
if(rect.PtInRect(point))
for(columnNum=0;columnNum<ncolumncount;columnNum++)
{
int colwidth=this->GetColumnWidth(columnNum);
if(point.x>=rect.left&&point.x<=(rect.left+colwidth))
{
*pcolumn=columnNum;
return row;
}
rect.left+=colwidth;
}*/
}
(4) 求出具体CEdit移动坐标
int Cmylist::Item_X(int row, int column,CRect& rect_X)
{
int offset=0;
for(int i=0;i<column;i++)
offset+=GetColumnWidth(i);
CRect rect;
GetItemRect(row,rect,LVIR_BOUNDS);
//注意水平滚动条的影响,如果已经移动了水平滚动条,可能left为0,或者超出总大小
if(offset+rect.left<0||offset+rect.left>client_rect.right)
{
CSize size;//offset肯定为正,如果出现了rect.left为负
if(offset+rect.left>0)
size.cx=- (offset+rect.left);
else
size.cx=offset+rect.left;
size.cy=0;//垂直不用管
//如果某一列的一半在滚动条左边,一半在右边,就再次调整滚动条的位置.
Scroll(size);
rect.left - =size.cx;
}
rect.left+=offset+2;
rect.right=rect.left+GetColumnWidth(column)-2;//bottom和top不用管
rect_X=rect;
return rect.right;
}
(5) 移动CEdit
void Cmylist::EditSubItem(int Item, int Column)
{
CRect rect;//求出行列所在rect
this->Item_X(Item,Column,rect);
EditCellShow(rect,Item,Column,r);
}
void Cmylist::EditCellShow(CRect rect, int Item, int Column,CRect r)
{
//还记得r吗?在开始的双击函数OnLButtonDblClk中,它是CListCtrl在父窗口中的位置
rect.left+=r.left;
rect.top+=r.top+2;
rect.right+=r.left;
rect.bottom+=r.top+2;
//pedit是CEdit对象的指针,提供接口,只要在程序中让pedit指向一个对象即可
pedit->MoveWindow(rect,TRUE);
pedit->ShowWindow(TRUE);
pedit->SetFocus();
}
^_^!这样就完成了.效果还可以.当然你还要去响应CEdit失去焦点和得到焦点的事件.这个就不是我的任务了,因为每个人的要求不一样啊!
看看我的效果!
对话框条的制作CDialogBar
简单来说,就是对话框条的制作
1.创建对话框资源:在对话框资源编辑器内生成一个Dialog资源,并将其风格(Style)属性必须设置为Child,不能设置为Overlapped或Popup,否则运行肯定出错;至于边界属性则随用户自己喜欢,一般都是选择None。其余属性也随用户选择,一般没有特殊要求还是选择默认的好。
2.自己派生一个继承于CDialogBar的类,注意此时由于ClassWizard没有把CDialogBar列出来,所以只好自己手动编写.h和.cpp,然后加上必要的处理函数,如:
class CmyDlgWnd : public CDialogBar
{
public:
CmyDlgWnd(CWnd *pParent=NULL);
virtual ~CmyDlgWnd();
//{{AFX_VIRTUAL(CmyDlgWnd)
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
//}}AFX_VIRTUAL
// Implementation
protected:
// Generated message map functions
//{{AFX_MSG(myDlgWnd)
// NOTE: the ClassWizard will add member functions here
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
3.假设在对话框添加了一个按钮,现在要响应,可以手动编写处理函数:
.h:
afx_msg void OnClose();
.cpp:
BEGIN_MESSAGE_MAP(CmyDlgWnd, CDialogBar)
//{{AFX_MSG_MAP(CmyDlgWnd)
// NOTE: the ClassWizard will add message map macros here
//}}AFX_MSG_MAP
ON_BN_CLICKED(IDC_CLOSE,OnClose)
END_MESSAGE_MAP()
4.同上,如果想添加关联变量,可以这样做:
.h:
CString m_edit;
.cpp:
void CmyDlgWnd::DoDataExchange(CDataExchange* pDX)
{
CDialogBar::DoDataExchange(pDX);
//{{AFX_DATA_MAP(Csql)
DDX_Text(pDX, IDC_EDIT1, m_edit);
//}}AFX_DATA_MAP
}
5.处理完毕后,在CMainFrame中添加:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
...
//创建控制条
if(!m_wndDlg.Create(this,IDD_DIALOG2,CBRS_LEFT,100))
return -1;
//停泊控制条
m_wndDlg.EnableDocking(CBRS_ORIENT_HORZ);
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);
DockControlBar(&m_wndDlg);
}
6.注意此时对话框控制条并不能响应消息,因为消息被CMainFrame截获,因此要想办法把消息转发给CDialogBar,方法就是重载OnCmdMsg,具体的消息转发知识理论将在下次专题中推出:
BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)
{
if(CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
return TRUE;
return m_wndDlg.OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}
以上就是一种处理方法,还有一种方法是:(转载),该方法可以
提供ClassWizard帮助,比较好
用对话框创建CDialogBar派生的类并在CReBar上添加
摘要:本文详细解说了CDialogBar的具体使用过程,可以做为VC++和MFC新手学习总结用。
一、创建DialogBar的派生类
首先,创建对话框资源:在对话框资源编辑器内生成一个Dialog资源,并将其风格(Style)属性必须设置为Child,不能设置为Overlapped或Popup,否则运行肯定出错;至于边界属性则随用户自己喜欢,一般都是选择None。其余属性也随用户选择,一般没有特殊要求还是选择默认的好。
其次,创建基于CDialog的派生类:打开ClassWizard,为以上创建的资源添加一个以CDialog为基类的派生类(因为ClassWizard没有将CDialogBar列在基类目录清单中,所以用户只能先以CDialog类派生)。
再次,修改派生类以CDialogBar为基类:通常需要手工修改几处代码,在本例中派生类以
CDataStatus命名。(注:以后讲解中凡是手工改动都是以灰背景显示)
1、 在头文件中修改继承关系
将class CDataStatus : public CDialog 改为class CDataStatus : public CDialogBar
2、 在代码文件中修该构造函数继承关系
将CDataStatus::CDataStatus(CWnd* pParent /*=NULL*/)
: CDialog(CDataStatus::IDD, pParent)
{
//{{AFX_DATA_INIT(CDataStatus)
// NOTE: the ClassWizard will add member initialization here
//}}AFX_DATA_INIT
}
改为
CDataStatus::CDataStatus(CWnd* pParent /*=NULL*/)
{
//{{AFX_DATA_INIT(CDataStatus)
// NOTE: the ClassWizard will add member initialization here
//}}AFX_DATA_INIT
}
3、 将DDX绑定函数中的继承关系去掉
即将void CDataStatus::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CCurrentCheckDlg)
………..
//}}AFX_DATA_MAP
}
改为
void CDataStatus::DoDataExchange(CDataExchange* pDX)
{
//{{AFX_DATA_MAP(CCurrentCheckDlg)
………….
//}}AFX_DATA_MAP
}
4、 重新初始化函数(这个相当重要,如果不这么做的话,DDX函数形同虚设,当然用户的工具条如果没有用到DDX的话当然可以不加这段代码):
首先在ClassWizard的MessageMap中对消息该CDataStatus类的WM_INITDIALOG消息添加处理函数默认名为OnInitDialog。
其次手工修改代码如下:
1、 添加消息映射函数。由于对话框形式的初始化函数消息并未加载到消息映射内,为此我们需要手工添加,要不然代码无法拦截该工具条的初始化消息,形式如下:
将BEGIN_MESSAGE_MAP(CDataStatus, CDialogBar)
//{{AFX_MSG_MAP(CDataStatus)
.......
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
改为:
BEGIN_MESSAGE_MAP(CDataStatus, CDialogBar)
//{{AFX_MSG_MAP(CDataStatus)
.......
ON_MESSAGE(WM_INITDIALOG,OnInitDialog)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
2、 修改OnInitDialog函数,此函数并未传递参数,但是在这里我们需要让它传递参数,代码如下修改(当然头文件中,对声明也要做修改,在这里就不作赘述了)
将BOOL CDataStatus::OnInitDialog()
{
CDialogBar::OnInitDialog();
// TODO: Add extra initialization here
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
改为:
BOOL CDataStatus::OnInitDialog(UINT wParam,LONG lParam)
{
//CDialogBar::OnInitDialog();
// TODO: Add extra initialization here
BOOL bRet = HandleInitDialog(wParam,lParam);
if (!UpdateData(FALSE))
{
TRACE("InitCDataStatus Failed!");
}
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
二、在框架类中实现该派生类的对象化
首先,在框架类的头文件内声明实例对象,本例实例化:CDataStatus m_wndDataStatus;当然头文件中不可避免要包含新派生类的头文件。
其次,在框架类的OnCreate函数内创建对象并将对象绑定对话框资源。形式与创建ToolBar原理一样,本例实例如下:
if (!m_wndDataStatus.Create(this,IDD_DATASTATUS,WS_VISIBLE|WS_CHILD
|CBRS_SIZE_DYNAMIC|CBRS_BOTTOM,IDD_DATASTATUS))
{
TRACE0("Failed to create CDataStatus bar!");
return -1;
}
再次,最为关键的一点就是重写框架类的OnCmdMsg虚函数。如果不重写该函数,那么不光DDX功能无法实现,连最基本的OnCommand事件都无法实现。而且还得手工添加代码,形式如下:
BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
{
// TODO: Add your specialized code here and/or call the base class
return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}
改为:
BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)
{
// TODO: Add your specialized code here and/or call the base class
if (m_wndDataStatus.OnCmdMsg(nID,nCode,pExtra,pHandlerInfo))
return TRUE;
return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}
三、在CReBar上添加该实例化对象
其实这一步倒是相当简单,只是自己以前没用过这个类,所以在这里也顺便用了一下。
首先,在框架类的头文件中用CRebar声明一个对象,如CReBar m_wndReBar;
其次,在框架类的代码文件中的OnCreat函数体内,生成对象,代码如下:
if (!m_wndReBar.Create(this,RBS_BANDBORDERS,WS_CHILD |
WS_VISIBLE| CBRS_BOTTOM|WS_CLIPSIBLINGS|WS_CLIPCHILDREN))
{
TRACE0("Failed to create Rebar \n");
return -1;
}
再次,就是将所要添加的toolbar以及新生成的CDataStatus对象m_wndDataStatus加进Rebar的对象m_wndReBar中,代码如下:
m_wndReBar.AddBar(&m_wndDataStatus,NULL,NULL,
RBBS_GRIPPERALWAYS|RBBS_FIXEDBMP);
CListCtrl学习笔记(3)---中级篇(2)
专题2:CListCtrl中如何实现排序功能?
1.排序功能的实现需要用到下面这个函数:
BOOL SortItems( PFNLVCOMPARE pfnCompare, DWORD dwData );
其中 第一个参数是回调函数的指针,后面是所要传递的参数
而回调函数一般定义如下:
int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2,
LPARAM lParamSort);其中lParam1是其中list的一项数据,而lparam2是另外一项,第三个参数是刚才传递过来的参数.
2.举例如下:
(1)回调函数的编写
注意回调函数必须是全局或者静态的,并且不能调用成员变量,所以要凭借最后一个参数传递某些数据.
int CALLBACK CompareFunc(LPARAM lParam1,LPARAM lParam2,LPARAM lParamSort)
{//传递list指针
Cmylist *list=(Cmylist*)lParamSort;//获得指定行的索引号
LVFINDINFO findInfo;
findInfo.flags = LVFI_PARAM;
findInfo.lParam = lParam1;
int iItem1 = list->FindItem(&findInfo, -1);
findInfo.lParam = lParam2;
int iItem2 = list->FindItem(&findInfo, -1);//根据指定列的索引号,得到所在项的数据
CString strItem1 =list->GetItemText(iItem1,list->selectedField);
CString strItem2 =list->GetItemText(iItem2,list->selectedField);//判断是升序还是降序
if(!list->shunxu)
if(index==1)//判断数据类型是整型还是字符串
return strcmp(strItem2, strItem1);
else
return atoi(strItem2)-atoi(strItem1);
else
if(index==1)
return -strcmp(strItem2, strItem1);
else
return atoi(strItem1)-atoi(strItem2);
}(2)调用函数的编写
如果是点击列头响应排序功能,那么请响应LVN_COLUMNCLICK:
如果是其他,例如响应菜单项:
void Cmylist::OnSheng()
{
// TODO: Add your command handler code here
shunxu=TRUE;
SortItems(CompareFunc,(LPARAM)this);
}void Cmylist::OnJiang()
{
// TODO: Add your command handler code here
shunxu=FALSE;
SortItems(CompareFunc,(LPARAM)this);
}即可完成排序功能!!!
CListCtrl学习笔记(4)---中级篇(3)
专题3:如何使列头响应右击消息?
我们知道在ClistCtrl中是可以响应列头单击的,如响应消息LVN_COLUMNCLICK,但是不能响应右击.怎么办呢?
(1)
实际上我们可以把CListCtrl(Report风格)看成由子窗口CHeaderCtrl和下面的视图组成,所以我们决定重写CHeaderCtrl.
派生一个类于CHeaderCtrl,响应右击:
void CMyHeadCtrl::OnRButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CHeaderCtrl::OnRButtonDown(nFlags, point);
}在其中编写你想要的代码.这里完成了第一步.
(2)
此时还没有安装到ClistCtrl 上呢?想什么办法安装呢?以前在重写控件的时候,是通过在ClassWizard中定义变量来实现的,现在显然不行.因为ClistCtrl自己就是一个控件了.难道就没有办法了吗?
有.在CListCtrl中不是有个函数是GetHeaderCtrl吗?它不就是获得列头的指针吗?我们应该怎样利用它呢?
在想想子类化的方法:
如果我们重写了一个控件,如CMyEdit(派生于CEdit),现在要安装,除了在classwizard中安装以外,还可以在OnInitDialog中进行子类化:
.h:
CMyEdit m_edit;
.cpp:
m_edit.SubClassDlgItem(IDC_EDIT1,this);
(3) 回到我们这里的话题.显然不能用subclassDlgItem了,因为列头没有什么标识号,不过还可以用SubClassWindow,参数正好是一个句柄.
实现如下:
重写Cmylist(CListCtrl的派生类)虚函数:
void Cmylist::PreSubclassWindow()
{
// TODO: Add your specialized code here and/or call the base class
m_headctrl.SubclassWindow(GetHeaderCtrl()->GetSafeHwnd());
CListCtrl::PreSubclassWindow();
}
安装成功!!!
CTreeCtrl学习笔记1--基础篇
以下的说明中,注意以下变量的定义:
CTreeCtrl m_treectrl;//是关联变量
1. 如何获得选中项句柄?
HTREEITEM h=m_treectrl.GetSelectedItem();
2.如何获得右击项句柄?
void CLayerDialog::OnRclick(NMHDR* pNMHDR, LRESULT* pResult)
{
// TODO: Add your control notification handler code here
NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
//右击获取所选项
CPoint point,p;
TVHITTESTINFO HitTestInfo;
GetCursorPos(&point);
m_treectrl.ScreenToClient(&point);
HitTestInfo.pt = point;
HTREEITEM h = m_treectrl.HitTest(&HitTestInfo);
if(h!=NULL)
{
。。。。。//需要代码
}
}
3.如何响应checkbox被单击?
响应单击事件,并且进行判断
void CLayerDialog::OnLclick(NMHDR *pNMHDR,LRESULT *pResult)
{
NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
// TODO: Add your control notification handler code here
CPoint p;
GetCursorPos(&p);
m_treectrl.ScreenToClient(&p);
UINT nFlag;
HTREEITEM h=m_treectrl.HitTest(p,&nFlag);
if ((h != NULL) && (TVHT_ONITEMSTATEICON & nFlag))
{
。。。。。//需要代码
}
}
4.设置和获取checkbox的状态函数
GetCheck( ) SetCheck( )
5.选中特定项
HTREEITEM h=…;
m_treectrl.SelecteItem(h);
6.插入标记
这是拖曳时经常用到的函数。
BOOL SetInsertMark( HTREEITEM hItem, BOOL fAfter = TRUE );
TRUE表示在hItem下面显示横杠,而FALSE则表示在上面。
同类函数还有:
SetInsertMarkColor,GetInsertMarkColor
7 .获得兄弟姐妹项
HTREEITEM GetPrevSiblingItem( HTREEITEM hItem );
HTREEITEM GetNextSiblingItem( HTREEITEM hItem );
很好用的函数。但是要注意如果hItem是第一项,那么GetPrevSiblingItem返回NULL。此时要注意判断。GetNextSiblingItem同理。
CTreeCtrl学习笔记2-专题篇
如何对CTreeCtrl的项进行拖曳
(1) 取消TVS_DISABLEDRAGDROP 样式,该样式会禁止发送TVN_BEGINDRAG消息
(2) 派生一个类于CTreeCtrl,定义几个成员变量:
CImageList *m_pDragImage;//拖曳图像列表指针
BOOL m_bLDragging;//是否拖曳
HTREEITEM m_hitemDrag,m_hitemDrop;//拖曳项和目标项
(3)响应拖曳消息TVN_BEGINDRAG
void CTreeCtrlEx::OnBegindrag(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
// TODO: Add your control notification handler code here
m_hitemDrag=pNMTreeView->itemNew.hItem;//获得拖曳项
m_hitemDrop=NULL;
m_pDragImage=CreateDragImage(m_hitemDrag);//创建拖曳图像
if(!m_pDragImage)
return;
this->m_bLDragging=TRUE;
m_pDragImage->BeginDrag(0,CPoint(-15,-15));//后面的point表示拖曳图像离拖曳项的相对位置
POINT pt=pNMTreeView->ptDrag;
ClientToScreen(&pt);
m_pDragImage->DragEnter(NULL,pt);//开始显示拖曳图像
SetCapture();//锁定鼠标
*pResult = 0;
}
(4)响应鼠标移动消息
void CTreeCtrlEx::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
HTREEITEM hitem;
UINT flags;
if(m_bLDragging)
{
POINT pt=point;
ClientToScreen(&pt);
CImageList::DragMove(pt);//当鼠标移动时也移动拖曳的图像
if((hitem=HitTest(point,&flags))!=NULL)//确定鼠标所在的项
{
CImageList::DragShowNolock(FALSE);//隐藏拖曳图像
SelectDropTarget(hitem);
m_hitemDrop=hitem;//确定目标项
CImageList::DragShowNolock(TRUE);//重新显示
}
}
CTreeCtrl::OnMouseMove(nFlags, point);
}
(5)鼠标弹起消息
void CTreeCtrlEx::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
if(m_bLDragging)//是否拖动
{
m_bLDragging=FALSE;
CImageList::DragLeave(this);
CImageList::EndDrag();//结束拖曳
ReleaseCapture();
delete m_pDragImage;
SelectDropTarget(NULL);
if(m_hitemDrag==m_hitemDrop)
return;
MessageBox("拖曳成功!");
。。。。现在已经获知了拖曳项和目标项,就看你自己需要什么代码了,略去。
}
CTreeCtrl::OnLButtonUp(nFlags, point);
}
CTreeCtrl学习笔记3--专题篇
如何给树控件加入工具提示 |
|
|
工具栏上快速添加文字
m_wndToolBar.SetButtonText(0," 控制 ");
m_wndToolBar.SetButtonText(1," 编辑 ");
/调整工具条/
CRect rc(0, 0, 0, 0);
CSize sizeMax(0, 0);
CToolBarCtrl& bar = m_wndToolBar.GetToolBarCtrl();
//可能由于字数的不同,所以要找到一个图形最大的工具栏项
for (int nIndex = bar.GetButtonCount() - 1; nIndex >= 0; nIndex--)
{
bar.GetItemRect(nIndex, rc);
rc.NormalizeRect();
sizeMax.cx = __max(rc.Size().cx, sizeMax.cx);
sizeMax.cy = __max(rc.Size().cy, sizeMax.cy);
}
m_wndToolBar.SetSizes(sizeMax, CSize(16,15));
c中的陷阱
先看个题目:
void fun(char *p1)
{
p1=p1+1;}
main()
{
char *p="abcd";
fun(p);
cout<<*p;
}
这个题目看起来非常简单,但对于初学者来说却是个巨大的陷阱.很多初学者在学习c的时候就会听老师说过:指针是可以用来传值,甚至可以不要return;这句话本身没有错误,错的是初学者的理解!
首先说一下答案:结果是a不是b
这也许出乎你的意料.但是仔细一想就明白了.首先定义了一个字符型指针p,让它指向一个字符串的首地址,如果没有fun函数,那么*p='a';但是现在调用了fun,难道对p没有影响?
是的.调用fun的时候,会将p的值传给p1,也就是说现在p1也指向这个字符串的首地址了.然后执行:p1=p1+1;这样p1会指向下一个字符,也就是'b',但是p还是指向首字符!!!
所以结果也就出来了.这时候你可能会怀疑你以前学的指针是不是出现了问题.再来看这个题目,你就明白了:
void fun(char *p1)
{
*p1=*p1+2;
}
主函数不变,结果是'c'(a+2),同样p1开始指向了首地址,就当作一个箱子,然后把箱子里的东西换了(*p1),等调用结束以后,下个人(主函数)再来看的时候,箱子里的东西已经变化了!
总之,希望初学者在学习的时候多加琢磨一些关键的话语,这对你的分析能力是很有帮助的.
2.
main()
{
char p[]={'a','b','c'};
char q[]="abc";
printf("%d,%d,%d,%d",strlen(p),strlen(q),sizeof(p),sizeof(q));
}
感兴趣的读者,你能在不上机的情况下说出结果吗?
答案 是3,3,3,4.
我们知道strlen测的 是字符串的实际长度,不包括'\0',而sizeof测的是开始分配的空间.对于char q[]="abc";实际上应该是q[4],而对于char p[]={'a','b','c'};这种写法在很多C语言书上是不允许的,因为没有结束符.所以它的结果可能会出乎意料.在此不多作申明.不过如果写成char p[]={'a','b','c','\0'};那么sizeof(p)=4
3.new的问题
初学者都知道new是用来动态分配空间的,可以不用初始化,而直接赋值.如:
int *p=new int;
*p=5;//ok
但是
int *p;//只是一个地址变量,并不知道所指的空间
*p=5;//error
除了这些之外,你还知道关于它的什么吗?可能你还知道要释放.
那不释放会出现什么呢?
在<<c++程序设计教程>>(钱能著)的第14章有段很重要的话:
"c++程序的内存格局通常有4个区,
- 全局代码区
- 代码区
- 栈区
- 堆区
全局变量,静态数据,常量存放在全局数据区,所有类成员函数和非成员函数代码存放在代码区,为运行函数而分配的局部变量,函数参数,返回数据,返回地址,等存放在栈区,余下的空间都被作为堆区."
也就是说我们用new创建的空间是分配在堆区了,那堆区有什么性质呢?
"操作堆内存时,如果分配了内存,就有责任回收它,否则运行的程序会造成内存泄漏.但是在栈区,只有局部对象退出了作用域,系统自动回收.注意,对于类而言,堆对象的作用域是整个程序生命期,除非调用了delete,否则就不调用其析构函数."
如此说来,如果不用delete释放内存,就会造成内存泄漏,问题可就大了,但是有的时候并不是刚用new创建就要求析构.它的好处就是和整个程序同生命周期,那这样只要你保存了内存地址,就可以随时访问了.
最后再DELETE也不迟.
下面举出一个在MFC中非常经典的例子:
创建模态窗口与非模态是不同的,前者通常用如下代码:
Cmydlg dlg;
dlg.Domodal();
有人担心dlg是局部对象会被析构,并非如此,在执行到domodal时,程序会暂停执行,等待执行结果,传值给domodal,不过这里不是我要讲的主题.
再看看非模态窗口的创建过程:
(1)Cmydlg dlg;
dlg.Create(..);
dlg.ShowWindow();
这样的创建程序是会出错的,因为dlg获得是栈上内存,会自动析构,那关联的资源就没了.
(2)Cmydlg *dlg=new Cmydlg;
dlg->Create(..);
dlg->ShowWindow();
这种就是对的了.因为此时分配的是堆上内存,生命期为整个程序,所以不用担心内存被回收,也就不用担心窗口会显示不出了.
当然如果关闭了对话框那么这段内存就要回收了,可以用下面的代码:
void CMydlg::PostNcDestroy()
{
delete this;
}