关于OnOK()、OnCancel()、OnClose()、OnDestroy() 模式对话框 非模式对话框

简介: 总结OnOK()、OnCancel()、OnClose()、OnDestroy()之间的区别(转) 2009年09月22日 下午 08:33 第一,OnOK()和OnCancel()是CDialog基类的成员函数,而OnClose()和OnDestroy()是CWnd基类的成员函数,即WM消息响应函数。

总结OnOK()、OnCancel()、OnClose()、OnDestroy()之间的区别(转)

2009年09月22日 下午 08:33

第一,OnOK()和OnCancel()是CDialog基类的成员函数,而OnClose()和OnDestroy()是CWnd基类的成员函数,即WM消息响应函数。从应用程序结构的角度,拿对话框来说,红色的X对应的是CWnd,而处于对话框中的“确定”、“取消”按钮则对应了CDialog。

第二,OnClose()和OnDestroy()

在单视图程序中,根据<<深入浅出MFC>>所讲,程序退出时执行的操作顺序为(从点X按钮开始)
(1)用户点击X退出按钮,发送了WM_CLOSE消息----->响应OnClose()
(2)在WM_CLOSE消息的处理函数中,调用DestroyWindow()----->销毁与指定CWnd窗口对象关联的窗口,但未销毁CWnd对象
(3)在DestroyWindow()中发送了WM_DESTROY消息----->窗口销毁后响应OnDestroy()
(4)在WM_DESTROY消息中调用PostQuitMessage(),发送WM_QUIT消息,结束消息循环

可以看到,程序的退出过程,是先响应OnClose(),然后响应OnDestroy(),在响应OnDestroy()之前,窗口对象已经被销毁。OnDestroy()到底干了什么呢?它就像一个teller,先通知CWnd对象告诉它即将被销毁,尔后OnDestroy的真正运行是在CWnd对象已经从屏幕上清除以后被调用的。

第三,OnOK()、OnCancel()()、OnClose()、OnDestroy()

CDialog::OnOK首先调用UpdateData(TRUE)将数据传给对话框成员变量,然后调用CDialog::EndDialog关闭对话框;  
CDialog::OnCancel只调用CDialog::EndDialog关闭对话框;  
OnClose()是响应   WM_CLOSE   的.一定程度上可以说CDialog::EndDialog()和OnClose()完成类似的工作,但处理的机制不一样,前者是CDialog的对象机制,后者是WM的消息映射机制。

CDialog::EndDialog()-------->OnDestroy()

                 OnClose()-------->OnDestroy()

EndDialog()和OnClose()属于“同级别”的,所以我们在按下OK按钮的时候,程序是不会执行OnClose()的,但两种机制都必须经过OnDestroy()

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/wind1987321/archive/2009/09/21/4576585.aspx

 

 

然后发现

下面这两种说法不明晰

(1) EndDialog(-1);
关闭模态对话框,并且将参数作为父对话框调用的返回值。
(2)  DestroyWindow();  ::PostQuitMessage(0);
DestroyWindow 关闭非模态对话框。 退出消息循环,真正结束进程。有不少程序窗口关闭,但是不等于退出运行。

 

 

cDialog::onok(),enddialog(),destroywindow区别。 收藏

模式和无模式对话的中止是不一样的:模式对话通过调用CDialog : : EndDialog 来中止,无模式对话则是调用CWnd: : DestroyWindow来中止的,函数CDialog : : OnOK和CDialog : : OnCancel调用EndDialog ,所以需要调用DestroyWindow并重置无模式对话的函数。

 

最后三个还不错

Windows API一日一练(18)EndDialog函数 收藏
上一次介绍了怎么样显示对话框的函数,那么怎么样关闭对话框呢?这就需要使用到函数EndDialog。这个函数只能在对话框的消息处理函数里使用,并且这个函数调用之后,没有立即就删除对话框的,而是设置了操作系统里的结束标志。当操作系统查检到有这个标志时,就去删除对话框的消息循环,同时也去释放对话框占用的资源。其实对话框的生命周期是这样的,先由函数DialogBox创建对话框,这样函数DialogBox完成创建对话框但还没有显示前会发出消息WM_INITDIALOG,让对话框有机会初始化上面所有窗口或控件的显示,比如设置文本框的字符串等。最后当用户点出确定或者取消的按钮,就收到两个命令IDOK或IDCANCEL,这时就可以调用函数EndDialog来结束对话框的生命。
函数EndDialog声明如下:
WINUSERAPI
BOOL
WINAPI
EndDialog(
    __in HWND hDlg,
    __in INT_PTR nResult);
