|
|
Comments and Discussions
|
|
 |

|
It seems that there is a big problem when using fade. At least on w7 aero it is quite annoying. This happens when fade flag is set. You can test the bug on the demo exe provided at the top of this page.
I found the cause of the problem.
Here is the fix. Find:
DWORD dwExStyle = WS_EX_TOOLWINDOW;
and add the correct flag for the window:
if(NULL != m_fnAnimateWindow ){
if (!(m_unOptions&unDISABLE_FADEIN))
dwExStyle = WS_EX_TOOLWINDOW|WS_EX_LAYERED;
}
This should give you a flicker free faded tray baloon.
|
|
|
|

|
Hello,
Is there support for RTL reading order?
Thanks,
Dragan.
|
|
|
|

|
Will not compile on VS2010
balloonhelp.cpp(205): error C3867: 'CBalloonHelp::KeyboardHookProc': function call missing argument list; use '&CBalloonHelp::KeyboardHookProc' to create a pointer to member
balloonhelp.cpp(206): error C3867: 'CBalloonHelp::MouseHookProc': function call missing argument list; use '&CBalloonHelp::MouseHookProc' to create a pointer to member
balloonhelp.cpp(207): error C3867: 'CBalloonHelp::CallWndRetProc': function call missing argument list; use '&CBalloonHelp::CallWndRetProc' to create a pointer to member
balloonhelp.cpp(1085): error C2440: 'static_cast' : cannot convert from 'UINT (__thiscall CBalloonHelp::* )(CPoint)' to 'LRESULT (__thiscall CWnd::* )(CPoint)'
1> Cast from base to derived requires dynamic_cast or static_cast
|
|
|
|

|
Just change KeyBoardHookProc to &CBalloonHelp::KeyBoardHookProc and it would help!
|
|
|
|

|
Hello everyone!
I just discovered that in Visual Studio 2010 this class will cause random stack corruption due to the line "pragma pack(push,1)". The data alignment instruction is NOT reset at the end of the header file, so classes that include this header may suffer from improper data alignment.
You can safely delete this line and that class still works!
Note that older visual studios may not show a stack corruption error, and that the error isn't always evident even in Vs2k10, but may manifest in strange behavior of the program.
|
|
|
|

|
There is a #pragma pack(pop) instruction at the end of the _ThunkImpl declaration. Wouldn't this reset things properly?
Do you remove that line as well?
|
|
|
|

|
Versions that use class _ThunkImpl (Release #3, #2 and WTL port) are not Data Execution Prevention (DEP) compliant. I.e. _ThunkImpl dynamicly generates machine instructions, saves it as data and then tries to execute. If DEP is enabled (Start->right-click My Computer->Properties->Advanced System Settings->Performance, Settings->Data Execution Prevention tab->Turn on for all programs...), balloon window creation throws exception 0xc0000005 (access violation) with stack trace similar to this:
0105f920()
user32.dll!_DispatchHookA@16() + 0x52 bytes
user32.dll!_fnHkINLPCWPRETSTRUCTA@20() + 0x5e bytes
user32.dll!___fnINLPCREATESTRUCT@4() + 0x56 bytes
ntdll.dll!_KiUserCallbackDispatcher@12() + 0x13 bytes
user32.dll!_NtUserCreateWindowEx@60() + 0xc bytes
user32.dll!__CreateWindowEx@52() + 0xb1 bytes
user32.dll!_CreateWindowExA@48() + 0x33 bytes
mfc80.dll!AfxCtxCreateWindowExA() + 0x60 bytes
mfc80.dll!CWnd::CreateEx() + 0x29 bytes
mfc80.dll!CWnd::CreateEx() + 0x3f bytes
myapp!CBalloonHelp::Create() + 0x41 bytes
myapp!CBalloonHelp::CBalloonHelp()
To fix it, add in _ThunkImpl::InitThunk()
DWORD dwOldProt = 0;
VirtualProtect(this, sizeof(*this), PAGE_EXECUTE_READWRITE, &dwOldProt);
right before FlushInstructionCache(GetCurrentProcess(), this, sizeof(*this));
So that the final function looks like:
void InitThunk(TMFP method, T* pThis)
{
union { DWORD func; TMFP method; } addr;
addr.method = (TMFP)method;
m_mov = 0xB9;
m_this = reinterpret_cast<DWORD>(pThis);
m_jmp = 0xE9;
m_relproc = addr.func - reinterpret_cast<DWORD>(this+1);
DWORD dwOldProt = 0;
VirtualProtect(this, sizeof(*this), PAGE_EXECUTE_READWRITE, &dwOldProt);
FlushInstructionCache(GetCurrentProcess(), this, sizeof(*this));
}
I borrowed this solution from the latest sources of CAuxThunk by Andrew Nosenko who originally wrote _ThunkImpl class.
|
|
|
|

|
Thank you very much mate
This solved an issue i had.
Interestingly i only had this error when compiling the project with Visual Studio 2010. If the exe is compiled with Visual Studio 2005 it works fine even without your fix...
|
|
|
|

|
Just found the same solution and was just adding my comment.
The I found your comment.... Same solution but some months ago...
Would have save my an hour or two. Thanks for sharing !
Greetings from The Netherlands
|
|
|
|

|
I am using this balloon with ActiveX component that runs under IE, everything is fine till IE7. But in IE8 it gets crashed(to be very specific in CreateEx()) funtion. Another clue is, this crash is not happening in all machines, but in few machines.
Your help is much appreciated.
|
|
|
|

|
Hi Shog,
Thank you for sharing the application.This was the one I searched for long time.Is it possible to integrate this Balloon Help in C# application .As a C# Developer, I need your help on this.
Thanks,
Mathi.
|
|
|
|
|

|
Hi, I'm using the balloon control a will in my app, but when i tried it on a configuration with two screens, it does not display on the second screen !
What's wrong ? please any indication to resolve this issue.
btw, good work
|
|
|
|

|
The balloon will appear on the same screen as the anchor point you specify.
Citizen 20.1.01 'The question is,' said Humpty Dumpty, 'which is to be master - that's all.'
|
|
|
|

|
I want to have my Balloon on the top with the AnchorPoint at the bottom. Anyone know how to do this?
John
|
|
|
|

|
In CBalloonHelp::SetIconScaled, there is a call to CBitmap::CreateCompatibleBitmap for bmpIcon, but there is no call to CBitmap::DeleteObject. According to the documentation for CreateCompatibleBitmap, deletion of the bitmap object is required.
|
|
|
|

|
The call is made in the destructor for CGdiObject (from which CBitmap is derived).
every night, i kneel at the foot of my bed and thank the Great Overseeing Politicians for protecting my freedoms by reducing their number, as if they were deer in a state park. -- C hris L osinger, Online Poker Players?
|
|
|
|

|
Hello,
What is the best way to manually close a Balloon? I will be using one or both of these types:
CBalloonHelp::LaunchBalloon("test1",
"I'm here", CPoint(0,200), IDI_WARNING,
CBalloonHelp::unCLOSE_ON_RBUTTON_UP|CBalloonHelp::unSHOW_INNER_SHADOW, this, "", 0);
or
CRect rect;
GetClientRect(&rect);
bhPersistent.Create("2", "Press Here", (0,80), CBalloonHelp::unSHOW_TOPMOST|CBalloonHelp::unSHOW_INNER_SHADOW|CBalloonHelp::unDISABLE_XP_SHADOW, this);
I want to keep the original dialog up and just display balloons that I can close not using a timmer or user interface.
Please let me know if you can help.
Thanks,
John
|
|
|
|

|
Hello again,
one thing that annoyed me a little, is that the balloon tip is activated when clicked. This produces unnecessary flicker. The solution is to return MA_NOACTIVATE from OnMouseActivate().
|
|
|
|

|
Hey Shog,
there's a small bug regarding the delay close. If I interpret your header file correctly, the balloon should close itself after a delay, if I specify unDELAY_CLOSE. Unfortunately, its logic is reversed.void ShowBalloon(void)
{
ShowWindow(SW_SHOWNOACTIVATE);
if ( !(m_unOptions&unDELAY_CLOSE) )
SetTimeout(m_unTimeout); }You should remove the !, I think.
|
|
|
|

|
Joergen Sigvardsson wrote: If I interpret your header file correctly, the balloon should close itself after a delay
Not quite - you can always specify a timeout after which the balloon will close; unDELAY_CLOSE changes the meaning of this timeout such that it doesn't start when the balloon is shown, but waits until some other action occurs.
every night, i kneel at the foot of my bed and thank the Great Overseeing Politicians for protecting my freedoms by reducing their number, as if they were deer in a state park. -- C hris L osinger, Online Poker Players?
|
|
|
|

|
Hello,
againt my cpmliment to this great work!
Does anybody know how to use my own icons in the balloon?
In the comment of tze code I found the following:
// szIcon | One of:
// IDI_APPLICATION
// IDI_INFORMATION IDI_ASTERISK (same)
// IDI_ERROR IDI_HAND (same)
// IDI_EXCLAMATION IDI_WARNING (same)
// IDI_QUESTION
// IDI_WINLOGO
// NULL (no icon)
// unIconID | ID of icon to display (loaded from resources)
I do not know how to handle the unIconID.
Thank you for your help
|
|
|
|

|
I think it great that I could just include your cpp and go. (vc7.1) Here are a couple of wrappers I added to hide the class where I will use it. void ShowBalloonHelp( LPCTSTR strTittle, LPCTSTR strContent, HWND hControl ) { WINDOWINFO info; ::GetWindowInfo( hControl, &info ); CRect rect( info.rcClient ); CBalloonHelp::LaunchBalloon( strTittle, strContent, rect.BottomRight( ), IDI_ERROR, CBalloonHelp::unCLOSE_ON_KEYPRESS | CBalloonHelp::unDELAY_CLOSE, NULL, "", 3000 ); } void ShowBalloonHelpEdit( LPCTSTR strTittle, LPCTSTR strContent, CEdit& cEdit ) { ASSERT( DYNAMIC_DOWNCAST( CEdit, &cEdit ) ); ShowBalloonHelp( strTittle, strContent, cEdit ); ::SetFocus( cEdit ); cEdit.SetSel( 0, -1 ); } And the use becomes: ... cAdjust.GetWindowText( t ); if( _ttol( t ) < -120 || _ttol( t ) > 120 ) { ShowBalloonHelpEdit( _T("Out of Range Entry") ,_T("Please enter an 'Adjustment' between -120 and 120") ,cAdjust ); pDX->Fail( ); } ... It works great! Thanks, Dan.
|
|
|
|

|
Hi,
I am trying to use CBalloonHelp::LaunchBalloon() from a tray icon, which is launched using CSystemTray (downloaded from code project).
The balloon is popped up from the tray icon but when I move the cursor over the displayed balloon, mouse changes to hour glass. I am neither able to click the mouse or exit the balloon pop up. I have to explicitly kill the exe to come out of it.
CBalloonHelp::LaunchBalloon("Well...", "\tWhat do you think? Is this good? Is this useful? Does it need a lot of work? Or is it a complete waste of time... Let me know your thoughts!\n\n\tClick this balloon to send me some feedback...",
ptIcon, IDI_QUESTION, CBalloonHelp::unSHOW_CLOSE_BUTTON|
CBalloonHelp::unSHOW_TOPMOST |
CBalloonHelp::unCLOSE_ON_LBUTTON_DOWN,
this , "", 10);
Please let me know what could be wrong.
Also, strangely, if I launch the balloon from the dialog box, which is obtained by right clicking my icon, the balloon pop up works perfectly fine. Looks like the window pointer that is passed to the LaunchBalloon is causing issue. Can someone let me know what could be wrong.
Thanks in advance,
Anu
AnuradhaBhakta
|
|
|
|

|
I have modified the code to not use the dumb Thunk tehcnique,
because it does not allow you to port to another CPU architecture
I have implemented another technique, so still every instance of the class can get its own hook procedure!
And I have made also some more improvements...
I have sent the code to the author, hopefully he will post it here...
-----
Daniel Cohen Gindi
danielgindi (at) gmail dot com
|
|
|
|

|
I have experienced the problem with the Thunk class because I want to use the balloons with 64-bit Vista.
Can we get this modified version?
Rusty
|
|
|
|

|
You are going to save me a bunch of time (and not just by using CPHog to post this message!)
|
|
|
|

|
can't auto tranlate from const char[] to const CString
i have to chanage code follow:
BOOL Create(const CString& strTitle, // title of balloon
const CString& strContent, // content of balloon
const CPoint& ptAnchor, // anchor (tail position) of balloon
unsigned int unOptions, // options (see above)
CWnd* pParentWnd = NULL, // parent window (NULL == MFC main window)
const TCHAR* strURL = NULL, // URL to open (ShellExecute()) when clicked
unsigned int unTimeout = 0, // delay before closing automatically (milliseconds)
HICON hIcon = NULL); // icon to display
// Show a help balloon on screen.
static void LaunchBalloon(const CString& strTitle, const CString& strContent,
const CPoint& ptAnchor,
LPCTSTR szIcon = IDI_EXCLAMATION,
unsigned int unOptions = unSHOW_CLOSE_BUTTON,
CWnd* pParentWnd = NULL,
const TCHAR* strURL = NULL,
unsigned int unTimeout = 10000);
|
|
|
|

|
I Wanted To Have Explicit Destroying Functionality For Tooltip.
I have Tried But I am getting Error Of Handle Of ToolTip is NULL.
Please Reply Quickly......
rahulwani.
|
|
|
|

|
If you explicitly Create() it, you'll have a handle you can explicitly Close() or Destroy().
----
It appears that everybody is under the impression that I approve of the documentation. You probably also blame Ken Burns for supporting slavery.
--Raymond Chen on MSDN
|
|
|
|

|
c:\program files\microsoft visual studio\vc98\atl\include\atlwin.h(66) : error C2065: '_Module' : undeclared identifier
c:\program files\microsoft visual studio\vc98\atl\include\atlwin.h(2913) : error C2228: left of '.GetModuleInstance' must have class/struct/union type
c:\program files\microsoft visual studio\vc98\atl\include\atlwin.h(2975) : error C2228: left of '.GetModuleInstance' must have class/struct/union type
f:\balloontooltip\balloonhelp.h(42) : fatal error C1189: #error : BalloonHelp.H requires atlmisc.h to be included first
Can U Help me.
rahulwani.
|
|
|
|

|
Are you... using the WTL version?! It's really not gonna work in a normal MFC app...
----
It appears that everybody is under the impression that I approve of the documentation. You probably also blame Ken Burns for supporting slavery.
--Raymond Chen on MSDN
|
|
|
|

|
How I proceed to add these class & how to use it ?
Please Reply Quickly.;)
rahulwani.
|
|
|
|
|

