寻求在非模式对话框上使用定时器的方法,即使非模式对话框本身或其上子窗口在SetTimer后响应OnTimer函数

时间:2022-08-03 23:29:19
如题。难道在非模式对话框上就不能使用定时器吗?

21 个解决方案

#1


没有自己的消息循环,恐怕不行啊

#2


用钩子吧

#3


wokao

#4


不会呀,我的非模式对话框就能响应定时器呀!请先说说您是怎么做的,为什么不能响应?

#5


你需要重载主窗口的PreMessageFilter在消息过滤时做判断是不是对话框消息
用IsDialogMessage函数。他会自动将消息给你的对话框

#6


同意speakboy(一个菜鸟的自白---我想变成高手!) 的见解,先说说你是怎么做的吧,很可能是其他问题导致的。

#7


我是这样做的,
见:
http://www.csdn.net/Expert/TopicView1.asp?id=947034

#8


因为你是在CGradientbar中调用的SetTimer,所有windows会把所有的WM_TIMER消息都发送到CGradientbar窗口,但你把对WM_TIMER的响应放到了CStatic中,它是收不到WM_TIMER消息的。我觉得你把ON_WM_TIMER移到CGradientbar中就可以了。

#9


汗....

绝对是这个原因,CWnd::SetTimer是调用::SetTimer(hwnd,...)实现的,timer在把WM_TIMER消息送到消息队列时会用hwnd的值填充MSG结构的hwnd成员,结果DispatchMessage就把WM_TIMER发给hwnd也就是调用SetTimer的窗口的Proc了,其它的窗口收不到。

原贴竟然讨论了那么多...

#10


to webber84(糕鱼昏):
你是如何知道我把对WM_TIMER的响应放到了CStatic中?我不是已经在CGradientbar的cpp里定义了ON_WM_TIMER吗?
BEGIN_MESSAGE_MAP(CGradientbar, CStatic)
//{{AFX_MSG_MAP(CGradientbar)
ON_WM_PAINT()
ON_WM_TIMER()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

to 汗.....
我就是在CGradientbar::Start();里调用的SetTimer(),是这个不对吗?

为什么同样的代码,如果对话框是模式的就可以,非模式的就不可以呢?

要不,谁亲自做一个可以响应对话况或使其子窗口响应各自OnTimer函数的非模式对话框?

#11


to iProgram (小癞蛤蟆):
   没看清楚你的原贴,不好意思。不过CGradientbar也的确不像是个static的名字,我还以为是一个dialogbar呢。等我回学校一定要试试看。

#12


作为主对话框的一个成员,然后在主对话框中作一个onTimer,在其中调用m_child.OnTimerEx(timeid),OnTimerEx为m_child的公有成员,就可以,这样就可以了。不过,在调用之前,一定是窗口追chuangjian了

#13


to iProgram : 对不起,我没有看清就回了贴,见笑了。

这是我在一个Modeless Dialog上运行的一个Static控件,处理了WM_TIMER消息,可以运行:

class CMyStatic : public CStatic
{
public:
CMyStatic();

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CMyStatic)
protected:
virtual void PreSubclassWindow();
//}}AFX_VIRTUAL

virtual ~CMyStatic() {};

protected:
UINT m_iTimer;

// Generated message map functions

//{{AFX_MSG(CMyStatic)
afx_msg void OnDestroy();
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnTimer(UINT nIDEvent);
//}}AFX_MSG

DECLARE_MESSAGE_MAP()
};

CMyStatic::CMyStatic()
{
m_iTimer = (UINT)-1;
}

BEGIN_MESSAGE_MAP(CMyStatic, CStatic)
//{{AFX_MSG_MAP(CMyStatic)
ON_WM_DESTROY()
ON_WM_CREATE()
ON_WM_TIMER()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

void CMyStatic::PreSubclassWindow() 
{
if (m_iTimer == (UINT)-1)
m_iTimer = SetTimer(::GetWindowLong(GetSafeHwnd(), GWL_ID), 500, NULL);

CStatic::PreSubclassWindow();
}

void CMyStatic::OnDestroy() 
{
CStatic::OnDestroy();

if (m_iTimer != (UINT)-1)
{
KillTimer(m_iTimer);
m_iTimer = (UINT)-1;
}
}

int CMyStatic::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
if (CStatic::OnCreate(lpCreateStruct) == -1)
return -1;

if (m_iTimer == (UINT)-1)
m_iTimer = SetTimer(::GetWindowLong(GetSafeHwnd(), GWL_ID), 500, NULL);

return 0;
}

void CMyStatic::OnTimer(UINT nIDEvent) 
{
CString strStatic;
GetWindowText(strStatic);

TCHAR tchar = strStatic[0];
strStatic.Delete(0);
strStatic += tchar;

SetWindowText(strStatic);

CStatic::OnTimer(nIDEvent);
}

模式对话框是这样创建的:
m_pAboutDlg = new CAboutDlg;
m_pAboutDlg->Create(CAboutDlg::IDD);
m_pAboutDlg->ShowWindow(SW_SHOW);
m_pAboutDlg->UpdateWindow();

CMyStatic作为一个DDX_Control加在对话框上
DDX_Control(pDX, IDC_MYSTATIC, m_myStatic);

不知道是不是和你的情况一致。

#14


void CTestDlg::OnTimer(UINT nIDEvent)
{
CTime t2 = CTime::GetCurrentTime();;
m_smsg.Format("%d:%d:%d",t2.GetHour(),t2.GetMinute(),t2.GetSecond());
UpdateData(FALSE);
CDialog::OnTimer(nIDEvent);
}

int CTestDlg::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CDialog::OnCreate(lpCreateStruct) == -1)
return -1;
SetTimer(1,1000,NULL);
return 0;
}

#15


有了一些进展。

我要实现的效果是,类似win98启动画面那种位图滚动的效果,把这种效果加在我程序的splash中,于是我做了一个类CGradientbar实现这种滚动效果,CGradientbar中用到了定时器。

我要在CMainFrame::OnCreate中显示这个Splash,因为要在OnCreate加载很多东西,耗时15秒种,因此会在OnCreate进行中,不断把所作的事情显示在splash的一个Static中(这个一直工作正常),起初因为splash只出现在OnCreate中,我直接在OnCreate的堆栈上创建splash对话框,发现定时器无效。后来改在堆上创建(CSplashDlg* m_pSplashDlg作为类成员在.h里声明),定时器依然无效,但我在OnCreate返回之前没有删除splash对象(没有delete m_pSplashDlg;),发现CMainFrame::OnCreate返回之后定时器才开始工作。所以问题不是出在非模式对话框上,问题应该是为什么在CMainFrame::OnCreate函数开始时启动的m_pSplashDlg中的定时器直到CMainFrame::OnCreate返回后才开始工作?

相关代码:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
        return -1;
    m_pSplash=new CSplashDlg(this);
    m_pSplash->Create(IDD_DIALOG_SPLASH); 
    m_pSplash->SetWindowPos(&(CWnd::wndTopMost),0,0,380,280,SWP_NOMOVE|SWP_DRAWFRAME);
    // m_wndBar是CGradientbar对象,CGradientbar::Start(0做一些初始工作,并启动CGradientbar的定时器
    m_pSplash->m_wndBar.Start();
    m_pSplash->ShowWindow(SW_SHOW);
    m_pSplash->UpdateWindow();
    //现在开始做耗时很长的加载工作
    ........................................................
    //其间不断的把工作显示在m_pSplash上:
    m_pSplash->GetDlgItem(IDC_STATIC_TODO)->SetWindowText("正在加载xxxxxx");
    ..........................................................
    //delete m_pSplash;  //如果此时不delete,return 0后m_pSplash->m_wndBar的定时器才开始工作,TNND
    return 0;
}

