Jump to content

Recommended Posts

Posted (edited)

This concept was used in my zPlayer to display long file names in a short label control.
 

#include <GUIConstants.au3>

$guiWidth = 300
$hGUI = GUICreate("Main GUI", $guiWidth, 70)
$sString = "This is a long string that scrolls smoothly and infinitely in a short label control."

; Create a child GUI for scrolling label
$iMargin = 10
$sGap = "     "
$iLabelWidth = $guiWidth - $iMargin * 2
$hChildGUI = GUICreate("", $iLabelWidth, 20, 10, 10, $WS_CHILD, -1, $hGUI)
GUISetFont(12, 400, 0, "Arial")

; Create a label wide enough to hold a long string without truncation
$idLabel = GUICtrlCreateLabel($sString, 0, 0, 2000, 20, BitOR($SS_NOPREFIX, $SS_LEFTNOWORDWRAP))

; Get the string width
$tmpLabel = GUICtrlCreateLabel($sString, 0, 0, -1, 20, BitOR($SS_NOPREFIX, $SS_LEFTNOWORDWRAP))
$iStringWidth = ControlGetPos($hChildGUI, "", $tmpLabel)[2] - 9 ; Label is wider than the string width by 9 pixels
GUICtrlDelete($tmpLabel)

GUISetState(@SW_SHOW, $hGUI)
GUISetState(@SW_SHOW, $hChildGUI)

; Update the label data if the string width is larger than the label width
If $iStringWidth > $iLabelWidth Then
    GUICtrlSetData($idLabel, $sString & $sGap & $sString)
    $iScrollPos = 0
    $iMarquee = 0
    $iScrollDelay = 40
    AdlibRegister("_Marquee", $iScrollDelay)
EndIf

Do
Until GUIGetMsg() = $GUI_EVENT_CLOSE

Func _Marquee()
    $iMarquee += 1
    If $iMarquee < 3000/$iScrollDelay Then Return   ; 3 seconds of halt when $sString comes to the initial position
    $iScrollPos -= 1
    If -$iScrollPos = $iStringWidth + StringLen($sGap) * 4 Then ; Initialize the $idLabel's position
        $iMarquee = 0
        $iScrollPos = 0
    EndIf
    GUICtrlSetPos($idLabel, $iScrollPos, 0)
EndFunc

 

Edited by CYCho
  • CYCho changed the title to Smooth and infinite marquee style scroll of a long string in a short control
  • 2 weeks later...
Posted (edited)

Nice mate. :)

its possible to reduce the flicker a bit by double buffering. So hope you don't mind - but here's another approach

#include <GUIConstants.au3>
#include <GDIPlus.au3>
#include <WinAPI.au3>

Global $iGUIWidth = 300
Global $hGUI = GUICreate("Main GUI", $iGUIWidth, 70)
Global $sString = "This is a long string that scrolls smoothly and infinitely in a short label control."
Global $iLabW = $iGUIWidth - 8, $iLabH = 30

;The border style is here to mark the label boundry - for demo purposes!
Global $hLabel = GUICtrlCreateLabel("", 4, 4, $iGUIWidth - 8, $iLabH, $WS_BORDER)
GUISetState()

Global $hLabGraphic, $hBuffGraphic, $hBuffBitMap
Global $hBrush, $hFormat, $tLayout, $hFamily, $hFont, $iBkColour

_GDIPlus_Startup()

;Graphic of the static control - its possible to draw directly to this, but we'll introduce flicker.
$hLabGraphic = _GDIPlus_GraphicsCreateFromHWND(GUICtrlGetHandle($hLabel))

;Create Buffer - Draw to this. Copy to the label once the image is complete.
$hBuffBitMap = _GDIPlus_BitmapCreateFromGraphics($iLabW, $iLabH, $hLabGraphic)
$hBuffGraphic = _GDIPlus_ImageGetGraphicsContext($hBuffBitMap)

;Create font etc..
$hBrush = _GDIPlus_BrushCreateSolid(0xFFAA0000) ;ARGB
$hFormat = _GDIPlus_StringFormatCreate()
$hFamily = _GDIPlus_FontFamilyCreate("Arial")
$hFont = _GDIPlus_FontCreate($hFamily, 12, 3) ;Size, Italic + Bold
$iBkColour = BitOR(0xFF000000, _WinAPI_GetSysColor($COLOR_3DFACE)) ;dialog background colour.

