CScrollerCtrl test app, running under Windows XP.
Contents
Well... I was bored the other day, and not wanting to do anything
necessary, decided to write a little autoscroller. It turned out quite nicely,
so I'm posting it here in the off chance it might be useful to someone else.
An autoscroller is a control that displays text, scrolling it
automatically at a pre-determined speed. They are generally used for displaying
credits in about dialogs, or other such trivial tasks; by design they are not
friendly enough to be used for displaying important things, since users will be
frustrated if they read faster or slower than the text is scrolled. My scroller
has an optional scrollbar though, just to make it easier for such users should
it ever be used to display text they will actually want to read.
- Smooth scrolling, with configurable speed.
- Optional manual scrolling, via scrollbar, mouse wheel, or keyboard.
- Configurable delay before and after scrolling is complete and after manual
scrolling.
- Optional bitmap logo header: useful for displaying company logo on about
dialog type things.
- Optional bitmap background pattern, either tiled or centered.
- Configurable font, configurable background and text colors.
The public API for CScrollerCtrl
consists of the following
methods:
BOOL Create(const RECT& rect, CWnd* pParentWnd,
UINT uStyle = WS_CHILD|WS_VISIBLE|WS_VSCROLL|
WS_TABSTOP|WS_GROUP, UINT nID = 0);
void SetWrapping(BOOL bWrap);
void SetBgColor(COLORREF clrBg);
void SetFgColor(COLORREF clrBg);
void SetFont(const CString& strName, int nSize, int nWeight);
void SetText(const CString& strText);
CBitmap* SetLogo(CBitmap* pbmpLogo);
CBitmap* SetPattern(CBitmap* pbmpPattern, BOOL bTile);
void SetScrollDelay(int nScrollDelay);
void SetScrollPause(int nScrollPause);
Calling Create()
gives the control its initial size and
position, as well as the styles in effect. To disable the scroll bar, remove the
WS_VSCROLL
style; to disable all manual scrolling, remove both
WS_VSCROLL
and WS_TABSTOP
.
All other methods can be called either before or after the control is
created; if the control has been created, they will take effect immediately,
otherwise they will be in effect when Create()
is called. All
attributes have defaults, so you only need to set the ones you need. Most are
self-explanatory, but a couple of things should be known:
- If a background pattern is set, text will be drawn with a shadow to aid in
visibility. The shadow color is calculated from the foreground and background
colors. The shadow offset is calculated at 1/10th the font size.
SetPattern()
takes a boolean flag as its second parameter; if
false, it will center the bitmap instead of tiling it.
SetWrapping()
changes the wrapping mode. This is on by default,
meaning that as the end of the text (or logo if no text) scrolls up the screen,
it is immediately followed by the beginning of the logo (or text if no logo
set). If SetWrapping()
is called with false as the parameter,
content is scrolled completely off the screen before it is scrolled back on
again. See the contrast between the two dialogs in the demo; it's a lot simpler
than it sounds.
The demo consists of a main dialog and child dialog. The main dialog displays
the introduction above, along with a logo bitmap, in an auto-scrolling window
with a scrollbar. The child dialog illustrates changing the text on the fly by
presenting a scrolling tips window.
The demo is mostly autogenerated, so you can safely ignore most of the code.
The important bits are in CScrollerTestDlg::OnInitDialog()
,
CTipsDialog::OnInitDialog()
, and
CTipsDialog::SwitchTip()
.
Here i begin by initializing the scroller with one of two sets of background
patterns and colors:
if ( ::GetTickCount()%2 )
{
m_scroller.SetPattern(CBitmap::FromHandle(
(HBITMAP)::LoadImage(AfxGetResourceHandle(),
MAKEINTRESOURCE(IDB_BACKGROUND),
IMAGE_BITMAP, 0,0, LR_SHARED)), FALSE);
m_scroller.SetBgColor(RGB(255,255,255));
m_scroller.SetFgColor(RGB(0,127,0));
}
else
{
m_scroller.SetPattern(CBitmap::FromHandle(
(HBITMAP)::LoadImage(AfxGetResourceHandle(),
MAKEINTRESOURCE(IDB_BACKGROUND2),
IMAGE_BITMAP, 0,0, LR_SHARED)), TRUE);
m_scroller.SetFgColor(RGB(255,255,225));
}
Note that in the first case, the bitmap is centered, while in the second it
is tiled.
Next, I set the logo bitmap and text:
m_scroller.SetLogo(CBitmap::FromHandle(
(HBITMAP)::LoadImage(AfxGetResourceHandle(),
MAKEINTRESOURCE(IDB_SCROLLER),
IMAGE_BITMAP, 0,0, LR_SHARED)));
CString strIntro;
strIntro.LoadString(IDS_INTRO);
m_scroller.SetText(strIntro);
Finally, I create the scroller to fill the entire client are:
CRect rect;
GetClientRect(&rect);
m_scroller.Create(rect, this);
It's probably worth noting here that this dialog is resizeable, and that the
scroller is resized in OnSize()
to always fill the client area. I
set the WS_CLIPCHILDREN
style for the dialog to prevent flashing
during resizing.
After creating the scroller, I create and show the tips dialog:
m_dlgTips.Create(CTipsDialog::IDD, this);
m_dlgTips.SetWindowPos(NULL,0, 0, 0,0, SWP_NOZORDER
|SWP_NOSIZE|SWP_NOACTIVATE|SWP_SHOWWINDOW);
Here I begin by initializing the scroller with a custom font, a message,
and turning off wrapping. I'll explain the reason for turning off wrapping
shortly:
m_scroller.SetFont("Microsoft Sans Serif", 10, FW_SEMIBOLD);
m_scroller.SetText("\tHello!");
m_scroller.SetWrapping(FALSE);
The messages displayed in this scroller aren't long enough that they need to
be readable while scrolling, so I let them scroll as fast as possible,
pausing when the tip is fully shown.
m_scroller.SetScrollDelay(0);
m_scroller.SetScrollPause(6000);
Finally do the creation. Note that I give the control an ID (1) and omit the
WS_VSCROLL
and WS_TABSTOP
styles; I don't want
the user to be able to scroll this manually.
m_scroller.SetScrollDelay(0);
m_scroller.SetScrollPause(6000);
Ok, now an explanation for turning off wrapping mode. The purpose of this
dialog is to display tips, one after another, and to do this by changing the
text at appropriate times. If wrapping is turned on, the text will never be
completely off screen, and switching will not look good. Now that that's clear,
on to how the text is actually switched...
CScrollerCtrl
is an output only control; the only user input it
accepts is manual scrolling, and this it handles internally. However, it does
send one command message to its parent when the content has scrolled off the
screen completely. There is a constant defined
(CScrollerCtrl::SC_SCROLL_COMPLETE
) to identify this command
message, but since it is the only command message sent by this control, it is
not really necessary to check this. SwitchTip()
handles the command
message when it is received, and changes the text. The new text then scrolls on
screen, and life goes on.
This is a fairly simple control... Output is double-buffered to ensure smooth
updates. Two timers are used: the first is active for the life of the window,
ticking off at the scroll rate. The second is used to clear the paused state
when autoscrolling is paused for whatever reason; it is killed as soon as it is
triggered. All drawing is contained in one of three methods:
FillBackground()
clears the back buffer when the drawing of a
new frame begins. If you wish to have a fancier background (animated, etc.) this
would be the place to do it.
DrawLogo()
draws the logo bitmap, if one is set. It takes a
parameter specifying the offset to draw at, and another specifying whether to
actually draw, or just calculate the size necessary to draw.
DrawBodyText()
draws the actual text. It also takes parameters
specifying the offset to draw at, and whether to draw or just calculate the size
necessary.
These methods are all virtual, so if you create a derived class, you can
override any or all of them to do something interesting. (display rich text,
etc.)
I've tested this on Windows XP, 2000, and 98. It will compile with both
Visual Studio 6 and Visual Studio .NET
- Nothing. It is perfect as-is.
- Write some sort of simple HTML renderer and use it to replace current text
renderer.
- Go find a pizza and some beer.