#16


对你的问题有一些理解了:

WM_CREATE是一个不进队消息,当调用CreateWindow(Ex)创建窗口时,会直接调用窗口过程处理WM_CREATE消息,而且该CreateWindow(Ex)调用直到窗口过程处理WM_CREATE返回后才返回。

CMainFrame的创建是在MFC进入消息循环前进行的,如上所述,它将立即调用CMainFrame::OnCreate,然后直到CMainFrame::OnCreate返回,MFC才从创建CMainFrame的调用中返回,然后进入消息循环。

由于你在非模态对话框显示的过程中,一直没有让CMainFrame::OnCreate返回,结果主线程不能进入消息循环,导致放入队列的WM_TIMER消息无法响应。

如果采用模态对话框,从DoModal代码中可以看出,DoModal函数内部有一个消息泵,负责在DoModal未返回前取出和响应进入消息队列的消息,通过这个泵,WM_TIMER消息正确的被处理了。调用MessageBox就可以响应的原因也是这样。

#17


我想可能的解决办法是写一个TimerProc,作为SetTimer的参数。这样WM_TIMER消息不会进入队列而是直接调用TimerProc响应。

#18


原来是这样。原因In355Hz(好象一条狗)已经讲得很清楚了,不过他提出的解决办法不一定能行。因为即使给出了一个TimerProc,也需要程序进入消息循环才可以,只是此时你不用再处理WM_TIMER消息,而是由DefWindowProc来调用你设置的回调函数TimerProc,与前面一样的原因,OnCreate返回以前WM_TIMER是不能进入到DefWindowProc中的。当然,那只是我的分析,你先试试看。
    如果不行的的话,你看看这个API,也许对你有用:SetWaitableTimer。

#19


想想还是用两个线程来实现比较好。

#20


TimerProc好像试过,不打算再试乐。

开一个线程为了splash,只有MS会这么做:)

这部分的开发期限过去了,过两天抽时间在开N个线程,嘿嘿

#21


原来用TimerProc也需要消息队列,我以前都没有试验过。

#1


没有自己的消息循环,恐怕不行啊

#2


用钩子吧

#3


wokao

#4


不会呀,我的非模式对话框就能响应定时器呀!请先说说您是怎么做的,为什么不能响应?

#5


你需要重载主窗口的PreMessageFilter在消息过滤时做判断是不是对话框消息
用IsDialogMessage函数。他会自动将消息给你的对话框

#6


同意speakboy(一个菜鸟的自白---我想变成高手!) 的见解,先说说你是怎么做的吧,很可能是其他问题导致的。

#7


我是这样做的,
见:
http://www.csdn.net/Expert/TopicView1.asp?id=947034

#8


因为你是在CGradientbar中调用的SetTimer,所有windows会把所有的WM_TIMER消息都发送到CGradientbar窗口,但你把对WM_TIMER的响应放到了CStatic中,它是收不到WM_TIMER消息的。我觉得你把ON_WM_TIMER移到CGradientbar中就可以了。

#9


汗....

绝对是这个原因,CWnd::SetTimer是调用::SetTimer(hwnd,...)实现的,timer在把WM_TIMER消息送到消息队列时会用hwnd的值填充MSG结构的hwnd成员,结果DispatchMessage就把WM_TIMER发给hwnd也就是调用SetTimer的窗口的Proc了,其它的窗口收不到。

原贴竟然讨论了那么多...

#10


to webber84(糕鱼昏):
你是如何知道我把对WM_TIMER的响应放到了CStatic中?我不是已经在CGradientbar的cpp里定义了ON_WM_TIMER吗?
BEGIN_MESSAGE_MAP(CGradientbar, CStatic)
//{{AFX_MSG_MAP(CGradientbar)
ON_WM_PAINT()
ON_WM_TIMER()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

to 汗.....
我就是在CGradientbar::Start();里调用的SetTimer(),是这个不对吗?