|
Hi
When I click on my balloons to open any URL I always get the following error:
Unhandled exception at 0x782ab573 (mfc80d.dll) in BALLOON.exe: 0xC0000005: Access violation reading location 0xfeeefeee.
I found this LINK already that some of you posted lately.
But unfortunately it didn't help me. After I had modified the balloon class like it was shown on this site, it still did not work properly. Still got the same error.
Does anyone know how to fix this?
|
|
|
|

|
Does the example program exhibit the same problem?
If it does, please provide some details as to your configuration (OS, compiler version, DEP enabled / disabled, installed global hooks (these can usually be found by examining the modules list in the debugger)). Emailing me a crash dump of the example program would be even better.
Otherwise, please post a small code sample that illustrates the problem.
----
It appears that everybody is under the impression that I approve of the documentation. You probably also blame Ken Burns for supporting slavery.
--Raymond Chen on MSDN
|
|
|
|

|
Anyone have issues with MFC 8? Any fixes yet?
Mike
Mike "Cop" Pulice
|
|
|
|

|
There is a known issue when running on systems with DEP (data execution protection) enabled, a possible solution is described here:
http://www.codeproject.com/miscctrl/balloonhelp.asp?msg=1383510#xx1383510xx[^]
----
It appears that everybody is under the impression that I approve of the documentation. You probably also blame Ken Burns for supporting slavery.
--Raymond Chen on MSDN
|
|
|
|

|
Oh cool thanks for the quick reply, Shog!!
Mike "Cop" Pulice
|
|
|
|

|
Hello all. I cannot use the balloons on Windows XP x64 Edition when compiling in 64 bits, because the class _ThunkImpl is not still prepared for that. The balloons appears, but do not close. Any ideas to solve that. Thanks in advance,
HGC
|
|
|
|

|
I tried to patch the code with the instructions at:
Re: Fails under Windows 2003 SP1 - How to fix the DEP Problem
http://www.codeproject.com/miscctrl/balloonhelp.asp?msg=1383510#xx1383510xx[^]
http://www.codeproject.com/miscctrl/balloonhelp.asp?msg=1462896#xx1462896xx[^]
http://www.codeproject.com/miscctrl/balloonhelp.asp?msg=1531900#xx1531900xx[^]
After the three fixes I got rid of _ThunkImpl class and it works on x64!!!.
If anyone wants to get the full code with all the fixes, here it is:
BalloonHelp.h :
#ifndef _BALLOON_HELP_H_INCLUDED_
#define _BALLOON_HELP_H_INCLUDED_
#include <string>
#include <list>
#include <afxmt.h>
class CBalloonHelp : public CWnd
{
public:
CBalloonHelp();
virtual ~CBalloonHelp();
// options
static const unsigned int unCLOSE_ON_LBUTTON_UP; // closes window on WM_LBUTTON_UP
static const unsigned int unCLOSE_ON_MBUTTON_UP; // closes window on WM_MBUTTON_UP
static const unsigned int unCLOSE_ON_RBUTTON_UP; // closes window on WM_RBUTTON_UP
static const unsigned int unCLOSE_ON_LBUTTON_DOWN; // closes window on WM_LBUTTON_DOWN
static const unsigned int unCLOSE_ON_MBUTTON_DOWN; // closes window on WM_MBUTTON_DOWN
static const unsigned int unCLOSE_ON_RBUTTON_DOWN; // closes window on WM_RBUTTON_DOWN
static const unsigned int unCLOSE_ON_MOUSE_MOVE; // closes window when user moves mouse past threshhold
static const unsigned int unCLOSE_ON_KEYPRESS; // closes window on the next keypress message sent to this thread.
static const unsigned int unCLOSE_ON_ANYTHING; // all of the above
static const unsigned int unDELAY_CLOSE; // when a user action triggers the close, begins timer. closes when timer expires.
static const unsigned int unDELETE_THIS_ON_CLOSE; // deletes object when window is closed. Used by LaunchBalloon(), use with care
static const unsigned int unSHOW_CLOSE_BUTTON; // shows close button in upper right
static const unsigned int unSHOW_INNER_SHADOW; // draw inner shadow in balloon
static const unsigned int unSHOW_TOPMOST; // place balloon above all other windows
static const unsigned int unDISABLE_XP_SHADOW; // disable Windows XP static const unsigned int unDISABLE_FADEIN; // disable the fade-in effect (overrides system and user settings)
static const unsigned int unDISABLE_FADEOUT; // disable the fade-out effect (overrides system and user settings)
static const unsigned int unDISABLE_FADE; // disable the fade-in/fade-out effects (overrides system and user settings)
BOOL Create(const CString& strTitle, // title of balloon
const CString& strContent, // content of balloon
const CPoint& ptAnchor, // anchor (tail position) of balloon
unsigned int unOptions, // options (see above)
CWnd* pParentWnd = NULL, // parent window (NULL == MFC main window)
const CString strURL = "", // URL to open (ShellExecute()) when clicked
unsigned int unTimeout = 0, // delay before closing automatically (milliseconds)
HICON hIcon = NULL); // icon to display
// Show a help balloon on screen.
static void LaunchBalloon(const CString& strTitle, const CString& strContent,
const CPoint& ptAnchor,
LPCTSTR szIcon = IDI_EXCLAMATION,
unsigned int unOptions = unSHOW_CLOSE_BUTTON,
CWnd* pParentWnd = NULL,
const CString strURL = "",
unsigned int unTimeout = 10000);
// Sets the font used for drawing the balloon title. Deleted by balloon, do not use CFont* after passing to this function.
void SetTitleFont(CFont* pFont);
// Sets the font used for drawing the balloon content. Deleted by balloon, do not use CFont* after passing to this function.
void SetContentFont(CFont* pFont);
// Sets the icon displayed in the top left of the balloon (pass NULL to hide icon)
void SetIcon(HICON hIcon);
// Sets the icon displayed in the top left of the balloon (pass NULL to hide icon)
void SetIconScaled(HICON hIcon, int cx, int cy);
// Sets the icon displayed in the top left of the balloon (pass NULL hBitmap to hide icon)
void SetIcon(HBITMAP hBitmap, COLORREF crMask);
// Sets the icon displayed in the top left of the balloon
void SetIcon(HBITMAP hBitmap, HBITMAP hMask);
// Set icon displayed in the top left of the balloon to image # nIconIndex from pImageList
void SetIcon(CImageList* pImageList, int nIconIndex);
// Sets the URL to be opened when balloon is clicked. Pass "" to disable.
void SetURL(const CString& strURL);
// Sets the number of milliseconds the balloon can remain open. Set to 0 to disable timeout.
void SetTimeout(unsigned int unTimeout);
// Sets the distance the mouse must move before the balloon closes when the unCLOSE_ON_MOUSE_MOVE option is set.
void SetMouseMoveTolerance(int nTolerance);
// Sets the point to which the balloon is "anchored"
void SetAnchorPoint(CPoint ptAnchor, CWnd* pWndAnchor = NULL);
// Sets the title of the balloon
void SetTitle(const CString& strTitle);
// Sets the content of the balloon (plain text only)
void SetContent(const CString& strContent);
// Sets the forground (text and border) color of the balloon
void SetForegroundColor(COLORREF crForeground);
// Sets the background color of the balloon
void SetBackgroundColor(COLORREF crBackground);
protected:
// layout constants
static const int nTIP_TAIL;
static const int nTIP_MARGIN;
// calculate anchor position (adjust for client coordinates if used)
CPoint GetAnchorPoint();
// determine bounds of screen anchor is on (Multi-Monitor compatibility)
void GetAnchorScreenBounds(CRect& rect);
// determine section of the screen balloon is on
enum BALLOON_QUADRANT { BQ_TOPRIGHT, BQ_TOPLEFT, BQ_BOTTOMRIGHT, BQ_BOTTOMLEFT };
BALLOON_QUADRANT GetBalloonQuadrant();
// Draw the non-client area
virtual void DrawNonClientArea(CDC* pDC);
// Draw the client area
virtual void DrawClientArea(CDC* pDC);
// Calculate the dimensions and draw the balloon header
virtual CSize DrawHeader(CDC* pDC, bool bDraw = TRUE);
// Calculate the dimensions and draw the balloon contents
virtual CSize DrawContent(CDC* pDC, int nTop, bool bDraw = TRUE);
// Calculate the dimensions required to draw the balloon header
CSize CalcHeaderSize(CDC* pDC) { return DrawHeader(pDC, FALSE); }
// Calculate the dimensions required to draw the balloon content
CSize CalcContentSize(CDC* pDC) { return DrawContent(pDC, 0, FALSE); }
// Calculate the total size needed by the balloon window
CSize CalcWindowSize();
// Calculate the total size needed by the client area of the balloon window
CSize CalcClientSize();
// Size and position the balloon window on the screen.
void PositionWindow();
// Displays the balloon on the screen, performing fade-in if enabled.
void ShowBalloon(void);
// Removes the balloon from the screen, performing the fade-out if enabled
void HideBalloon(void);
// Returns the class ATOM for a BalloonHelp control. Registers the class first, if necessary.
static ATOM GetClassAtom(BOOL bShadowed);
// message handlers
afx_msg void OnShowWindow(BOOL bShow, UINT nStatus);
afx_msg LRESULT OnPrint(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT OnPrintClient(WPARAM wParam, LPARAM lParam);
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
afx_msg void OnPaint();
afx_msg void OnNcPaint();
afx_msg void OnLButtonDown(UINT, CPoint point);
afx_msg void OnLButtonUp(UINT, CPoint point);
afx_msg void OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS FAR* lpncsp);
afx_msg void OnTimer(UINT_PTR nIDEvent);
afx_msg void OnMouseMove(UINT, CPoint point);
afx_msg LRESULT OnNcHitTest(CPoint point);
afx_msg void OnClose();
afx_msg void OnDestroy();
virtual void PostNcDestroy();
DECLARE_MESSAGE_MAP()
private:
// Keyboard hook
void SetKeyboardHook();
void RemoveKeyboardHook();
// Mouse hook
void SetMouseHook();
void RemoveMouseHook();
// Call Window Return hook
void SetCallWndRetHook();
void RemoveCallWndRetHook();
protected:
// Keyboard hook callback
LRESULT KeyboardHookProc( int code, WPARAM wParam, LPARAM lParam);
// Mouse hook callback
LRESULT MouseHookProc(int code, WPARAM wParam, LPARAM lParam);
// Call Window Return hook callback (automatic following)
LRESULT CallWndRetProc(int code, WPARAM wParam, LPARAM lParam);
private:
// animate window API, if available
typedef BOOL (WINAPI* FN_ANIMATE_WINDOW)(HWND,DWORD,DWORD);
FN_ANIMATE_WINDOW m_fnAnimateWindow;
// hook handles, if set
bool m_bKeyboardHook;
bool m_bMouseHook;
bool m_bCallWndRetHook;
unsigned int m_unOptions;
unsigned int m_unTimeout; // max time to show, in milliseconds
UINT_PTR m_unTimerClose; // ID of kill timer
CString m_strContent; // text to show in content area
CString m_strURL; // url to open, if clicked.
HWND m_hwndAnchor; // window to anchor to (can be NULL for desktop anchor)
CPoint m_ptAnchor; // "anchor" (point of tail)
CImageList m_ilIcon; // icon
CFont* m_pTitleFont; // font to use for title
CFont* m_pContentFont; // font to use for content
COLORREF m_crBackground; // Background color for balloon
COLORREF m_crForeground; // Foreground color for balloon
CRect m_screenRect; // bounds of screen anchor is on
CRgn m_rgnComplete; // Clipping / Drawing region
CPoint m_ptMouseOrig; // original mouse position; for hiding on mouse move
UINT m_uCloseState; // current state of the close button
int m_nMouseMoveToleranceX; // distance mouse has to move before balloon will close.
int m_nMouseMoveToleranceY; // distance mouse has to move before balloon will close.
// class atoms (shadowed/not shadowed)
static ATOM s_ClassAtom;
static ATOM s_ClassAtomShadowed;
protected:
//Singleton that broadcast hook events to all classes that currently exist.
class HookMan
{
public:
// hook handles, if set
HHOOK m_hKeyboardHook;
HHOOK m_hMouseHook;
HHOOK m_hCallWndRetHook;
std::list<CBalloonHelp*> m_lstClasses;
int m_nCountKeyboard;
int m_nCountMouse;
int m_nCountCallWnd;
CCriticalSection m_cs;
HookMan();
~HookMan();
void AddClass(CBalloonHelp* pClass);
void RemoveClass(CBalloonHelp* pClass);
// Keyboard hook
void SetKeyboardHook();
void RemoveKeyboardHook();
// Mouse hook
void SetMouseHook();
void RemoveMouseHook();
// Call Window Return hook
void SetCallWndRetHook();
void RemoveCallWndRetHook();
static LRESULT CALLBACK KeyboardHookProcStatic( int code, WPARAM wParam, LPARAM lParam);
static LRESULT CALLBACK MouseHookProcStatic(int code, WPARAM wParam, LPARAM lParam);
static LRESULT CALLBACK CallWndRetProcStatic(int code, WPARAM wParam, LPARAM lParam);
};
friend class HookMan;
static HookMan s_hookMan;
};
#endif // _BALLOON_HELP_H_INCLUDED_
BalloonHelp.cpp :
// ******************************************************************************
// BalloonHelp.cpp : implementation file
// Copyright 2001-2002, Joshua Heyer
// You are free to use this code for whatever you want, provided you
// give credit where credit is due. (I seem to get a lot of questions
// about that statement... All i mean is, don// and then claim you wrote it. You don// box or anything. Though i// you want :~) )
// I// gotten much use out of other peoples code over the years.
// If you see value in it, make some improvements, etc., i would appreciate it
// if you sent me some feedback.
//
// ******************************************************************************
#include "stdafx.h"
#include "BalloonHelp.h"
// allow multimonitor-aware code on Win95 systems
// comment out the first line if you have already define it in another file
// comment out both lines if you don//#define COMPILE_MULTIMON_STUBS
//#include "multimon.h"
// debug helpers
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
//
// constants that may not be defined if you don// (but i like to use them anyway)
//
#ifndef DFCS_HOT
#define DFCS_HOT 0x1000
#endif
#ifndef AW_HIDE
#define AW_HIDE 0x00010000
#define AW_BLEND 0x00080000
#endif
#ifndef CS_DROPSHADOW
#define CS_DROPSHADOW 0x00020000
#endif
#ifndef SPI_GETDROPSHADOW
#define SPI_GETDROPSHADOW 0x1024
#endif
#ifndef SPI_GETTOOLTIPANIMATION
#define SPI_GETTOOLTIPANIMATION 0x1016
#endif
#ifndef SPI_GETTOOLTIPFADE
#define SPI_GETTOOLTIPFADE 0x1018
#endif
/////////////////////////////////////////////////////////////////////////////
// CBalloonHelp
// option constants (bits)
const unsigned int CBalloonHelp::unCLOSE_ON_LBUTTON_UP = 0x0001;
const unsigned int CBalloonHelp::unCLOSE_ON_MBUTTON_UP = 0x0002;
const unsigned int CBalloonHelp::unCLOSE_ON_RBUTTON_UP = 0x0004;
const unsigned int CBalloonHelp::unCLOSE_ON_LBUTTON_DOWN = 0x0008;
const unsigned int CBalloonHelp::unCLOSE_ON_MBUTTON_DOWN = 0x0010;
const unsigned int CBalloonHelp::unCLOSE_ON_RBUTTON_DOWN = 0x0020;
const unsigned int CBalloonHelp::unCLOSE_ON_MOUSE_MOVE = 0x0040;
const unsigned int CBalloonHelp::unCLOSE_ON_KEYPRESS = 0x0080;
const unsigned int CBalloonHelp::unCLOSE_ON_ANYTHING = CBalloonHelp::unCLOSE_ON_MOUSE_MOVE|CBalloonHelp::unCLOSE_ON_RBUTTON_DOWN|CBalloonHelp::unCLOSE_ON_RBUTTON_DOWN|CBalloonHelp::unCLOSE_ON_MBUTTON_DOWN|CBalloonHelp::unCLOSE_ON_LBUTTON_DOWN|CBalloonHelp::unCLOSE_ON_RBUTTON_UP|CBalloonHelp::unCLOSE_ON_MBUTTON_UP|CBalloonHelp::unCLOSE_ON_LBUTTON_UP;
const unsigned int CBalloonHelp::unDELAY_CLOSE = 0x0100;
const unsigned int CBalloonHelp::unDELETE_THIS_ON_CLOSE = 0x0200;
const unsigned int CBalloonHelp::unSHOW_CLOSE_BUTTON = 0x0400;
const unsigned int CBalloonHelp::unSHOW_INNER_SHADOW = 0x0800;
const unsigned int CBalloonHelp::unSHOW_TOPMOST = 0x1000;
const unsigned int CBalloonHelp::unDISABLE_XP_SHADOW = 0x2000;
const unsigned int CBalloonHelp::unDISABLE_FADEIN = 0x4000;
const unsigned int CBalloonHelp::unDISABLE_FADEOUT = 0x8000;
const unsigned int CBalloonHelp::unDISABLE_FADE = CBalloonHelp::unDISABLE_FADEIN|CBalloonHelp::unDISABLE_FADEOUT;
// layout constants (should prolly be configurable, but whoconst int CBalloonHelp::nTIP_TAIL = 20;
const int CBalloonHelp::nTIP_MARGIN = 8;
// class atom (why donATOM CBalloonHelp::s_ClassAtom = NULL;
ATOM CBalloonHelp::s_ClassAtomShadowed = NULL;
// Kill timer
#define ID_TIMER_CLOSE 1
//
// The launchers
//
//
// Show a help balloon on screen
// Parameters:
// strTitle | Title of balloon
// unTitle | Title of balloon (id of string resource)
// strContent | Content of balloon
// unContent | Content of balloon (id of string resource)
// ptAnchor | point tail of balloon will be "anchor"ed to
// szIcon | One of:
// IDI_APPLICATION
// IDI_INFORMATION IDI_ASTERISK (same)
// IDI_ERROR IDI_HAND (same)
// IDI_EXCLAMATION IDI_WARNING (same)
// IDI_QUESTION
// IDI_WINLOGO
// NULL (no icon)
// unIconID | ID of icon to display (loaded from resources)
// unOptions | One or more of:
// : unCLOSE_ON_LBUTTON_UP | closes window on WM_LBUTTON_UP
// : unCLOSE_ON_MBUTTON_UP | closes window on WM_MBUTTON_UP
// : unCLOSE_ON_RBUTTON_UP | closes window on WM_RBUTTON_UP
// : unCLOSE_ON_LBUTTON_DOWN | closes window on WM_LBUTTON_DOWN
// : unCLOSE_ON_MBUTTON_DOWN | closes window on WM_MBUTTON_DOWN
// : unCLOSE_ON_RBUTTON_DOWN | closes window on WM_RBUTTON_DOWN
// : unCLOSE_ON_MOUSE_MOVE | closes window when user moves mouse past threshhold
// : unCLOSE_ON_KEYPRESS | closes window on the next keypress message sent to this thread.
// : unCLOSE_ON_ANYTHING | all of the above.
// : unDELAY_CLOSE | when a user action triggers the close, begins timer. closes when timer expires.
// : unSHOW_CLOSE_BUTTON | shows close button in upper right
// : unSHOW_INNER_SHADOW | draw inner shadow in balloon
// : unSHOW_TOPMOST | place balloon above all other windows
// : unDISABLE_XP_SHADOW | disable Windows XP// : unDISABLE_FADE | disable the fade-in/fade-out effects (overrides system and user settings)
// : unDISABLE_FADEIN | disable the fade-in effect
// : unDISABLE_FADEOUT | disable the fade-out effect
// pParentWnd | Parent window. If NULL will be set to AfxGetMainWnd(), and anchor to screen
// strURL | If not empty, when the balloon is clicked ShellExecute() will
// | be called, with strURL passed in.
// unTimeout | If not 0, balloon will automatically close after unTimeout milliseconds.
//
void CBalloonHelp::LaunchBalloon(const CString& strTitle, const CString& strContent,
const CPoint& ptAnchor,
LPCTSTR szIcon /*= IDI_EXCLAMATION*/,
unsigned int unOptions /*= unSHOW_CLOSE_BUTTON*/,
CWnd* pParentWnd /*= NULL*/,
const CString strURL /*= ""*/,
unsigned int unTimeout /*= 10000*/)
{
CBalloonHelp* pbh = new CBalloonHelp;
if ( NULL != szIcon )
{
// Note: Since i // than the standard small icon if the close button is.
CSize sizeIcon(max(::GetSystemMetrics(SM_CXSIZE), ::GetSystemMetrics(SM_CXSMICON)), max(::GetSystemMetrics(SM_CYSIZE), ::GetSystemMetrics(SM_CYSMICON)));
HICON hIcon = (HICON)::LoadImage(NULL, szIcon, IMAGE_ICON, sizeIcon.cx, sizeIcon.cy, LR_SHARED);
if (NULL != hIcon)
pbh->SetIconScaled(hIcon, sizeIcon.cx, sizeIcon.cy);
}
pbh->Create(strTitle, strContent, ptAnchor, unOptions|unDELETE_THIS_ON_CLOSE,
pParentWnd, strURL, unTimeout, NULL);
}
//
// The class
//
CBalloonHelp::CBalloonHelp()
: m_fnAnimateWindow(NULL),
m_unOptions(0),
m_unTimeout(0),
m_unTimerClose(0),
m_strURL(""),
m_ptAnchor(0,0),
m_hwndAnchor(NULL),
m_screenRect(0,0,0,0),
m_strContent(""),
m_nMouseMoveToleranceX(34), // later retrieved from system
m_nMouseMoveToleranceY(14), // later retrieved from system
m_ptMouseOrig(0,0),
m_uCloseState(0),
m_pTitleFont(NULL),
m_pContentFont(NULL),
m_crForeground(::GetSysColor(COLOR_INFOTEXT)),
m_crBackground(::GetSysColor(COLOR_INFOBK)),
m_bKeyboardHook(false),
m_bMouseHook(false),
m_bCallWndRetHook(false)
{
// hook manager must know about us.
CBalloonHelp::s_hookMan.AddClass(this);
// retrieve window animation API if available
HMODULE hUser32 = GetModuleHandle(_T("USER32.DLL"));
// can if ( NULL != hUser32 )
m_fnAnimateWindow = (FN_ANIMATE_WINDOW)GetProcAddress(hUser32, _T("AnimateWindow"));
else
m_fnAnimateWindow = NULL;
// get system tolerance values
int nTol = 0;
if ( ::SystemParametersInfo(SPI_GETMOUSEHOVERWIDTH, 0, &nTol, 0) && nTol > 0 )
{
m_nMouseMoveToleranceX = nTol+30;
m_nMouseMoveToleranceY = nTol+10;
}
}
CBalloonHelp::~CBalloonHelp()
{
// hook manager should no longer send us events.
CBalloonHelp::s_hookMan.RemoveClass(this);
if ( NULL != m_pTitleFont )
delete m_pTitleFont;
m_pTitleFont = NULL;
if ( NULL != m_pContentFont )
delete m_pContentFont;
m_pContentFont = NULL;
}
// Sets the font used for drawing the balloon title. Deleted by balloon, do not use CFont* after passing to this function.
void CBalloonHelp::SetTitleFont(CFont* pFont)
{
if ( NULL != m_pTitleFont )
delete m_pTitleFont;
m_pTitleFont = pFont;
// if already visible, resize & move
if ( NULL != m_hWnd )
PositionWindow();
}
// Sets the font used for drawing the balloon content. Deleted by balloon, do not use CFont* after passing to this function.
void CBalloonHelp::SetContentFont(CFont* pFont)
{
if ( NULL != m_pContentFont )
delete m_pContentFont;
m_pContentFont = pFont;
// if already visible, resize & move
if ( NULL != m_hWnd )
PositionWindow();
}
// Sets the icon displayed in the top left of the balloon (pass NULL to hide icon)
void CBalloonHelp::SetIcon(HICON hIcon)
{
if ( NULL != m_ilIcon.m_hImageList )
m_ilIcon.DeleteImageList();
ICONINFO iconinfo;
if ( NULL != hIcon && ::GetIconInfo(hIcon, &iconinfo) )
{
SetIcon(iconinfo.hbmColor, iconinfo.hbmMask);
::DeleteObject(iconinfo.hbmColor);
::DeleteObject(iconinfo.hbmMask);
}
// if already visible, resize & move (icon size may have changed)
if ( NULL != m_hWnd )
PositionWindow();
}
// Sets the icon displayed in the top left of the balloon (pass NULL to hide icon)
void CBalloonHelp::SetIconScaled(HICON hIcon, int cx, int cy)
{
// i now have two device contexts and two bitmaps.
// i will select a bitmap in each device context,
// draw the icon into the first one,
// scale it into the second one,
// and set the second one as the balloon icon.
// This is a rather long process to get a scaled icon,
// but ensures maximum compatibility between different
// versions of Windows, while producing the best possible
// results on each version (quite good in WinNT and better, sorta ok in Win9x).
ICONINFO iconinfo;
if ( NULL != hIcon && ::GetIconInfo(hIcon, &iconinfo) )
{
BITMAP bm;
if (::GetObject(iconinfo.hbmColor, sizeof(bm),(LPVOID)&bm))
{
CDC dc;
CDC dcTmp1;
CDC dcTmp2;
CBitmap bmpIcon;
CBitmap bmpIconScaled;
dc.Attach(::GetDC(NULL));
dcTmp1.CreateCompatibleDC(&dc);
dcTmp2.CreateCompatibleDC(&dc);
bmpIcon.CreateCompatibleBitmap(&dc, bm.bmWidth, bm.bmHeight);
bmpIconScaled.CreateCompatibleBitmap(&dc, cx, cy);
::ReleaseDC(NULL, dc.Detach());
CBitmap* pbmpOld1 = dcTmp1.SelectObject(&bmpIcon);
CBitmap* pbmpOld2 = dcTmp2.SelectObject(&bmpIconScaled);
dcTmp1.FillSolidRect(0,0,bm.bmWidth,bm.bmHeight, m_crBackground);
::DrawIconEx(dcTmp1, 0,0,hIcon,bm.bmWidth,bm.bmHeight,0,NULL,DI_NORMAL);
dcTmp2.SetStretchBltMode(HALFTONE);
dcTmp2.StretchBlt(0,0,cx,cy,&dcTmp1, 0,0,bm.bmWidth,bm.bmHeight,SRCCOPY);
dcTmp1.SelectObject(pbmpOld1);
dcTmp2.SelectObject(pbmpOld2);
SetIcon(bmpIconScaled, m_crBackground);
}
::DeleteObject(iconinfo.hbmColor);
::DeleteObject(iconinfo.hbmMask);
}
}
// Sets the icon displayed in the top left of the balloon (pass NULL hBitmap to hide icon)
void CBalloonHelp::SetIcon(HBITMAP hBitmap, COLORREF crMask)
{
if ( NULL != m_ilIcon.m_hImageList )
m_ilIcon.DeleteImageList();
if ( NULL != hBitmap )
{
BITMAP bm;
if (::GetObject(hBitmap, sizeof(bm),(LPVOID)&bm))
{
m_ilIcon.Create(bm.bmWidth, bm.bmHeight, ILC_COLOR24|ILC_MASK,1,0);
m_ilIcon.Add(CBitmap::FromHandle(hBitmap), crMask);
}
}
// if already visible, resize & move (icon size may have changed)
if ( NULL != m_hWnd )
PositionWindow();
}
// Sets the icon displayed in the top left of the balloon
void CBalloonHelp::SetIcon(HBITMAP hBitmap, HBITMAP hMask)
{
if ( NULL != m_ilIcon.m_hImageList )
m_ilIcon.DeleteImageList();
ASSERT(NULL != hBitmap);
ASSERT(NULL != hMask);
BITMAP bm;
if (::GetObject(hBitmap, sizeof(bm),(LPVOID)&bm))
{
m_ilIcon.Create(bm.bmWidth, bm.bmHeight, ILC_COLOR24|ILC_MASK,1,0);
m_ilIcon.Add(CBitmap::FromHandle(hBitmap), CBitmap::FromHandle(hMask));
}
// if already visible, resize & move (icon size may have changed)
if ( NULL != m_hWnd )
PositionWindow();
}
// Set icon displayed in the top left of the balloon to image # nIconIndex from pImageList
void CBalloonHelp::SetIcon(CImageList* pImageList, int nIconIndex)
{
// sanity checks
ASSERT_VALID(pImageList);
ASSERT(nIconIndex >= 0 && nIconIndex < pImageList->GetImageCount() );
HICON hIcon = NULL;
if ( NULL != pImageList && nIconIndex >= 0 && nIconIndex < pImageList->GetImageCount() )
hIcon = pImageList->ExtractIcon(nIconIndex);
SetIcon(hIcon);
if ( NULL != hIcon )
::DestroyIcon(hIcon);
// if already visible, resize & move (icon size may have changed)
if ( NULL != m_hWnd )
PositionWindow();
}
// Sets the URL to be opened when balloon is clicked. Pass "" to disable.
void CBalloonHelp::SetURL(const CString& strURL)
{
m_strURL = strURL;
}
// Sets the number of milliseconds the balloon can remain open. Set to 0 to disable timeout.
void CBalloonHelp::SetTimeout(unsigned int unTimeout)
{
m_unTimeout = unTimeout;
// if timer is already set, reset.
if ( NULL != m_hWnd )
{
if ( m_unTimeout > 0 )
{
m_unTimerClose = SetTimer(ID_TIMER_CLOSE, m_unTimeout, NULL);
}
else
{
KillTimer(m_unTimerClose);
}
}
}
// Sets the point to which the balloon is "anchored"
void CBalloonHelp::SetAnchorPoint(CPoint ptAnchor, CWnd* pWndAnchor /*= NULL*/)
{
m_ptAnchor = ptAnchor;
m_hwndAnchor = pWndAnchor->GetSafeHwnd();
// if we if ( NULL != m_hwndAnchor )
SetCallWndRetHook();
else
RemoveCallWndRetHook();
// if already visible, move
if ( NULL != m_hWnd )
{
// reposition
PositionWindow();
}
}
// Sets the title of the balloon
void CBalloonHelp::SetTitle(const CString& strTitle)
{
SetWindowText(strTitle);
// if already visible, resize & move
if ( NULL != m_hWnd )
PositionWindow();
}
// Sets the content of the balloon (plain text only)
void CBalloonHelp::SetContent(const CString& strContent)
{
m_strContent = strContent;
// if already visible, resize & move
if ( NULL != m_hWnd )
PositionWindow();
}
// Sets the forground (text and border) color of the balloon
void CBalloonHelp::SetForegroundColor(COLORREF crForeground)
{
m_crForeground = crForeground;
// repaint if visible
if ( NULL != m_hWnd )
Invalidate(FALSE);
}
// Sets the background color of the balloon
void CBalloonHelp::SetBackgroundColor(COLORREF crBackground)
{
m_crBackground = crBackground;
// repaint if visible
if ( NULL != m_hWnd )
Invalidate(FALSE);
}
// Sets the distance the mouse must move before the balloon closes when the unCLOSE_ON_MOUSE_MOVE option is set.
void CBalloonHelp::SetMouseMoveTolerance(int nTolerance)
{
m_nMouseMoveToleranceX = nTolerance;
m_nMouseMoveToleranceY = nTolerance;
}
//
// creates a new balloon window
// Parameters:
// strTitle | Title of balloon
// strContent | Content of balloon
// ptAnchor | point tail of balloon will be "anchor"ed to
// unOptions | One or more of:
// : unCLOSE_ON_LBUTTON_UP | closes window on WM_LBUTTON_UP
// : unCLOSE_ON_MBUTTON_UP | closes window on WM_MBUTTON_UP
// : unCLOSE_ON_RBUTTON_UP | closes window on WM_RBUTTON_UP
// : unCLOSE_ON_LBUTTON_DOWN | closes window on WM_LBUTTON_DOWN
// : unCLOSE_ON_MBUTTON_DOWN | closes window on WM_MBUTTON_DOWN
// : unCLOSE_ON_RBUTTON_DOWN | closes window on WM_RBUTTON_DOWN
// : unCLOSE_ON_MOUSE_MOVE | closes window when user moves mouse past threshhold
// : unCLOSE_ON_KEYPRESS | closes window on the next keypress message sent to this thread.
// : unCLOSE_ON_ANYTHING | all of the above.
// : unDELAY_CLOSE | when a user action triggers the close, begins timer. closes when timer expires.
// : unDELETE_THIS_ON_CLOSE | deletes object when window is closed. Used by LaunchBalloon(), use with care
// : unSHOW_CLOSE_BUTTON | shows close button in upper right
// : unSHOW_INNER_SHADOW | draw inner shadow in balloon
// : unSHOW_TOPMOST | place balloon above all other windows
// : unDISABLE_XP_SHADOW | disable Windows XP// : unDISABLE_FADE | disable the fade-in/fade-out effects (overrides system and user settings)
// : unDISABLE_FADEIN | disable the fade-in effect
// : unDISABLE_FADEOUT | disable the fade-out effect
// pParentWnd | Parent window. If NULL will be set to AfxGetMainWnd() and anchor to screen
// strURL | If not empty, when the balloon is clicked ShellExecute() will
// | be called, with strURL passed in.
// unTimeout | If not 0, balloon will automatically close after unTimeout milliseconds.
// hIcon | If not NULL, the icon indicated by hIcon will be displayed at top-left of the balloon.
//
// Returns:
// TRUE if successful, else FALSE
//
BOOL CBalloonHelp::Create(const CString& strTitle, const CString& strContent,
const CPoint& ptAnchor, unsigned int unOptions,
CWnd* pParentWnd /*=NULL*/,
const CString strURL /*= ""*/,
unsigned int unTimeout /*= 0*/,
HICON hIcon /*= NULL*/)
{
m_strContent = strContent;
SetAnchorPoint(ptAnchor, pParentWnd);
m_unOptions = unOptions;
m_strURL = strURL;
m_unTimeout = unTimeout;
if ( NULL != hIcon )
SetIcon(hIcon);
pParentWnd = GetSafeOwner(pParentWnd);
if ( NULL == pParentWnd )
{
// something is wrong; MFC should be able to find a main window.
ASSERT(FALSE);
return FALSE;
}
// if no fonts set, use defaults
if ( NULL == m_pContentFont )
{
m_pContentFont = new CFont;
if ( !m_pContentFont->CreateStockObject(DEFAULT_GUI_FONT) )
return FALSE;
}
// title font defaults to bold version of content font
if ( NULL == m_pTitleFont )
{
m_pTitleFont = new CFont;
LOGFONT LogFont;
m_pContentFont->GetLogFont(&LogFont);
LogFont.lfWeight = FW_BOLD;
if ( !m_pTitleFont->CreateFontIndirect(&LogFont) )
return FALSE;
}
ATOM wndClass = GetClassAtom(!(m_unOptions&unDISABLE_XP_SHADOW));
if ( NULL == wndClass ) // couldn return FALSE;
// check system settings: if fade effects are disabled or unavailable, disable here too
BOOL bFade = FALSE;
::SystemParametersInfo(SPI_GETTOOLTIPANIMATION, 0, &bFade, 0);
if (bFade)
::SystemParametersInfo(SPI_GETTOOLTIPFADE, 0, &bFade, 0);
if (!bFade || NULL == m_fnAnimateWindow)
m_unOptions |= unDISABLE_FADE;
// create invisible at arbitrary position; then position, set region, and finally show
// the idea with WS_EX_TOOLWINDOW is, you can DWORD dwExStyle = WS_EX_TOOLWINDOW;
if ( m_unOptions&unSHOW_TOPMOST ) // make topmost, if requested
dwExStyle |= WS_EX_TOPMOST;
if ( !CreateEx(dwExStyle, (LPCTSTR)wndClass, strTitle, WS_POPUP, CRect(0,0,10,10), pParentWnd, 0, NULL) )
return FALSE;
PositionWindow();
if ( (m_unOptions&unCLOSE_ON_MOUSE_MOVE)
||(m_unOptions&unCLOSE_ON_LBUTTON_UP)
||(m_unOptions&unCLOSE_ON_LBUTTON_DOWN)
||(m_unOptions&unCLOSE_ON_MBUTTON_UP)
||(m_unOptions&unCLOSE_ON_MBUTTON_DOWN)
||(m_unOptions&unCLOSE_ON_RBUTTON_UP)
||(m_unOptions&unCLOSE_ON_RBUTTON_DOWN) )
{
::GetCursorPos(&m_ptMouseOrig);
SetMouseHook();
}
// these need to take effect even if the window receiving them
// is not owned by this process. So, if this process does not
// already have the mouse captured, capture it!
if ( (m_unOptions&unCLOSE_ON_LBUTTON_UP)
||(m_unOptions&unCLOSE_ON_MBUTTON_UP)
||(m_unOptions&unCLOSE_ON_RBUTTON_UP) )
{
// no, i don // where a balloon is being created and another program has captured
// the mouse. If you need it, it shouldn if ( NULL == GetCapture() )
SetCapture();
}
if ( m_unOptions&unCLOSE_ON_KEYPRESS )
SetKeyboardHook();
ShowBalloon();
return TRUE;
}
// calculate anchor position (adjust for client coordinates if used)
CPoint CBalloonHelp::GetAnchorPoint()
{
CPoint ptAnchor = m_ptAnchor;
// assume if window was given, point is in client coords
if ( NULL != m_hwndAnchor )
::ClientToScreen(m_hwndAnchor, &ptAnchor);
return ptAnchor;
}
// determine bounds of screen anchor is on (Multi-Monitor compatibility)
void CBalloonHelp::GetAnchorScreenBounds(CRect& rect)
{
if ( m_screenRect.IsRectEmpty() )
{
// get the nearest monitor to the anchor
HMONITOR hMonitor = MonitorFromPoint(GetAnchorPoint(), MONITOR_DEFAULTTONEAREST);
// get the monitor bounds
MONITORINFO mi;
mi.cbSize = sizeof(mi);
GetMonitorInfo(hMonitor, &mi);
// work area (area not obscured by task bar, etc.)
m_screenRect = mi.rcWork;
}
rect = m_screenRect;
}
// calculates the area of the screen the balloon falls into
// this determins which direction the tail points
CBalloonHelp::BALLOON_QUADRANT CBalloonHelp::GetBalloonQuadrant()
{
CRect rectDesktop;
GetAnchorScreenBounds(rectDesktop);
CPoint ptAnchor = GetAnchorPoint();
if ( ptAnchor.y < rectDesktop.top + rectDesktop.Height()/2 )
{
if ( ptAnchor.x < rectDesktop.left + rectDesktop.Width()/2 )
{
return BQ_TOPLEFT;
}
else
{
return BQ_TOPRIGHT;
}
}
else
{
if ( ptAnchor.x < rectDesktop.left + rectDesktop.Width()/2 )
{
return BQ_BOTTOMLEFT;
}
else
{
return BQ_BOTTOMRIGHT;
}
}
// unreachable
}
// Draw the non-client area
void CBalloonHelp::DrawNonClientArea(CDC* pDC)
{
CRect rect;
GetWindowRect(&rect);
ScreenToClient(&rect);
CRect rectClient;
GetClientRect(&rectClient);
rectClient.OffsetRect(-rect.left, -rect.top);
rect.OffsetRect(-rect.left, -rect.top);
pDC->ExcludeClipRect(&rectClient);
pDC->FillSolidRect(&rect, m_crBackground);
pDC->SelectClipRgn(NULL);
ASSERT(NULL != m_rgnComplete.m_hObject);
CBrush brushFg;
brushFg.CreateSolidBrush(m_crForeground);
if ( m_unOptions & unSHOW_INNER_SHADOW )
{
CBrush brushHL;
// slightly lighter color
int red = 170 + GetRValue(m_crBackground)/3;
int green = 170 + GetGValue(m_crBackground)/3;
int blue = 170 + GetBValue(m_crBackground)/3;
brushHL.CreateSolidBrush(RGB(red,green,blue));
m_rgnComplete.OffsetRgn(1,1);
pDC->FrameRgn(&m_rgnComplete, &brushHL, 2, 2);
// slightly darker color
red = GetRValue(m_crForeground)/3 + GetRValue(m_crBackground)/3*2;
green = GetGValue(m_crForeground)/3 + GetGValue(m_crBackground)/3*2;
blue = GetBValue(m_crForeground)/3 + GetBValue(m_crBackground)/3*2;
brushHL.DeleteObject();
m_rgnComplete.OffsetRgn(-2,-2);
brushHL.CreateSolidBrush(RGB(red,green,blue));
pDC->FrameRgn(&m_rgnComplete, &brushHL, 2, 2);
m_rgnComplete.OffsetRgn(1,1);
}
// outline
pDC->FrameRgn(&m_rgnComplete, &brushFg, 1, 1);
}
// Draw the client area
void CBalloonHelp::DrawClientArea(CDC* pDC)
{
CSize sizeHeader = DrawHeader(pDC);
DrawContent(pDC, sizeHeader.cy+nTIP_MARGIN);
}
// Calculate the dimensions and draw the balloon header
CSize CBalloonHelp::DrawHeader(CDC* pDC, bool bDraw)
{
CSize sizeHdr(0,0);
CRect rectClient;
GetClientRect(&rectClient); // use this for positioning when drawing
// else if content is wider than title, centering wouldn
// calc & draw icon
if ( NULL != m_ilIcon.m_hImageList )
{
int x = 0;
int y = 0;
ImageList_GetIconSize(m_ilIcon, &x, &y);
sizeHdr.cx += x;
sizeHdr.cy = max(sizeHdr.cy, y);
m_ilIcon.SetBkColor(m_crBackground);
if (bDraw)
m_ilIcon.Draw(pDC, 0, CPoint(0,0), ILD_NORMAL);//ILD_TRANSPARENT);
rectClient.left += x;
}
// calc & draw close button
if ( m_unOptions & unSHOW_CLOSE_BUTTON )
{
int nBtnWidth = ::GetSystemMetrics(SM_CXSIZE);
// if something is already in the header (icon) leave space
if ( sizeHdr.cx > 0 )
sizeHdr.cx += nTIP_MARGIN;
sizeHdr.cx += nBtnWidth;
sizeHdr.cy = max(sizeHdr.cy, ::GetSystemMetrics(SM_CYSIZE));
if (bDraw)
pDC->DrawFrameControl(CRect(rectClient.right-nBtnWidth,0,rectClient.right,::GetSystemMetrics(SM_CYSIZE)), DFC_CAPTION, DFCS_CAPTIONCLOSE|DFCS_FLAT);
rectClient.right -= nBtnWidth;
}
// calc title size
CString strTitle;
GetWindowText(strTitle);
if ( !strTitle.IsEmpty() )
{
CFont* pOldFont = pDC->SelectObject(m_pTitleFont);
// if something is already in the header (icon or close button) leave space
if ( sizeHdr.cx > 0 )
sizeHdr.cx += nTIP_MARGIN;
CRect rectTitle(0,0,0,0);
pDC->DrawText(strTitle, &rectTitle, DT_CALCRECT | DT_NOPREFIX | DT_EXPANDTABS | DT_SINGLELINE);
sizeHdr.cx += rectTitle.Width();
sizeHdr.cy = max(sizeHdr.cy, rectTitle.Height());
// draw title
if ( bDraw )
{
pDC->SetBkMode(TRANSPARENT);
pDC->SetTextColor(m_crForeground);
pDC->DrawText(strTitle, &rectClient, DT_CENTER | DT_NOPREFIX | DT_EXPANDTABS | DT_SINGLELINE);
}
// cleanup
pDC->SelectObject(pOldFont);
}
return sizeHdr;
}
// Calculate the dimensions and draw the balloon contents
CSize CBalloonHelp::DrawContent(CDC* pDC, int nTop, bool bDraw)
{
CRect rectContent;
GetAnchorScreenBounds(rectContent);
rectContent.OffsetRect(-rectContent.left, -rectContent.top);
rectContent.top = nTop;
// limit to half screen width
rectContent.right -= rectContent.Width()/2;
// calc size
CFont* pOldFont = pDC->SelectObject(m_pContentFont);
if ( !m_strContent.IsEmpty() )
pDC->DrawText(m_strContent, &rectContent, DT_CALCRECT | DT_LEFT | DT_NOPREFIX | DT_EXPANDTABS | DT_WORDBREAK);
else
rectContent.SetRectEmpty(); // don
// draw
if (bDraw)
{
pDC->SetBkMode(TRANSPARENT);
pDC->SetTextColor(m_crForeground);
pDC->DrawText(m_strContent, &rectContent, DT_LEFT | DT_NOPREFIX | DT_WORDBREAK | DT_EXPANDTABS);
}
// cleanup
pDC->SelectObject(pOldFont);
return rectContent.Size();
}
// calculates the client size necessary based on title and content
CSize CBalloonHelp::CalcClientSize()
{
ASSERT(NULL != m_hWnd);
CWindowDC dc(this);
CSize sizeHeader = CalcHeaderSize(&dc);
CSize sizeContent = CalcContentSize(&dc);
return CSize(max(sizeHeader.cx,sizeContent.cx), sizeHeader.cy + nTIP_MARGIN + sizeContent.cy);
}
// calculates the size for the entire window based on content size
CSize CBalloonHelp::CalcWindowSize()
{
CSize size = CalcClientSize();
size.cx += nTIP_MARGIN*2;
size.cy += nTIP_TAIL+nTIP_MARGIN*2;
//size.cx = max(size.cx, nTIP_MARGIN*2+nTIP_TAIL*4);
return size;
}
// this routine calculates the size and position of the window relative
// to it// created and set here.
void CBalloonHelp::PositionWindow()
{
CSize sizeWnd = CalcWindowSize();
CPoint ptTail[3];
CPoint ptTopLeft(0,0);
CPoint ptBottomRight(sizeWnd.cx, sizeWnd.cy);
// force recalculation of desktop
m_screenRect.SetRectEmpty();
switch (GetBalloonQuadrant())
{
case BQ_TOPLEFT:
ptTopLeft.y = nTIP_TAIL;
ptTail[0].x = (sizeWnd.cx-nTIP_TAIL)/4 + nTIP_TAIL;
ptTail[0].y = nTIP_TAIL+1;
ptTail[2].x = (sizeWnd.cx-nTIP_TAIL)/4;
ptTail[2].y = ptTail[0].y;
ptTail[1].x = ptTail[2].x;
ptTail[1].y = 1;
break;
case BQ_TOPRIGHT:
ptTopLeft.y = nTIP_TAIL;
ptTail[0].x = (sizeWnd.cx-nTIP_TAIL)/4*3;
ptTail[0].y = nTIP_TAIL+1;
ptTail[2].x = (sizeWnd.cx-nTIP_TAIL)/4*3 + nTIP_TAIL;
ptTail[2].y = ptTail[0].y;
ptTail[1].x = ptTail[2].x;
ptTail[1].y = 1;
break;
case BQ_BOTTOMLEFT:
ptBottomRight.y = sizeWnd.cy-nTIP_TAIL;
ptTail[0].x = (sizeWnd.cx-nTIP_TAIL)/4 + nTIP_TAIL;
ptTail[0].y = sizeWnd.cy-nTIP_TAIL-2;
ptTail[2].x = (sizeWnd.cx-nTIP_TAIL)/4;
ptTail[2].y = ptTail[0].y;
ptTail[1].x = ptTail[2].x;
ptTail[1].y = sizeWnd.cy-2;
break;
case BQ_BOTTOMRIGHT:
ptBottomRight.y = sizeWnd.cy-nTIP_TAIL;
ptTail[0].x = (sizeWnd.cx-nTIP_TAIL)/4*3;
ptTail[0].y = sizeWnd.cy-nTIP_TAIL-2;
ptTail[2].x = (sizeWnd.cx-nTIP_TAIL)/4*3 + nTIP_TAIL;
ptTail[2].y = ptTail[0].y;
ptTail[1].x = ptTail[2].x;
ptTail[1].y = sizeWnd.cy-2;
break;
}
// adjust for very narrow balloons
if ( ptTail[0].x < nTIP_MARGIN )
ptTail[0].x = nTIP_MARGIN;
if ( ptTail[0].x > sizeWnd.cx - nTIP_MARGIN )
ptTail[0].x = sizeWnd.cx - nTIP_MARGIN;
if ( ptTail[1].x < nTIP_MARGIN )
ptTail[1].x = nTIP_MARGIN;
if ( ptTail[1].x > sizeWnd.cx - nTIP_MARGIN )
ptTail[1].x = sizeWnd.cx - nTIP_MARGIN;
if ( ptTail[2].x < nTIP_MARGIN )
ptTail[2].x = nTIP_MARGIN;
if ( ptTail[2].x > sizeWnd.cx - nTIP_MARGIN )
ptTail[2].x = sizeWnd.cx - nTIP_MARGIN;
// get window position
CPoint ptAnchor = GetAnchorPoint();
CPoint ptOffs(ptAnchor.x - ptTail[1].x, ptAnchor.y - ptTail[1].y);
// adjust position so all is visible
CRect rectScreen;
GetAnchorScreenBounds(rectScreen);
int nAdjustX = 0;
int nAdjustY = 0;
if ( ptOffs.x < rectScreen.left )
nAdjustX = rectScreen.left-ptOffs.x;
else if ( ptOffs.x + sizeWnd.cx >= rectScreen.right )
nAdjustX = rectScreen.right - (ptOffs.x + sizeWnd.cx);
if ( ptOffs.y + nTIP_TAIL < rectScreen.top )
nAdjustY = rectScreen.top - (ptOffs.y + nTIP_TAIL);
else if ( ptOffs.y + sizeWnd.cy - nTIP_TAIL >= rectScreen.bottom )
nAdjustY = rectScreen.bottom - (ptOffs.y + sizeWnd.cy - nTIP_TAIL);
// reposition tail
// uncomment two commented lines below to move entire tail
// instead of just anchor point
//ptTail[0].x -= nAdjustX;
ptTail[1].x -= nAdjustX;
//ptTail[2].x -= nAdjustX;
ptOffs.x += nAdjustX;
ptOffs.y += nAdjustY;
// place window
MoveWindow(ptOffs.x, ptOffs.y, sizeWnd.cx, sizeWnd.cy, TRUE);
// apply region
CRgn region;
CRgn regionRound;
CRgn regionComplete;
region.CreatePolygonRgn(&ptTail[0], 3, ALTERNATE);
regionRound.CreateRoundRectRgn(ptTopLeft.x,ptTopLeft.y,ptBottomRight.x,ptBottomRight.y,nTIP_MARGIN*3,nTIP_MARGIN*3);
regionComplete.CreateRectRgn(0,0,1,1);
regionComplete.CombineRgn(®ion, ®ionRound, RGN_OR);
if ( NULL == m_rgnComplete.m_hObject )
m_rgnComplete.CreateRectRgn(0,0,1,1);
if ( !m_rgnComplete.EqualRgn(®ionComplete) )
{
m_rgnComplete.CopyRgn(®ionComplete);
SetWindowRgn((HRGN)regionComplete.Detach(), TRUE);
// There is a bug with layered windows and NC changes in Win2k
// As a workaround, redraw the entire window if the NC area changed.
// Changing the anchor point is the ONLY thing that will change the
// position of the client area relative to the window during normal
// operation.
RedrawWindow(NULL, NULL, RDW_UPDATENOW| RDW_ERASE | RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN);
}
}
// Returns the class ATOM for a BalloonHelp control. Registers the class first, if necessary.
ATOM CBalloonHelp::GetClassAtom(BOOL bShadowed)
{
if ( NULL == s_ClassAtom )
{
WNDCLASSEX wcx;
// Fill in the window class structure with parameters
// that describe the main window.
wcx.cbSize = sizeof(wcx); // size of structure
wcx.style = CS_DBLCLKS|CS_SAVEBITS
|CS_DROPSHADOW; // notify of double clicks, save screen under, show dropshadow
wcx.lpfnWndProc = AfxWndProc; // points to window procedure
wcx.cbClsExtra = 0; // no extra class memory
wcx.cbWndExtra = 0; // no extra window memory
wcx.hInstance = AfxGetInstanceHandle(); // handle to instance
wcx.hIcon = NULL; // no app. icon
wcx.hCursor = LoadCursor(NULL,IDC_ARROW); // predefined arrow
wcx.hbrBackground = ::GetSysColorBrush(COLOR_WINDOW); // no background brush
wcx.lpszMenuName = NULL; // no menu resource
wcx.lpszClassName = "BalloonHelpClassDS"; // name of window class
wcx.hIconSm = NULL; // no small class icon
// Register the window class (this may not work if dropshadows are not supported)
s_ClassAtomShadowed = RegisterClassEx(&wcx);
// Register shadow-less class
wcx.style &= ~CS_DROPSHADOW;
wcx.lpszClassName = "BalloonHelpClass";
s_ClassAtom = RegisterClassEx(&wcx);
}
if ( bShadowed && NULL != s_ClassAtomShadowed )
return s_ClassAtomShadowed;
return s_ClassAtom;
}
// Displays the balloon on the screen, performing fade-in if enabled.
void CBalloonHelp::ShowBalloon(void)
{
ShowWindow(SW_SHOWNOACTIVATE);
if ( !(m_unOptions&unDELAY_CLOSE) )
SetTimeout(m_unTimeout); // start close timer
}
// Removes the balloon from the screen, performing the fade-out if enabled
void CBalloonHelp::HideBalloon(void)
{
if ( m_unOptions&unDELAY_CLOSE )
{
m_unOptions &= ~(unDELAY_CLOSE|unCLOSE_ON_ANYTHING); // close only via timer or button
SetTimeout(m_unTimeout); // start close timer
return;
}
ShowWindow( SW_HIDE );
if ( GetCapture() == this )
ReleaseCapture();
DestroyWindow();
}
//
// Keyboard hook
//
void CBalloonHelp::SetKeyboardHook()
{
if (m_bKeyboardHook){ return; }
CBalloonHelp::s_hookMan.SetKeyboardHook();
m_bKeyboardHook=true;
}
void CBalloonHelp::RemoveKeyboardHook()
{
if (!m_bKeyboardHook){ return; }
CBalloonHelp::s_hookMan.RemoveKeyboardHook();
m_bKeyboardHook=false;
}
//
// Mouse hook
//
void CBalloonHelp::SetMouseHook()
{
if (m_bMouseHook){ return; }
CBalloonHelp::s_hookMan.SetMouseHook();
m_bMouseHook=true;
}
void CBalloonHelp::RemoveMouseHook()
{
if (!m_bMouseHook){ return; }
CBalloonHelp::s_hookMan.RemoveMouseHook();
m_bMouseHook=false;
}
//
// Call Window Return hook
//
void CBalloonHelp::SetCallWndRetHook()
{
if (m_bCallWndRetHook){ return; }
CBalloonHelp::s_hookMan.SetCallWndRetHook();
m_bCallWndRetHook=true;
}
void CBalloonHelp::RemoveCallWndRetHook()
{
if (!m_bCallWndRetHook){ return; }
CBalloonHelp::s_hookMan.RemoveCallWndRetHook();
m_bCallWndRetHook=false;
}
/////////////////////////////////////////////////////////////////////////////
// CBalloonHelp message handlers
BEGIN_MESSAGE_MAP(CBalloonHelp, CWnd)
ON_WM_ERASEBKGND()
ON_WM_PAINT()
ON_WM_NCPAINT()
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONUP()
ON_WM_NCCALCSIZE()
ON_WM_TIMER()
ON_WM_NCHITTEST()
ON_WM_MOUSEMOVE()
ON_WM_DESTROY()
ON_WM_CLOSE()
ON_WM_SHOWWINDOW()
ON_MESSAGE(WM_PRINT, OnPrint)
ON_MESSAGE(WM_PRINTCLIENT, OnPrintClient)
END_MESSAGE_MAP()
void CBalloonHelp::OnShowWindow(BOOL bShow, UINT)
{
if ( NULL != m_fnAnimateWindow )
{
if ( bShow && !(m_unOptions&unDISABLE_FADEIN) )
m_fnAnimateWindow( m_hWnd, 200, AW_BLEND);
else if ( !bShow && !(m_unOptions&unDISABLE_FADEOUT) )
m_fnAnimateWindow( m_hWnd, 200, AW_HIDE | AW_BLEND );
}
}
// Erase client area of balloon
BOOL CBalloonHelp::OnEraseBkgnd(CDC* pDC)
{
CRect rect;
GetClientRect(&rect);
pDC->FillSolidRect(&rect, m_crBackground);
return TRUE;
}
// draw balloon client area (title & contents)
void CBalloonHelp::OnPaint()
{
CPaintDC dc(this); // device context for painting
DrawClientArea(&dc);
}
// draw balloon shape & border
void CBalloonHelp::OnNcPaint()
{
CWindowDC dc(this);
DrawNonClientArea(&dc);
}
// draw the window into the specified device context
LRESULT CBalloonHelp::OnPrint(WPARAM wParam, LPARAM lParam)
{
CDC* pDC = CDC::FromHandle((HDC)wParam);
if ( lParam & PRF_NONCLIENT )
DrawNonClientArea(pDC);
return Default();
}
// draw the client area into the specified device context
LRESULT CBalloonHelp::OnPrintClient(WPARAM wParam, LPARAM lParam)
{
CDC* pDC = CDC::FromHandle((HDC)wParam);
if ( lParam & PRF_ERASEBKGND )
SendMessage( WM_ERASEBKGND, wParam );
if ( lParam & PRF_CLIENT )
DrawClientArea(pDC);
return 0;
}
// Close button handler
void CBalloonHelp::OnLButtonDown(UINT, CPoint point)
{
if (m_unOptions & unSHOW_CLOSE_BUTTON)
{
CRect rect;
GetClientRect(&rect);
rect.left = rect.right-::GetSystemMetrics(SM_CXSIZE);
rect.bottom = rect.top+::GetSystemMetrics(SM_CYSIZE);
if ( rect.PtInRect(point) )
{
m_uCloseState |= DFCS_PUSHED;
SetCapture();
OnMouseMove(0, point);
}
}
}
// Close button handler,
// URL handler
void CBalloonHelp::OnLButtonUp(UINT, CPoint point)
{
if ( (m_unOptions & unSHOW_CLOSE_BUTTON) && (m_uCloseState & DFCS_PUSHED) )
{
ReleaseCapture();
m_uCloseState &= ~DFCS_PUSHED;
CRect rect;
GetClientRect(&rect);
rect.left = rect.right-::GetSystemMetrics(SM_CXSIZE);
rect.bottom = rect.top+::GetSystemMetrics(SM_CYSIZE);
if ( rect.PtInRect(point) )
HideBalloon();
}
else if ( !m_strURL.IsEmpty() )
{
CRect rect;
GetClientRect(&rect);
if ( rect.PtInRect(point) )
{
::ShellExecute(NULL, NULL, m_strURL, NULL, NULL, SW_SHOWNORMAL);
HideBalloon();
}
}
}
//
// Ensure WM_MOUSEMOVE messages are sent for the entire window
//
LRESULT CBalloonHelp::OnNcHitTest(CPoint)
{
return HTCLIENT;
}
//
// do mouse tracking:
// Tracking for close button;
//
void CBalloonHelp::OnMouseMove(UINT, CPoint point)
{
if (m_unOptions & unSHOW_CLOSE_BUTTON)
{
CRect rect;
GetClientRect(&rect);
rect.left = rect.right-::GetSystemMetrics(SM_CXSIZE);
rect.bottom = rect.top+::GetSystemMetrics(SM_CYSIZE);
CClientDC dc(this);
UINT uState = DFCS_CAPTIONCLOSE;
BOOL bPushed = m_uCloseState&DFCS_PUSHED;
m_uCloseState &= ~DFCS_PUSHED;
if ( rect.PtInRect(point) )
{
uState |= DFCS_HOT;
if ( bPushed )
uState |= DFCS_PUSHED;
}
else
{
uState |= DFCS_FLAT;
}
if ( uState != m_uCloseState )
{
dc.DrawFrameControl(&rect, DFC_CAPTION, uState);
m_uCloseState = uState;
}
if ( bPushed )
m_uCloseState |= DFCS_PUSHED;
}
}
// Ensures client area is the correct size relative to window size,
// presearves client contents if possible when moving.
void CBalloonHelp::OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS FAR* lpncsp)
{
// nTIP_MARGIN pixel margin on all sides
::InflateRect(&lpncsp->rgrc[0], -nTIP_MARGIN,-nTIP_MARGIN);
// nTIP_TAIL pixel "tail" on side closest to anchor
switch ( GetBalloonQuadrant() )
{
case BQ_TOPRIGHT:
case BQ_TOPLEFT:
lpncsp->rgrc[0].top += nTIP_TAIL;
break;
case BQ_BOTTOMRIGHT:
case BQ_BOTTOMLEFT:
lpncsp->rgrc[0].bottom -= nTIP_TAIL;
break;
}
// sanity: ensure rect does not have negative size
if ( lpncsp->rgrc[0].right < lpncsp->rgrc[0].left )
lpncsp->rgrc[0].right = lpncsp->rgrc[0].left;
if ( lpncsp->rgrc[0].bottom < lpncsp->rgrc[0].top )
lpncsp->rgrc[0].bottom = lpncsp->rgrc[0].top;
if ( bCalcValidRects )
{
// determine if client position has changed relative to the window position
// if so, don if ( !::EqualRect(&lpncsp->rgrc[0], &lpncsp->rgrc[2]) )
{
::SetRectEmpty(&lpncsp->rgrc[2]);
}
}
}
// handle kill timer
void CBalloonHelp::OnTimer(UINT_PTR nIDEvent)
{
// really shouldn if ( nIDEvent == ID_TIMER_CLOSE )
{
KillTimer(m_unTimerClose);
HideBalloon();
}
}
// Called as the window is being destroyed. Completes destruction after removing keyboard hook.
void CBalloonHelp::OnDestroy()
{
// remove hooks
RemoveMouseHook();
RemoveKeyboardHook();
RemoveCallWndRetHook();
// destroy
CWnd::OnDestroy();
}
// close the balloon, performing any set transition effect.
void CBalloonHelp::OnClose()
{
HideBalloon();
}
// Called after window has been destroyed. Destroys the object if option is set.
void CBalloonHelp::PostNcDestroy()
{
CWnd::PostNcDestroy();
// free object if requested
// be careful with this one :D
if ( m_unOptions & unDELETE_THIS_ON_CLOSE )
delete this;
}
// Keyboard hook: used to implement the unCLOSE_ON_KEYPRESS option
LRESULT CBalloonHelp::KeyboardHookProc( int code, WPARAM wParam, LPARAM lParam)
{
// Skip if the key was released or if it // Bit 31: Specifies the transition state. The value is 0 if the key
// is being pressed and 1 if it is being released (see MSDN).
if ( code>=0 && !(lParam&0x80000000) && NULL != m_hWnd )
{
PostMessage(WM_CLOSE);
}
return 0;
// return ::CallNextHookEx(m_hKeyboardHook, code, wParam, lParam);
}
// Mouse hook: used to implement un-obtrusive mouse tracking
LRESULT CBalloonHelp::MouseHookProc(int code, WPARAM wParam, LPARAM lParam)
{
if (code>=0 && NULL != m_hWnd )
{
switch ( (UINT)wParam )
{
case WM_NCMOUSEMOVE:
case WM_MOUSEMOVE:
if ((m_unOptions & unCLOSE_ON_MOUSE_MOVE))
{
CPoint pt;
::GetCursorPos(&pt);
if ((abs(pt.x-m_ptMouseOrig.x) > m_nMouseMoveToleranceX || abs(pt.y-m_ptMouseOrig.y) > m_nMouseMoveToleranceY) ){
PostMessage(WM_CLOSE);
return 1;
}
}
break;
case WM_NCLBUTTONDOWN:
case WM_LBUTTONDOWN:
if ((m_unOptions & unCLOSE_ON_LBUTTON_DOWN)){
PostMessage(WM_CLOSE);
return 1;
}
break;
case WM_NCMBUTTONDOWN:
case WM_MBUTTONDOWN:
if ((m_unOptions & unCLOSE_ON_MBUTTON_DOWN)){
PostMessage(WM_CLOSE);
return 1;
}
break;
case WM_NCRBUTTONDOWN:
case WM_RBUTTONDOWN:
if ((m_unOptions& unCLOSE_ON_RBUTTON_DOWN)){
PostMessage(WM_CLOSE);
return 1;
}
break;
case WM_NCLBUTTONUP:
case WM_LBUTTONUP:
if ((m_unOptions & unCLOSE_ON_LBUTTON_UP)){
PostMessage(WM_CLOSE);
return 1;
}
break;
case WM_NCMBUTTONUP:
case WM_MBUTTONUP:
if ((m_unOptions & unCLOSE_ON_MBUTTON_UP)){
PostMessage(WM_CLOSE);
return 1;
}
break;
case WM_NCRBUTTONUP:
case WM_RBUTTONUP:
if ((m_unOptions & unCLOSE_ON_RBUTTON_UP)){
PostMessage(WM_CLOSE);
return 1;
}
break;
}
}
return 0;
}
// Window Return hook: used to implement window following
LRESULT CBalloonHelp::CallWndRetProc(int code, WPARAM wParam, LPARAM lParam)
{
if (code>=0 && NULL != m_hWnd )
{
CWPRETSTRUCT* pcwpr = (CWPRETSTRUCT*)lParam;
if ( WM_MOVE == pcwpr->message && pcwpr->hwnd == m_hwndAnchor )
PositionWindow();
}
return 0;
// return ::CallNextHookEx(m_hCallWndRetHook, code, wParam, lParam);
}
//---------------------------------------------------------------------------------
//CBalloonHelp::HookMan
// Singleton that broadcast hook events to all classes that currently exist.
//---------------------------------------------------------------------------------
CBalloonHelp::HookMan::HookMan()
{
m_hKeyboardHook = NULL;
m_hMouseHook = NULL;
m_hCallWndRetHook = NULL;
m_nCountKeyboard = 0;
m_nCountMouse = 0;
m_nCountCallWnd = 0;
}
CBalloonHelp::HookMan::~HookMan()
{
while(m_nCountKeyboard > 0){ RemoveKeyboardHook(); }
while(m_nCountMouse > 0){ RemoveMouseHook(); }
while(m_nCountCallWnd > 0){ RemoveCallWndRetHook(); }
m_hKeyboardHook = NULL;
m_hMouseHook = NULL;
m_hCallWndRetHook = NULL;
}
void CBalloonHelp::HookMan::AddClass(CBalloonHelp* pClass)
{
RemoveClass(pClass);
CSingleLock monCS(&m_cs);
//Newer balloons get message precedence over older balloons
m_lstClasses.push_front(pClass);
}
void CBalloonHelp::HookMan::RemoveClass(CBalloonHelp* pClass)
{
CSingleLock monCS(&m_cs);
std::list<CBalloonHelp*>::iterator itrEnd = m_lstClasses.end();
for (std::list<CBalloonHelp*>::iterator itr = m_lstClasses.begin(); itr != itrEnd; ){
if (*itr == pClass){ itr = m_lstClasses.erase(itr); }
else{ itr++; }
}
}
// Keyboard hook
void CBalloonHelp::HookMan::SetKeyboardHook()
{
CSingleLock monCS(&m_cs);
m_nCountKeyboard++;
if ( NULL==m_hKeyboardHook )
{
m_hKeyboardHook = ::SetWindowsHookEx(WH_KEYBOARD,KeyboardHookProcStatic, NULL, ::GetCurrentThreadId());
}
}
void CBalloonHelp::HookMan::RemoveKeyboardHook()
{
CSingleLock monCS(&m_cs);
m_nCountKeyboard--;
if (m_nCountKeyboard <= 0 && NULL != m_hKeyboardHook){
::UnhookWindowsHookEx(m_hKeyboardHook);
m_hKeyboardHook=NULL;
m_nCountKeyboard=0;
}
}
// Mouse hook
void CBalloonHelp::HookMan::SetMouseHook()
{
CSingleLock monCS(&m_cs);
m_nCountMouse++;
if ( NULL==m_hMouseHook )
{
m_hMouseHook = ::SetWindowsHookEx(WH_MOUSE,MouseHookProcStatic, NULL, ::GetCurrentThreadId());
}
}
void CBalloonHelp::HookMan::RemoveMouseHook()
{
CSingleLock monCS(&m_cs);
m_nCountMouse--;
if (m_nCountMouse <= 0 && NULL != m_hMouseHook){
::UnhookWindowsHookEx(m_hMouseHook);
m_hMouseHook=NULL;
m_nCountMouse=0;
}
}
// Call Window Return hook
void CBalloonHelp::HookMan::SetCallWndRetHook()
{
CSingleLock monCS(&m_cs);
m_nCountCallWnd++;
if ( NULL==m_hCallWndRetHook )
{
m_hCallWndRetHook = ::SetWindowsHookEx(WH_CALLWNDPROCRET, CallWndRetProcStatic, NULL, ::GetCurrentThreadId());
}
}
void CBalloonHelp::HookMan::RemoveCallWndRetHook()
{
CSingleLock monCS(&m_cs);
m_nCountCallWnd--;
if (m_nCountCallWnd <= 0 && NULL != m_hCallWndRetHook){
::UnhookWindowsHookEx(m_hCallWndRetHook);
m_hCallWndRetHook=NULL;
m_nCountCallWnd=0;
}
}
LRESULT CALLBACK CBalloonHelp::HookMan::KeyboardHookProcStatic( int code, WPARAM wParam, LPARAM lParam)
{
try{
CSingleLock monCS(&CBalloonHelp::s_hookMan.m_cs);
try{
std::list<CBalloonHelp*>::iterator itrEnd = CBalloonHelp::s_hookMan.m_lstClasses.end();
for (std::list<CBalloonHelp*>::iterator itr = CBalloonHelp::s_hookMan.m_lstClasses.begin(); itr != itrEnd; itr++){
try{ if (((CBalloonHelp*)(*itr))->KeyboardHookProc(code, wParam, lParam)){ break; } }catch(...){}
}
}catch(...){}
if (NULL != CBalloonHelp::s_hookMan.m_hKeyboardHook){ return ::CallNextHookEx(CBalloonHelp::s_hookMan.m_hKeyboardHook, code, wParam, lParam); }
}catch(...){}
return 0;
}
LRESULT CALLBACK CBalloonHelp::HookMan::MouseHookProcStatic(int code, WPARAM wParam, LPARAM lParam)
{
try{
CSingleLock monCS(&CBalloonHelp::s_hookMan.m_cs);
try{
std::list<CBalloonHelp*>::iterator itrEnd = CBalloonHelp::s_hookMan.m_lstClasses.end();
for (std::list<CBalloonHelp*>::iterator itr = CBalloonHelp::s_hookMan.m_lstClasses.begin(); itr != itrEnd; itr++){
try{ if (((CBalloonHelp*)(*itr))->MouseHookProc(code, wParam, lParam)){ break; } }catch(...){}
}
}catch(...){}
if (NULL != CBalloonHelp::s_hookMan.m_hMouseHook){ return ::CallNextHookEx(CBalloonHelp::s_hookMan.m_hMouseHook, code, wParam, lParam); }
}catch(...){}
return 0;
}
LRESULT CALLBACK CBalloonHelp::HookMan::CallWndRetProcStatic(int code, WPARAM wParam, LPARAM lParam)
{
try{
CSingleLock monCS(&CBalloonHelp::s_hookMan.m_cs);
try{
std::list<CBalloonHelp*>::iterator itrEnd = CBalloonHelp::s_hookMan.m_lstClasses.end();
for (std::list<CBalloonHelp*>::iterator itr = CBalloonHelp::s_hookMan.m_lstClasses.begin(); itr != itrEnd; itr++){
try{ if (((CBalloonHelp*)(*itr))->CallWndRetProc(code, wParam, lParam)){ break; } }catch(...){}
}
}catch(...){}
if (NULL != CBalloonHelp::s_hookMan.m_hCallWndRetHook){ return ::CallNextHookEx(CBalloonHelp::s_hookMan.m_hCallWndRetHook, code, wParam, lParam); }
}catch(...){}
return 0;
}
CBalloonHelp::HookMan CBalloonHelp::s_hookMan;
End.
|
|
|
|

