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

相关文章

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

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

Node.js学习记录(二)

目录 一、express 1、初识express 2、安装express 3、创建并启动web服务器 4、监听 GET&POST 请求、响应内容给客户端 5、获取URL中携带的查询参数 6、获取URL中动态参数 7、静态资源托管 二、工具nodemon 三、express路由 1、express中路由 2、路由的匹配 3、路由模块化 4、路由模块添加前缀 四、中间件

RabbitMQ练习(AMQP 0-9-1 Overview)

1、What is AMQP 0-9-1 AMQP 0-9-1(高级消息队列协议)是一种网络协议,它允许遵从该协议的客户端(Publisher或者Consumer)应用程序与遵从该协议的消息中间件代理(Broker,如RabbitMQ)进行通信。 AMQP 0-9-1模型的核心概念包括消息发布者(producers/publisher)、消息(messages)、交换机(exchanges)、

记录每次更新到仓库 —— Git 学习笔记 10

记录每次更新到仓库 文章目录 文件的状态三个区域检查当前文件状态跟踪新文件取消跟踪(un-tracking)文件重新跟踪(re-tracking)文件暂存已修改文件忽略某些文件查看已暂存和未暂存的修改提交更新跳过暂存区删除文件移动文件参考资料 咱们接着很多天以前的 取得Git仓库 这篇文章继续说。 文件的状态 不管是通过哪种方法,现在我们已经有了一个仓库,并从这个仓

【Rust练习】12.枚举

练习题来自:https://practice-zh.course.rs/compound-types/enum.html 1 // 修复错误enum Number {Zero,One,Two,}enum Number1 {Zero = 0,One,Two,}// C语言风格的枚举定义enum Number2 {Zero = 0.0,One = 1.0,Two = 2.0,}fn m

MySql 事务练习

事务(transaction) -- 事务 transaction-- 事务是一组操作的集合,是一个不可分割的工作单位,事务会将所有的操作作为一个整体一起向系统提交或撤销请求-- 事务的操作要么同时成功,要么同时失败-- MySql的事务默认是自动提交的,当执行一个DML语句,MySql会立即自动隐式提交事务-- 常见案例:银行转账-- 逻辑:A给B转账1000:1.查询

html css jquery选项卡 代码练习小项目

在学习 html 和 css jquery 结合使用的时候 做好是能尝试做一些简单的小功能,来提高自己的 逻辑能力,熟悉代码的编写语法 下面分享一段代码 使用html css jquery选项卡 代码练习 <div class="box"><dl class="tab"><dd class="active">手机</dd><dd>家电</dd><dd>服装</dd><dd>数码</dd><dd