为什么同样的代码,如果对话框是模式的就可以,非模式的就不可以呢?

要不,谁亲自做一个可以响应对话况或使其子窗口响应各自OnTimer函数的非模式对话框?

#11


to iProgram (小癞蛤蟆):
   没看清楚你的原贴,不好意思。不过CGradientbar也的确不像是个static的名字,我还以为是一个dialogbar呢。等我回学校一定要试试看。

#12


作为主对话框的一个成员,然后在主对话框中作一个onTimer,在其中调用m_child.OnTimerEx(timeid),OnTimerEx为m_child的公有成员,就可以,这样就可以了。不过,在调用之前,一定是窗口追chuangjian了

#13


to iProgram : 对不起,我没有看清就回了贴,见笑了。

这是我在一个Modeless Dialog上运行的一个Static控件,处理了WM_TIMER消息,可以运行:

class CMyStatic : public CStatic
{
public:
CMyStatic();

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CMyStatic)
protected:
virtual void PreSubclassWindow();
//}}AFX_VIRTUAL

virtual ~CMyStatic() {};

protected:
UINT m_iTimer;

// Generated message map functions

//{{AFX_MSG(CMyStatic)
afx_msg void OnDestroy();
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnTimer(UINT nIDEvent);
//}}AFX_MSG

DECLARE_MESSAGE_MAP()
};

CMyStatic::CMyStatic()
{
m_iTimer = (UINT)-1;
}

BEGIN_MESSAGE_MAP(CMyStatic, CStatic)
//{{AFX_MSG_MAP(CMyStatic)
ON_WM_DESTROY()
ON_WM_CREATE()
ON_WM_TIMER()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

void CMyStatic::PreSubclassWindow() 
{
if (m_iTimer == (UINT)-1)
m_iTimer = SetTimer(::GetWindowLong(GetSafeHwnd(), GWL_ID), 500, NULL);

CStatic::PreSubclassWindow();
}

void CMyStatic::OnDestroy() 
{
CStatic::OnDestroy();

if (m_iTimer != (UINT)-1)
{
KillTimer(m_iTimer);
m_iTimer = (UINT)-1;
}
}

int CMyStatic::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
if (CStatic::OnCreate(lpCreateStruct) == -1)
return -1;

if (m_iTimer == (UINT)-1)
m_iTimer = SetTimer(::GetWindowLong(GetSafeHwnd(), GWL_ID), 500, NULL);

return 0;
}

void CMyStatic::OnTimer(UINT nIDEvent) 
{
CString strStatic;
GetWindowText(strStatic);

TCHAR tchar = strStatic[0];
strStatic.Delete(0);
strStatic += tchar;

SetWindowText(strStatic);

CStatic::OnTimer(nIDEvent);
}

模式对话框是这样创建的:
m_pAboutDlg = new CAboutDlg;
m_pAboutDlg->Create(CAboutDlg::IDD);
m_pAboutDlg->ShowWindow(SW_SHOW);
m_pAboutDlg->UpdateWindow();

CMyStatic作为一个DDX_Control加在对话框上
DDX_Control(pDX, IDC_MYSTATIC, m_myStatic);

不知道是不是和你的情况一致。

#14


void CTestDlg::OnTimer(UINT nIDEvent)
{
CTime t2 = CTime::GetCurrentTime();;
m_smsg.Format("%d:%d:%d",t2.GetHour(),t2.GetMinute(),t2.GetSecond());
UpdateData(FALSE);
CDialog::OnTimer(nIDEvent);
}

int CTestDlg::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CDialog::OnCreate(lpCreateStruct) == -1)
return -1;
SetTimer(1,1000,NULL);
return 0;
}

#15


有了一些进展。

我要实现的效果是,类似win98启动画面那种位图滚动的效果,把这种效果加在我程序的splash中,于是我做了一个类CGradientbar实现这种滚动效果,CGradientbar中用到了定时器。

