📄 scrollerctrl.cpp
字号:
#include "stdafx.h"
#include "ScrollerCtrl.h"
// command messages:
// sent when text has scrolled completely off the window
const int CScrollerCtrl::SC_SCROLL_COMPLETE = 0;
// defaults
const int CScrollerCtrl::nSCROLL_DELAY = 80; // time between each frame (milliseconds)
const int CScrollerCtrl::nSCROLL_PAUSE = 5000; // time to pause before autoscrolling (milliseconds)
const int CScrollerCtrl::nMARGIN = 5; // (pixels)
const int CScrollerCtrl::nFONT_SIZE = 12; // (points)
const int CScrollerCtrl::nFONT_WEIGHT = FW_SEMIBOLD;
const char* CScrollerCtrl::szFONT_NAME = "Comic Sans MS";
// initialization
CScrollerCtrl::CScrollerCtrl()
{
m_crBackground = RGB(0,0,0);
m_crForeground = RGB(255,255,255);
m_pbmpPattern = NULL;
m_pbmpLogo = NULL;
m_nContentHeight = 0;
m_nScrollOffset = 0;
m_unTimerPause = 2;
m_nScrollDelay = nSCROLL_DELAY;
m_nScrollPause = nSCROLL_PAUSE;
m_eState = PAUSED;
m_bTilePattern = TRUE;
m_bShowScroll = FALSE;
m_bWrap = TRUE;
m_sizeBuffer = CSize(0,0);
}
// create the window:
// remove WS_VSCROLL to avoid showing scrollbar.
// remove WS_TABSTOP to disable keyboard scrolling.
BOOL CScrollerCtrl::Create(const RECT& rect, CWnd* pParentWnd, UINT uStyle, UINT nID)
{
if ( NULL == m_font.GetSafeHandle() )
SetFont(szFONT_NAME, nFONT_SIZE, nFONT_WEIGHT);
// remember if user specified the style, but don't show initially
m_bShowScroll = uStyle&WS_VSCROLL;
uStyle &= ~WS_VSCROLL;
if ( CWnd::Create(::AfxRegisterWndClass(CS_HREDRAW|CS_PARENTDC|CS_VREDRAW,::LoadCursor(NULL,IDC_ARROW)), "Scroller", uStyle, rect, pParentWnd, nID) )
{
m_eState = PAUSED;
SetTimer(1, m_nScrollDelay, NULL);
m_unTimerPause = SetTimer(m_unTimerPause, m_nScrollPause, NULL);
return TRUE;
}
return FALSE;
}
// activate/deactivate wrapping mode:
// if not set, content is scrolled completely off screen
// before being repeated.
void CScrollerCtrl::SetWrapping(BOOL bWrap)
{
m_bWrap = bWrap;
if ( NULL != m_hWnd )
Invalidate(FALSE);
}
// Sets the color used for the background (if no pattern is set)
// or margins (if pattern is set and not tiled)
void CScrollerCtrl::SetBgColor(COLORREF clrBg)
{
m_crBackground = clrBg;
if ( NULL != m_hWnd )
Invalidate(FALSE);
}
// Sets the color used for text
void CScrollerCtrl::SetFgColor(COLORREF clrBg)
{
m_crForeground = clrBg;
if ( NULL != m_hWnd )
Invalidate(FALSE);
}
// Sets the font; size is in points, see LOGFONT documentation for weight constants
void CScrollerCtrl::SetFont(const CString& strName, int nSize, int nWeight)
{
if ( NULL != m_font.GetSafeHandle() )
m_font.DeleteObject();
LOGFONT logFont;
memset(&logFont,0,sizeof(logFont));
strncpy(logFont.lfFaceName,strName,LF_FACESIZE);
logFont.lfPitchAndFamily = FF_SWISS;
logFont.lfQuality = ANTIALIASED_QUALITY;
logFont.lfWeight = nWeight;
logFont.lfHeight = nSize*10;
// actually create the font; if for some reason this fails, use a default
if ( !m_font.CreatePointFontIndirect(&logFont) )
m_font.CreateStockObject(SYSTEM_FONT);
}
// Sets the text to be displayed
void CScrollerCtrl::SetText(const CString& strText)
{
m_strText = strText;
if ( NULL != m_hWnd )
{
// force RecalcLayout()
m_sizeBuffer = CSize(0,0);
Invalidate(FALSE);
}
}
// Sets the bitmap to be displayed above the text
CBitmap* CScrollerCtrl::SetLogo(CBitmap* pbmpLogo)
{
CBitmap* pbmpOld = m_pbmpLogo;
m_pbmpLogo = pbmpLogo;
if ( NULL != m_hWnd )
{
// force RecalcLayout()
m_sizeBuffer = CSize(0,0);
Invalidate(FALSE);
}
return pbmpOld;
}
// Sets the background pattern
CBitmap* CScrollerCtrl::SetPattern(CBitmap* pbmpPattern, BOOL bTile)
{
m_bTilePattern = bTile;
CBitmap* pbmpOld = m_pbmpPattern;
m_pbmpPattern = pbmpPattern;
if ( NULL != m_hWnd )
Invalidate(FALSE);
return pbmpOld;
}
// Sets the time (in milliseconds) between frames (autoscrolling speed)
// (will revert to default if less than 0)
void CScrollerCtrl::SetScrollDelay(int nScrollDelay)
{
m_nScrollDelay = nScrollDelay;
if ( m_nScrollDelay < 0 )
m_nScrollDelay = nSCROLL_DELAY;
if ( NULL != m_hWnd )
SetTimer(1, m_nScrollDelay, NULL);
}
// Sets the delay (in milliseconds) when autoscrolling pauses
// (will disable pausing if set less than scroll delay)
void CScrollerCtrl::SetScrollPause(int nScrollPause)
{
m_nScrollPause = nScrollPause;
}
BEGIN_MESSAGE_MAP(CScrollerCtrl, CWnd)
ON_WM_ERASEBKGND()
ON_WM_GETDLGCODE()
ON_WM_KEYDOWN()
ON_WM_MOUSEWHEEL()
ON_WM_PAINT()
ON_WM_TIMER()
ON_WM_VSCROLL()
ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()
// CScrollerCtrl message handlers
// entire window is updated in OnPaint() so do nothing here.
BOOL CScrollerCtrl::OnEraseBkgnd(CDC* pDC)
{
return FALSE;
}
// resize buffer first if necessary, then compose onto buffer,
// wrapping if specified, finally update the screen.
void CScrollerCtrl::OnPaint()
{
CPaintDC dc(this);
CDC dcBackBuffer;
dcBackBuffer.CreateCompatibleDC(&dc);
// resize buffer if neccessary, calculate content size.
RecalcLayout(&dc);
CBitmap* pOldBmp = dcBackBuffer.SelectObject(&m_bmpBackBuffer);
FillBackground(&dcBackBuffer);
int nOffset = nMARGIN + m_nScrollOffset;
do
{
nOffset += DrawLogo(&dcBackBuffer, nOffset) + nMARGIN;
nOffset += DrawBodyText(&dcBackBuffer, nOffset) + nMARGIN*2;
} while ( nOffset < m_sizeBuffer.cy && m_bWrap );
dc.BitBlt(0,0,m_sizeBuffer.cx,m_sizeBuffer.cy,&dcBackBuffer,0,0,SRCCOPY);
// cleanup
dcBackBuffer.SelectObject(pOldBmp);
}
// if buffer size does not match window size, resize buffer.
// Calculate content size.
void CScrollerCtrl::RecalcLayout(CDC* pDC)
{
CRect rectClient;
GetClientRect(&rectClient);
if ( m_sizeBuffer != rectClient.Size() )
{
m_sizeBuffer = rectClient.Size();
if ( m_bmpBackBuffer.GetSafeHandle() != NULL )
m_bmpBackBuffer.DeleteObject();
m_bmpBackBuffer.CreateCompatibleBitmap(pDC, m_sizeBuffer.cx, m_sizeBuffer.cy);
m_nContentHeight = nMARGIN*3;
m_nContentHeight += DrawLogo(pDC, 0, FALSE);
m_nContentHeight += DrawBodyText(pDC, 0, FALSE);
}
}
// Draw the background; uses background color unless a pattern
// bitmap is set, in which case that will be tiled or centered.
void CScrollerCtrl::FillBackground(CDC* pDC)
{
CRect rectClient;
GetClientRect(&rectClient);
if ( NULL == m_pbmpPattern )
{
pDC->FillSolidRect(rectClient, m_crBackground);
}
else
{
CDC dcPat;
dcPat.CreateCompatibleDC(pDC);
CBitmap* pbmpOld = dcPat.SelectObject(m_pbmpPattern);
BITMAP bitmap;
if ( m_pbmpPattern->GetBitmap(&bitmap) && bitmap.bmWidth > 0 && bitmap.bmHeight > 0 )
{
if ( m_bTilePattern )
{
for (int y=0; y<rectClient.bottom+bitmap.bmHeight; y += bitmap.bmHeight)
{
for (int x=0; x<rectClient.right+bitmap.bmWidth; x += bitmap.bmWidth)
{
pDC->BitBlt(x,y, bitmap.bmWidth, bitmap.bmHeight, &dcPat, 0,0, SRCCOPY);
}
}
}
else
{
pDC->FillSolidRect(rectClient, m_crBackground);
pDC->BitBlt((m_sizeBuffer.cx-bitmap.bmWidth)/2,(m_sizeBuffer.cy-bitmap.bmHeight)/2, bitmap.bmWidth, bitmap.bmHeight, &dcPat, 0,0, SRCCOPY);
}
}
dcPat.SelectObject(pbmpOld);
}
}
// Draws the logo (if specified) at the given offset.
// If bDraw is false, calculates height, but does not draw.
// Returns height of logo.
int CScrollerCtrl::DrawLogo(CDC* pDC, int nOffset, BOOL bDraw)
{
if ( NULL == m_pbmpLogo )
return 0;
BITMAP bitmap;
memset(&bitmap,0,sizeof(bitmap));
if ( m_pbmpLogo->GetBitmap(&bitmap) && bDraw && bitmap.bmWidth > 0 && bitmap.bmHeight > 0 )
{
CDC dcLogo;
dcLogo.CreateCompatibleDC(pDC);
CBitmap* pbmpOld = dcLogo.SelectObject(m_pbmpLogo);
pDC->BitBlt((m_sizeBuffer.cx-bitmap.bmWidth)/2,nOffset, bitmap.bmWidth, bitmap.bmHeight, &dcLogo, 0,0, SRCCOPY);
dcLogo.SelectObject(pbmpOld);
}
return bitmap.bmHeight;
}
// Draws the text at the specified offset.
// If bDraw is false, will calculate text height, but not draw.
// Returns height of text.
int CScrollerCtrl::DrawBodyText(CDC* pDC, int nOffset, BOOL bDraw)
{
CRect rect(nMARGIN,nOffset,m_sizeBuffer.cx-nMARGIN,m_sizeBuffer.cy);
UINT uFlags = bDraw ? DT_EXPANDTABS|DT_NOPREFIX|DT_WORDBREAK : DT_EXPANDTABS|DT_NOCLIP|DT_CALCRECT|DT_NOPREFIX|DT_WORDBREAK;
CFont* pOldFont = pDC->SelectObject(&m_font);
pDC->SetBkMode(TRANSPARENT);
// draw shadow if displayed over pattern
if ( bDraw && NULL != m_pbmpPattern )
{
// offset 1/10 of font size
LOGFONT logFont;
m_font.GetLogFont(&logFont);
int nShadowOffset = MulDiv(-logFont.lfHeight, 72, pDC->GetDeviceCaps(LOGPIXELSY)*10);
// get color between forground and background for shadow (not correct i'm sure) int red = (GetRValue(m_crForeground) + GetRValue(m_crBackground)*2)/3; int green = (GetGValue(m_crForeground) + GetGValue(m_crBackground)*2)/3; int blue = (GetBValue(m_crForeground) + GetBValue(m_crBackground)*2)/3; COLORREF crDarker = RGB(red, green, blue);
pDC->SetTextColor(crDarker);
rect.OffsetRect(nShadowOffset,nShadowOffset);
pDC->DrawText(m_strText, &rect, uFlags);
rect.OffsetRect(-nShadowOffset,-nShadowOffset);
}
if(m_eState == PAUSED)
pDC->SetTextColor(m_crForeground);
else
{
pDC->SetTextColor(RGB((int)160,0,0));
}
int nHeight = pDC->DrawText(m_strText, &rect, uFlags);
pDC->SelectObject(pOldFont);
return nHeight;
}
// indicate that this control will process arrow keystrokes.
UINT CScrollerCtrl::OnGetDlgCode()
{
if ( GetStyle()&WS_TABSTOP )
return DLGC_WANTARROWS;
return 0;
}
// Grab focus if required.
void CScrollerCtrl::OnLButtonDown(UINT nFlags, CPoint point)
{
if ( GetStyle()&WS_TABSTOP )
SetFocus();
}
// Handle keyboard scrolling.
void CScrollerCtrl::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
switch ( nChar )
{
case VK_UP:
PostMessage(WM_VSCROLL, SB_LINEUP);
break;
case VK_DOWN:
PostMessage(WM_VSCROLL, SB_LINEDOWN);
break;
case VK_PRIOR: // why not VK_PAGEUP?
PostMessage(WM_VSCROLL, SB_PAGEUP);
break;
case VK_NEXT: // why not VK_PAGEDOWN?
PostMessage(WM_VSCROLL, SB_PAGEDOWN);
break;
}
}
// Handle scrolling.
// This can be triggered by mouse wheel and keyboard,
// as well as the scrollbar.
void CScrollerCtrl::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
if ( !(GetStyle()&WS_TABSTOP) )
return;
// delay autoscroll after manual scroll
if ( m_nScrollPause > m_nScrollDelay )
{
m_eState = PAUSED;
m_unTimerPause = SetTimer(m_unTimerPause, m_nScrollPause, NULL);
}
switch (nSBCode)
{
case SB_BOTTOM: // Scroll to bottom.
m_nScrollOffset = m_sizeBuffer.cy-m_nContentHeight;
break;
case SB_TOP: // Scroll to top.
m_nScrollOffset = 0;
break;
case SB_LINEDOWN: // Scroll one line down.
m_nScrollOffset -= 8;
break;
case SB_LINEUP: // Scroll one line up.
m_nScrollOffset += 8;
break;
case SB_PAGEDOWN: // Scroll one page down.
m_nScrollOffset -= m_sizeBuffer.cy;
break;
case SB_PAGEUP: // Scroll one page up.
m_nScrollOffset += m_sizeBuffer.cy;
break;
case SB_THUMBPOSITION:
case SB_THUMBTRACK:
m_nScrollOffset = -((int)nPos);
break;
default:
return;
}
// constrain
if ( m_nScrollOffset < m_sizeBuffer.cy-m_nContentHeight )
m_nScrollOffset = m_sizeBuffer.cy-m_nContentHeight;
else if ( m_nScrollOffset > 0 )
m_nScrollOffset = 0;
// scroll
SetScrollPos(SB_VERT, -m_nScrollOffset);
Invalidate(FALSE);
}
// Handle mouse wheel scrolling.
// I'll put actual effort into handling non-WHEEL_DELTA increments
// when i actually get a mouse that sends them. ;~)
BOOL CScrollerCtrl::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
int nDist = abs(zDelta)/WHEEL_DELTA+1;
while ( nDist-- )
{
PostMessage(WM_VSCROLL, zDelta > 0 ? SB_LINEUP : SB_LINEDOWN);
}
return TRUE;
}
// Timers are used for three purposes:
// + Periodic checking to show/hide the scrollbar
// + Automatic scrolling (when m_eState == SCROLLING)
// + Ending automatic scrolling pauses (when m_eState == PAUSED)
// This is accomplished via two timers:
// The first does double duty, handling both the scrollbar and
// automatic scrolling; it is active for the life of the window.
// The second is used for ending autoscroll pauses.
void CScrollerCtrl::OnTimer(UINT nIDEvent)
{
// don't autoscroll or hide scrollbar when user is dragging the scroll button.
if ( this == GetCapture() )
return;
// the scrollbar can be shown if the following are true:
// + The content size is larger than the window
// + The mouse cursor is over the window
// + The top-level parent containing this control is active
if ( m_bShowScroll )
{
CRect rectWindow;
GetWindowRect(&rectWindow);
CPoint pntCursor;
::GetCursorPos(&pntCursor);
if ( m_sizeBuffer.cy < m_nContentHeight && GetTopLevelParent() == GetActiveWindow() && rectWindow.PtInRect(pntCursor) )
{
SetScrollRange(SB_VERT, 0, m_nContentHeight-m_sizeBuffer.cy, FALSE);
SetScrollPos(SB_VERT, -m_nScrollOffset, TRUE);
ShowScrollBar(SB_VERT, TRUE);
}
else if ( this != GetCapture() )
{
ShowScrollBar(SB_VERT, FALSE);
}
}
if ( nIDEvent == m_unTimerPause )
{
m_eState = SCROLLING;
KillTimer(m_unTimerPause);
}
if ( m_eState == SCROLLING )
{
--m_nScrollOffset;
if ( m_nContentHeight+m_nScrollOffset < 0 )
{
// scrolling is complete; restart
m_nScrollOffset = m_bWrap ? 0 : m_sizeBuffer.cy;
CWnd* pwndParent = GetParent();
if ( NULL != pwndParent )
pwndParent->SendMessage(WM_COMMAND, MAKELPARAM(GetDlgCtrlID(), SC_SCROLL_COMPLETE), (LPARAM)GetSafeHwnd());
}
// pause at top and bottom
if ( 0 == m_nScrollOffset || (m_nScrollOffset+m_nContentHeight == m_sizeBuffer.cy && m_sizeBuffer.cy < m_nContentHeight) )
{
if ( m_nScrollPause > m_nScrollDelay )
{
m_eState = PAUSED;
m_unTimerPause = SetTimer(m_unTimerPause, m_nScrollPause, NULL);
}
}
Invalidate(FALSE);
}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -