2005年12月14日

    一基本概念
    设备—windows操作系统上允许通信的任何东西,比如文件、目录、串行口、并行口、邮件槽、命名管道、无名管道、套接字、控制台、逻辑磁盘、物理磁盘等。绝大多数与设备打交道的函数都是CreateFile/ReadFile/WriteFile等。所以我们不能看到**File函数就只想到文件设备。
    与设备通信有两种方式,同步方式和异步方式。同步方式下,当调用ReadFile函数时,函数会等待系统执行完所要求的工作,然后才返回;异步方式下,ReadFile这类函数会直接返回,系统自己去完成对设备的操作,然后以某种方式通知完成操作。
    重叠I/O—-顾名思义,当你调用了某个函数(比如ReadFile)就立刻返回做自己的其他动作的时候,同时系统也在对I/0设备进行你要求的操作,在这段时间内你的程序和系统的内部动作是重叠的,因此有更好的性能。所以,重叠I/O是用于异步方式下使用I/O设备的。
    重叠I/O需要使用的一个非常重要的数据结构OVERLAPPED。
    完成端口—是一种WINDOWS内核对象。完成端口用于异步方式的重叠I/0情况下,当然重叠I/O不一定非使用完成端口不可,还有设备内核对象、事件对象、告警I/0等。但是完成端口内部提供了线程池的管理,可以避免反复创建线程的开销,同时可以根据CPU的个数灵活的决定线程个数,而且可以让减少线程调度的次数从而提高性能。
    二OVERLAPPED数据结构
    typedefstruct_OVERLAPPED{
    ULONG_PTRInternal;//被系统内部赋值,用来表示系统状态
    ULONG_PTRInternalHigh;//被系统内部赋值,传输的字节数
    union{
    struct{
    DWORDOffset;//和OffsetHigh合成一个64位的整数,用来表示从文件头部的多少字节开始
    DWORDOffsetHigh;//操作,如果不是对文件I/O来操作,则必须设定为0
    };
    PVOIDPointer;
    };
    HANDLEhEvent;//如果不使用,就务必设为0,否则请赋一个有效的Event句柄
    }OVERLAPPED,*LPOVERLAPPED;
    下面是异步方式使用ReadFile的一个例子
    OVERLAPPEDOverlapped;
    Overlapped.Offset=345;
    Overlapped.OffsetHigh=0;
    Overlapped.hEvent=0;
    //假定其他参数都已经被初始化
    ReadFile(hFile,buffer,sizeof(buffer),&dwNumBytesRead,&Overlapped);
    这样就完成了异步方式读文件的操作,然后ReadFile函数返回,由操作系统做自己的事情吧
    下面介绍几个与OVERLAPPED结构相关的函数
    等待重叠I/0操作完成的函数
    BOOLGetOverlappedResult(
    HANDLEhFile,
    LPOVERLAPPEDlpOverlapped,//接受返回的重叠I/0结构
    LPDWORDlpcbTransfer,//成功传输了多少字节数
    BOOLfWait//TRUE只有当操作完成才返回,FALSE直接返回,如果操作没有完成,通过调//用GetLastError()函数会返回ERROR_IO_INCOMPLETE
    );
    宏HasOverlappedIoCompleted可以帮助我们测试重叠I/0操作是否完成,该宏对OVERLAPPED结构的Internal成员进行了测试,查看是否等于STATUS_PENDING值。
    三完成端口的内部机制
    创建完成端口
    完成端口是一个内核对象,使用时他总是要和至少一个有效的设备句柄进行关联,完成端口是一个复杂的内核对象,创建它的函数是:
    HANDLECreateIoCompletionPort(
    INHANDLEFileHandle,
    INHANDLEExistingCompletionPort,
    INULONG_PTRCompletionKey,
    INDWORDNumberOfConcurrentThreads
    );
    通常创建工作分两步:
    第一步,创建一个新的完成端口内核对象,可以使用下面的函数:
    HANDLECreateNewCompletionPort(DWORDdwNumberOfThreads)
    {
    returnCreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,NULL,dwNumberOfThreads);
    };
    第二步,将刚创建的完成端口和一个有效的设备句柄关联起来,可以使用下面的函数:
    boolAssicoateDeviceWithCompletionPort(HANDLEhCompPort,HANDLEhDevice,DWORDdwCompKey)
    {
    HANDLEh=CreateIoCompletionPort(hDevice,hCompPort,dwCompKey,0);
    returnh==hCompPort;
    };
    说明
    1)CreateIoCompletionPort函数也可以一次性的既创建完成端口对象,又关联到一个有效的设备句柄
    2)CompletionKey是一个可以自己定义的参数,我们可以把一个结构的地址赋给它,然后在合适的时候取出来使用,最好要保证结构里面的内存不是分配在栈上,除非你有十分的把握内存会保留到你要使用的那一刻。
    3)NumberOfConcurrentThreads通常用来指定要允许同时运行的的线程的最大个数。通常我们指定为0,这样系统会根据CPU的个数来自动确定。
    创建和关联的动作完成后,系统会将完成端口关联的设备句柄、完成键作为一条纪录加入到这个完成端口的设备列表中。如果你有多个完成端口,就会有多个对应的设备列表。如果设备句柄被关闭,则表中自动删除该纪录。
    完成端口线程的工作原理
    完成端口可以帮助我们管理线程池,但是线程池中的线程需要我们使用_beginthreadex来创建,凭什么通知完成端口管理我们的新线程呢?答案在函数GetQueuedCompletionStatus。该函数原型:
    BOOLGetQueuedCompletionStatus(
    INHANDLECompletionPort,
    OUTLPDWORDlpNumberOfBytesTransferred,
    OUTPULONG_PTRlpCompletionKey,
    OUTLPOVERLAPPED*lpOverlapped,
    INDWORDdwMilliseconds
    );
    这个函数试图从指定的完成端口的I/0完成队列中抽取纪录。只有当重叠I/O动作完成的时候,完成队列中才有纪录。凡是调用这个函数的线程将被放入到完成端口的等待线程队列中,因此完成端口就可以在自己的线程池中帮助我们维护这个线程。
    完成端口的I/0完成队列中存放了当重叠I/0完成的结果—-一条纪录,该纪录拥有四个字段,前三项就对应GetQueuedCompletionStatus函数的2、3、4参数,最后一个字段是错误信息dwError。我们也可以通过调用PostQueudCompletionStatus模拟完成了一个重叠I/0操作。
    当I/0完成队列中出现了纪录,完成端口将会检查等待线程队列,该队列中的线程都是通过调用GetQueuedCompletionStatus函数使自己加入队列的。等待线程队列很简单,只是保存了这些线程的ID。完成端口会按照后进先出的原则将一个线程队列的ID放入到释放线程列表中,同时该线程将从等待GetQueuedCompletionStatus函数返回的睡眠状态中变为可调度状态等待CPU的调度。
    基本上情况就是如此,所以我们的线程要想成为完成端口管理的线程,就必须要调用
    GetQueuedCompletionStatus函数。出于性能的优化,实际上完成端口还维护了一个暂停线程列表,具体细节可以参考《Windows高级编程指南》,我们现在知道的知识,已经足够了。
    线程间数据传递
    线程间传递数据最常用的办法是在_beginthreadex函数中将参数传递给线程函数,或者使用全局变量。但是完成端口还有自己的传递数据的方法,答案就在于CompletionKey和OVERLAPPED参数。
    CompletionKey被保存在完成端口的设备表中,是和设备句柄一一对应的,我们可以将与设备句柄相关的数据保存到CompletionKey中,或者将CompletionKey表示为结构指针,这样就可以传递更加丰富的内容。这些内容只能在一开始关联完成端口和设备句柄的时候做,因此不能在以后动态改变。
    OVERLAPPED参数是在每次调用ReadFile这样的支持重叠I/0的函数时传递给完成端口的。我们可以看到,如果我们不是对文件设备做操作,该结构的成员变量就对我们几乎毫无作用。我们需要附加信息,可以创建自己的结构,然后将OVERLAPPED结构变量作为我们结构变量的第一个成员,然后传递第一个成员变量的地址给ReadFile函数。因为类型匹配,当然可以通过编译。当GetQueuedCompletionStatus函数返回时,我们可以获取到第一个成员变量的地址,然后一个简单的强制转换,我们就可以把它当作完整的自定义结构的指针使用,这样就可以传递很多附加的数据了。太好了!只有一点要注意,如果跨线程传递,请注意将数据分配到堆上,并且接收端应该将数据用完后释放。我们通常需要将ReadFile这样的异步函数的所需要的缓冲区放到我们自定义的结构中,这样当GetQueuedCompletionStatus被返回时,我们的自定义结构的缓冲区变量中就存放了I/0操作的数据。
    CompletionKey和OVERLAPPED参数,都可以通过GetQueuedCompletionStatus函数获得。
    线程的安全退出
    很多线程为了不止一次的执行异步数据处理,需要使用如下语句
    while(true)
    {
    .。。。。。。
    GetQueuedCompletionStatus(…);
    。。。。。。
    }
    那么如何退出呢,答案就在于上面曾提到的PostQueudCompletionStatus函数,我们可以用它发送一个自定义的包含了OVERLAPPED成员变量的结构地址,里面包含一个状态变量,当状态变量为退出标志时,线程就执行清除动作然后退出。

