How to create a vertical news-ticker (Phoenix)

Keywords : Ticker, News-sticker, Scrolling text control.


Introduction

I'm playing with wxPython since a few years (I am not a professional). I just created two open-source software for my pleasure. But I am so grateful to the contributors for their work in wxPython that I would now give me too my modest contribution. I'd like to sharesome tools that I worked on.

Here to start a simple adaptation that I made from wx.lib.ticker by Chris Mellon. This is a vertical scrolling control that can display one or more texts, one or more lines, with or without pause, etc. ... We often see this type of control on websites to display RSS feedsor ou latest articles. You can download below the widget and the demo. I hope it will be useful to someone. Feel free to make any desired changes to improve it ! (Noethys).


Demonstrating :

Tested py3.x, wx4.x and Win10.

Are you ready to use some samples ? ;)

Test, modify, correct, complete, improve and share your discoveries ! (!)


Example

img_sample_one.png

   1 # demo.py
   2 
   3 #-------------------------------------------------------------------------------
   4 # Site internet :  www.noethys.com
   5 # Auteur:          Noethys
   6 # Copyright:       (c) 2012 Noethys
   7 # Licence:         Licence wxWidgets
   8 # Based on wx.lib.ticker by Chris Mellon
   9 #-------------------------------------------------------------------------------
  10 
  11 import wx
  12 import newsticker
  13 import wx.lib.colourselect as csel
  14 
  15 # class DemoPanel
  16 # class DemoFrame
  17 
  18 PAGES = """<t>PAGE 1</t>This is the first page.
  19 <t>PAGE 2</t>This is the second page.
  20 This page doesn't have header.
  21 <t>INFO</t>Note this control supports multiline texts.
  22 <t>NOTE</t>Mouse over control stops scrolling.
  23 """
  24 
  25 #-------------------------------------------------------------------------------
  26 
  27 class DemoPanel(wx.Panel):
  28     def __init__(self, *args, **kwds):
  29         kwds["style"] = wx.TAB_TRAVERSAL
  30         wx.Panel.__init__(self, *args, **kwds)
  31 
  32         # Preview
  33         self.box_preview_staticbox = wx.StaticBox(self, -1, "Preview")
  34         self.ctrl_newsticker = newsticker.Newsticker(self)
  35         self.ctrl_newsticker.SetMinSize((-1, 100))
  36 
  37         # Properties
  38         self.box_properties_staticbox = wx.StaticBox(self, -1, "Properties")
  39         self.label_fcolor = wx.StaticText(self, -1, "Foreground Color :")
  40         self.ctrl_fcolor = csel.ColourSelect(self, -1, colour=self.ctrl_newsticker.GetForegroundColour())
  41         self.label_bcolor = wx.StaticText(self, -1, "Background Color :")
  42         self.ctrl_bcolor = csel.ColourSelect(self, -1, colour=self.ctrl_newsticker.GetBackgroundColour())
  43         self.label_font = wx.StaticText(self, -1, "Font :")
  44         self.ctrl_font = wx.FontPickerCtrl(self, -1,
  45                                            font=self.ctrl_newsticker.GetFont(),
  46                                            style=wx.FNTP_FONTDESC_AS_LABEL |
  47                                                  wx.FNTP_USEFONT_FOR_LABEL
  48                                                  )
  49         self.label_fps = wx.StaticText(self, -1, "Frame per second :")
  50         self.ctrl_fps = wx.Slider(self, -1, 20, 1, 100, style=wx.SL_HORIZONTAL)
  51         self.label_ppf = wx.StaticText(self, -1, "Pixels per frame :")
  52         self.ctrl_ppf = wx.Slider(self, -1, 2, 1, 10, style=wx.SL_HORIZONTAL)
  53         self.label_pause = wx.StaticText(self, -1, "Pause time (sec) :")
  54         self.ctrl_pause = wx.SpinCtrl(self, -1, "2", min=0, max=100, size=(60, -1))
  55         self.label_heading = wx.StaticText(self, -1, "Heading style :")
  56         self.ctrl_heading = wx.Choice(self, -1, choices=["Without heading",
  57                                                          "Style 1",
  58                                                          "Style 2",
  59                                                          "Style 3",
  60                                                          "Style 4",
  61                                                          "Style 5"])
  62         self.ctrl_heading.SetSelection(5)
  63 
  64         # Examples
  65         self.box_examples_staticbox = wx.StaticBox(self, -1, "Pages")
  66         self.label_intro = wx.StaticText(self, -1, "Just write a text per line :")
  67         self.ctrl_pages = wx.TextCtrl(self, -1, PAGES, style=wx.TE_MULTILINE)
  68 
  69         # Layout
  70         grid_sizer_base = wx.FlexGridSizer(2, 1, 10, 10)
  71         grid_sizer_parameters = wx.FlexGridSizer(1, 2, 10, 10)
  72 
  73         box_preview = wx.StaticBoxSizer(self.box_preview_staticbox, wx.VERTICAL)
  74         box_preview.Add(self.ctrl_newsticker, 1, wx.ALL|wx.EXPAND, 10)
  75         grid_sizer_base.Add(box_preview, 1, wx.LEFT|wx.RIGHT|wx.TOP|wx.EXPAND, 10)
  76 
  77         box_properties = wx.StaticBoxSizer(self.box_properties_staticbox, wx.VERTICAL)
  78         grid_sizer_properties = wx.FlexGridSizer(7, 2, 10, 10)
  79         grid_sizer_properties.Add(self.label_fcolor, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL, 0)
  80         grid_sizer_properties.Add(self.ctrl_fcolor, 0, 0, 0)
  81         grid_sizer_properties.Add(self.label_bcolor, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL, 0)
  82         grid_sizer_properties.Add(self.ctrl_bcolor, 0, 0, 0)
  83         grid_sizer_properties.Add(self.label_font, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL, 0)
  84         grid_sizer_properties.Add(self.ctrl_font, 0, 0, 0)
  85         grid_sizer_properties.Add(self.label_fps, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL, 0)
  86         grid_sizer_properties.Add(self.ctrl_fps, 0, wx.EXPAND, 0)
  87         grid_sizer_properties.Add(self.label_ppf, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL, 0)
  88         grid_sizer_properties.Add(self.ctrl_ppf, 0, wx.EXPAND, 0)
  89         grid_sizer_properties.Add(self.label_pause, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL, 0)
  90         grid_sizer_properties.Add(self.ctrl_pause, 0, 0, 0)
  91         grid_sizer_properties.Add(self.label_heading, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL, 0)
  92         grid_sizer_properties.Add(self.ctrl_heading, 0, 0, 0)
  93         box_properties.Add(grid_sizer_properties, 1, wx.ALL|wx.EXPAND, 10)
  94         grid_sizer_parameters.Add(box_properties, 1, wx.EXPAND, 0)
  95 
  96         box_examples = wx.StaticBoxSizer(self.box_examples_staticbox, wx.VERTICAL)
  97         box_examples.Add(self.label_intro, 0, wx.ALL|wx.EXPAND, 10)
  98         box_examples.Add(self.ctrl_pages, 1, wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.EXPAND, 10)
  99 
 100         grid_sizer_parameters.Add(box_examples, 1, wx.EXPAND, 0)
 101         grid_sizer_parameters.AddGrowableCol(1)
 102         grid_sizer_base.Add(grid_sizer_parameters, 1, wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.EXPAND, 10)
 103 
 104         self.SetSizer(grid_sizer_base)
 105         grid_sizer_base.Fit(self)
 106         grid_sizer_base.AddGrowableCol(0)
 107 
 108         # Binds
 109         self.Bind(csel.EVT_COLOURSELECT, self.OnChangeForegroundColor, self.ctrl_fcolor)
 110         self.Bind(csel.EVT_COLOURSELECT, self.OnChangeBackgroundColor, self.ctrl_bcolor)
 111         self.Bind(wx.EVT_FONTPICKER_CHANGED, self.OnChangeFont, self.ctrl_font)
 112         self.Bind(wx.EVT_COMMAND_SCROLL, self.OnChangePPS, self.ctrl_fps)
 113         self.Bind(wx.EVT_COMMAND_SCROLL, self.OnChangePPF, self.ctrl_ppf)
 114         self.Bind(wx.EVT_SPINCTRL, self.OnChangePause, self.ctrl_pause)
 115         self.Bind(wx.EVT_CHOICE, self.OnChangeHeading, self.ctrl_heading)
 116         self.Bind(wx.EVT_TEXT, self.OnChangePages, self.ctrl_pages)
 117 
 118         # Init pages
 119         self.ctrl_newsticker.SetPages(self.GetPages())
 120 
 121     #---------------------------------------------------------------------------
 122 
 123     def OnChangeForegroundColor(self, event):
 124         self.ctrl_newsticker.SetForegroundColour(event.GetValue())
 125 
 126     def OnChangeBackgroundColor(self, event):
 127         self.ctrl_newsticker.SetBackgroundColour(event.GetValue())
 128 
 129     def OnChangeFont(self, event):
 130         self.ctrl_newsticker.SetFont(event.GetFont())
 131 
 132     def OnChangePPS(self, event):
 133         self.ctrl_newsticker.SetFPS(event.GetPosition())
 134 
 135     def OnChangePPF(self, event):
 136         self.ctrl_newsticker.SetPPF(event.GetPosition())
 137 
 138     def OnChangePause(self, event):
 139         self.ctrl_newsticker.SetPauseTime(event.EventObject.GetValue()*1000)
 140 
 141     def OnChangeHeading(self, event):
 142         self.ctrl_newsticker.SetHeadingStyle(event.GetSelection())
 143 
 144     def OnChangePages(self, event):
 145         self.ctrl_newsticker.SetPages(self.GetPages())
 146 
 147     def GetPages(self):
 148         text = self.ctrl_pages.GetValue()
 149         if len(text) == 0 :
 150             listePages = []
 151         else :
 152             listePages = text.split("\n")
 153         return listePages
 154 
 155 #-------------------------------------------------------------------------------
 156 
 157 class DemoFrame(wx.Frame):
 158     def __init__(self, *args, **kwds):
 159         wx.Frame.__init__(self, *args, **kwds)
 160         self.SetMinSize((720, 600))
 161         panel = DemoPanel(self)
 162         self.Layout()
 163         self.CenterOnScreen()
 164 
 165 #-------------------------------------------------------------------------------
 166 
 167 if __name__ == '__main__':
 168     app = wx.App(0)
 169     frame_1 = DemoFrame(None, -1, "DEMO Newsticker")
 170     app.SetTopWindow(frame_1)
 171     frame_1.Show()
 172     app.MainLoop()