; ----- Find dims of the text. ---------
Global $iMargin = 4
$tLayout = _GDIPlus_RectFCreate($iMargin, 0, 1, 1) ;Text rectangle. Margin of 4 from left

Local $aStrMeasure[4]

;Expand height until we can fit 2 lines
;Sanity checks are required here! This'll break if there's < 2 printable characters in the string.
Do
    $tLayout.Height += 1
    $aStrMeasure = _GDIPlus_GraphicsMeasureString($hLabGraphic, $sString, $hFont, $tLayout, $hFormat)
Until $aStrMeasure[2] = 2
$tLayout.Height -= 1 ; Gets us the max height of 1 line.

;Expand width until we can fit the entire string.
Do
    $tLayout.Width += 1
    $aStrMeasure = _GDIPlus_GraphicsMeasureString($hLabGraphic, $sString, $hFont, $tLayout, $hFormat)
Until $aStrMeasure[1] = StringLen($sString)

;Center vertically in the label
$tLayout.Y = Floor(($iLabH - $tLayout.Height)/2)

ConsoleWrite(StringFormat("text dims: [%d, %d, %d, %d]\r\n", $tLayout.X, $tLayout.Y, $tLayout.Width, $tLayout.Height))
;--------------------------------------

;Initial write to label - cause I'm too lazy to fix scrolling logic!
_GDIPlus_GraphicsDrawStringEx($hLabGraphic, $sString, $hFont, $tLayout, $hFormat, $hBrush)

Local $hTimer = TimerInit(), $iShift = 3 ; $iShift = Pos shift on each iteration.

While 1

    If GUIGetMsg() = $GUI_EVENT_CLOSE Then ExitLoop

    ;Pause at string start.
    If TimerDiff($hTimer) < 2000 Then ContinueLoop

    ;wipe buffer
    _GDIPlus_GraphicsClear($hBuffGraphic, $iBkColour)

    ;Redraw the string at new position.
    $tLayout.X -= $iShift
    If ($tLayout.X + $tLayout.Width) < 0  Then ;If we've fallen off the label..
        $tLayout.X = $iMargin ;Start at our initial position
        $hTimer = TimerInit()
    EndIf

    _GDIPlus_GraphicsDrawStringEx($hBuffGraphic, $sString, $hFont, $tLayout, $hFormat, $hBrush)

    ;Copy the buffer to the label
    _GDIPlus_GraphicsDrawImageRect($hLabGraphic, $hBuffBitMap, 0, 0, $iLabW, $iLabH)

    Sleep(40)
WEnd

;Cleanup
_GDIPlus_FontDispose($hFont)
_GDIPlus_FontFamilyDispose($hFamily)
_GDIPlus_StringFormatDispose($hFormat)
_GDIPlus_BrushDispose($hBrush)
_GDIPlus_GraphicsDispose($hBuffGraphic)
_GDIPlus_ImageDispose($hBuffBitMap)
_GDIPlus_GraphicsDispose($hLabGraphic)
_GDIPlus_Shutdown()

GUIDelete($hGUI)
Edited by MattyD
Minor code touchup
Posted (edited)

Here my take on it :

#include <GUIConstants.au3>

Local $hGUI = GUICreate("Main GUI", 300, 70, -1, -1, -1, $WS_EX_COMPOSITED)
Global $sString = "This is a long string that scrolls smoothly and infinitely in a short label control."

Global $idLabel = GUICtrlCreateLabel("", 10, 10, 280, 30, $SS_LEFTNOWORDWRAP)
GUICtrlSetFont(-1, 11)

$sString &= "          " ; pad with space

GUICtrlSetData($idLabel, $sString)
GUISetState()

AdlibRegister(Scroll, 150)

Do
Until GUIGetMsg() = $GUI_EVENT_CLOSE

Func Scroll()
  Local Static $iPos = 0, $sScroll = $sString, $iLen = StringLen($sString)
  $iPos = Mod($iPos, $iLen) + 1
  $sScroll = StringTrimLeft($sScroll, 1) & StringMid($sString, Mod($iPos - 1, $iLen) + 1, 1)
  GUICtrlSetData($idLabel, $sScroll)
EndFunc

No flicker on my side...