2005年12月04日

即时消息介绍
即时消息(Instant Messaging)为用户提供了一种方便快捷的通过Internet与朋友交流的方式,通过它人们可以在线交谈、互传文件、语音对话及进行视频会议,甚至用手机双向交流。未来即时消息的功能还会包括股票交易、在线购物、企业采购与调拨等电子商务运作。即时消息在发展初期更多地以其娱乐性引起了人们的关注,而现在其快速增多的企业用户表明这一技术已经被开始融入到现代商务活动中。

即时消息已经成为语音及文本的在线实时通信的主要技术,它必将成为未来移动商务、在线协作及Internet应用的核心,同时也将继承IP技术的开放风格,扮演比电子邮件更为重要的角色。

该即时消息软件参照Jabber协议,使用VC、SQL Server、XML开发,
已实现的基本功能
1. 可以手动添加删除好友
2. 阻塞某人的信息(黑名单)
3. 与目前标准IM软件相同的消息收发功能
4. 协议高可扩展性,使用xml
5. 支持多服务器,超大容量在线分布
6. unicode多国语言兼容
7. 灵活分组(可多级分组)并且保存数据到服务器
8. 透过网络代理功能
9. 自定义用户在线状态

服务端界面

客户端界面

2005年12月02日

两个小程序,学习《Programming windows with MFC》时完成的两个作业,实现MFC的一些简单应用

1, 菜单、绘图、字体、ToolTips、scroolview

2,类似windows的paint程序,画一些几何图形,并实现了视图间的切换

  消息是windows操作系统和应用程序之间进行通信的载体,操作系统将包括用
户在内的各种事件以消息的形式发送至目标,目标系统再根据消息具体的内容进
行相应的处理。
  在VC++6.0中,大多数的窗口消息可以从ClassWizard中找到,但是一些不
常用的消息在ClassWizard中并没有封装,比如热键处理消息(WM_HOTKEY),用
户必须事先定义热键,然后不论该程序在前台或后台运行,只要用户按下了这个
热键,该程序就会立即在前台运行,并收到热键消息,消息处理函数执行热键中
定义的操作。要实现这些功能,用户必须在代码级进行工作,也就是说要手工编
写代码。具体步骤如下:
  假设用户已经用AppWizard生成了一个单文档界面框架,在工程中主要有下列
几个文件,向导为应用程序创建了如下类:
  CtestView CtestDoc CtestApp CmainFrame CAboutDlg
  请按下列步骤进行:
  1、在视图类的声明文件中,即testview.h文件中,找到消息映射的声名处,
在下列语句处加入热键处理函数声明:(划线部分)
   //{{AFX_MSG(CTestView)
   afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
   //}}AFX_MSG
  LRESULT OnHotKey(WPARAM wParam,LPARAM lParam);
   DECLARE_MESSAGE_MAP()
  2、在视图类的实现文件,即TestView.cpp文件中,找到消息映射的定义处,
这里是使函数和消息发生关联的地方,当发生某消息时,会调用这里定义的相应
消息处理函数,也就是说用消息映射宏使消息与相应的处理函数发生关系。

  下面的语句说明WM_HOTKEY消息和OnHotkey函数发生关联。
   BEGIN_MESSAGE_MAP(CTestView, CView)
   //{{AFX_MSG_MAP(CTestView)
   ON_WM_CREATE()
   //}}AFX_MSG_MAP
   // Standard printing commands
   ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
   ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
   ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
   ON_MESSAGE(WM_HOTKEY,OnHotkey) //消息和函数发生关联
  END_MESSAGE_MAP()
  3、在OnCreate函数中加入初始化代码,向系统登记热键。
   RegisterHotKey(hWnd,1001,MOD_CONTROL|MOD_ALT,‘z’);
   RegisterHotKey(hWnd,1002,MOD_CONTROL|MOD_ALT,‘Z’);
  本例中定义的热键为Ctrl+Alt+z。
  4、在OnHotkey()处理函数中处理热键,即检查是否是所期望的热键,如果是
