wxPython和pycairo练习记录13

编程入门 行业动态 更新时间:2024-10-09 06:28:13

<a href=https://www.elefans.com/category/jswz/34/1760781.html style=wxPython和pycairo练习记录13"/>

wxPython和pycairo练习记录13

实现更美观的滚动字幕效果

之前虽然通过设置窗口样式去掉了标题栏,但是背景看起来还是有些碍眼。通过 SetTransparent 只能调整整体透明度,文字和背景都透明了。希望实现的效果是背景透明,而文字不透明。

透明背景的文字窗体

wxPython demo 中有两个实现形状窗口的例子,ShapedWindow 和 AdvancedSplash,其实都是通过 SetShape(wx.Region) 实现的。region 翻译为区域,不太理解,还有另一个签名 SetShape(wx.GraphicsPath),所以实际是设置像 PS 里的路径?

没有找到怎么用文字来设置形状,所以把文字先转为图像。通过 wx.GraphicsContext 创建透明背景文字 Bitmap,把图像设置为 Frame 的 shape。显示文字会有明显锯齿,且当字体设置较小时,文字周围会有黑边。

在 OnPaint 中绘制一个文字颜色的同窗口大小的矩形,文字黑边消失。后绘制的矩形是显示在文字上层,文字图片变成蒙版了?

效果:

源码:

# -*- coding: utf-8 -*-
import wxclass MyFrame(wx.Frame):def __init__(self, *args, **kwargs):super(MyFrame, self).__init__(*args, **kwargs)self.Center()w, h = self.GetSize()self.bg = wx.Bitmap.FromRGBA(w, h)dc = wx.MemoryDC(self.bg)gc = wx.GraphicsContext.Create(dc)font = wx.Font(32, family=wx.SWISS, style=wx.NORMAL, weight=wx.BOLD, faceName=u"宋体")gc.SetFont(font, "#ff0000")gc.DrawText(u"你好,世界!Hello World!😍", 0, 0)  # emoji 不能显示region = wx.Region(self.bg, wx.TransparentColour)self.SetShape(region)self.Bind(wx.EVT_PAINT,self.onPaint)self.SetTransparent(150)def onPaint(self, evt):dc= wx.PaintDC(self)dc.DrawBitmap(self.bg, 0, 0, True)dc.SetBrush(wx.Brush(wx.RED))dc.DrawRectangle(0, 0, *self.GetSize())app = wx.App()
frame = MyFrame(parent=None,size=(1075, 53),style=wx.FRAME_SHAPED | wx.NO_BORDER | wx.STAY_ON_TOP | wx.TRANSPARENT_WINDOW)
frame.Show()
app.MainLoop()

还有一种方法,直接通过 wx.ScreenDC 在电脑屏幕上任意位置绘制文字,不需要交互的话应该是可以的。脱离了程序窗口,如果和鼠标、键盘交互,要怎么处理呢?

锯齿问题待解决,使用 SetAntialiasMode 方法设置没有效果。抗锯齿参考帖子

wx.GraphicsContext wraps GDI+, which is a system API, so it is still dependent on the system settings for AA. You could try using wx.lib.graphics.GraphicsContext instead, it uses Cairo on all the platforms instead of the native APIs.
来源:

大略就是自带的 GDI 是调用系统 API,需要使用优化的第三方库。后续再尝试。

整合滚动字幕

向歌词工具进发?下面是某音乐播放器的歌词显示效果。SetShape 方法传递一个空的 wx.Region 就能使原本的矩形窗口显示出来,可以用来实现歌词的鼠标移入移出效果。文字描边怎么实现的?还是先继续整合滚动弹幕吧。

wx.lib.ticker.Ticker 类是继承自 wx.Control , 而 SetShape 方法是继承自 wx.NonOwnedWindow,不能作用于 Ticker。直接继承 wx.Frame 重写 Ticker 控件?好像并不需要做很大改动,直接把基类改一下就能用。

还有些待完善:1.多线程用法;2.清空形状图像(如果用 wx.GCDC 的 Clear 也会显示矩形窗体);3.文字窗体用鼠标点击时,文字镂空范围是选择不到的,需要歌词那样的效果。

源码:

