May 15, 2024, 12:01:28 PM

News:

Own IWBasic 2.x ? -----> Get your free upgrade to 3.x now.........


Marquee control

Started by Ionic Wind Support Team, December 05, 2008, 12:50:30 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Ionic Wind Support Team

December 05, 2008, 12:50:30 PM Last Edit: December 06, 2008, 01:48:22 PM by Paul Turley
***UPDATED***

Here is the source and include file for a general purpose marquee control (text scroller).  I've been using it in a larger project.


$include "windows.inc"
$include "marquee1.inc"

/*****************testing code*****************/
'openconsole
window testwin
openwindow testwin,0,0,300,300,@CAPTION,NULL,"Marquee test",&testhandler
SetWindowColor testwin,RGB(0,0,255)

CreateMarqueeControl( testwin,10,10,280,25,MARQUEE_STOPAT1 | MARQUEE_BORDER,99 )
SetFont testwin,"Arial",12,800,0,99
SetControlColor testwin,99,RGB(255,255,0),RGB(0,0,155)
SetMarqueeSpeed(testwin,99,25,2)
AddMarqueeText(testwin,99,"\n\nTesting 123...\n")
AddMarqueeText(testwin,99,"This is line 2...")
AddMarqueeText(testwin,99,"A very long line 3 that is longer than the display window")
AddMarqueeText(testwin,99,"A very long line 4 that is longer than the display window")
StartTimer testwin,10000,2
WAITUNTIL testwin.hwnd = 0
END

sub testhandler
select @message
case @idclosewindow
closewindow testwin
case @idtimer
if @code = 2
AddMarqueeText(testwin,99,"End of Text")
Stoptimer testwin,2
Starttimer testwin,20000,3
else
AddMarqueeText(testwin,99,"\n\nNew text added dynamically")
Stoptimer testwin,3
endif
endselect
endsub
/*****************testing code*****************/


DECLARE IMPORT,InvalidateRect(hWnd as UINT,lpRect as POINTER,bErase as INT),INT


TYPE MRQWINDOW
WINDOW win
int pixelinterval
int speed
int textexpire
pointer textList
int textcount
int cx
int cy
int currpos
int style
ENDTYPE

TYPE MRQTEXT
UINT timeadded
pointer pText
ENDTYPE