,这里为了简单,弹出一个对话框,显示“You Press Ctrl+Alt+z(Z)”。
  LRESULT CTestView::OnHotkey(WPARAM wParam,LPARAM lParam)
  {
   if(wParam==1001||wParam==1002)
   MessageBox(“You Press Ctrl+Alt+z(Z)”);
  return 0;
  }
  注意:OnHotkey()函数必须有返回值。
  5、最后,不要忘了在OnDestroy()函数中解除函数登记,释放系统资源。
  UnregisterHotKey(m_hWnd,1001);
  UnregisterHotKey(m_hWnd,1002);
  这样程序运行后,无论程序在前台或后台运行,只要你按下Ctrl+Alt+z,就
会执行热键处理函数中的代码,这里弹出对话框。读者可以自行在自己的热键处
理函数中加入需要的代码来完成特定的功能。该程序在Pwin98平台上,在Visual
C++6.0中调试通过。
  (沈阳 金波)

函数功能:该函数定义一个系统范围的热键。

    函数原型:BOOL RegisterHotKey(HWND hWnd,intid,UINT fsModifiers,UINT vk);

    参数:

    hWnd:接收热键产生WM_HOTKEY消息的窗口句柄。若该参数NULL,传递给调用线程的WM_HOTKEY消息必须在消息循环中中进行处
理。

    id:定义热键的标识符。调用线程中的其他热键不能使用同样的标识符。应用功能程序必须定义一个0X0000-0xBFFF范围的值。一个共享
的动态链接库(DLL)必须定义一个0xC000-0xFFFF范围的值伯GlobalAddAtom函数返回该范围)。为了避免与其他动态链接库定义的热键冲
突,一个DLL必须使用GlobalAddAtom函数获得热键的标识符。

    fsModifoers:定义为了产生WM_HOTKEY消息而必须与由nVirtKey参数定义的键一起按下的键。该参数可以是如下值的组合:

    MOD_ALT:按下的可以是任一Alt键。MOD_CONTROL:按下的可以是任一Ctrl键。

    MOD_SHIFT:按下的可以是任一Shift键。

    MOD_WIN:按下的可以是任一Windows按键。这些键可以用Microsoft Windows日志记录下来。

    vk:定义热键的虚拟键码。

    返回值:若函数调用成功,返回一个非O值。若函数调用失败,则返回值为0。若要获得更多的错误信息,可以调用GetLastError函数。

    备注:当某键被接下时,系统在所有的热键中寻找匹配者。一旦找到一个匹配的热键,系统将把WM_HOTKEY消息传递给登记了该热键
的线程的消息队列。该消息被传送到队列头部,因此它将在下一轮消息循环中被移去。该函数不能将热键同其他线程创建的窗口关联起
来。

    若为一热键定义的击键己被其他热键所定义,则RegisterHotKey函数调用失败。

    若hWnd参数标识的窗口已用与id参数定义的相同的标识符登记了一个热键,则参数fsModifiers和vk的新值将替代这些参数先前定义的值。

    Windows CE:Windows CE 2.0以上版本对于参数fsModifiers支持一个附加的标志位。叫做MOD_KEYUP。

    若设置MOD_KEYUP位,则当发生键被按下或被弹起的事件时,窗口将发送WM_HOTKEY消息。

    RegisterHotKey可以被用来在线程之间登记热键。

    速查:Windows NT:3.1及以上版本;Windows:95及以上版本;Windows CE:不支持;头文件:winuser.h;库文件:user32.lib。
 

      在使用vc写简繁体通用程序时,对实现菜单、界面、Tooltips等的文字时无法实现通用。如果写简体和繁体各一套程序,那肯定不现实。查找所有资料和文章都只能实现菜单的多语言,且使用起来也不方便;并且无法实现界面、Tooltips等的多语言。因而,采用纯资源的DLL文件来实现多语言。

      纯资源DLL文件实现步骤如下:

      (1)创建简繁体MDI或SDI程序后,把VC框架自动创建的.rc,.rc2,.ico,.bmp等文件从程序框架中删除;

      (2)使用Projects的Win32 Dynamic-Link Library分别创建简繁体纯资源dll文件,把由MDI或SDI程序创建的.rc,.rc2,.ico,.bmp等文件加入到该工程文件中;

     (3)在Project菜单项选择Settings然后选择Project Settings的Link标签;然后在Project Options框内输入/NOENTRY。编译后就产生纯资源DLL文件;

     纯资源DLL文件加入没有任何资源的MDI或SDI程序,其步骤如下:

     (1)在主应用程序类的.h文件中定义protected 的 HINSTANCE 类型变量(如:mhInstMenu);    

  (2)在主应用程序类的.cpp文件::InitInstance()函数过程中添加下面代码:

    mhInstMenu = ::LoadLibrary("Menu.dll");  // Menu.dll为多语言纯资源的dll文件

    if (mhInstMenu == NULL) 

    { 

        return FALSE; // failed to load the localized resources 

    } 

    else { 

        AfxSetResourceHandle(m_hInstMenu); // get resources from the DLL 

    }
     (3)在主应用程序类的.cpp文件::ExitInstance()函数过程中添加下面代码:

FreeLibrary(mhInstMenu);

 

注意:Menu.dll文件可以有三种放置方法:

    A、和.exe文件在同一个路径下面;

    B、放在Windows98的System或Windows2000的System32路径下面;

    C、放在通过Path设置的路径下面。

具体方法如下:新建一个 dll 项目,然后把你程序的资源文件复制到 dll 项目资源中即可,接下来,修改 dll 项目中资源的语言(这可能需要花一点时间)。然后只要简单修改一下你的程序代码就可以了:

在 CWinApp 继承类中,添加一个成员变量(我使用的是日语):


HINSTANCE hJapaneseDll //Global var
…..
在 InitInstance 中添加一下代码(粗体部分是我添加的):

CMultiLangApp::InitInstance()
{
……
//Get Language Setting from INI
uLanguage = GetProfileInt("Language", "Language",0);

if (uLanguage == 1)
{
//Language is set for Japanese.
hJapaneseDll = AfxLoadLibrary("Japanese.dll");
ASSERT(hJapaneseDll);
AfxSetResourceHandle(hJapaneseDll);
}
…..
//create dialog or main frame
…..
}
最后,你可以添加一个菜单项或者单选按钮,用于切换语言,具体请看代码