# -*- coding: utf-8 -*-
# Author: SmileBasic
import threading
import wx
import tickerclass Ticker(ticker.Ticker):def __init__(self, *args, **kwargs):self._text = []  # 滚动字符串存储列表,格式[[字符串尺寸, 字符串], [(56, 22), "test"]]self._text_max = 500  # _text 最大可存储字符串数量self._gap = 0  # 滚动字符串之间间距self._gap_pixels = 0self._index = 0  # 显示字符串头节点self._num = 1  # 显示字符串数量self._dc = wx.MemoryDC()  # 用于获取文本尺寸super(Ticker, self).__init__(*args, **kwargs)self.Center()self._bg = wx.Bitmap.FromRGBA(*self.GetSize())self._gc = wx.GraphicsContext.Create(wx.MemoryDC(self._bg))self._fgcolor = kwargs["fgcolor"]self._gc.SetFont(self.GetFont(), self._fgcolor)# 需要一个初始形状,不然会显示原矩形窗体self._gc.DrawText("SmileBasic", 0, 0)region = wx.Region(self._bg, wx.TransparentColour)self.SetShape(region)self.Bind(wx.EVT_RIGHT_UP, self.OnClose)self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)self.Bind(wx.EVT_MOTION, self.OnMouseMove)def SetFont(self, font):"""设置控件使用的字体"""wx.Frame.SetFont(self, font)self._dc.SetFont(font)self._gc.SetFont(font, self._fgcolor)self.SetGap(self._gap)# 更新存储文本尺寸for text in self._text:text[0] = self._dc.GetTextExtent(text[1])def SetText(self, text):"""将字符串插入滚动字符串列表尾部"""if text.strip():lens = len(self._text)if lens >= self._text_max:if self._index != 0:self._text = self._text[self._index % lens:]self._index = 0else:self._text.pop(0)size = self._dc.GetTextExtent(text)self._text.append([size, text])self.Refresh()def SetGap(self, gap):"""设置字符串显示间距"""self._gap = gapself._gap_pixels = self._dc.GetTextExtent(self._gap * " ")[0]def UpdateExtent(self, dc):"""更新字符串尺寸"""passdef OnTick(self, evt):"""更新滚动文字坐标"""if not self._text:returnself._offset += self._ppf  # offset 偏移,ppf(pixels per frame) 每帧滚动的像素# 头节点滚出显示区w1 = self.GetSize()[0]w2 = self._text[self._index][0][0]if self._offset >= w1 + w2:self._offset = self._offset - w2 - self._gap_pixelsself._index = (self._index + 1) % len(self._text)self._num -= 1# 尾节点与显示区尾部相距超过指定间距w3 = sum([self._text[i % len(self._text)][0][0] for i in range(self._index, self._index + self._num)])w3 = w3 + self._gap_pixels * self._numif self._offset >= w3:self._num += 1self.Refresh()def OnPaint(self, evt):"""PAINT事件处理"""dc = wx.BufferedPaintDC(self)brush = wx.Brush(self.GetBackgroundColour())dc.SetBackground(brush)dc.Clear()dc.DrawBitmap(self._bg, 0, 0, True)dc.SetBrush(wx.Brush(self._fgcolor))dc.DrawRectangle(0, 0, *self.GetSize())self.DrawText(dc)def DrawText(self, dc):"""绘制滚动文字"""if not self._text or self._offset <= 0:return# 笨办法重置 bitmapself._bg = wx.Bitmap.FromRGBA(*self.GetSize())self._gc = wx.GraphicsContext.Create(wx.MemoryDC(self._bg))self._gc.SetFont(self.GetFont(), self._fgcolor)if self._dir == "ltr":offx = self._offset - self._text[self._index][0][0]fix_dir = -1fix_size = 1else:offx = self.GetSize()[0] - self._offsetfix_dir = 1fix_size = 0lens = len(self._text)for i in range(self._index, self._index + self._num):offy = (self.GetSize()[1] - self._text[i % lens][0][1]) / 2self._gc.DrawText(self._text[i % lens][1], offx, offy)if i < self._index + self._num - 1:offx = offx + self._text[(i + fix_size) % lens][0][0] * fix_dir + self._gap_pixels * fix_dirregion = wx.Region(self._bg, wx.TransparentColour)# SetShape 会阻塞主线程# wx.CallAfter(self.SetShape, region)t = threading.Thread(target=wx.CallAfter, args=(self.SetShape, region))t.start()def OnClose(self, evt):self.Close()def OnLeftDown(self, evt):self.CaptureMouse()x, y = self.ClientToScreen(evt.GetPosition())originx, originy = self.GetPosition()dx = x - originxdy = y - originyself.delta = ((dx, dy))def OnLeftUp(self, evt):if self.HasCapture():self.ReleaseMouse()def OnMouseMove(self, evt):if evt.Dragging() and evt.LeftIsDown():x, y = self.ClientToScreen(evt.GetPosition())fp = (x - self.delta[0], y - self.delta[1])self.Move(fp)class App(wx.App):def OnInit(self):tk = Ticker(parent=None, id=-1, text="你好,世界!Hello World!",fgcolor="#ff0000", bgcolor="#ffff00", start=True,ppf=2, fps=20, direction="rtl",size=(1075, 53),style=wx.FRAME_SHAPED | wx.NO_BORDER | wx.STAY_ON_TOP | wx.TRANSPARENT_WINDOW,name="Ticker")tk.SetFont(wx.Font(24, family=wx.SWISS, style=wx.NORMAL, weight=wx.BOLD, faceName=u"宋体"))tk.SetGap(5)tk.SetText("CSDN")tk.SetText("SmileBasic")tk.Start()tk.Show()return Truedef OnExit(self):return 0if __name__ == "__main__":app = App()app.MainLoop()
# ticker.py
#----------------------------------------------------------------------
# Name:        wx.lib.ticker
# Purpose:     A news-ticker style scrolling text control
#
# Author:      Chris Mellon
#
# Created:     29-Aug-2004
# Copyright:   (c) 2004 by Chris Mellon
# Licence:     wxWindows license
# Tags:        phoenix-port, unittest, documented, py3-port
#----------------------------------------------------------------------"""
News-ticker style scrolling text control* Can scroll from right to left or left to right.* Speed of the ticking is controlled by two parameters:- Frames per Second(FPS): How many times per second the ticker updates- Pixels per Frame(PPF): How many pixels the text moves each updateLow FPS with high PPF will result in "jumpy" text, lower PPF with higher FPS
is smoother (but blurrier and more CPU intensive) text.
"""import wx#----------------------------------------------------------------------class Ticker(wx.Frame):def __init__(self,parent,id=-1,text=wx.EmptyString,fgcolor = wx.BLACK,bgcolor = wx.WHITE,start=True,ppf=2,fps=20,direction="rtl",pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.NO_BORDER,name="Ticker"):"""Default class constructor.:param wx.Window `parent`: the parent:param integer `id`: an identifier for the control: a value of -1 is taken to mean a default:param string `text`: text in the ticker:param wx.Colour `fgcolor`: text/foreground color:param wx.Colour `bgcolor`: background color:param boolean `start`: if True, the ticker starts immediately:param int `ppf`: pixels per frame:param int `fps`: frames per second:param `direction`: direction of ticking, 'rtl' or 'ltr':param wx.Point `pos`: the control position. A value of (-1, -1) indicates a default position,chosen by either the windowing system or wxPython, depending on platform:param `name`: the control name"""wx.Frame.__init__(self, parent, id=id, pos=pos, size=size, style=style, name=name)self.timer = wx.Timer(owner=self)self._extent = (-1, -1)  #cache value for the GetTextExtent callself._offset = 0self._fps = fps  #frames per secondself._ppf = ppf  #pixels per frameself.SetDirection(direction)self.SetText(text)self.SetInitialSize(size)self.SetForegroundColour(fgcolor)self.SetBackgroundColour(bgcolor)self.Bind(wx.EVT_TIMER, self.OnTick)self.Bind(wx.EVT_PAINT, self.OnPaint)self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnErase)if start:self.Start()def Stop(self):"""Stop moving the text"""self.timer.Stop()def Start(self):"""Starts the text moving"""if not self.timer.IsRunning():self.timer.Start(1000 // self._fps)def IsTicking(self):"""Is the ticker ticking? ie, is the text moving?"""return self.timer.IsRunning()def SetFPS(self, fps):"""Adjust the update speed of the ticker.:param int `fps`: frames per second."""self._fps = fpsself.Stop()self.Start()def GetFPS(self):"""Get the frames per second speed of the ticker."""return self._fpsdef SetPPF(self, ppf):"""Set the number of pixels per frame the ticker moves - ie,how "jumpy" it is.:param int `ppf`: the pixels per frame setting."""self._ppf = ppfdef GetPPF(self):"""Get pixels per frame setting."""return self._ppfdef SetFont(self, font):"""Set the font for the control.:param wx.Font `font`: the font to be used."""self._extent = (-1, -1)wx.Control.SetFont(self, font)def SetDirection(self, dir):"""Sets the direction of the ticker: right to left (rtl) orleft to right (ltr).:param `dir`: the direction 'rtl' or 'ltr'"""if dir == "ltr" or dir == "rtl":if self._offset != 0:#Change the offset so it's correct for the new directionself._offset = self._extent[0] + self.GetSize()[0] - self._offsetself._dir = direlse:raise TypeErrordef GetDirection(self):"""Get the set direction."""return self._dirdef SetText(self, text):"""Set the ticker text.:param string `text`: the ticker text"""self._text = textself._extent = (-1, -1)if not self._text:self.Refresh() #Refresh here to clear away the old text.def GetText(self):"""Get the current ticker text."""return self._textdef UpdateExtent(self, dc):"""Updates the cached text extent if needed.:param wx.DC `dc`: the dc to use."""if not self._text:self._extent = (-1, -1)returnif self._extent == (-1, -1):self._extent = dc.GetTextExtent(self.GetText())def DrawText(self, dc):"""Draws the ticker text at the current offset using the provided DC.:param wx.DC `dc`: the dc to use."""dc.SetTextForeground(self.GetForegroundColour())dc.SetFont(self.GetFont())self.UpdateExtent(dc)if self._dir == "ltr":offx = self._offset - self._extent[0]else:offx = self.GetSize()[0] - self._offsetoffy = (self.GetSize()[1] - self._extent[1]) / 2 #centered verticallydc.DrawText(self._text, int(offx), int(offy))def OnTick(self, evt):"""Handles the ``wx.EVT_TIMER`` event for :class:`Ticker`.:param `evt`: a :class:`TimerEvent` event to be processed."""self._offset += self._ppfw1 = self.GetSize()[0]w2 = self._extent[0]if self._offset >= w1+w2:self._offset = 0self.Refresh()def OnPaint(self, evt):"""Handles the ``wx.EVT_PAINT`` event for :class:`Ticker`.:param `evt`: a :class:`PaintEvent` event to be processed."""dc = wx.BufferedPaintDC(self)brush = wx.Brush(self.GetBackgroundColour())dc.SetBackground(brush)dc.Clear()self.DrawText(dc)def OnErase(self, evt):"""Noop because of double bufferingHandles the ``wx.EVT_ERASE_BACKGROUND`` event for :class:`Ticker`.:param `evt`: a :class:`EraseEvent` event to be processed."""passdef AcceptsFocus(self):"""Non-interactive, so don't accept focus"""return Falsedef DoGetBestSize(self):"""Width we don't care about, height is either -1, or the characterheight of our text with a little extra padding"""if self._extent == (-1, -1):if not self._text:h = self.GetCharHeight()else:h = self.GetTextExtent(self.GetText())[1]else:h = self._extent[1]return (100, h+5)def ShouldInheritColours(self):"""Don't get colours from our parent."""return False#testcase/demo
if __name__ == '__main__':app = wx.App()t = Ticker(None, text="Some sample ticker text", size=(1070, 50))t.Center()t.Show()app.MainLoop()