Edited by Nine
Posted
42 minutes ago, Nine said:

Here my take on it :

Thanks for your interest in this. Yours is similar to one of my previous versions in that it scrolls one character at a time. Even though I set the Adlib time at 500 ms, it seemed a bit stuttering. Then in the next version, I made the control itself to move  one pixel at a time. It looked OK, but the scroll spanned the entire width of the GUI. It looked like the string spilling over the GUI. The current version, shown in the above post, has a child GUI, the whole of which is a laabel control. This control scrolls the whole width of its parent GUI, but that GUI is a child of the main GUI and the scroll does not reach the perimeter of the main GUI. So far I am pretty much satisfied with it.

Posted (edited)

Understand what you mean now.  Here a new version with no flickers.  It uses a similar approach of yours. :)

#include <GUIConstants.au3>

Local $hGUI = GUICreate("Main GUI", 300, 70)
Local $sString = "This is a long string that scrolls smoothly and infinitely without flickers..."
GUISetState()

Global $iLen = StringLen($sString) * 6.5  ; <<<<< adjust as needed or use M23 UDF StringSize for more precision

Local $hGUI2 = GUICreate("", 280, 30, 10, 10, $WS_CHILD, $WS_EX_COMPOSITED, $hGUI)
GUISetFont(11)
Global $idLabel1 = GUICtrlCreateLabel($sString, 0, 0, $iLen, 30, $SS_LEFTNOWORDWRAP)
Global $idLabel2 = GUICtrlCreateLabel($sString, $iLen, 0, $iLen, 30, $SS_LEFTNOWORDWRAP)
GUISetState()

AdlibRegister(Scroll, 25)

Do
Until GUIGetMsg() = $GUI_EVENT_CLOSE

Func Scroll()
  Local Static $iPos = 0
  $iPos -= 1
  If -$iPos >= $iLen Then $iPos = 0
  GUICtrlSetPos($idLabel1, $iPos)
  GUICtrlSetPos($idLabel2, $iLen + $iPos)
EndFunc

 

Edited by Nine
Posted (edited)
5 hours ago, Nine said:
Local $hGUI2 = GUICreate("", 280, 30, 10, 10, $WS_CHILD, $WS_EX_COMPOSITED, $hGUI)

$WS_EX_COMPOSITED seems to help reduce flickers when the adlib time is set to 25.  When the time is set to 40, it doesn't seem to help. Maybe it's due to my bad eye sights. Thanks for the hint anyway.
As to the string size, creating a temporary label with width set to -1 seems to give precise width. I tried it with diferrent languages and different font weights, and so far, I am happy with the results. According to my observations, the label width is always 9 pixels longer than the actual string size.

Edited by CYCho
Posted

If you want the text scrolling to continue even when you drag the GUI or even when an MsgBox() is in action, you can use _WinAPI_SetTimer instead of AdlibRegister, as in this slightly modified version of your script.
... But if you don't care, then forget it ...  :)

#include <GUIConstants.au3>
#include <WinAPISysWin.au3>

$guiWidth = 300
$hGUI = GUICreate("Main GUI", $guiWidth, 70)
$sString = "This is a long string that scrolls smoothly and infinitely in a short label control."

; Create a child GUI for scrolling label
$iMargin = 10
$sGap = "     "
$iLabelWidth = $guiWidth - $iMargin * 2
$hChildGUI = GUICreate("", $iLabelWidth, 20, 10, 10, $WS_CHILD, -1, $hGUI)
GUISetFont(12, 400, 0, "Arial")

; Create a label wide enough to hold a long string without truncation
$idLabel = GUICtrlCreateLabel($sString, 0, 0, 2000, 20, BitOR($SS_NOPREFIX, $SS_LEFTNOWORDWRAP))

; Get the string width
$tmpLabel = GUICtrlCreateLabel($sString, 0, 0, -1, 20, BitOR($SS_NOPREFIX, $SS_LEFTNOWORDWRAP))
$iStringWidth = ControlGetPos($hChildGUI, "", $tmpLabel)[2] - 9 ; Label is wider than the string width by 9 pixels
GUICtrlDelete($tmpLabel)

GUISetState(@SW_SHOW, $hGUI)
GUISetState(@SW_SHOW, $hChildGUI)