来自:yesky
如何制作透明窗体

  使用SetLayeredWindowAttributes可以方便的制作透明窗体,此函数在w2k以上才支持,而且如果希望直接使用的话,可能需要下载最新的SDK。不过此函数在w2k的user32.dll里有实现,所以如果你不希望下载巨大的sdk的话,可以直接使用GetProcAddress获取该函数的指针。 

  SetLayeredWindowAttributes的函数原型如下:

BOOL SetLayeredWindowAttributes(
HWND hwnd, // handle to the layered window
COLORREF crKey, // specifies the color key
BYTE bAlpha, // value for the blend function
DWORD dwFlags // action
);

Windows NT/2000/XP: Included in Windows 2000 and later.
Windows 95/98/Me: Unsupported.(注意了,在win9x里没法使用的)
Header: Declared in Winuser.h; include Windows.h.
Library: Use User32.lib.
 

  一些常量:
 

WS_EX_LAYERED = 0×80000;
LWA_ALPHA = 0×2;
LWA_COLORKEY=0×1;

  其中dwFlags有LWA_ALPHA和LWA_COLORKEY

  LWA_ALPHA被设置的话,通过bAlpha决定透明度.

  LWA_COLORKEY被设置的话,则指定被透明掉的颜色为crKey,其他颜色则正常显示.

  要使使窗体拥有透明效果,首先要有WS_EX_LAYERED扩展属性(旧的sdk没有定义这个属性,所以可以直接指定为0×80000).

  例子代码:

  在OnInitDialog()加入:

//加入WS_EX_LAYERED扩展属性
SetWindowLong(this->GetSafeHwnd(),GWL_EXSTYLE,
GetWindowLong(this->GetSafeHwnd(),GWL_EXSTYLE)^0×80000);
HINSTANCE hInst = LoadLibrary("User32.DLL");
if(hInst)
{
 typedef BOOL (WINAPI *MYFUNC)(HWND,COLORREF,BYTE,DWORD);
 MYFUNC fun = NULL;
 //取得SetLayeredWindowAttributes函数指针
 fun=(MYFUNC)GetProcAddress(hInst, "SetLayeredWindowAttributes");
 if(fun)fun(this->GetSafeHwnd(),0,128,2);
 FreeLibrary(hInst);
}

  稍加修改还可以作出淡出淡入的效果. 注意第三个参数(128)不要取得太小了,为0的话就完全透明,看不到了。

  如何使框架窗口的图标为动画显示

  可以用TIMER,但是TIMER不能有效的定时。因为TIMER发送的是窗口消息,当窗口忙于处理键盘、鼠标等消息时就不能及时处理TIMER,会使间隔时间变得很长 。

  可以考虑用一个单独得TIMER线程,用Sleep()定时来解决此问题。
 

UINT Timer(LPVOID param)
{
 HWND hWnd=(HWND)param;
 while(1)
 {
  Sleep(ms);
  PostMessage(hWnd,CH_PICTURE,NULL,NULL)
 }
}

  Sleep(ms)后发送自定义消息。消息处理函数就选择某一个ICON或BITMAP来显示。如 :
 

MyBotton.SetBitmap((HBITMAP)Bitmap[i]);

  Bitmap是一个位图数组,存放有j个位图。消息处理函数运行一次,i就累加一次,当i==j时,i就回到0;

  防止窗口闪烁的方法

  1、将Invalidate()替换为InvalidateRect()。

  Invalidate()会导致整个窗口的图象重画,需要的时间比较长,而InvalidateRect()仅仅重画Rect区域内的内容,所以所需时间会少一些。虫虫以前很懒,经常为一小块区域的重画就调用Invalidate(),不愿意自己去计算需要重画的Rect,但是事实是,如果你确实需要改善闪烁的情况,计算一个Rect所用的时间比起重画那些不需要重画的内容所需要的时间要少得多。

  2、禁止系统搽除你的窗口。

  系统在需要重画窗口的时候会帮你用指定的背景色来搽除窗口。可是,也许需要重画的区域也许非常小。或者,在你重画这些东西之间还要经过大量的计算才能开始。这个时候你可以禁止系统搽掉原来的图象。直到你已经计算好了所有的数据,自己把那些需要搽掉的部分用背景色覆盖掉(如:dc.FillRect(rect,&brush);rect是需要搽除的区域,brush是带背景色的刷子),再画上新的图形。要禁止系统搽除你的窗口,可以重载OnEraseBkgnd()函数,让其直接返回TRUE就可以了。如

BOOL CMyWin::OnEraseBkgnd(CDC* pDC)
{
return TRUE;
//return CWnd::OnEraseBkgnd(pDC);//把系统原来的这条语句注释掉。
}

  3、有效的进行搽除。

  搽除背景的时候,不要该搽不该搽的地方都搽。比如,你在一个窗口上放了一个很大的Edit框,几乎占了整个窗口,那么你频繁的搽除整个窗口背景将导致Edit不停重画形成剧烈的闪烁。事实上你可以CRgn创建一个需要搽除的区域,只搽除这一部分。如

GetClientRect(rectClient);
rgn1.CreateRectRgnIndirect(rectClient);
rgn2.CreateRectRgnIndirect(m_rectEdit);
if(rgn1.CombineRgn(&rgn1,&rgn2,RGN_XOR) == ERROR)//处理后的rgn1只包括了Edit框之外的客户区域,这样,Edit将不会被我的背景覆盖而导致重画。
{
ASSERT(FALSE);
return ;
}
brush.CreateSolidBrush(m_clrBackgnd);
pDC->FillRgn(&rgn1,&brush);
brush.DeleteObject();

  注意:在使用这个方法的时候要同时使用方法二。别忘了,到时候又说虫虫的办法不灵。

  4、使用MemoryDC先在内存里把图画好,再复制到屏幕上。

  这对于一次画图过程很长的情况比较管用。毕竟内存操作比较快,而且复制到屏幕又是一次性的,至少不会出现可以明显看出一个东东从左画到右的情况。

void CMyWin::OnPaint()
{
CPaintDC dc1(this); // device context for painting
dcMemory.CreateCompatibleDC(&dc1);
CBitmap bmp;//这里的Bitmap是必须的,否则当心弄出一个大黑块哦。
bmp.CreateCompatibleBitmap(&dc1,rectClient.Width(),rectClient.Height());
dcMemory.SelectObject(&bmp);
//接下来你想怎么画就怎么画吧。
//dcMemory.FillRect(rectClient,&brush);

dc1.BitBlt(0,0,rectClient.Width(),rectClient.Height(),&dcMemory,0,0,SRCCOPY);
dcMemory.DeleteDC();
// Do not call CWnd::OnPaint() for painting messages
}
 