|
How is the Win32 version coming along? This will allow everyone to cut back on those annoying popups and not just the MFC world.
Thanks!
|
|
|
|

|
I get the following error on VS 2005:
BalloonHelp.cpp
c:\documents and settings\whitewolf\escritorio\balloontest demo\balloonhelp.cpp(205) : error C3867: 'CBalloonHelp::KeyboardHookProc': function call missing argument list; use '&CBalloonHelp::KeyboardHookProc' to create a pointer to member
c:\documents and settings\whitewolf\escritorio\balloontest demo\balloonhelp.cpp(206) : error C3867: 'CBalloonHelp::MouseHookProc': function call missing argument list; use '&CBalloonHelp::MouseHookProc' to create a pointer to member
c:\documents and settings\whitewolf\escritorio\balloontest demo\balloonhelp.cpp(207) : error C3867: 'CBalloonHelp::CallWndRetProc': function call missing argument list; use '&CBalloonHelp::CallWndRetProc' to create a pointer to member
c:\documents and settings\whitewolf\escritorio\balloontest demo\balloonhelp.cpp(1085) : error C2440: 'static_cast' : cannot convert from 'UINT (__thiscall CBalloonHelp::* )(CPoint)' to 'LRESULT (__thiscall CWnd::* )(CPoint)'
Cast from base to derived requires dynamic_cast or static_cast
The balloons are very usefull but, how can I fix this problem?
|
|
|
|