GLOBAL SUB CreateMarqueeControl(window parent,int l,int t,int w,int h,int style,int id),UINT
UINT _hRet,_winstyle:_hRet = NULL
POINTER _pWin
_pWin = NEW(MRQWINDOW,1)
if(_pWin)
_winstyle = @NOAUTODRAW | @NOCAPTION
if style & MARQUEE_BORDER then _winstyle |= WS_DLGFRAME
'this works because the first member of a UDT has the same address as the UDT itself
OpenWindow #<WINDOW>_pWin,l,t,w,h,_winstyle,parent,"",&MarqueeHandler
if(#<WINDOW>_pWin.hWnd)
_SetProp(#<WINDOW>_pWin.hWnd,"MRQWINDOW",_pWin+0)
_SetWindowLong(#<WINDOW>_pWin.hWnd, GWL_ID, id)
_hRet = #<WINDOW>_pWin.hWnd
'defaults
#<MRQWINDOW>_pWin.pixelinterval = 3
#<MRQWINDOW>_pWin.speed = 25
#<MRQWINDOW>_pWin.textexpire = 20000
#<MRQWINDOW>_pWin.textcount = 0
#<MRQWINDOW>_pWin.cx = w
#<MRQWINDOW>_pWin.cy = h
#<MRQWINDOW>_pWin.currpos = w
#<MRQWINDOW>_pWin.style = style
endif
endif
return _hRet
ENDSUB

GLOBAL SUB AddMarqueeText(window win,int id,string text)
UINT _hControl
POINTER _pWin,_pData
_hControl = _GetDlgItem(win.hwnd,id)
if(_hControl)
_pWin = _GetProp(_hControl,"MRQWINDOW")
if(_pWin)
if(#<MRQWINDOW>_pWin.textList = NULL)
#<MRQWINDOW>_pWin.textList = ListCreate()
StartTimer(#<WINDOW>_pWin,#<MRQWINDOW>_pWin.Speed)
endif
_pData = ListAdd(#<MRQWINDOW>_pWin.textList,new(MRQTEXT,1))
#<MRQTEXT>_pData.pText = new(char,len(text)+1)
#<MRQTEXT>_pData.#<string>pText = stripnl(text)
#<MRQTEXT>_pData.timeadded = Millisecs()
#<MRQWINDOW>_pWin.textcount++
InvalidateRect(_hControl,0,0)
endif
endif
ENDSUB

SUB stripnl(string text),heap
pointer _pRet,_pIn,_pOut
_pIn = text
_pRet = AllocHeap(len(text)+1)
_pOut = _pRet
while #<char>_pIn <> NULL
if(#<char>_pIn <> 0x0a) and (#<char>_pIn <> 0x0d)
#<char>_pOut = #<char>_pIn
_pOut++
endif
_pIn++
endwhile
return #<string>_pRet
ENDSUB

GLOBAL SUB SetMarqueeTimeout(window win,int id,int timeout)
UINT _hControl
POINTER _pWin
_hControl = _GetDlgItem(win.hwnd,id)
if(_hControl)
_pWin = _GetProp(_hControl,"MRQWINDOW")
if(_pWin)
#<MRQWINDOW>_pWin.textexpire = timeout
endif
endif
ENDSUB

GLOBAL SUB SetMarqueeSpeed(window win,int id,int speed, int pixelinterval)
UINT _hControl
POINTER _pWin
_hControl = _GetDlgItem(win.hwnd,id)
if(_hControl)
_pWin = _GetProp(_hControl,"MRQWINDOW")
if(_pWin)
#<MRQWINDOW>_pWin.speed = speed
#<MRQWINDOW>_pWin.pixelinterval = pixelinterval
endif
endif
ENDSUB

GLOBAL SUB MarqueeRemoveAll(window win, int id)
UINT _hControl
pointer _pData,_pWin
_hControl = _GetDlgItem(win.hwnd,id)
if(_hControl)
_pWin = _GetProp(_hControl,"MRQWINDOW")
if(_pWin)
if(#<MRQWINDOW>_pWin.textList <> NULL)
FOR _pData = EACH #<MRQWINDOW>_pWin.textList AS MRQTEXT
if #_pData.pText then delete #_pData.pText
NEXT
ListRemoveAll(#<MRQWINDOW>_pWin.textList,TRUE)
#<MRQWINDOW>_pWin.textList = NULL
StopTimer #<WINDOW>_pWin
InvalidateRect(_hControl,0,0)
endif
endif
endif
ENDSUB

SUB MarqueeHandler
POINTER _pWin,_pCreate,_pData
UINT _hBmp,_hOldBmp,_hDC,_hMemDC,_hBrush
Select @MESSAGE
Case @IDCREATE
_pCreate = @LPARAM + 0
_hDC = _GetDC(#<WINDOW>@hitwindow.hwnd)
_hBmp = _CreateCompatibleBitmap(_hDC,#<CREATESTRUCT>_pCreate.cx, #<CREATESTRUCT>_pCreate.cy)
if(_hBmp)
_SetProp(#<WINDOW>@hitwindow.hwnd,"MRQBITMAP",_hBmp)
'Give the bitmap a default color
_hMemDC = _CreateCompatibleDC(_hDC)
_hOldBmp = _SelectObject(_hMemDC,_hBmp)
_FillSolidRect(_hMemDC,0,0,#<CREATESTRUCT>_pCreate.cx, #<CREATESTRUCT>_pCreate.cy,_GetSysColor(5))
_SelectObject(_hMemDC,_hOldBmp)
_DeleteDC(_hMemDC)
_ReleaseDC(#<WINDOW>@hitwindow.hwnd,_hDC)
'a few more default properties
_SetProp(#<WINDOW>@hitwindow.hwnd,"BGRND",_GetSysColor(5))
_SetProp(#<WINDOW>@hitwindow.hwnd,"FGRND",0)
else
'end the creation of the window.
_ReleaseDC(#<WINDOW>@hitwindow.hwnd,_hDC)
return -1
endif
Case @IDDESTROY
StopTimer #<WINDOW>@hitwindow
_hBmp = _GetProp(#<WINDOW>@hitwindow.hwnd,"MRQBITMAP")
if(_hBmp) then _DeleteObject(_hBmp)
if #<WINDOW>@hitwindow.hPen then _DeleteObject(#<WINDOW>@hitwindow.hPen)
if #<WINDOW>@hitwindow.hFont then _DeleteObject(#<WINDOW>@hitwindow.hFont)
_RemoveProp(#<WINDOW>@hitwindow.hwnd,"MRQBITMAP")
_RemoveProp(#<WINDOW>@hitwindow.hwnd,"BGRND")
_RemoveProp(#<WINDOW>@hitwindow.hwnd,"FGRND")
_hBrush = _RemoveProp(#<WINDOW>@hitwindow.hwnd,"BBRUSH")
if _hBrush
_DeleteObject(_hBrush)
endif
_pWin = _GetProp(#<WINDOW>@hitwindow.hwnd,"MRQWINDOW")
if(_pWin)
if(#<MRQWINDOW>_pWin.textList <> NULL)
FOR _pData = EACH #<MRQWINDOW>_pWin.textList AS MRQTEXT
if #_pData.pText then delete #_pData.pText
NEXT
ListRemoveAll(#<MRQWINDOW>_pWin.textList,TRUE)
endif
endif
case WM_NCDESTROY
_pWin = _GetProp(#<WINDOW>@hitwindow.hwnd,"MRQWINDOW")
if(_pWin)
_RemoveProp(#<WINDOW>@hitwindow.hwnd,"MRQWINDOW")
delete _pWin
endif
Case WM_SETFONT
if #<WINDOW>@hitwindow.hfont
_DeleteObject(#<WINDOW>@hitwindow.hfont)
endif
#<WINDOW>@hitwindow.hFont = @WPARAM
Case WM_GETFONT
return #<WINDOW>@hitwindow.hFont
Case @IDPAINT
UpdateDisplay(#<WINDOW>@hitwindow.hwnd)
int l,t,w,h
GetClientSize #<WINDOW>@hitwindow,l,t,w,h
_hBmp = _GetProp(#<WINDOW>@hitwindow.hwnd,"MRQBITMAP")
if _hBmp
_hDC = _GetDC(#<WINDOW>@hitwindow.hwnd)
_hMemDC = _CreateCompatibleDC(_hDC)
_hOldBmp = _SelectObject(_hMemDC,_hBmp)
_BitBlt(_hDC,0,0,w,h,_hMemDC,0,0,0x00CC0020)
_SelectObject(_hMemDC,_hOldBmp)
_DeleteDC(_hMemDC)
_ReleaseDC(#<WINDOW>@hitwindow.hwnd,_hDC)

endif
Case @IDTIMER

InvalidateRect(#<WINDOW>@hitwindow.hwnd,0,0)
ENDSELECT
RETURN
ENDSUB

DECLARE IMPORT,ExtTextOutA(hdc as UINT,x as int,y as int,fuOptions as UINT,rcOpaque as WINRECT,str as POINTER,count as UINT,lpDX as UINT),int
SUB _FillSolidRect(_hdc as UINT, _x as int, _y as int, _width as int, _height as int, _cFill as UINT)
DEF _rectFill as WINRECT
if(_hdc)
_SetBkColor(_hdc,_cFill)
_rectFill.left = _x
_rectFill.top = _y
_rectFill.right = _x + _width
_rectFill.bottom = _y + _height
ExtTextOutA(_hdc,0,0,2,_rectFill,0,0,0)
endif
RETURN
ENDSUB


SUB UpdateDisplay(UINT hwnd)
UINT _hBmp,_hMemDC,_hOldBmp,_hOldFont,_hOldPen,_fg,_bg
int _l,_t,_w,_h,_outx,_textX,_textY
pointer _pWin,_pList, _pData, _pos
_pWin = _GetProp(hwnd,"MRQWINDOW")
if(_pWin)
GetClientSize #<WINDOW>_pWin,_l,_t,_w,_h
_hBmp = _GetProp(hwnd,"MRQBITMAP")
if _hBmp
_fg = _GetProp(hwnd,"FGRND")
_bg = _GetProp(hwnd,"BGRND")
FrontPen #<WINDOW>_pWin,_fg
_hMemDC = _CreateCompatibleDC(NULL)
_hOldBmp = _SelectObject(_hMemDC,_hBmp)
_hOldFont = _SelectObject(_hMemDC,#<WINDOW>_pWin.hFont)
_hOldPen = _selectObject(_hMemDC,#<WINDOW>_pWin.hPen)
_SetTextColor(_hMemDC,_fg)
'background
_FillSolidRect(_hMemDC,0, 0,_w, _h, _bg)
'foreground
_pList = #<MRQWINDOW>_pWin.textList
if(_pList)
_outx = #<MRQWINDOW>_pWin.currpos
_pos = ListGetFirst(_pList)
WHILE _pos <> 0
_pData = ListGetData(_pos)
'figure out where to print
GetTextSize #<WINDOW>_pWin,#<MRQTEXT>_pData.#<String>pText,_textX,_textY
if ((_outx + _textX) > 0) and (_outx < _w)
_TextOut(_hMemDC,_outx, _h/2-(_textY/2), #<MRQTEXT>_pData.#<String>pText, len(#<MRQTEXT>_pData.#<String>pText))
endif
IF ((_outx + _textx) < 0 ) and ((Millisecs()-#<MRQTEXT>_pData.timeAdded) > #<MRQWINDOW>_pWin.textexpire) and (#<MRQWINDOW>_pWin.textcount > 1)
#<MRQWINDOW>_pWin.currpos = _outX + _textX + 10
delete #<MRQTEXT>_pData.pText
_pos = ListRemove(_pos,TRUE)
#<MRQWINDOW>_pWin.textcount --
ELSE
_pos = ListGetNext(_pos)
ENDIF
_outX += _textX + 10
ENDWHILE
if _outX < 0
#<MRQWINDOW>_pWin.currpos = _w
endif
'...
endif
_SelectObject(_hMemDC,_hOldPen)
_SelectObject(_hMemDC,_hOldFont)
_SelectObject(_hMemDC,_hOldBmp)
_DeleteDC(_hMemDC)
endif
#<MRQWINDOW>_pWin.currpos -= #<MRQWINDOW>_pWin.pixelinterval
if(#<MRQWINDOW>_pWin.textcount = 1) and (#<MRQWINDOW>_pWin.currpos < 0) and (#<MRQWINDOW>_pWin.style & MARQUEE_STOPAT1)
#<MRQWINDOW>_pWin.currpos = 0
endif
endif
ENDSUB


And the include file


const MARQUEE_STOPAT1 = 0x100
const MARQUEE_BORDER = 0x200

Declare Extern CreateMarqueeControl(window parent,int l,int t,int w,int h,int style,int id),UINT
Declare Extern AddMarqueeText(window win,int id,string text)
Declare Extern SetMarqueeTimeout(window win,int id,int timeout)
Declare Extern SetMarqueeSpeed(window win,int id,int speed, int pixelinterval)
Declare Extern MarqueeRemoveAll(window win, int id)


You can use it in your own projects by removing the testing code at the top of the source, and $include the include file to use the functions.  The control resonds correctly to SetFont and SetControlColor.

Attached is the compiled test version and source

Paul.

Ionic Wind Support Team

LarryMc

Paul,

Thanks for posting.  That peice of code answered/clarified several questions I had that didn't have anything to do with a marquee.

Larry
LarryMc
Larry McCaughn :)
Author of IWB+, Custom Button Designer library, Custom Chart Designer library, Snippet Manager, IWGrid control library, LM_Image control library

Barney

Thank you for this useful control, but even greater thank you for showing us how some things ought to be done in a nice and concise manner.

Barney

LarryMc

Playing with the code:
I add a 2nd marquee to the program.
Every entry for ID 99 is duplicated with a ID 98 entry.

Both marquees appear and scroll to completion.
If I change the pixelinterval of one of the 2 marquees they scroll but before the scrolling finished the program closes.

Larry
LarryMc
Larry McCaughn :)
Author of IWB+, Custom Button Designer library, Custom Chart Designer library, Snippet Manager, IWGrid control library, LM_Image control library

Ionic Wind Support Team

December 06, 2008, 01:49:00 PM #4 Last Edit: December 06, 2008, 01:53:54 PM by Paul Turley
Source and download updated in the first post to correct it Larry ;)

In case you didn't spot the changes...

#1. Around line 300

this:

_pos = ListRemove(_pos,TRUE)
delete #<MRQTEXT>_pData.pText

Was backwards and should have been this:

delete #<MRQTEXT>_pData.pText
_pos = ListRemove(_pos,TRUE)

#2. Added WM_NCDESTROY handling to delete the allocated UDT.  Deleting it in @IDDESTROY would remove the handler address of the embedded WINDOW variable and cause a debug exception when windows tried to send the WM_NCDESTROY message.  Only reared its head in debug mode of course.

Paul.
Ionic Wind Support Team

Haim

Thanks for the control!
Can you please explain the usage of "+0" in the line:
Why in the line:  _SetProp(#<WINDOW>_pWin.hWnd,"MRQWINDOW",_pWin+0)

Haim

Ionic Wind Support Team

It's not needed now, but at one time it was ;) 

Long ago trying to pass a pointer to an integer parameter would cause "no appropriate conversion exists" error, so using +0 would effectively add 0 to the address in the pointer, and at the same time convert it to an integer.

Force of habit on my part.   You'll also see me use it the other way around when dealing with @lparam.  @lparam is an actual variable, so when certain Windows messages sends a pointer you can copy it to another one like:

pTemp = @lparam+0

Otherwise pTemp would have the address of @lparam, and not the pointer contained therein.  Just a little trick is all.

Paul.

Ionic Wind Support Team

Haim

Paul,
Thanks for your very prompt response and the explanation.

Haim

LarryMc

LarryMc
Larry McCaughn :)
Author of IWB+, Custom Button Designer library, Custom Chart Designer library, Snippet Manager, IWGrid control library, LM_Image control library