如何实现全屏显示

 

  全屏显示是一些应用软件程序必不可少的功能。比如在用VC++编辑工程源文件或编辑对话框等资源时,选择菜单“ViewFull Screen”,即可进入全屏显示状态,按“Esc”键后会退出全屏显示状态。

  在VC++6.0中我们用AppWizard按默认方式生成单文档界面的应用程序框架。下面将先讨论点击菜单项“ViewFull Screen”实现全屏显示的方法,再讲述按“Esc”键后如何退出全屏显示状态。

  1) 在CMainFrame类中,增加如下三个成员变量。

  private:
    WINDOWPLACEMENT m_OldWndPlacement; //用来保存原窗口位置
    BOOL m_bFullScreen; //全屏显示标志
    CRect m_FullScreenRect; //表示全屏显示时的窗口位置 

  2)在资源编辑器中编辑菜单IDR_MAINFRAME。在“View”菜单栏下添加菜单项“Full Screen”。在其属性框中,ID设置为ID_FULL_SCREEN,Caption为“Full Screen”。还可以在工具栏中添加新的工具图标,并使之与菜单项“Full Screen”相关联,即将其ID值也设置为ID_FULL_SCREEN。

  3)设计全屏显示处理函数,在CMainFrame类增加上述菜单项ID_FULL_SCREEN消息的响应函数。响应函数如下:
 

  void CMainFrame::OnFullScreen()
  {
GetWindowPlacement(&m_OldWndPlacement);
   CRect WindowRect;
   GetWindowRect(&WindowRect);
   CRect ClientRect;
   RepositionBars(0, 0xffff, AFX_IDW_PANE_FIRST, reposQuery, &ClientRect);
   ClientToScreen(&ClientRect);
   // 获取屏幕的分辨率
   int nFullWidth=GetSystemMetrics(SM_CXSCREEN);
   int nFullHeight=GetSystemMetrics(SM_CYSCREEN);
   // 将除控制条外的客户区全屏显示到从(0,0)到(nFullWidth, nFullHeight)区域, 将(0,0)和(nFullWidth, nFullHeight)两个点外扩充原窗口和除控制条之外的 客户区位置间的差值, 就得到全屏显示的窗口位置
   m_FullScreenRect.left=WindowRect.left-ClientRect.left;
   m_FullScreenRect.top=WindowRect.top-ClientRect.top;
   m_FullScreenRect.right=WindowRect.right-ClientRect.right+nFullWidth;
   m_FullScreenRect.bottom=WindowRect.bottom-ClientRect.bottom+nFullHeight;
   m_bFullScreen=TRUE; // 设置全屏显示标志为 TRUE
   // 进入全屏显示状态
   WINDOWPLACEMENT wndpl;
   wndpl.length=sizeof(WINDOWPLACEMENT);
   wndpl.flags=0;
   wndpl.showCmd=SW_SHOWNORMAL;
   wndpl.rcNormalPosition=m_FullScreenRect;
   SetWindowPlacement(&wndpl);

}
 

  4)重载CMainFrame类的OnGetMinMaxInfo函数,在全屏显示时提供全屏显示的位置信息。
 

  void CMainFrame::OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI)
  {
if(m_bFullScreen)
   {

lpMMI->ptMaxSize.x=m_FullScreenRect.Width();
   lpMMI->ptMaxSize.y=m_FullScreenRect.Height();
   lpMMI->ptMaxPosition.x=m_FullScreenRect.Width();
   lpMMI->ptMaxPosition.y=m_FullScreenRect.Height();
   //最大的Track尺寸也要改变
   lpMMI->ptMaxTrackSize.x=m_FullScreenRect.Width();
   lpMMI->ptMaxTrackSize.y=m_FullScreenRect.Height();
   }

CFrameWnd::OnGetMinMaxInfo(lpMMI) ;
  }
 

  完成上面的编程后,可以联编执行FullScreen.exe,选择菜单“ViewFull Screen”或点击与之关联的工具栏按钮即可进入全屏显示状态。但现在还需要增加用户退出全屏显示状态的操作接口,下面讲述如何编程实现按“Esc”键退出全屏显示状态。

  1)在ClassView中选中CMainFrame并单击鼠标右键,选择“Add Member Function…”,添加public类型的成员函数EndFullScreen,该函数将完成退出全屏显示的操作。
 

  void CMainFrame::EndFullScreen()
  {
if(m_bFullScreen)
   {// 退出全屏显示, 恢复原窗口显示
  ShowWindow(SW_HIDE);
   SetWindowPlacement(&m_OldWndPlacement);

}

}
 

  2)函数EndFullScreen可以退出全屏显示状态,问题是如何在“Esc”键被按下之后调用执行此函数。由于视图类可以处理键盘输入的有关消息(如WM_KEYDOWN表示用户按下了某一个键),我们将在视图类CFullScreenView中添加处理按键消息WM_KEYDOWN的响应函数OnKeyDown。判断如果按的键为“Esc”键,则调用CMainFrame类的函数EndFullScreen,便可退出全屏显示状态。
 

  void CFullScreenView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
  {
if(nChar==VK_ESCAPE) // 如果按的键为Esc键
   {// 获取主框架窗口的指针

   CMainFrame *pFrame=(CMainFrame*)AfxGetApp()->m_pMainWnd;
   // 调用主窗口类的自定义函数 EndFullScreen ,便可退出全屏显示状态
  pFrame->EndFullScreen();

}
  CView::OnKeyDown(nChar, nRepCnt, nFlags);

}
 

  更改窗口图标并将其显示在任务栏

  以下两个函数可以为应用程序中的各子窗口显示一个任务条到任务栏并更改它们的图标。对那些象QQ一样隐藏主窗口的应用程序特别有用。