|
1. Replace
BHKeybHookThunk::InitThunk((TMFP)KeyboardHookProc, this);
BHMouseHookThunk::InitThunk((TMFP)MouseHookProc, this);
BHCallWndRetHookThunk::InitThunk((TMFP)CallWndRetProc, this);
with
BHKeybHookThunk::InitThunk((TMFP)&CBalloonHelp::KeyboardHookProc, this);
BHMouseHookThunk::InitThunk((TMFP)&CBalloonHelp::MouseHookProc, this);
BHCallWndRetHookThunk::InitThunk((TMFP)&CBalloonHelp::CallWndRetProc, this);
2. Replace
UINT CBalloonHelp::OnNcHitTest(CPoint)
with
LRESULT CBalloonHelp::OnNcHitTest(CPoint)
3. Replace
afx_msg UINT OnNcHitTest(CPoint point);
with
afx_msg LRESULT OnNcHitTest(CPoint point);
|
|
|
|

|
I have made this changes, now it compiles but it is still not working
it throws the following error on runtime when I call create
Debug Assertion Failed
...
m_mov = 0xB9
...
Thanks
|
|
|
|

|
I've used your class heavily in the past, but I'm now experiencing severe errors running under Windows 2003 SP1. Even your BalloonTest.exe app blows up the minute it is run under Windows 2003.
Is there ever going to be an update for the latest OS?
Thanks for a great class and I hope it's fixed soon.
|
|
|
|

