Мне нужен был скроллер текста. Чтоб плавно крутил текст по горизонтали. Итак, сделал вроде бы контрол.
На утечку GDI объектов проверил через диспетчер задач, вроде бы все чисто, хотя хотелось бы услышать ваше мнение.
[more=>>>]unit F_Scroller;
interface
uses
Windows, Messages;
const
{ сброс позиции прокрутчика }
WM_RESETSCROLL = WM_USER + 880;
{ прокрутка справа налево }
WM_LEFTSCROLL = WM_USER + 881;
{ прокрутка слева направо }
WM_RIGHTSCROLL = WM_USER + 882;
{ установка скорости прокрутки }
WM_SPEEDSCROLL = WM_USER + 883;
var
TScrollMode: Integer;
TViewUpdate: Integer;
procedure Scroller_Create(hWnd: Thandle);
procedure Scroller_Delete;
implementation
var
iScrlWidth : Integer;
iScrlHeight: Integer;
hScrlMemHdc: HDC;
hScrlMemNew: hBitmap;
hScrlMemOld: hBitmap;
hScrlTmpHdc: HDC;
hScrlTmpNew: hBitmap;
hScrlTmpOld: hBitmap;
hThread : Cardinal;
lpScroller : TRect;
ScrlOldProc: Pointer;
hTmpDC : HDC;
TextBuffer : Array [0..MAX_PATH] of WideChar;
xCopyPos : Integer;
BitmapWidth: Integer;
TextWidth : Integer;
hControl : Thandle;
{}
function GetTextWidth(szText: WideString): Integer;
var
TmpDC: HDC;
size : TSize;
fText: HFONT;
begin
TmpDC := GetDC(hControl);
fText := CreateFontW(13, 0, 0, 0, 800, 0, 0, 0, RUSSIAN_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, 'Tahoma');
SelectObject(TmpDC, fText);
GetTextExtentPoint32W(TmpDC, PWideChar(szText), Length(szText), size);
ReleaseDC(hControl, TmpDC);
DeleteObject(fText);
Result := size.cx;
end;
{}
procedure DeInitializeBitmap;
begin
SelectObject(hScrlTmpHdc, hScrlTmpOld);
DeleteObject(hScrlTmpNew);
DeleteDC(hScrlTmpHdc);
end;
{}
procedure InitializeBitmap;
begin
DeInitializeBitmap;
TextWidth := GetTextWidth(TextBuffer);
BitmapWidth := TextWidth + (2 * iScrlWidth);
hTmpDC := GetDC(hControl);
hScrlTmpHdc := CreateCompatibleDC(hTmpDC);
hScrlTmpNew := CreateCompatibleBitmap(hTmpDC, BitmapWidth, iScrlHeight);
ReleaseDC(hControl, hTmpDC);
hScrlTmpOld := SelectObject(hScrlTmpHdc, hScrlTmpNew);
end;
function ScrlNewProc(hWnd: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM) : LRESULT; stdcall;
var
PS: TPaintStruct;
begin
case uMsg of
{}
WM_PAINT:
begin
BeginPaint(hWnd, PS);
BitBlt(PS.HDC, 0, 0, iScrlWidth, iScrlHeight, hScrlMemHdc, 0, 0, SRCCOPY);
EndPaint(hWnd, PS);
Result := 0;
end;
{}
WM_SETTEXT:
begin
lstrcpynw(TextBuffer, PWideChar(lParam), lParam);
InitializeBitmap;
end;
{}
WM_RESETSCROLL:
begin
case TScrollMode of
1:
xCopyPos := (iScrlWidth + TextWidth + 25);
2:
xCopyPos := 0;
end;
end;
{}
WM_LEFTSCROLL:
TScrollMode := 1;
{}
WM_RIGHTSCROLL:
TScrollMode := 2;
{}
WM_SPEEDSCROLL:
TViewUpdate := lParam;
else
Result := CallWindowProcW(ScrlOldProc, hWnd, uMsg, wParam, lParam);
end;
end;
procedure DrawScrollBitmap(DC: HDC);
var
fText: HFONT;
begin
GetClientRect(hControl, lpScroller);
lpScroller.Left := 0;
lpScroller.Right := BitmapWidth;
FillRect(hScrlTmpHdc, lpScroller, HBRUSH(COLOR_BTNFACE + 10));
SetBkMode(DC, TRANSPARENT);
SetTextColor(DC, RGB(0, 0, 0));
fText := CreateFontW(13, 0, 0, 0, 800, 0, 0, 0, RUSSIAN_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, 'Tahoma');
SelectObject(hScrlTmpHdc, fText);
DrawTextW(DC, PWideChar(@TextBuffer), -1, lpScroller, DT_SINGLELINE or DT_LEFT);
DeleteObject(fText);
end;
function ScrollerThread(lParam: Pointer): DWORD; stdcall;
begin
SetThreadPriority(hThread, THREAD_PRIORITY_NORMAL);
while TRUE do
begin
InvalidateRect(hControl, nil, FALSE);
Sleep(TViewUpdate);
{ очищаем фон элемента для перерисовки }
GetClientRect(hControl, lpScroller);
FillRect(hScrlMemHdc, lpScroller, HBRUSH(COLOR_BTNFACE + 10));
case TScrollMode of
1:
begin
Dec(xCopyPos);
if (xCopyPos < 0) then
xCopyPos := (iScrlWidth + TextWidth + 25);
end;
2:
begin
Inc(xCopyPos);
if (xCopyPos > (iScrlWidth + TextWidth + 25)) then
xCopyPos := 0;
end;
3:
begin
Inc(xCopyPos);
if (xCopyPos > (iScrlWidth + TextWidth + 25)) then
xCopyPos := 0;
end;
end;
DrawScrollBitmap(hScrlTmpHdc);
BitBlt(hScrlMemHdc, xCopyPos - TextWidth, 0, BitmapWidth, iScrlHeight, hScrlTmpHdc, 0, 0, SRCCOPY);
end;
Result := 0;
end;
procedure Scroller_Create(hWnd: Thandle);
begin
{ получаем хэндл прорисовываемого элемента }
hControl := hWnd;
{ первичная инициализация элемента }
SendMessageW(hControl, WM_RIGHTSCROLL, 0, 0);
SendMessageW(hControl, WM_SPEEDSCROLL, 0, 35);
{ получаем координаты для высоты и длинны элемента }
GetClientRect(hControl, lpScroller);
iScrlWidth := lpScroller.Right - lpScroller.Left;
iScrlHeight := lpScroller.Bottom - lpScroller.Top;
{ создаем временный битмап для отрисовки }
hTmpDC := GetDC(0);
hScrlMemHdc := CreateCompatibleDC(hTmpDC);
hScrlMemNew := CreateCompatibleBitmap(hTmpDC, iScrlWidth, iScrlHeight);
ReleaseDC(0, hTmpDC);
hScrlMemOld := SelectObject(hScrlMemHdc, hScrlMemNew);
{ создаем временный битмап для прокрутки }
InitializeBitmap;
{ создаем поток для происиовки элементов }
hThread := CreateThread(nil, 0, @ScrollerThread, nil, 0, PLongWord(nil)^);
{ назначаем новую оконную функцию в элемент }
ScrlOldProc := Pointer(SetWindowLongW(hControl, GWL_WNDPROC, LongInt(@ScrlNewProc)));
end;
procedure Scroller_Delete;
var
ExitCode: Cardinal;
begin
{ получаем код выхода и уничтожаем поток }
GetExitCodeThread(hThread, ExitCode);
TerminateThread(hThread, ExitCode);
{ возвращаем старую оконную функцию в элемент }
SetWindowLongW(hControl, GWL_WNDPROC, LongInt(ScrlOldProc));
{ удаляем временный битмап для отрисовки }
SelectObject(hScrlMemHdc, hScrlMemOld);
DeleteObject(hScrlMemNew);
DeleteDC(hScrlMemHdc);
{ удаляем временный битмап для прокрутки }
DeInitializeBitmap;
end;
end.[/more]
И еще я бы хотел сделать такую фичу у контрола. Например когда текст скроллится слева направо, то когда он доскроллится до конца, он начал бы скроллиться обратно. Ну и наоборот. Я уж было хотел в case TScrollMode of под цифрой 3 такую реализацию сделать, но пока недополнял что делать при различных значениях. Например
Inc(xCopyPos);
if (xCopyPos > (iScrlWidth + TextWidth + 25)) then
xCopyPos := 0;
заменить на
Inc(xCopyPos);
if (xCopyPos > (iScrlWidth + TextWidth + 25)) then
xCopyPos := iScrlWidth + TextWidth + 25;
и сменить тип или что чтобы BitBlt в обратную пошел. Чего-то не придумывается алгоритм.
И вот сейчас у меня фоновый цвет непрозрачный. Попробовал через WM_CTLCOLORSTATIC вернуть кисть Result := GetStockObject(NULL_BRUSH), но фон так и остался стандартный. Может как-то через BitBlt можно перекопировать только нужные части, например ведь можно же вроде копировать белый/черный текст с флагами в этой функции. Правда наверное нужно красить черным цветом например созданный битмап для HDC и например белый цвет возвращать в надпись?