//函数用途:更改一个窗口的图标并将其显示在任务栏、任务切换条、任务管理器里
//参数说明:
//hWnd 要改变图标的窗口句柄
//hLargeIcon 显示到任务切换条上的图标 32*32
//hSmallIcon 显示到除任务切换条之外的图标 16*16
//hIcon 显示的图标,32*32,在显示到任务切换条之外的其余地方时会被自动压缩成16*16的。
//注释:
//此函数对于模式对话框无能为力。
//如果HICON 为NULL,函数不改变窗口图标,但是将原有图标显示到任务栏、
// 任务切换条、任务管理器里。
//此函数是通过将窗口的父窗口指针置空来实现将图标显示到任务栏、任务切换条、
// 任务管理器里的,所以调用完成后,其父窗口指针不再可用。
BOOL SendWndIconToTaskbar(HWND hWnd,HICON hLargeIcon,HICON hSmallIcon);
BOOL SendWndIconToTaskbar(HWND hWnd,HICON hIcon);
BOOL CUIApp::SendWndIconToTaskbar(HWND hWnd,HICON hLargeIcon,HICON hSmallIcon)
{
 BOOL ret = TRUE;
 ASSERT(hWnd);
 if(!::IsWindow(hWnd))
  return FALSE;
 //获取窗口指针
 CWnd* pWnd;
 pWnd = pWnd->FromHandle(hWnd);
 ASSERT(pWnd);
 if(!pWnd)
  return FALSE;
 //将父窗口设为NULL
 if(pWnd->GetParent())
  if(::SetWindowLong(hWnd,GWL_HWNDPARENT,NULL) == 0)
   return FALSE;

  if(!(pWnd->ModifyStyle(NULL,WS_OVERLAPPEDWINDOW)))
   ret = FALSE;
  //设置窗口图标
  if(hLargeIcon && hSmallIcon)
  {
   pWnd->SetIcon(hSmallIcon,FALSE);
   pWnd->SetIcon(hLargeIcon,TRUE);
  }

  return ret;
 }

BOOL CUIApp::SendWndIconToTaskbar(HWND hWnd,HICON hIcon)
{
 BOOL ret = TRUE;
 ASSERT(hWnd);
 if(!::IsWindow(hWnd))
  return FALSE;
  //获取窗口指针
 CWnd* pWnd;
 pWnd = pWnd->FromHandle(hWnd);
 ASSERT(pWnd);
 if(!pWnd)
  return FALSE;
 //将父窗口设为NULL
 if(pWnd->GetParent())
  if(::SetWindowLong(hWnd,GWL_HWNDPARENT,NULL) == 0)
   return FALSE;

 if(!(pWnd->ModifyStyle(NULL,WS_OVERLAPPEDWINDOW)))
  ret = FALSE;
 //设置窗口图标
 pWnd->SetIcon(hIcon,TRUE);
 pWnd->SetIcon(hIcon,FALSE);

 return ret;
}

 
 

  如何隐藏应用程序在任务栏上的显示

  对于CFrameWnd可以在PreCreateWindow()函数中修改窗口的风格。

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
cs.style |=WS_POPUP;//使主窗口不可见
cs.dwExStyle |=WS_EX_TOOLWINDOW;//不显示任务按钮
return CFrameWnd::PreCreateWindow(cs);
}

  对于其他窗口,可以在窗口被Create出来之后ShowWindow之前使用ModifyStyle()和ModifyStyleEx()来修改它的风格。

  如何控制窗口框架的最大最小尺寸?

  要控制一个框架的的最大最小尺寸,你需要做两件事情。

  第一步:在CFrameWnd的继承类中处理消息WM_GETMINMAXINFO,结构MINMAXINFO设置了整个窗口类的限制,因此记住要考虑工具条,滚动条等等的大小。

// 最大最小尺寸的象素点 – 示例
#define MINX 200
#define MINY 300
#define MAXX 300
#define MAXY 400
void CMyFrameWnd::OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI)
{
 CRect rectWindow;
 GetWindowRect(&rectWindow);

 CRect rectClient;
 GetClientRect(&rectClient);

 // get offset of toolbars, scrollbars, etc.
 int nWidthOffset = rectWindow.Width() – rectClient.Width();
 int nHeightOffset = rectWindow.Height() – rectClient.Height();

 lpMMI->ptMinTrackSize.x = MINX + nWidthOffset;
 lpMMI->ptMinTrackSize.y = MINY + nHeightOffset;
 lpMMI->ptMaxTrackSize.x = MAXX + nWidthOffset;
 lpMMI->ptMaxTrackSize.y = MAXY + nHeightOffset;
}
 
 

  第二步:在CFrameWnd的继承类的PreCreateWindow函数中去掉WS_MAXIMIZEBOX消息,否则在最大化时你将得不到预料的结果.

BOOL CMyFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
{
 cs.style &= ~WS_MAXIMIZEBOX;
 return CFrameWnd::PreCreateWindow(cs);
}
 

 
 

  如何修改frame窗口的背景颜色?

  MDI窗口的客户区是由frame窗口拥有的另一个窗口覆盖的。为了改变frame窗口背景的颜色,只需要这个客户区的背景颜色就可以了。你必须自己处理WM_ERASEBKND消息。下面是工作步骤:

  创建一个从CWnd类继承的类,就叫它CMDIClient吧;

  在CMDIFrameWnd中加入CMDIClient变量;(具体情况看下面的代码)
 

#include "MDIClient.h"
class CMainFrame : public CMDIFrameWnd
{

protected:
CMDIClient m_wndMDIClient;
}

  重载CMDIFrameWnd::OnCreateClient,下面是这段代码,请注意其中的SubclassWindow();
 

BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
if ( CMDIFrameWnd::OnCreateClient(lpcs, pContext) )
{
m_wndMDIClient.SubclassWindow(m_hWndMDIClient);
return TRUE;
}
else
return FALSE;
}

  最后要在CMDIClient中加入处理WM_ERASEBKGND的函数。

  如何改变view的背景颜色?

  若要改变CView,CFrameWnd或CWnd对象的背景颜色需要处理WM_ERASEBKGND消息,下面就是一个范例代码:

BOOL CSampleView::OnEraseBkgnd(CDC* pDC)
{
//设置brush为希望的背景颜色
CBrush backBrush(RGB(255, 128, 128));

//保存旧的brush
CBrush* pOldBrush = pDC->SelectObject(&backBrush);
CRect rect;
pDC->GetClipBox(&rect);

//画需要的区域
pDC->PatBlt(rect.left, rect.top, rect.Width(), rect.Height(), PATCOPY);
pDC->SelectObject(pOldBrush);

return TRUE;

}
 

  若要改变CFromView继承类的背景颜色,下面是一个范例代码:

HBRUSH CMyFormView::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
 switch (nCtlColor)
 {

  case CTLCOLOR_BTN:
  case CTLCOLOR_STATIC:
  {

   pDC->SetBkMode(TRANSPARENT);
   //不加任何处理或设置背景为透明

  }
  case CTLCOLOR_DLG:
  {

   CBrush* back_brush;
   COLORREF color;
   color = (COLORREF) GetSysColor(COLOR_BTNFACE);
   back_brush = new CBrush(color);
   return (HBRUSH) (back_brush->m_hObject);

  }

 }

 return(CFormView::OnCtlColor(pDC, pWnd, nCtlColor));

}
 

