wxPython和pycairo练习记录13

2023-11-20 18:30

本文主要是介绍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 方法设置没有效果。抗锯齿参考帖子 https://discuss.wxpython.org/t/anti-aliased-text-with-wx-graphicscontext/25021/27

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.
来源:https://discuss.wxpython.org/t/anti-aliased-text-with-wx-graphicscontext/25021/4

大略就是自带的 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的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/396589

相关文章

Spring Retry 实现乐观锁重试实践记录

《SpringRetry实现乐观锁重试实践记录》本文介绍了在秒杀商品SKU表中使用乐观锁和MybatisPlus配置乐观锁的方法,并分析了测试环境和生产环境的隔离级别对乐观锁的影响,通过简单验证,... 目录一、场景分析 二、简单验证 2.1、可重复读 2.2、读已提交 三、最佳实践 3.1、配置重试模板

在 Spring Boot 中使用异步线程时的 HttpServletRequest 复用问题记录

《在SpringBoot中使用异步线程时的HttpServletRequest复用问题记录》文章讨论了在SpringBoot中使用异步线程时,由于HttpServletRequest复用导致... 目录一、问题描述:异步线程操作导致请求复用时 Cookie 解析失败1. 场景背景2. 问题根源二、问题详细分

关于Spring @Bean 相同加载顺序不同结果不同的问题记录

《关于Spring@Bean相同加载顺序不同结果不同的问题记录》本文主要探讨了在Spring5.1.3.RELEASE版本下,当有两个全注解类定义相同类型的Bean时,由于加载顺序不同,最终生成的... 目录问题说明测试输出1测试输出2@Bean注解的BeanDefiChina编程nition加入时机总结问题说明

将sqlserver数据迁移到mysql的详细步骤记录

《将sqlserver数据迁移到mysql的详细步骤记录》:本文主要介绍将SQLServer数据迁移到MySQL的步骤,包括导出数据、转换数据格式和导入数据,通过示例和工具说明,帮助大家顺利完成... 目录前言一、导出SQL Server 数据二、转换数据格式为mysql兼容格式三、导入数据到MySQL数据

关于rpc长连接与短连接的思考记录

《关于rpc长连接与短连接的思考记录》文章总结了RPC项目中长连接和短连接的处理方式,包括RPC和HTTP的长连接与短连接的区别、TCP的保活机制、客户端与服务器的连接模式及其利弊分析,文章强调了在实... 目录rpc项目中的长连接与短连接的思考什么是rpc项目中的长连接和短连接与tcp和http的长连接短

Oracle查询优化之高效实现仅查询前10条记录的方法与实践

《Oracle查询优化之高效实现仅查询前10条记录的方法与实践》:本文主要介绍Oracle查询优化之高效实现仅查询前10条记录的相关资料,包括使用ROWNUM、ROW_NUMBER()函数、FET... 目录1. 使用 ROWNUM 查询2. 使用 ROW_NUMBER() 函数3. 使用 FETCH FI

Python MySQL如何通过Binlog获取变更记录恢复数据

《PythonMySQL如何通过Binlog获取变更记录恢复数据》本文介绍了如何使用Python和pymysqlreplication库通过MySQL的二进制日志(Binlog)获取数据库的变更记录... 目录python mysql通过Binlog获取变更记录恢复数据1.安装pymysqlreplicat

Servlet中配置和使用过滤器的步骤记录

《Servlet中配置和使用过滤器的步骤记录》:本文主要介绍在Servlet中配置和使用过滤器的方法,包括创建过滤器类、配置过滤器以及在Web应用中使用过滤器等步骤,文中通过代码介绍的非常详细,需... 目录创建过滤器类配置过滤器使用过滤器总结在Servlet中配置和使用过滤器主要包括创建过滤器类、配置过滤

正则表达式高级应用与性能优化记录

《正则表达式高级应用与性能优化记录》本文介绍了正则表达式的高级应用和性能优化技巧,包括文本拆分、合并、XML/HTML解析、数据分析、以及性能优化方法,通过这些技巧,可以更高效地利用正则表达式进行复杂... 目录第6章:正则表达式的高级应用6.1 模式匹配与文本处理6.1.1 文本拆分6.1.2 文本合并6

python与QT联合的详细步骤记录

《python与QT联合的详细步骤记录》:本文主要介绍python与QT联合的详细步骤,文章还展示了如何在Python中调用QT的.ui文件来实现GUI界面,并介绍了多窗口的应用,文中通过代码介绍... 目录一、文章简介二、安装pyqt5三、GUI页面设计四、python的使用python文件创建pytho