4.Notify消息流程

1.简介

如果想响应Notify事件,在自己的窗口的 HandleMessage() 函数中必须调用CPaintManagerUI::MessageHandler(),该函数处理了绝大部分常用的消息响应。

DuiLib将发送的Notify消息分为了同步和异步消息。同步就是立即调用,异步就是先放到队列中,下次再处理(类似PostMessage与SendMessage)。

2.发送Notify消息

CPaintManagerUI中有个函数用于程序主动发送Notify消息:

  • CPaintManagerUI::SendNotify(CControlUI*, LPCTSTR, WPARAM, LPARAM, bool)

该函数将PostMessage与SendMessage两个函数的功能集成在一个函数中,通过最后一个bool参数来区分同步消息或者异步消息:true:异步消息,false:同步消息。同步消息可以立马被执行,在消息被处理后,SendNotify() 才能返回;异步消息是先放在消息队列中,然后 SendNotify() 直接返回,异步消息会在窗口函数收到下一条消息的时候被处理。详细可以看代码:

void CPaintManagerUI::SendNotify(CControlUI* pControl, LPCTSTR pstrMessage, WPARAM wParam /*= 0*/, LPARAM lParam /*= 0*/, bool bAsync /*= false*/)
{
    //对消息进行了封装
    TNotifyUI Msg;
    Msg.pSender = pControl;
    Msg.sType = pstrMessage;
    Msg.wParam = wParam;
    Msg.lParam = lParam;

    //发送消息还得看下面这个函数
    SendNotify(Msg, bAsync);
}

void CPaintManagerUI::SendNotify(TNotifyUI& Msg, bool bAsync /*= false*/)
{
    Msg.ptMouse = m_ptLastMousePos;
    Msg.dwTimestamp = ::GetTickCount();
    if( m_bUsedVirtualWnd )
    {
        Msg.sVirtualWnd = Msg.pSender->GetVirtualWnd();
    }

    if( !bAsync ) 
    {
        // Send to all listeners
        if( Msg.pSender != NULL ) 
        {
            //控件本身处理此消息
            if( Msg.pSender->OnNotify ) 
                Msg.pSender->OnNotify(&Msg);
        }
        for( int i = 0; i < m_aNotifiers.GetSize(); i++ ) 
        {
            //同步消息,被立即执行
            static_cast<INotifyUI*>(m_aNotifiers[i])->Notify(Msg);
        }
    }
    else 
    {
        //将消息存到堆上去
        TNotifyUI *pMsg = new TNotifyUI;
        pMsg->pSender = Msg.pSender;
        pMsg->sType = Msg.sType;
        pMsg->wParam = Msg.wParam;
        pMsg->lParam = Msg.lParam;
        pMsg->ptMouse = Msg.ptMouse;
        pMsg->dwTimestamp = Msg.dwTimestamp;

        //异步消息,被放在消息队列中
        m_aAsyncNotify.Add(pMsg);
    }
}

3.异步消息被处理

异步消息的处理时机:在窗口过程被执行的时候,即执行窗口的 HandleMessage() 的时候,且在 HandleMessage() 中调用了 CPaintManagerUI::MessageHandler()。原因在于异步消息只在CPaintManagerUI::MessageHandler()中进行处理。如下代码,将其他与异步消息无关代码已简化,并将异步消息处理提出成了一个函数:

bool CPaintManagerUI::MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lRes)
{
    //异步消息处理
    handleAsyncNotifyMsg();

    // 消息过滤

    // 普通消息处理
    switch( uMsg ) 
    {
        case WM_APP + 1:
        case WM_CLOSE:
        case WM_ERASEBKGND:
        case WM_PAINT: //重要,负责绘制窗口中的各个控件
        ....
    }

    // 异步消息处理:此处是为了处理上面switch时可能产生的一些异步消息
    handleAsyncNotifyMsg();

    return false;
}

//异步消息处理:循环取出异步消息,依次处理
void handleAsyncNotifyMsg()
{
    TNotifyUI* pMsg = NULL;
    while( pMsg = static_cast<TNotifyUI*>(m_aAsyncNotify.GetAt(0)) ) 
    {
        m_aAsyncNotify.Remove(0);
        if( pMsg->pSender != NULL ) 
        {
            if( pMsg->pSender->OnNotify ) pMsg->pSender->OnNotify(pMsg);
        }
        for( int j = 0; j < m_aNotifiers.GetSize(); j++ ) 
        {
            static_cast<INotifyUI*>(m_aNotifiers[j])->Notify(*pMsg);
        }
        delete pMsg;
    }
}

4.窗口的 Notify(TNotifyUI& msg) 函数

// Listener interface
class INotifyUI
{
public:
    virtual void Notify(TNotifyUI& msg) = 0;
};

bool CPaintManagerUI::AddNotifier(INotifyUI* pNotifier)
{
    ASSERT(m_aNotifiers.Find(pNotifier)<0);
    return m_aNotifiers.Add(pNotifier);
}

从上可知,Notify() 函数为 INotifyUI 定义的一个接口函数,所有的窗口都可以实现该函数。通过利用函数 AddNotifier() 注册监听,即可以处理Notify消息。

通过搜索发现, Notify() 函数在以下地方被调用:

  • void CPaintManagerUI::SendNotify(TNotifyUI& Msg, bool bAsync /= false/);
  • bool CPaintManagerUI::MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lRes);

在上述第一个函数中,用于处理同步消息;在第二个函数中处理异步消息。而异步消息的入口也是 SendNotify() 函数,通过此函数将异步消息添加到队列中。依次可以得出结论:所有的notify消息都是通过 SendNotify() 函数来进入到notify消息处理中的。

5.结束

Duilib的所有消息类型如下:

#define DUI_MSGTYPE_MENU                   (_T("menu"))
#define DUI_MSGTYPE_LINK                   (_T("link"))

#define DUI_MSGTYPE_TIMER                  (_T("timer"))
#define DUI_MSGTYPE_CLICK                  (_T("click"))

#define DUI_MSGTYPE_RETURN                 (_T("return"))
#define DUI_MSGTYPE_SCROLL                 (_T("scroll"))

#define DUI_MSGTYPE_DROPDOWN               (_T("dropdown"))
#define DUI_MSGTYPE_SETFOCUS               (_T("setfocus"))

#define DUI_MSGTYPE_KILLFOCUS              (_T("killfocus"))
#define DUI_MSGTYPE_ITEMCLICK              (_T("itemclick"))
#define DUI_MSGTYPE_TABSELECT              (_T("tabselect"))

#define DUI_MSGTYPE_ITEMSELECT             (_T("itemselect"))
#define DUI_MSGTYPE_ITEMEXPAND             (_T("itemexpand"))
#define DUI_MSGTYPE_WINDOWINIT             (_T("windowinit"))
#define DUI_MSGTYPE_BUTTONDOWN             (_T("buttondown"))
#define DUI_MSGTYPE_MOUSEENTER             (_T("mouseenter"))
#define DUI_MSGTYPE_MOUSELEAVE             (_T("mouseleave"))

#define DUI_MSGTYPE_TEXTCHANGED            (_T("textchanged"))
#define DUI_MSGTYPE_HEADERCLICK            (_T("headerclick"))
#define DUI_MSGTYPE_ITEMDBCLICK            (_T("itemdbclick"))
#define DUI_MSGTYPE_SHOWACTIVEX            (_T("showactivex"))

#define DUI_MSGTYPE_ITEMCOLLAPSE           (_T("itemcollapse"))
#define DUI_MSGTYPE_ITEMACTIVATE           (_T("itemactivate"))
#define DUI_MSGTYPE_VALUECHANGED           (_T("valuechanged"))