hDlg是对话框窗口的句柄。
nResult是设置给函数DialogBox的返回值。
调用这个函数的例子如下:
#001 // 显示关于对话框。
#002 //
#003 // 蔡军生 2007/07/12
#004 //
#005 INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
#006 {
#007  UNREFERENCED_PARAMETER(lParam);
#008  switch (message)
#009  {
#010  case WM_INITDIALOG:
#011         return (INT_PTR)TRUE;
#012
#013  case WM_COMMAND:
#014         if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
#015         {
#016              EndDialog(hDlg, LOWORD(wParam));
#017               return (INT_PTR)TRUE;
#018         }
#019         break;
#020  }
#021  return (INT_PTR)FALSE;
#022 }
第16行就是调用函数EndDialog来关闭对话框。 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/caimouse/archive/2007/07/30/1716140.aspx

 

 

非模式对话框

2009-03-23 17:10

1、非模式对话框的打开:
// 弹出无模式对话框
    CDevPproperty *pDlg= new CDevPproperty;
    pDlg->Create(IDD_DEV_PROPERTY,GetDesktopWindow());
    // 填充设备属性,用SetDlgItemInt()时就不用再定义一个CString来将数据转换成字符串了
    pDlg->SetDlgItemInt(IDC_EDT_DEV_ID,i,TRUE);

    // 为了实现向组合框发送选项,而又不想定义变量,所以这里用了消息传递  
    pWnd=pDlg->GetDlgItem(IDC_CMB_DEV_STATUS);
    pWnd->SetFocus();   // 设置对话框中的焦点
    pWnd->SendMessage(CB_SETCURSEL,CDeviceInfo[i].status,0);

    // 显示窗口
    pDlg->ShowWindow(SW_SHOW);

2、关闭非模式对话框:
DestroyWindow();
delete this;
3、在非模式对话框中向主对话框发送消息:

// 获取全局句柄,然后调用Invalidate()来更新窗口
AfxGetMainWnd()->Invalidate();

4、主窗口中向非模式对话框发送消息

    // 填充设备属性,用SetDlgItemInt()时就不用再定义一个CString来将数据转换成字符串了
    pDlg->SetDlgItemInt(IDC_EDT_DEV_ID,i,TRUE);

    // 为了实现向组合框发送选项,而又不想定义变量,所以这里用了消息传递  
    pWnd=pDlg->GetDlgItem(IDC_CMB_DEV_STATUS);
    pWnd->SetFocus();   // 设置对话框中的焦点
    pWnd->SendMessage(CB_SETCURSEL,CDeviceInfo[i].status,0);
5、将非模式对话框显示在父窗口后面,并且可以切换

一种解决办法是:
建立非模式对话框时Create的第二个参数用GetDesktopWindow(),
m_pDlg->Create(IDD_,GetDesktopWindow());

如果需要恢复Toolbar的属性:
m_pDlg->SetWindowPos(&wndTopMost,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE);

现在有出现了一个问题:系统的任务栏上出现了非模式对话框的图标,好像该对话框和父窗口是两个应用。解决的办法是:
1 定义对象 CWnd *m_pWnd,该对象的父窗口为GetDesktopWindow,设置该对象ShowWindow(SW_HIDE);
2 将非模式对话框的父窗口设置为m_pWnd。

6、非模式对话框与主对话框是一个消息循环

7、如何取得非模式对话框的父窗口指针

取父窗口指针用GetParent()

class CWnd* hWnd = FindWindow(NULL,"窗口标题");

8、基于文档/视图的主窗口均是CMainFrame对象,需要在CView内响应的消息应该这样发送:
CMainFrame *pwnd = (CMainFrame *)GetParent();
pwnd->GetActiveView()->SendMessage(...)

9、怎样才能在线程中实现对话框的顶层显示。

不知为什么设置成WS_EX_TOPMOST并不能实现,可能我们还没有真正理解它该怎样使用。但我用另外的方法实现了:

SetWindowPos (&wndTopMost, 0, 0, 0, 0,SWP_NOMOVE | SWP_NOSIZE)。

10、建立非模态对话框时,它总是在主窗口的最上面,如何才能使它的主窗口显示在上面.
答:1)你有没有试过AfxGetMainWnd()->SetForegroundWindow(),在建立你的非模态对话框之后?
2)当你建立对话框时,向导建立的构造函数有一个指针指出该对话框的父窗口,如果你输入一个窗口,那么该对话框将总是显示在该窗口的上面,如果你输入一个NULL 那么该对话框就可以在主程序窗口的上面或者下面了.不过这时要仔细考虑用户界面,如果非模态对话框在主窗口消失,会不会让你的用户产生误会?是否将非模态对话框显示在任务条上.

11、MSDN中非模式对话框的代码
CMyDialog* pDialog;

void CMyWnd::OnSomeAction()
{
   //pDialog initialized to NULL in the constructor of CMyWnd class
   pDialog = new CMyDialog();
   //Check if new succeeded and we got a valid pointer to a dialog object
   if(pDialog != NULL)
   {
      BOOL ret = pDialog->Create(IDD_MYDIALOG,this);
      if(!ret)   //Create failed.
         AfxMessageBox("Error creating Dialog");
      pDialog->ShowWindow(SW_SHOW);
   }
   else
      AfxMessageBox("Error Creating Dialog Object");
}

14、
由于非模式对话框是在堆中动态分配的,所以每次弹出时,其中的一些变量如果弹出多个的话会有些冲突,比如我在显示每个设备的电量时,由于要用图形显示出来,所以要保存好原来的位置,然后再从第一个位置开始循环画点,这个时候就会发现,弹出的多个对话框中的值是相同的,因此必须要区分开来,我用了两种办法,一是用数组,可以是二维,也可以是一维的,这样呢,用其中的id号作标识。另一个方法是将这些全局变量声明在类内,作为类的变量存在,这样就不会互相干扰了。看代码

在非模式对话框头文件中加入变量:其中m_pt[]是为了保存所有的点的位置,而m_pt_num保存的是点的数目,m_index保存的是一个循环的索引

CPoint m_pt[X_GRID_NUM];
int m_pt_num;
int m_index;

在非模式对话框程序中加入:

CBrush drawBrush;
drawBrush.CreateSolidBrush(RGB(255,255,0));   // 初始化画刷,为黄色
pDC->SelectObject(&drawBrush);       // 选择画刷
pDC->Ellipse(CRect(-3,-3,3,3)); // 画圆,RFD类型为圆圈