|
I have faced the same problem. Obviously the problem is trick with hooking by using _ThunkImpl class. So I have changed code to use simple hooking along with list of recipients, and it seems to work.
Igor Green
http://www.grigsoft.com/ - files and folders comparison tools
|
|
|
|

|
The problem is that CBalloonHelp assembles code into a data section. And by default, XP SP2 and Windows 2003 SP1 don't allow execution of code in data sections. The class needs to be changed to add a line like this:
::VirtualProtect(this, sizeof(*this), PAGE_EXECUTE_READWRITE, &oldProt);
before the call to FlushInstructionCache()
More details left to the reader. I think you'd want to UNDO this change of page protection when the object is destroyed. (And by the way, calling VirtualProtect this way allows execution of ANY data as code for any data that happens to fall into the page containing this object. Is that a good idea? I don't know. Perhaps a new approach is needed.
doctorcja
|
|
|
|

|
This will also fail if you switch on Hardware Execute Prevention which is more common on machines these days. The easiest solution is to remove the thunk code and switch calls backs to
static CALLBACK LRESULT.....
and create a static private variable in CBalloonHelp which points to itself in order to give you access to CBalloonHelp elements and variables. It works and does not require to ::VirtualProtect(this, sizeof(*this), PAGE_EXECUTE_READWRITE, &oldProt); problem sorted - no risk that I can see
|
|
|
|

|
If you could post a bit more detail on how to do this so lesser beings such as myself could implement the change it would be very much appreciated.
Thanks.
Adrian
|
|
|
|
 |
|
|
General News Suggestion Question Bug Answer Joke Rant Admin
|
Although sometimes useful, message boxes used to display information are often just annoying. This article describes a non-modal replacement.
| Type | Article |
| Licence | CPOL |
| First Posted | 9 Dec 2001 |
| Views | 530,476 |
| Downloads | 10,166 |
| Bookmarked | 219 times |
|
|