📄 balloonhelp.cpp
字号:
{
m_nMouseMoveTolerance = 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_RBUTTON_UP | closes window on WM_RBUTTON_UP
// : 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. (!!! probably not thread safe !!!)
// : 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_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()
// 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,
HWND pParentWnd, //=NULL
const CString strURL, //= ""
unsigned int unTimeout, //= 0
HICON hIcon) //= NULL
{
m_strContent = strContent;
m_ptAnchor = ptAnchor;
m_unOptions = unOptions;
m_strURL = strURL;
m_unTimeout = unTimeout;
if ( NULL != hIcon )
SetIcon(hIcon);
if (NULL == pParentWnd)
return FALSE;
// if no fonts set, use defaults
if ( NULL == m_pContentFont )
{
m_pContentFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
if (!m_pContentFont)
return FALSE;
}
// title font defaults to bold version of content font
if ( NULL == m_pTitleFont )
{
LOGFONT LogFont;
GetObject(m_pContentFont, sizeof(LogFont), &LogFont);
LogFont.lfWeight = FW_BOLD;
if (!(m_pTitleFont = (HFONT)CreateFontIndirect(&LogFont)))
return FALSE;
}
// check system settings: if fade effects are disabled, disable here too
BOOL bFade = FALSE;
::SystemParametersInfo(SPI_GETTOOLTIPANIMATION, 0, &bFade, 0);
if (bFade)
::SystemParametersInfo(SPI_GETTOOLTIPFADE, 0, &bFade, 0);
if (!bFade || NULL == m_fnSetLayeredWindowAttributes)
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't switch to this using alt+tab
DWORD dwExStyle = WS_EX_TOOLWINDOW;
if(!((m_unOptions & unDISABLE_FADE) == unDISABLE_FADE))
dwExStyle |= WS_EX_LAYERED;
if ( m_unOptions&unSHOW_TOPMOST ) // make topmost, if requested
dwExStyle |= WS_EX_TOPMOST;
if (!__super::Create(pParentWnd, CRect(0, 0, 10, 10), strTitle, WS_POPUP, dwExStyle))
return FALSE;
PositionWindow();
// show, possibly fading
if(!(m_unOptions & unHIDE_AFTER_CREATED))
ShowBalloon();
if ( m_unOptions & unCLOSE_ON_MOUSE_MOVE )
{
::GetCursorPos(&m_ptMouseOrig);
ScreenToClient(&m_ptMouseOrig);
SetTimer(ID_TIMER_HOTTRACK, 100, NULL);
}
if ( (m_unOptions & unCLOSE_ON_LBUTTON_UP) || (m_unOptions & unCLOSE_ON_RBUTTON_UP) )
SetCapture();
if ( m_unOptions&unCLOSE_ON_KEYPRESS )
SetKeyboardHook();
// show, but don't take focus away from parent (or whatever)
// ShowWindow(SW_SHOWNOACTIVATE);
return TRUE;
}
// Wrapper for possibly-existing API call
BOOL CBalloonHelp::SetLayeredWindowAttributes(COLORREF crKey, int nAlpha, DWORD dwFlags)
{
if ( NULL != m_fnSetLayeredWindowAttributes )
return (*m_fnSetLayeredWindowAttributes)(m_hWnd, crKey, (BYTE)nAlpha, dwFlags);
return FALSE;
}
// calculates the area of the screen the balloon falls into
// this determins which direction the tail points
CBalloonHelp::BALLOON_QUADRANT CBalloonHelp::GetBalloonQuadrant()
{
CRect rectDesktop;
::GetWindowRect(::GetDesktopWindow(), &rectDesktop);
if ( m_ptAnchor.y < rectDesktop.Height()/2 )
{
if ( m_ptAnchor.x < rectDesktop.Width()/2 )
{
return BQ_TOPLEFT;
}
else
{
return BQ_TOPRIGHT;
}
}
else
{
if ( m_ptAnchor.x < rectDesktop.Width()/2 )
{
return BQ_BOTTOMLEFT;
}
else
{
return BQ_BOTTOMRIGHT;
}
}
// unreachable
}
// Calculate the dimensions and draw the balloon header
CSize CBalloonHelp::DrawHeader(HDC 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't work
// calc & draw icon
if ( NULL != m_ilIcon )
{
int x = 0;
int y = 0;
ImageList_GetIconSize(m_ilIcon, &x, &y);
sizeHdr.cx += x;
sizeHdr.cy = max(sizeHdr.cy, y);
ImageList_SetBkColor(m_ilIcon, m_crBackground);
if (bDraw)
ImageList_Draw(m_ilIcon, 0, pDC, 0, 0, ILD_NORMAL);//ILD_TRANSPARENT);
rectClient.left += x;
}
// make some space between left icon and title
rectClient.left += 5;
// calc & draw close button
if ( m_unOptions & unSHOW_CLOSE_BUTTON )
{
// if something is already in the header (icon) leave space
if ( sizeHdr.cx > 0 )
sizeHdr.cx += nTIP_MARGIN;
sizeHdr.cx += 16;
sizeHdr.cy = max(sizeHdr.cy, 16);
if (bDraw)
DrawFrameControl(pDC, CRect(rectClient.right-16,0,rectClient.right,16), DFC_CAPTION, DFCS_CAPTIONCLOSE|DFCS_FLAT);
rectClient.right -= 16;
}
// calc title size
CString strTitle;
GetWindowText(strTitle);
if ( !strTitle.IsEmpty() )
{
HFONT pOldFont = (HFONT)SelectObject(pDC, 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);
DrawText(pDC, strTitle, strTitle.GetLength(), &rectTitle, DT_CALCRECT | DT_NOPREFIX | DT_EXPANDTABS | DT_SINGLELINE);
sizeHdr.cx += rectTitle.Width();
sizeHdr.cy = max(sizeHdr.cy, rectTitle.Height());
//slightly adjust the y position of title to center it vertically with the left icon
rectClient.top = (sizeHdr.cy - rectTitle.Height()) /2;
// draw title
if ( bDraw )
{
SetBkMode(pDC, TRANSPARENT);
SetTextColor(pDC, m_crForeground);
DrawText(pDC, strTitle, strTitle.GetLength(), &rectClient, DT_NOPREFIX | DT_EXPANDTABS | DT_SINGLELINE);
}
// cleanup
SelectObject(pDC, pOldFont);
}
return sizeHdr;
}
// Calculate the dimensions and draw the balloon contents
CSize CBalloonHelp::DrawContent(HDC pDC, int nTop, bool bDraw)
{
CRect rectContent;
::GetClientRect(::GetDesktopWindow(), &rectContent);
rectContent.top = nTop;
// limit to half screen width
rectContent.right -= rectContent.Width()/2;
// calc size
HFONT pOldFont = (HFONT)SelectObject(pDC, m_pContentFont);
if ( !m_strContent.IsEmpty() )
DrawText(pDC, m_strContent, m_strContent.GetLength(), &rectContent, DT_CALCRECT | DT_LEFT | DT_NOPREFIX | DT_EXPANDTABS | DT_WORDBREAK);
else
rectContent.SetRectEmpty(); // don't want to leave half the screen for empty strings ;)
// draw
if (bDraw)
{
SetBkMode(pDC, TRANSPARENT);
SetTextColor(pDC, m_crForeground);
DrawText(pDC, m_strContent, m_strContent.GetLength(), &rectContent, DT_LEFT | DT_NOPREFIX | DT_WORDBREAK | DT_EXPANDTABS);
}
// cleanup
SelectObject(pDC, pOldFont);
return rectContent.Size();
}
// calculates the client size necessary based on title and content
CSize CBalloonHelp::CalcClientSize()
{
_ASSERTE(m_hWnd);
HDC hdc = GetDC();
CSize sizeHeader = CalcHeaderSize(hdc);
CSize sizeContent = CalcContentSize(hdc);
ReleaseDC(hdc);
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's anchor point, and moves the window accordingly. The region is also
// created and set here.
void CBalloonHelp::PositionWindow(bool forceRedraw/*=false*/)
{
CSize sizeWnd = CalcWindowSize();
CPoint ptTail[3];
CPoint ptTopLeft(0, 0);
CPoint ptBottomRight(sizeWnd.cx, sizeWnd.cy);
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 ptOffs(m_ptAnchor.x - ptTail[1].x, m_ptAnchor.y - ptTail[1].y);
// adjust position so all is visible
CRect rectScreen;
::GetWindowRect(::GetDesktopWindow(), &rectScreen);
int nAdjustX = 0;
int nAdjustY = 0;
if ( ptOffs.x < 0 )
nAdjustX = -ptOffs.x;
else if ( ptOffs.x + sizeWnd.cx >= rectScreen.right )
nAdjustX = rectScreen.right - (ptOffs.x + sizeWnd.cx);
if ( ptOffs.y < 0 )
nAdjustY = -ptOffs.y;
else if ( ptOffs.y + sizeWnd.cy >= rectScreen.bottom )
nAdjustY = rectScreen.bottom - (ptOffs.y + sizeWnd.cy);
// 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
BOOL bRegionChanged = TRUE;
HRGN region; //the tail of balloon
HRGN regionRound; //the body of balloon
HRGN regionComplete;//the total part
region = CreatePolygonRgn(&ptTail[0], 3, ALTERNATE);
regionRound = CreateRoundRectRgn(ptTopLeft.x,ptTopLeft.y,ptBottomRight.x,ptBottomRight.y,nTIP_MARGIN*2,nTIP_MARGIN*2);
regionComplete = CreateRectRgn(0,0,1,1);
CombineRgn(regionComplete, region, regionRound, RGN_OR);
if ( NULL == m_rgnComplete )
m_rgnComplete = CreateRectRgn(0, 0, 1, 1);
else if ( EqualRgn(m_rgnComplete, regionComplete) )
bRegionChanged = FALSE;
::CombineRgn(m_rgnComplete, regionComplete, NULL, RGN_COPY);
SetWindowRgn(regionComplete, TRUE);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -