How to create a vertical news-ticker (Phoenix)
Keywords : Ticker, News-sticker, Scrolling text control.
Contents
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
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()
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
Additional Information
Link :
https://groups.google.com/forum/#!topic/wxpython-users/HKEN12uCPnI
- - - - -
https://wiki.wxpython.org/TitleIndex
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....