在任务栏状态区如何显示应用程序图标

  有关的数据由NOTIFYICONDATA结构描述:

 

typedef struct _NOTIFYICONDATA
{
DWORD cbSize; //结构的大小,必须设置
HWND hWnd; //接受回调消息的窗口的句柄
UINT uID; //应用程序定义的图标标志
UINT uFlags; //标志,可以是NIF_ICON、NIF_MESSAGE、NIF_TIP或其组合
UINT uCallbackMessage;//应用程序定义的回调消息标志
HICON hIcon; //图标句柄
char szTip[64]; //提示字串
} NOTIFYICONDATA, *PNOTIFYICONDATA;

  函数说明

  由Shell_NotifyIcon()函数向系统发送添加、删除、更改图标的消息。

WINSHELLAPI BOOL WINAPI Shell_NotifyIcon(DWORD dwMessage,PNOTIFYICONDATA pnid);

  DwMessage为所发送消息的标志:

   NIM_ADD 添加图标到任务栏通知区;

   NIM_DELETE 删除任务栏通知区的图标;

   NIM_MODIFY 更改任务栏通知区的图标、回调消息标志、回调窗口句柄或提示字串;

   pnid为NOTIFYICONDATA结构的指针。

  回调信息的获得及处理

  如果一个任务栏图标有应用程序定义的回调消息,那么当这个图标有鼠标操作时,系统将给hWnd所标志的窗口发送下列的消息:
 

messageID = uCallbackMessage
wParam = uID
lParam = mouse event(例如WM_LBUTTONDOWN)

  通过这种方式,系统通知应用程序用户对图标的操作。如果一个应用程序生成了两个以上的图标,那么你可以根据wParam来判断是哪个图标返回的鼠标操作。通常,标准的Win95任务栏图标有以下鼠标操作响应:

  当鼠标停留在图标上时,系统应显示提示信息tooltip;

  当使用鼠标右键单击图标时,应用程序应显示快捷菜单;

  当使用鼠标左键双击图标时,应用程序应执行快捷菜单的缺省菜单项。

  在Microsoft Windows环境中,0×8000到0xBFFF的消息是保留的,应用程序可以定义自定义消息。

  关于消息处理的详细内容,请参考下一部分。

  源码及实现

  在本文中关于任务栏图标的类叫做CTrayIcon,这个类由CCmdTarget(或CObject)类派生,它有如下的成员变量和成员函数:

// TrayIcon.h
// CTrayIcon command target
class CTrayIcon : public CCmdTarget
{
public:
NOTIFYICONDATA m_nid;//NOTIFYICONDATA结构,你的图标要用的啊
BOOL m_IconExist;//标志,看看图标是不是已经存在了
CWnd* m_NotificationWnd;//接受回调消息的窗口,有它就不必经常AfxGetMainWnd了
public:
CWnd* GetNotificationWnd() const;//得到m_NotificationWnd
BOOL SetNotificationWnd(CWnd* pNotifyWnd);//设置(更改)m_NotificationWnd
CTrayIcon();//构造函数
virtual ~CTrayIcon();//析构函数
BOOL CreateIcon(CWnd* pNotifyWnd, UINT uID, HICON hIcon,
LPSTR lpszTip, UINT CallBackMessage);//在任务栏上生成图标
BOOL DeleteIcon();//删除任务栏上的图标
virtual LRESULT OnNotify(WPARAM WParam, LPARAM LParam);//消息响应函数
BOOL SetTipText(UINT nID);//设置(更改)提示字串
BOOL SetTipText(LPCTSTR lpszTip);//设置(更改)提示字串
BOOL ChangeIcon(HICON hIcon);//更改图标
BOOL ChangeIcon(UINT nID);//更改图标
BOOL ChangeIcon(LPCTSTR lpszIconName);//更改图标
BOOL ChangeStandardIcon(LPCTSTR lpszIconName);//更改为标准图标
……
};
 

  下面是成员函数的定义:

// TrayIcon.cpp
// CTrayIcon
CTrayIcon::CTrayIcon()
{//初始化参数
m_IconExist = FALSE;
m_NotificationWnd = NULL;
memset(&m_nid, 0, sizeof(m_nid));
m_nid.cbSize = sizeof(m_nid);//这个参数不会改变
}

CTrayIcon::~CTrayIcon()
{
if (m_IconExist)
DeleteIcon();//删除图标
}

BOOL CTrayIcon::CreateIcon(CWnd* pNotifyWnd, UINT uID, HICON hIcon,
LPSTR lpszTip, UINT CallBackMessage)
{
//确定接受回调消息的窗口是有效的
ASSERT(pNotifyWnd && ::IsWindow(pNotifyWnd->GetSafeHwnd()));

ASSERT(CallBackMessage >= WM_USER);//确定回调消息不发生冲突

ASSERT(_tcslen(lpszTip) <= 64);//提示字串不能超过64个字符

m_NotificationWnd = pNotifyWnd;//获得m_NotificationWnd

//设置NOTIFYICONDATA结构
m_nid.hWnd = pNotifyWnd->GetSafeHwnd();
m_nid.uID = uID;
m_nid.hIcon = hIcon;
m_nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
m_nid.uCallbackMessage = CallBackMessage;

//设置NOTIFYICONDATA结构的提示字串
if (lpszTip)
lstrcpyn(m_nid.szTip, lpszTip, sizeof(m_nid.szTip));
else
m_nid.szTip[0] = ’’;

//显示图标
m_IconExist = Shell_NotifyIcon(NIM_ADD, &m_nid);
return m_IconExist;
}

BOOL CTrayIcon::DeleteIcon()
{//删除图标
if (!m_IconExist)
return FALSE;
m_IconExist = FALSE;
return Shell_NotifyIcon(NIM_DELETE, &m_nid);
}