我要在CMainFrame::OnCreate中显示这个Splash,因为要在OnCreate加载很多东西,耗时15秒种,因此会在OnCreate进行中,不断把所作的事情显示在splash的一个Static中(这个一直工作正常),起初因为splash只出现在OnCreate中,我直接在OnCreate的堆栈上创建splash对话框,发现定时器无效。后来改在堆上创建(CSplashDlg* m_pSplashDlg作为类成员在.h里声明),定时器依然无效,但我在OnCreate返回之前没有删除splash对象(没有delete m_pSplashDlg;),发现CMainFrame::OnCreate返回之后定时器才开始工作。所以问题不是出在非模式对话框上,问题应该是为什么在CMainFrame::OnCreate函数开始时启动的m_pSplashDlg中的定时器直到CMainFrame::OnCreate返回后才开始工作?

相关代码:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
        return -1;
    m_pSplash=new CSplashDlg(this);
    m_pSplash->Create(IDD_DIALOG_SPLASH); 
    m_pSplash->SetWindowPos(&(CWnd::wndTopMost),0,0,380,280,SWP_NOMOVE|SWP_DRAWFRAME);
    // m_wndBar是CGradientbar对象,CGradientbar::Start(0做一些初始工作,并启动CGradientbar的定时器
    m_pSplash->m_wndBar.Start();
    m_pSplash->ShowWindow(SW_SHOW);
    m_pSplash->UpdateWindow();
    //现在开始做耗时很长的加载工作
    ........................................................
    //其间不断的把工作显示在m_pSplash上:
    m_pSplash->GetDlgItem(IDC_STATIC_TODO)->SetWindowText("正在加载xxxxxx");
    ..........................................................
    //delete m_pSplash;  //如果此时不delete,return 0后m_pSplash->m_wndBar的定时器才开始工作,TNND
    return 0;
}

#16


对你的问题有一些理解了:

WM_CREATE是一个不进队消息,当调用CreateWindow(Ex)创建窗口时,会直接调用窗口过程处理WM_CREATE消息,而且该CreateWindow(Ex)调用直到窗口过程处理WM_CREATE返回后才返回。

CMainFrame的创建是在MFC进入消息循环前进行的,如上所述,它将立即调用CMainFrame::OnCreate,然后直到CMainFrame::OnCreate返回,MFC才从创建CMainFrame的调用中返回,然后进入消息循环。

由于你在非模态对话框显示的过程中,一直没有让CMainFrame::OnCreate返回,结果主线程不能进入消息循环,导致放入队列的WM_TIMER消息无法响应。

如果采用模态对话框,从DoModal代码中可以看出,DoModal函数内部有一个消息泵,负责在DoModal未返回前取出和响应进入消息队列的消息,通过这个泵,WM_TIMER消息正确的被处理了。调用MessageBox就可以响应的原因也是这样。

#17


我想可能的解决办法是写一个TimerProc,作为SetTimer的参数。这样WM_TIMER消息不会进入队列而是直接调用TimerProc响应。

#18


原来是这样。原因In355Hz(好象一条狗)已经讲得很清楚了,不过他提出的解决办法不一定能行。因为即使给出了一个TimerProc,也需要程序进入消息循环才可以,只是此时你不用再处理WM_TIMER消息,而是由DefWindowProc来调用你设置的回调函数TimerProc,与前面一样的原因,OnCreate返回以前WM_TIMER是不能进入到DefWindowProc中的。当然,那只是我的分析,你先试试看。
    如果不行的的话,你看看这个API,也许对你有用:SetWaitableTimer。

#19


想想还是用两个线程来实现比较好。

#20


TimerProc好像试过,不打算再试乐。

开一个线程为了splash,只有MS会这么做:)

这部分的开发期限过去了,过两天抽时间在开N个线程,嘿嘿

#21


原来用TimerProc也需要消息队列,我以前都没有试验过。