#define DUI_MSGTYPE_SELECTCHANGED          (_T("selectchanged"))
<think>我们分析给定的C语言代码段: ```c if( info.notify_cnt > enco_ui_para.enco_link_info.notify_cnt ) { enco_ui_para.enco_link_info.notify_cnt = info.notify_cnt; if( (!info.notify.bat_notify) && enco_ui_para.enco_link_info.notify.bat_notify ) { info.notify.bat_notify = enco_true; ENCO_TS_LOG (0, "enco_link_info already on bat_notify" ); } if( (!info.notify.status_notify) && (enco_ui_para.enco_link_info.notify.status_notify) ) { info.notify.status_notify = enco_true; ENCO_TS_LOG (0, "enco_link_info already on status_notify" ); } if( (!info.notify.anc_change_notify) && (enco_ui_para.enco_link_info.notify.anc_change_notify) ) { info.notify.anc_change_notify = enco_true; ENCO_TS_LOG (0, "enco_link_info already on anc_change_notify" ); } memcpy((void *)&enco_ui_para.enco_link_info.notify, (void *)&info.notify, sizeof(enco_ui_notify_t)); } ``` 该代码段是一个条件语句块,当条件满足时执行大括号内的代码。具体执行部分如下: 1. **条件判断**:首先判断`info.notify_cnt`是否大于`enco_ui_para.enco_link_info.notify_cnt`。只有当这个条件为真(true)时,才会执行花括号内的代码。 2. **更新计数**:如果条件满足,则将`enco_ui_para.enco_link_info.notify_cnt`更新为`info.notify_cnt`的值。 3. **检查并恢复通知标志**:接下来有三个相似的条件判断,分别检查三种通知标志(bat_notify, status_notify, anc_change_notify): - 对于`bat_notify`:如果`info.notify.bat_notify`为假(false)而`enco_ui_para.enco_link_info.notify.bat_notify`为真(true),则将`info.notify.bat_notify`设置为`enco_true`,并打印日志。 - 同样,对`status_notify``anc_change_notify`执行相同的操作:如果新的信息中该标志为假,但旧信息中为真,则恢复新的信息中的标志为真,并打印日志。 4. **内存拷贝**:最后,使用`memcpy`函数将`info.notify`结构体的内容复制到`enco_ui_para.enco_link_info.notify`中。这里复制的是整个`enco_ui_notify_t`类型结构体。 因此,当条件满足时,整个代码块都会执行,包括更新计数、逐个检查并可能修改通知标志,以及最终的内存拷贝。 注意:在整个过程中,日志输出(`ENCO_TS_LOG`)可能被执行0次、1次、2次或3次,具体取决于三个条件分支中有多少个满足。 另外,需要注意的是,这段代码似乎是在处理通知状态的更新,确保在更新计数更大的情况下,将新的通知状态同步过来,但同时保留原有的某些通知标志(如果新的通知标志为假,而旧的为真,则将新的标志恢复为真,避免丢失设置)。 ### 代码执行流程总结 1. 判断`info.notify_cnt > enco_ui_para.enco_link_info.notify_cnt`,若成立则: - 更新`enco_ui_para.enco_link_info.notify_cnt`为`info.notify_cnt` - 检查并可能恢复`bat_notify`标志(并可能打印日志) - 检查并可能恢复`status_notify`标志(并可能打印日志) - 检查并可能恢复`anc_change_notify`标志(并可能打印日志) - 将整个`info.notify`结构体复制到`enco_ui_para.enco_link_info.notify` 如果条件不成立,则整个代码块都不会执行。 ### 相关数据结构推测 根据代码,我们可以推测存在以下数据结构: - `info` 是一个结构体,包含: - `notify_cnt`:一个计数器,可能是版本号或序列号。 - `notify`:一个结构体,包含三个布尔类型的字段(可能): - `bat_notify` - `status_notify` - `anc_change_notify` - `enco_ui_para` 是一个全局结构体(或静态变量),其中包含`enco_link_info`成员,而`enco_link_info`又包含: - `notify_cnt`:同上 - `notify`:与`info.notify`相同的结构体类型(`enco_ui_notify_t`) ### 注意事项 - 在修改`info.notify`的字段时,实际上是修改了即将被复制到`enco_ui_para.enco_link_info.notify`的数据。因此,在`memcpy`之前,`info.notify`可能已经被修改(三个标志位可能被设置为`enco_true`)。 - 这段代码在更新通知状态时,试图保留旧状态中的某些设置(如果新状态中关闭了,但旧状态是开启的,那么就把新状态的对应项重新开启)。这可能是为了确保某些通知不会因为更新而被意外关闭。 - 日志信息表明这是在记录已经存在的通知设置(“already on ...”)。 §§相关问题§§ 1. 在C语言中,结构体复制有哪些注意事项? 2. 如何设计版本兼容的数据结构? 3. 在嵌入式系统中,日志记录有哪些常用方法? </think>### 代码执行逻辑分析 给定代码段的核心是条件判断语句 `if( info.notify_cnt > enco_ui_para.enco_link_info.notify_cnt )`。具体执行路径如下: 1. **条件判断阶段**: - 比较 `info.notify_cnt`(新通知计数) `enco_ui_para.enco_link_info.notify_cnt`(当前存储的计数) - **仅当**不等式 $info.notify\_cnt > enco\_ui\_para.enco\_link\_info.notify\_cnt$ 成立时,才会执行花括号 `{}` 内的代码块(否则跳过整个代码段) 2. **条件成立时的执行流程**: | 执行顺序 | 代码片段 | 功能说明 | |----------|--------------------------------------------------------|--------------------------------------------------------------------------| | (1) | `enco_ui_para.enco_link_info.notify_cnt = info.notify_cnt;` | 更新计数为最新值 | | (2) | 三个独立的 `if` 条件检查 | 检查是否需要保留历史通知标志(详细见下方分解) | | (3) | `memcpy((void *)&enco_ui_para... sizeof(enco_ui_notify_t));` | 将更新后的 `info.notify` 完整复制到存储区[^3] | 3. **标志保留逻辑分解**: 每组条件检查的结构均为: ```c if( (!info.notify.X) && enco_ui_para.enco_link_info.notify.X ) { info.notify.X = enco_true; // 恢复标志位 ENCO_TS_LOG(...); // 记录日志 } ``` - **判断条件**:当新数据中标志 $X$ 为假 `false`,但存储区中标志 $X$ 为真 `true` 时 - **执行操作**:将新数据的标志 $X$ 强制设为 `true` - **涉及标志**: - `bat_notify`(电池通知) - `status_notify`(状态通知) - `anc_change_notify`(降噪模式变更通知) ### 完整执行流程图 ```mermaid graph TD A[开始] --> B{info.notify_cnt ><br>存储区计数?} B -- 是 --> C[更新存储区计数] C --> D1{新bat_notify=false?<br>且存储区=true?} D1 -- 是 --> E1[置位bat_notify=true] D1 -- 否 --> D2{新status_notify=false?<br>且存储区=true?} D2 -- 是 --> E2[置位status_notify=true] D2 -- 否 --> D3{新anc_change_notify=false?<br>且存储区=true?} D3 -- 是 --> E3[置位anc_change_notify=true] D3 -- 否 --> F[复制整个notify结构体] B -- 否 --> G[跳过代码块] F --> H[结束] G --> H ``` ### 关键设计思想 1. **版本控制机制**:通过 `notify_cnt` 实现版本更新控制(类似序列号) 2. **状态继承**:当新数据中关闭了某些通知标志,但存储区内该标志仍处于激活状态时,强制保留该通知状态 3. **原子更新**:最后使用 `memcpy` 一次性完成通知状态更新,确保数据一致性[^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值