Sleep(100);
len = (rc.right-20)/X_GRID_NUM;
m_pt_num = (m_pt_num+1) % X_GRID_NUM;
m_pt[m_pt_num].x=len*m_pt_num;
m_pt[m_pt_num].y=-CDeviceInfo[id].power/2;
for(m_index=1; m_index<=m_pt_num; m_index++)
{
   pDC->Ellipse(CRect(m_pt[m_index].x-1,m_pt[m_index].y-1,m_pt[m_index].x+2,m_pt[m_index].y+2));
    pDC->MoveTo(m_pt[m_index-1]);
    pDC->LineTo(m_pt[m_index]);

 

 

 

这个很棒

http://zhidao.baidu.com/question/62651044.html

对话框默认用的两个按钮的ID分别是IDOK和IDCANCEL,这两个都是在winuser.h 中预定义的系统标准控件ID。 对于标准ID,你不重载时MFC会自动调用父类的相应处理函数。 比如IDOK映射到CDialog::OnOK()函数,IDCANCEL映射到CDialog::OnCancel()。 在这两个函数的源码如下: void CDialog::OnOK() { if (!UpdateData(TRUE)) { TRACE(traceAppMsg, 0, "UpdateData failed during dialog termination.\n"); // the UpdateData routine will set focus to correct item return; } EndDialog(IDOK); } void CDialog::OnCancel() { EndDialog(IDCANCEL); } 可以看出点击这两个按钮,都会调用EndDialog()来关闭对话框,只是返回值不同。 EndDialog()函数调用了DestroyWindow()函数,DestroyWindow()函数又发送了WM_DESTROY消息,该消息的处理函数是OnDestroy(),对话框的生存期最后一个函数是PostNcDestroy()函数。 点那个叉叉呢,首先向对话框发送WM_CLOSE消息,由OnClose()函数处理,它调用DestroyWindow(),其后是和上面一样的路由。 可以看出点叉叉的时候绕过了OnOK()和OnCancel()。 小结一下: 1. 点“确定”、“取消”时的关闭路由为 OnOK()或OnCancel() ---> EndDialog() ---> DestroyWindow() ---> OnDestroy() ---> PostNcDestroy() 2. 点“关闭”标题栏按钮的关闭路由为 OnClose()---> DestroyWindow() ---> OnDestroy() ---> PostNcDestroy() 回答楼主的问题: 请注意,上面提到的这些函数统统都是可以重载的,在重载时加入了你自己的代码后,应该调用父类CDialog同名的函数才能正确路由下去,否则就关不了对话框了。 举个例子,重载了关闭的小叉叉 void CAboutDlg::OnClose() { // TODO: 在此添加消息处理程序代码和/或调用默认值 DoSomthing(0; // 执行自己的判断等等 // CDialog::OnClose(); // 把向导生成的父类调用给注释了,这时就关不了对话框了。 } 补充回答,点叉叉会发送WM_CLOSE消息,如果需要重载的话,应该在对话框的属性窗口中,选择WM_CLOSE消息来添加消息处理函数。 VS的IDE会自动添加如下三段: 1. xxx.h文件,类声明中加入OnClose()函数声明 afx_msg void OnClose(); 2. xxx.cpp文件,加入消息映射宏 ON_WM_CLOSE() // 对于Windows标准消息,都是这种简短的格式。 3. xxx.cpp文件,加入函数体 void CMyDlg::OnClose() { CDialog::OnClose(); } 上述3处如果都正常的话,叉叉就映射到OnClose()了。你说的映射到OnCancel()个人觉得有两种可能,第一、缺ON_WM_CLOSE()宏,却多个一个ON_BN_CLICKED(IDCLOSE, &CMyDlg::OnCancel)宏第二、在OnClose()中调用了OnCancel()

 

 

论模式和非模式对话框

2009-03-23 17:11

论模式和非模式对话框... 1

1:摘要... 1

2:模式对话框的显示... 1

3:模式对话框的循环等待... 3

4:模式对话框的循环终止... 6

5:与OK和Cancle按钮的联系... 6

1:摘要

模式对话框使用dlg.DoModal()函数,程序会在你按下OK或者Cancle按钮之前处于等待状态。然后点击OK或者Cancle按钮,就可以调用EndDialog函数消除模式对话框。

相比之下,非模式对话框可能要显得复杂,你要使用Create函数创建非模式对话框,并且在推出时,必须调用CWnd::DestroyWindow函数销毁窗口。而且要注意的是,你若想点击OK按钮使非模式对话框推出,要重写OnOK函数,使其调用CWnd::DestroyWindow。

那么,这是为什么呢?模式对话框的实现真的比非模式要简单吗?让我们看一下CDialog::DoModal()的源代码。

2:模式对话框的显示

INT_PTR CDialog::DoModal()

{

***********************************************

//加载模板资源

************************************************

ASSERT(m_lpszTemplateName != NULL || m_hDialogTemplate != NULL ||

m_lpDialogTemplate != NULL);

// load resource as necessary

LPCDLGTEMPLATE lpDialogTemplate = m_lpDialogTemplate;

HGLOBAL hDialogTemplate = m_hDialogTemplate;

HINSTANCE hInst = AfxGetResourceHandle();

if (m_lpszTemplateName != NULL)

{

hInst = AfxFindResourceHandle(m_lpszTemplateName, RT_DIALOG);

HRSRC hResource = ::FindResource(hInst, m_lpszTemplateName, RT_DIALOG);

hDialogTemplate = LoadResource(hInst, hResource);

}

if (hDialogTemplate != NULL)

lpDialogTemplate = (LPCDLGTEMPLATE)LockResource(hDialogTemplate);

// return -1 in case of failure to load the dialog template resource

if (lpDialogTemplate == NULL)

return -1;

***********************************************

//使父窗口无效

***********************************************

HWND hWndParent = PreModal();

AfxUnhookWindowCreate();

BOOL bEnableParent = FALSE;

if (hWndParent && hWndParent != ::GetDesktopWindow() && ::IsWindowEnabled(hWndParent))

{

::EnableWindow(hWndParent, FALSE);

bEnableParent = TRUE;

}

TRY

{

***********************************************

//创建非模式对话框

***********************************************

AfxHookWindowCreate(this);

if (CreateDlgIndirect(lpDialogTemplate,

CWnd::FromHandle(hWndParent), hInst))

{

if (m_nFlags & WF_CONTINUEMODAL)

{

// enter modal loop

DWORD dwFlags = MLF_SHOWONIDLE;

if (GetStyle() & DS_NOIDLEMSG)

dwFlags |= MLF_NOIDLEMSG;

***********************************************

//关键:调用RunModalLoop函数,程序进入其内的for循环

//所以,模式对话框在点击OK或Cancel前,程序会暂时等待。

***********************************************

VERIFY(RunModalLoop(dwFlags) == m_nModalResult);

}

***********************************************

//在父窗口可用前,先隐藏对话框(注:暂时还没有销毁)

***********************************************

if (m_hWnd != NULL)

SetWindowPos(NULL, 0, 0, 0, 0, SWP_HIDEWINDOW|

SWP_NOSIZE|SWP_NOMOVE|SWP_NOACTIVATE|SWP_NOZORDER);

}

}

CATCH_ALL(e)

{

DELETE_EXCEPTION(e);

m_nModalResult = -1;

}

END_CATCH_ALL

***********************************************

//使父窗口可用,并且激活父窗口

***********************************************

if (bEnableParent)

::EnableWindow(hWndParent, TRUE);

if (hWndParent != NULL && ::GetActiveWindow() == m_hWnd)

::SetActiveWindow(hWndParent);

***********************************************

//销毁对话框

***********************************************

// destroy modal window

DestroyWindow();

PostModal();

// unlock/free resources as necessary

if (m_lpszTemplateName != NULL || m_hDialogTemplate != NULL)

UnlockResource(hDialogTemplate);

if (m_lpszTemplateName != NULL)

FreeResource(hDialogTemplate);

return m_nModalResult;

}

3:模式对话框的循环等待

从上面的代码,我们可以发现,模式对话框的底层为我们实现了对话框的create和destroywindow,所以我们可以只管dlg.domoadl()来显示,然后调用EndDialog来结束。那么EndDialog 的作用是什么呢?我们看它里面的循环函数,就可以理解,原来Enddialog的作用其实是为了跳出循环函数RunModalLoop,使程序继续执行。

具体代码如下:

int CWnd::RunModalLoop(DWORD dwFlags)

{

ASSERT(::IsWindow(m_hWnd)); // window must be created

ASSERT(!(m_nFlags & WF_MODALLOOP)); // window must not already be in modal state

// for tracking the idle time state

BOOL bIdle = TRUE;

LONG lIdleCount = 0;

BOOL bShowIdle = (dwFlags & MLF_SHOWONIDLE) && !(GetStyle() & WS_VISIBLE);

HWND hWndParent = ::GetParent(m_hWnd);

m_nFlags |= (WF_MODALLOOP|WF_CONTINUEMODAL);

MSG *pMsg = AfxGetCurrentMessage();

**************************************************

//通过for (;;),使程序处于循环等待状态。

****************************************************

for (;;)

{

ASSERT(ContinueModal());

// phase1: check to see if we can do idle work

while (bIdle &&

!::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE))

{

ASSERT(ContinueModal());

// show the dialog when the message queue goes idle

if (bShowIdle)

{

ShowWindow(SW_SHOWNORMAL);

UpdateWindow();

bShowIdle = FALSE;

}

// call OnIdle while in bIdle state

if (!(dwFlags & MLF_NOIDLEMSG) && hWndParent != NULL && lIdleCount == 0)

{

// send WM_ENTERIDLE to the parent

::SendMessage(hWndParent, WM_ENTERIDLE, MSGF_DIALOGBOX, (LPARAM)m_hWnd);

}

if ((dwFlags & MLF_NOKICKIDLE) ||

!SendMessage(WM_KICKIDLE, MSGF_DIALOGBOX, lIdleCount++))

{

// stop idle processing next time

bIdle = FALSE;

}

}

// phase2: pump messages while available

do

{

ASSERT(ContinueModal());

// pump message, but quit on WM_QUIT

if (!AfxPumpMessage())

{

AfxPostQuitMessage(0);

return -1;

}

// show the window when certain special messages rec'd

if (bShowIdle &&

(pMsg->message == 0x118 || pMsg->message == WM_SYSKEYDOWN))

{

ShowWindow(SW_SHOWNORMAL);

UpdateWindow();

bShowIdle = FALSE;

}

*************************************************************8

//通过判断,跳出循环,可以断定,EndDialog 和ContinueModal有联系

**************************************************************

if (!ContinueModal())

goto ExitModal;

// reset "no idle" state after pumping "normal" message

if (AfxIsIdleMessage(pMsg))

{

bIdle = TRUE;

lIdleCount = 0;

}

} while (::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE));

}

ExitModal:

m_nFlags &= ~(WF_MODALLOOP|WF_CONTINUEMODAL);

return m_nModalResult;

}

4:模式对话框的循环终止

EndDialog函数调用EndMoadlLoop函数,以便跳出循环。

void CDialog::EndDialog(int nResult)

{

ASSERT(::IsWindow(m_hWnd));

if (m_nFlags & (WF_MODALLOOP|WF_CONTINUEMODAL))

EndModalLoop(nResult);

::EndDialog(m_hWnd, nResult);

}

BOOL CWnd::ContinueModal()

{

return m_nFlags & WF_CONTINUEMODAL;

}

void CWnd::EndModalLoop(int nResult)

{

ASSERT(::IsWindow(m_hWnd));

*****************************************************

// m_nModalResult的值为IDOK或者IDCANCEL,它将作为DoModal的返回值

*****************************************************

m_nModalResult = nResult;

// make sure a message goes through to exit the modal loop

if (m_nFlags & WF_CONTINUEMODAL)

{

m_nFlags &= ~WF_CONTINUEMODAL;

PostMessage(WM_NULL);

}

}

5:与OK和Cancle按钮的联系

为什么按下OK或者Cancle按钮会终止模式对话框呢?因为它们都调用了EndDialog函数,代码如下:

注:IDOK和IDCANCEL将会作为DoModal的返回值。

从下面的代码可以看出,OnOK()和OnCancel()消息响应函数并没有调用DestroyWindow,它们只是调用了EndDialog跳出循环,并没有销毁窗库。对模式对话框,DoModal函数自动调用DestroyWindow,而对非模式对话框,我们若要使用OK或者Cancle按钮结束对话框,必须重写OnOK按钮以使其调用DestroyWindow销毁窗口。

void CDialog::OnOK()

{

if (!UpdateData(TRUE))

{

TRACE(traceAppMsg, 0, "UpdateData failed during dialog termination.\n");

// the UpdateData routine will set focus to correct item

return;

}

EndDialog(IDOK);

}

void CDialog::OnCancel()

{

EndDialog(IDCANCEL);

}

目录
相关文章
|
6月前
|
C++
(13)对话框
(13)对话框
|
7月前
|
JavaScript API
选择用户对话框变到后面去了
选择用户对话框变到后面去了
43 0
C# “文件选择对话框“ 和 “保存对话框“
C# “文件选择对话框“ 和 “保存对话框“
|
程序员 Windows
【windows编程之对话框】对话框原理,对话框的创建
【windows编程之对话框】对话框原理,对话框的创建
|
容器
对话框和窗口的区别
“窗口”与“对话框”有什么区别?                       计算机类稿件的一大特点是文中的图很多。在这大量的图中,系统软件或应用软件的“界面”、“窗口”、“对话框”、“提示框”等的截图又占了很大的比例。
4650 0
|
Android开发
不可取消的对话框
牙叔教程 简单易懂
140 0
QT应用编程: QGraphicsTextItem单击选中、双击进入编辑状态
QT应用编程: QGraphicsTextItem单击选中、双击进入编辑状态
655 0
QT应用编程: QGraphicsTextItem单击选中、双击进入编辑状态