既然已经大致实现了透明背景动画效果窗体,那么做个桌面宠物也是可以的吧。桌面水印、抢购计时…以后再试试。

新的问题,滚动字幕通过视频号工具,添加窗口播放源会有黑色背景,添加进程则整个窗口都不显示,添加桌面倒是可以,但也不适合。

安装 pywin32 库,试试通过 AssociateHandle 关联其他程序窗口,获取并使用它的 DC。但是对系统的记事本有作用,对小程序没有影响。而且使用 dc.Clear 会影响原内容,不使用则在移动窗口时会显示历史文字。

# -*- coding: utf-8 -*-
import win32gui
import wxclass MyFrame(wx.Frame):def __init__(self, *args, **kwargs):super(MyFrame, self).__init__(*args, **kwargs)hld = win32gui.FindWindow(None, u"批量发文.txt - 记事本")print(hld)self.AssociateHandle(hld)self.Bind(wx.EVT_PAINT, self.OnPaint)self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnErase)self.timer = wx.Timer(owner=self)self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)self.timer.Start(100)def OnErase(self, event):passdef Draw(self, dc):w, h = self.GetClientSize()print(w, h)x, y = self.GetPosition()print(x, y)strs = "你好,世界!Hello World!"dc.SetTextForeground(wx.RED)dc.SetFont(wx.Font(32, wx.SWISS, wx.NORMAL, wx.BOLD))tw, th = dc.GetTextExtent(strs)dc.DrawText(strs, (w - tw) // 2, (h - th) // 2)def OnTimer(self, evt):dc = wx.ClientDC(self)# dc.Clear()self.Draw(dc)def OnPaint(self, evt):dc = wx.PaintDC(self)dc.Clear()self.Draw(dc)app = wx.App()
frame = MyFrame(parent=None)
frame.Show()
app.MainLoop()

所以,不懂的太多了,还是向有背景方向美化吧。两种美化效果参考:一种是LED滚动显示屏,一种是比特小队游戏里的商店滚动屏。

更多推荐

wxPython和pycairo练习记录13

本文发布于:2024-02-13 06:15:31,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1757314.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:wxPython   pycairo

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!