img_sample_two.png

   1 # newsticker.py
   2 
   3 #-------------------------------------------------------------------------------
   4 # Site internet :  www.noethys.com
   5 # Auteur:          Noethys
   6 # Copyright:       (c) 2012 Noethys
   7 # Licence:         Licence wxWidgets
   8 # Based on wx.lib.ticker by Chris Mellon
   9 #-------------------------------------------------------------------------------
  10 
  11 import wx
  12 from wx.lib.wordwrap import wordwrap
  13 
  14 # class Newsticker
  15 
  16 #-------------------------------------------------------------------------------
  17 
  18 class Newsticker(wx.Control):
  19     def __init__(self,
  20             parent,
  21             id=-1,
  22             pages=[],                       # list of pages or a text
  23             fgcolor = wx.BLACK,             # text/foreground color
  24             bgcolor = None,                 # background color
  25             start=True,                     # if True, the newsticker starts immediately
  26             ppf=2,                          # pixels per frame
  27             fps=20,                         # frames per second
  28             pauseTime = 2000,               # Pause time (in milliseconds)
  29             headingStyle = 5,               # Style of heading  : 1, 2, 3, 4 ou 5 or None for no heading
  30             pos=wx.DefaultPosition,
  31             size=wx.DefaultSize,
  32             style=wx.NO_BORDER,
  33             name="Newsticker"
  34         ):
  35         wx.Control.__init__(self, parent, id=id, pos=pos, size=size, style=style, name=name)
  36         self.timer = wx.Timer(self, -1)
  37         self.timerPause = wx.Timer(self, -1)
  38         self.textSize = (-1, -1)
  39         self.decalage = 0
  40         self._fps = fps
  41         self._ppf = ppf
  42         self.pauseTime = pauseTime
  43         self.pause = False
  44         self.pauseTemp = False
  45         self.indexPage = 0
  46         self.headingStyle = headingStyle
  47         self.headingHeight = 0
  48         self.SetPages(pages)
  49         self.SetInitialSize(size)
  50 
  51         if fgcolor != None :
  52             self.SetForegroundColour(fgcolor)
  53         if bgcolor != None :
  54             self.SetBackgroundColour(bgcolor)
  55 
  56         self.Bind(wx.EVT_TIMER, self.OnTick, self.timer)
  57         self.Bind(wx.EVT_TIMER, self.OnPause, self.timerPause)
  58         self.Bind(wx.EVT_PAINT, self.OnPaint)
  59         self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnErase)
  60         self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter)
  61         self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
  62         if start:
  63             self.Start()
  64 
  65     #---------------------------------------------------------------------------
  66 
  67     def SetPages(self, pages=[], restart=False):
  68         """ Set Pages to display """
  69         if restart == True :
  70             self.Restart()
  71         self.indexPage = 0
  72         self.listePages = []
  73         if type(pages) in (list, tuple) :
  74             if len(pages) == 0 :
  75                 pages = [wx.EmptyString,]
  76             self.listePages = pages
  77         else :
  78              self.listePages = [pages,]
  79         self.SetText(self.listePages[0])
  80 
  81     def OnEnter(self, event):
  82         """ When mouse enters control """
  83         if self.timerPause.IsRunning() :
  84             self.timerPause.Stop()
  85         self.pause = True
  86 
  87     def OnLeave(self, event):
  88         """ When mouse leaves control """
  89         if self.timerPause.IsRunning() == False :
  90             self.pause = False
  91 
  92     def Stop(self):
  93         """Stop moving the text"""
  94         self.timer.Stop()
  95 
  96     def Start(self):
  97         """Starts the text moving"""
  98         if not self.timer.IsRunning():
  99             self.timer.Start(int(1000 / self._fps))
 100 
 101     def IsTicking(self):
 102         """Is the ticker ticking? ie, is the text moving?"""
 103         return self.timer.IsRunning()
 104 
 105     def SetFPS(self, fps):
 106         """Adjust the update speed of the ticker"""
 107         self._fps = fps
 108         self.Stop()
 109         self.Start()
 110 
 111     def GetFPS(self):
 112         """Update speed of the ticker"""
 113         return self._fps
 114 
 115     def SetPPF(self, ppf):
 116         """Set the number of pixels per frame the ticker moves - ie, how "jumpy" it is"""
 117         self._ppf = ppf
 118 
 119     def GetPPF(self):
 120         """Pixels per frame"""
 121         return self._ppf
 122 
 123     def SetFont(self, font):
 124         """ Set Font """
 125         self.textSize = (-1, -1)
 126         wx.Control.SetFont(self, font)
 127 
 128     def SetPauseTime(self, milliseconds=2000):
 129         """ Set pause time in milliseconds """
 130         self.pauseTime = milliseconds
 131 
 132     def SetHeadingStyle(self, num=5):
 133         """ Set heading style : None, 1, 2, 3, 4, or 5 """
 134         self.headingStyle = num
 135 
 136     def SetText(self, text):
 137         """Set the ticker text."""
 138         self._text = text
 139         self.textSize = (-1, -1)
 140         if not self._text:
 141             self.Refresh()
 142 
 143     def GetText(self):
 144         """ Return actual page """
 145         return self._text
 146 
 147     def GetNextPage(self):
 148         """ Get the next page to display """
 149         if self.indexPage == len(self.listePages) - 1 :
 150             self.indexPage = 0
 151         else :
 152             self.indexPage += 1
 153         return self.listePages[self.indexPage]
 154 
 155     def UpdateExtent(self, dc, texte=""):
 156         """Updates the cached text extent if needed"""
 157         if not texte:
 158             self.textSize = (-1, -1)
 159             return
 160         largeurBloc, hauteurBloc, hauteurLigne = dc.GetFullMultiLineTextExtent(texte, dc.GetFont())
 161         self.textSize = (largeurBloc, hauteurBloc)
 162 
 163     def DrawText(self, dc):
 164         """Draws the ticker text at the current offset using the provided DC"""
 165         defaultFont = self.GetFont()
 166         dc.SetFont(defaultFont)
 167 
 168         # Extrait le titre s'il y en a un
 169         titre = ""
 170         try :
 171             if self._text.startswith("<t>") and "</t>" in self._text :
 172                 position = self._text.index("</t>")
 173                 titre = self._text[3:position]
 174                 texte = self._text[position+4:]
 175             else :
 176                 texte = self._text
 177         except :
 178             texte = ""
 179 
 180         # Adapte la longueur du texte
 181         texte = wordwrap(texte, self.GetSize()[0], dc, breakLongWords=True)
 182         self.UpdateExtent(dc, texte)
 183 
 184         # Calcule le decalage a appliquer
 185         y = self.GetSize()[1] - self.decalage
 186 
 187         # Titre
 188         self.headingHeight = 0
 189         if len(titre) > 0 :
 190 
 191             # Titre - Style 1
 192             if self.headingStyle == 1 :
 193                 dc.SetBrush(wx.Brush((200, 200, 200)))
 194                 dc.SetPen(wx.TRANSPARENT_PEN)
 195                 dc.DrawRectangle(0, y+8, dc.GetTextExtent(titre)[0] + 20, 10)
 196                 dc.SetFont(wx.Font(6, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, ""))
 197                 dc.SetTextForeground(self.GetBackgroundColour())
 198                 dc.DrawText(titre, 10, y+6)
 199                 self.headingHeight = 21
 200 
 201             # Titre - Style 2
 202             if self.headingStyle == 2 :
 203                 dc.SetBrush(wx.Brush((200, 200, 200)))
 204                 dc.SetPen(wx.TRANSPARENT_PEN)
 205                 dc.SetFont(wx.Font(6, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, ""))
 206                 dc.DrawRectangle(0, y+13, dc.GetTextExtent(titre)[0] + 15, 1)
 207                 dc.SetTextForeground((200, 200, 200))
 208                 dc.DrawText(titre, 0, y)
 209                 self.headingHeight = 16
 210 
 211             # Titre - Style 3
 212             if self.headingStyle == 3 :
 213                 dc.SetBrush(wx.Brush((200, 200, 200)))
 214                 dc.SetPen(wx.Pen((200, 200, 200)))
 215                 dc.SetFont(wx.Font(6, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, ""))
 216                 largeurTitre, hauteurTitre = dc.GetTextExtent(titre)
 217                 yTitre = 7
 218                 dc.DrawLine(0, y+yTitre, 10, y+yTitre)
 219                 dc.DrawLine(largeurTitre + 14, y+yTitre, largeurTitre + 24, y+yTitre)
 220                 dc.SetTextForeground((200, 200, 200))
 221                 dc.DrawText(titre, 12, y)
 222                 self.headingHeight = 14
 223 
 224             # Titre - Style 4
 225             if self.headingStyle == 4 :
 226                 dc.SetBrush(wx.Brush((200, 200, 200)))
 227                 dc.SetPen(wx.Pen((200, 200, 200)))
 228                 dc.SetFont(wx.Font(6, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, ""))
 229                 dc.DrawRectangle(2, y+5, 3, 3)
 230                 dc.SetTextForeground((200, 200, 200))
 231                 dc.DrawText(titre, 7, y)
 232                 self.headingHeight = 12
 233 
 234             # Titre - Style 5
 235             if self.headingStyle == 5 :
 236                 dc.SetFont(wx.Font(6, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, ""))
 237                 dc.SetTextForeground((200, 200, 200))
 238                 dc.DrawText(titre, 0, y)
 239                 self.headingHeight = 13
 240 
 241         # Texte
 242         dc.SetTextForeground(self.GetForegroundColour())
 243         dc.SetFont(defaultFont)
 244         dc.DrawLabel(texte, wx.Rect(x=0, y=y+self.headingHeight, width=self.GetSize()[0], height=self.GetSize()[1]))
 245 
 246     def OnPause(self, event):
 247         self.pause = False
 248         self.pauseTemp = True
 249         self.timerPause.Stop()
 250 
 251     def Restart(self):
 252         """ Restart the loop """
 253         self.timerPause.Stop()
 254         self.decalage = 0
 255         self.pauseTemp = False
 256         self.pause = False
 257 
 258     def OnTick(self, evt):
 259         """ Calcul du decalage """
 260         # Defilement continu
 261         if self.pause == False :
 262             self.decalage += self._ppf
 263             yHautBloc = self.GetSize()[1] - self.decalage + self.headingHeight
 264             yBasBloc = yHautBloc + self.textSize[1]
 265             if yBasBloc < 0 :
 266                 self.decalage = 0
 267                 self.pauseTemp = False
 268                 # Changement de texte
 269                 self.SetText(self.GetNextPage())
 270 
 271         # Calcul du decalage
 272         yHautBloc = self.GetSize()[1] - self.decalage
 273         yBasBloc = yHautBloc + self.textSize[1] + self.headingHeight
 274 
 275         # Pause
 276         if self.pauseTime > 0  and yHautBloc < 2 and self.pause == False and self.pauseTemp == False :
 277             self.pause = True
 278             self.pauseTemp = True
 279             self.timerPause.Start(self.pauseTime)
 280 
 281         self.Refresh()
 282 
 283     def OnPaint(self, evt):
 284         dc = wx.BufferedPaintDC(self)
 285         brush = wx.Brush(self.GetBackgroundColour())
 286         dc.SetBackground(brush)
 287         dc.Clear()
 288         self.DrawText(dc)
 289 
 290     def OnErase(self, evt):
 291         """Noop because of double buffering"""
 292         pass
 293 
 294     def AcceptsFocus(self):
 295         """Non-interactive, so don't accept focus"""
 296         return False
 297 
 298     def DoGetBestSize(self):
 299         """Width we don't care about, height is either -1, or the character
 300         height of our text with a little extra padding
 301         """
 302         return (100, 50)
 303 
 304     def ShouldInheritColours(self):
 305         """Don't get colours from our parent..."""
 306         return False
 307 
 308 #-------------------------------------------------------------------------------
 309 
 310 if __name__ == '__main__':
 311     """ DEMO FRAME"""
 312     app = wx.App()
 313     f = wx.Frame(None)
 314     p = wx.Panel(f)
 315     pages = ["<t>PAGE 1</t>This is the first page.",
 316              "<t>PAGE 2</t>This is the second page\nwith multiline text.",
 317              "This page is without heading"]
 318     t = Newsticker(p, pages=pages)
 319     s = wx.BoxSizer(wx.VERTICAL)
 320     s.Add(t, flag=wx.GROW, proportion=1)
 321     p.SetSizer(s)
 322     f.Show()
 323     app.MainLoop()


Download source

source.zip


Additional Information

Link :

https://groups.google.com/forum/#!topic/wxpython-users/HKEN12uCPnI

https://www.noethys.com/

- - - - -

https://wiki.wxpython.org/TitleIndex

https://docs.wxpython.org/


Thanks to

Noethys (demo.py and newsticker.py coding), the wxPython community...


About this page

Date (d/m/y) Person (bot) Comments :

4/08/19 - Ecco (Created page and updated example for wxPython Phoenix).


Comments

- blah, blah, blah....

How to create a vertical news-ticker (Phoenix) (last edited 2020-12-17 17:05:11 by Ecco)

NOTE: To edit pages in this wiki you must be a member of the TrustedEditorsGroup.