LRESULT CTrayIcon::OnNotify(WPARAM WParam, LPARAM LParam)
{//处理图标返回的消息
if (WParam != m_nid.uID)//如果不是该图标的消息则迅速返回
return 0L;

//准备快捷菜单
CMenu menu;
if (!menu.LoadMenu(IDR_POPUP))//你必须确定资源中有ID为IDR_POPUP的菜单
return 0;
CMenu* pSubMenu = menu.GetSubMenu(0);//获得IDR_POPUP的子菜单
if (!pSubMenu)
return 0;

if (LParam == WM_RBUTTONUP)
{//右键单击弹出快捷菜单

//设置第一个菜单项为缺省
::SetMenuDefaultItem(pSubMenu->m_hMenu, 0, TRUE);
CPoint pos;
GetCursorPos(&pos);

//显示并跟踪菜单
m_NotificationWnd->SetForegroundWindow();
pSubMenu->TrackPopupMenu(TPM_RIGHTALIGN|TPM_LEFTBUTTON
|TPM_RIGHTBUTTON, pos.x, pos.y, m_NotificationWnd, NULL);
}
else if (LParam == WM_LBUTTONDOWN)
{//左键单击恢复窗口
m_NotificationWnd->ShowWindow(SW_SHOW);//恢复窗口
m_NotificationWnd->SetForegroundWindow();//放置在前面
}
else if (LParam == WM_LBUTTONDBLCLK)
{//左键双击执行缺省菜单项
m_NotificationWnd->SendMessage(WM_COMMAND,
pSubMenu->GetMenuItemID(0), 0);
}
return 1L;
}

BOOL CTrayIcon::SetTipText(LPCTSTR lpszTip)
{//设置提示文字
if (!m_IconExist)
return FALSE;

_tcscpy(m_nid.szTip, lpszTip);
m_nid.uFlags |= NIF_TIP;

return Shell_NotifyIcon(NIM_MODIFY, &m_nid);
}

BOOL CTrayIcon::SetTipText(UINT nID)
{//设置提示文字
 CString szTip;
 VERIFY(szTip.LoadString(nID));

 return SetTipText(szTip);
}
BOOL CTrayIcon::ChangeIcon(HICON hIcon)
{//更改图标
if (!m_IconExist)
return FALSE;

m_nid.hIcon = hIcon;
m_nid.uFlags |= NIF_ICON;

return Shell_NotifyIcon(NIM_MODIFY, &m_nid);
}

BOOL CTrayIcon::ChangeIcon(UINT nID)
{//更改图标
 HICON hIcon = AfxGetApp()->LoadIcon(nID);
 return ChangeIcon(hIcon);
}

BOOL CTrayIcon::ChangeIcon(LPCTSTR lpszIconName)
{//更改图标
 HICON hIcon = AfxGetApp()->LoadIcon(lpszIconName);
 return ChangeIcon(hIcon);
}

BOOL CTrayIcon::ChangeStandardIcon(LPCTSTR lpszIconName)
{//更改为标准图标
 HICON hIcon = AfxGetApp()->LoadStandardIcon(lpszIconName);
 return ChangeIcon(hIcon);
}

BOOL CTrayIcon::SetNotificationWnd(CWnd * pNotifyWnd)
{//设置接受回调消息的窗口
 if (!m_IconExist)
  return FALSE;

 //确定窗口是有效的
 ASSERT(pNotifyWnd && ::IsWindow(pNotifyWnd->GetSafeHwnd()));

 m_NotificationWnd = pNotifyWnd;
 m_nid.hWnd = pNotifyWnd->GetSafeHwnd();
 m_nid.uFlags |= NIF_MESSAGE;

 return Shell_NotifyIcon(NIM_MODIFY, &m_nid);
}

CWnd* CTrayIcon::GetNotificationWnd() const
{//返回接受回调消息的窗口
 return m_NotificationWnd;
}
 

  三点补充:

  关于使用回调消息的补充说明:
 

  首先,在MainFrm.cpp中加入自己的消息代码;
 

// MainFrm.cpp : implementation of the CMainFrame class
//
#define MYWM_ICONNOTIFY WM_USER + 10//定义自己的消息代码

  第二步增加消息映射和函数声明,对于自定义消息不能由ClassWizard添加消息映射,只能手工添加。
 

// MainFrm.cpp : implementation of the CMainFrame class
BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd)
//{{AFX_MSG_MAP(CMainFrame)
//其他的消息映射
……
//}}AFX_MSG_MAP
ON_MESSAGE(WM_ICONNOTIFY,OnNotify)
END_MESSAGE_MAP()
并且在头文件中添加函数声明
// MainFrm.h
afx_msg LRESULT OnNotify(WPARAM WParam, LPARAM LParam);

  第三步增加消息处理函数定义
 

LRESULT CMainFrame::OnNotify(WPARAM WParam, LPARAM LParam)
{
return trayicon.OnNotify(WParam, LParam);//调用CTrayIcon类的处理函数
}

  如何隐藏任务栏上的按钮

  可以使用下列两种方法:

  1.在CreateWindowEx函数中使用WS_EX_TOOLWINDOW窗口式样(相反的如果要确保应用程序在任务栏上生成按钮,可以使用WS_EX_APPWINDOW窗口式样)。 The problem with this is that the window decorations are as for a small floating toolbar, which isn’t normally what’s wanted.

  2.生成一个空的隐藏的top-level窗口,并使其作为可视窗口的父窗口。

  3.在应用程序的InitInstance()函数中使用SW_HIDE式样调用ShowWindow()函数。
 

//pMainFrame->ShowWindow(m_nCmdShow);
pMainFrame->ShowWindow(SW_HIDE);
pMainFrame->UpdateWindow();

  如何动画任务栏上的图标
 

  在TrayIcon类中加入下列两个函数:

BOOL CTrayIcon::SetAnimateIcons(HICON* hIcon, UINT Number)
{//设置动画图标
 ASSERT(Number >= 2);//图标必须为两个以上
 ASSERT(hIcon);//图标必须不为空
 m_AnimateIcons = new HICON[Number];
 CopyMemory(m_AnimateIcons, hIcon, Number * sizeof(HICON));
 m_AnimateIconsNumber = Number;
 return TRUE;
}

BOOL CTrayIcon::Animate(UINT Index)
{//动画TrayIcon
 UINT i = Index % m_AnimateIconsNumber;
 return ChangeIcon(m_AnimateIcons[i]);
}
 

  怎样在应用程序中添加相应的菜单和函数

void CMainFrame::OnMenuAnimate()
{//动画TrayIcon,设置图标及定时器
 SetTimer(1, 500, NULL);
 HICON hIcon[3];
 hIcon[0] = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
 hIcon[1] = AfxGetApp()->LoadIcon(IDR_MYTURNTYPE);
 hIcon[2] = AfxGetApp()->LoadStandardIcon(IDI_HAND);
 trayicon.SetAnimateIcons(hIcon, 3);
}
void CMainFrame::OnTimer(UINT nIDEvent)
{//动画TrayIcon
 UINT static i;
 i += 1;
 trayicon.Animate(i);

 CMDIFrameWnd::OnTimer(nIDEvent);
}