; Update the label data if the string width is larger than the label width
If $iStringWidth > $iLabelWidth Then
    GUICtrlSetData($idLabel, $sString & $sGap & $sString)
    $iScrollPos = 0
    $iMarquee = 0
    $iScrollDelay = 40
    ; AdlibRegister("_Marquee", $iScrollDelay)

    ; -- setup timer--
    Local $hTimerProc = DllCallbackRegister('_Marquee', 'none', 'hwnd;uint;uint_ptr;dword')
    Local $iTimerID = _WinAPI_SetTimer(0, 0, $iScrollDelay, DllCallbackGetPtr($hTimerProc))
    ; ----------------
EndIf

MsgBox(64, "Info", "text scrolls")

Do
Until GUIGetMsg() = $GUI_EVENT_CLOSE

; -- clean timer--
_WinAPI_KillTimer(0, $iTimerID)
DllCallbackFree($hTimerProc)
; ----------------

Func _Marquee($hWnd, $iMsg, $iTimerID, $iTime)
    #forceref $hWnd, $iMsg, $iTimerID, $iTime
    $iMarquee += 1
    If $iMarquee < 3000 / $iScrollDelay Then Return ; 3 seconds of halt when $sString comes to the initial position
    $iScrollPos -= 1
    If - $iScrollPos = $iStringWidth + StringLen($sGap) * 4 Then ; Initialize the $idLabel's position
        $iMarquee = 0
        $iScrollPos = 0
    EndIf
    GUICtrlSetPos($idLabel, $iScrollPos, 0)
EndFunc   ;==>_Marquee

 

 

image.jpeg.9f1a974c98e9f77d824b358729b089b0.jpeg Chimp

small minds discuss people average minds discuss events great minds discuss ideas.... and use AutoIt....

Posted (edited)

Good catch @Gianni.  I found a somewhat weird issue when testing your code.  If you run the script from double clicking it in Explorer, it won't start scrolling unless you click on the window.  Do you guys have the same issue ?

BTW, it is not related to the SetTimer API.  I tested different codes (mine, @CYCho's) and all shows the same weird behavior...Scrolling won't start automatically.

edit : in my case, the scripts are freezing after the second GUISetState (the child GUI)

Reedit : it seems the script is loosing focus if you are double clicking normally.  But when you click fastest you can, it works correctly...Still weird.

Edited by Nine
Posted
Quote

 If you run the script from double clicking it in Explorer, it won't start scrolling unless you click on the window.  Do you guys have the same issue ?

No, i tried to reproduce this by compiling it in 32/64bits, clicking from the explorer, and even using WinActivate to set focus to another app (before the timer is started), but is starts here, always.
well, it takes about 2 seconds to start (with and without the message box). 

Some of my script sourcecode

Posted

Ok, thanks for testing.  There must be something on my side, but I have no idea what it could be.  All other methods of starting the code works as expected.  Only when I slowly double click that I have an issue.  The compiled script reacts the same with the same issue, sigh... 

Posted

FYI, I tested on other computers.  Older computer reacts correctly as it should be expected.  Faster (more recent) computer behave exactly as I am experiencing.  So this is not related to my own computer.  It seems to be another of those timing issues with AutoIt.  Sadly not the first, not the last...

For those who face the same problem as I have, here a simple solution :

#include <GUIConstants.au3>
#include <Constants.au3>

Example()

Func Example()
  Local $hGUI = GUICreate("Main GUI", 300, 70)
  GUISetState()

  Sleep(100)

  Local $hGUI2 = GUICreate("", 280, 30, 10, 10, $WS_CHILD, -1, $hGUI)
  GUISetFont(11)
  Local $idLabel = GUICtrlCreateLabel("This is a test", 0, 0, 100, 30)

  GUISetState()

  MsgBox($MB_OK, "", "Test")

  Do
  Until GUIGetMsg() = $GUI_EVENT_CLOSE
EndFunc   ;==>Example

Slow down the creation of child GUI by putting a Sleep in between.  It is working on all computers correctly now.

Posted
32 minutes ago, Nine said:

Slow down the creation of child GUI by putting a Sleep in between.

Thanks for your test. How long should the Sleep be? In my zPlayer, the child is created after creation of all the other controls and there is about a 20ms time difference between the creation of the main GUI and the child. Would I still need to put some Sleep in between?

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...