创建gtk.Window的截图

7 投票
1 回答
5137 浏览
提问于 2025-04-17 02:51

为了测试和记录,我想创建一个gtk.Window对象的截图。目前我正在跟着一个基本的pygtk示例。经过我的修改,示例代码看起来是这样的:

import gtk

def main():
    button = gtk.Button("Hello")
    scroll_win = gtk.ScrolledWindow()
    scroll_win.add(button)
    win = gtk.Window(gtk.WINDOW_TOPLEVEL)
    win.add(scroll_win)
    win.show_all()

    if win.is_drawable() is not True:
        raise RuntimeError("Not drawable?")

    width, height = win.get_size()
    pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8,
                            width, height)
    screenshot = pixbuf.get_from_drawable(win, win.get_colormap(),
                                          0, 0, 0, 0, width, height)
    screenshot.save('screenshot.png', 'png')

if __name__ == '__main__':
    main()
    gtk.main()

当我调用get_from_drawable方法时,出现了一个错误。

TypeError: Gdk.Pixbuf.get_from_drawable() argument 1 must be gtk.gdk.Drawable, not gtk.Window

显然,我合并到基本示例中的截图示例使用的是gtk.gdk.Window,这个窗口是从gtk.gdk.Drawable继承而来的。

我有几个问题:

  • 有没有其他方法可以让Pixbuf对象捕捉到这个窗口?
  • 我能否将gtk.Window变成gtk.gdk.Window,或者在gtk.Window中找到gtk.gdk.Window的功能?

1 个回答

11

要理解的关键区别是,gtk.gdk.Window 不是大多数人想象中的“窗口”。它并不是一个图形界面元素,而只是屏幕上的一个区域,充当逻辑显示区,正如文档中所解释的。从这个角度来看,它更像是一个低级对象。

一个 gtk.Window 对象(传统意义上的“窗口”)包含一个 window 属性,这个属性就是 gtk.gdk.Window 对象,它被 gtk.Window 使用。当窗口初始化时(在这个例子中,是在调用 win.show_all() 之后),这个对象就会被创建。

下面是你代码的一个修正版,可以正确保存图像:

import gtk
import gobject

def main():
    button = gtk.Button("Hello")
    scroll_win = gtk.ScrolledWindow()
    scroll_win.add(button)
    win = gtk.Window(gtk.WINDOW_TOPLEVEL)
    win.add(scroll_win)
    win.show_all()

    # Set timeout to allow time for the screen to be drawn
    # before saving the window image
    gobject.timeout_add(1000, drawWindow, win)

def drawWindow(win):
    width, height = win.get_size()
    pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, width, height)

    # Retrieve the pixel data from the gdk.window attribute (win.window)
    # of the gtk.window object
    screenshot = pixbuf.get_from_drawable(win.window, win.get_colormap(), 
                                          0, 0, 0, 0, width, height)

    screenshot.save('screenshot.png', 'png')

    # Return False to stop the repeating interval
    return False

if __name__ == '__main__':
    main()
    gtk.main()

设置超时是必要的,因为即使 gtk.gdk.Window 对象已经创建,屏幕仍然没有被绘制,直到 gtk.main() 开始运行。我认为如果你在 win.show_all() 之后立即读取像素缓冲区,它会保存一张图像,但这张图像将是窗口之后占据的屏幕区域。如果你想自己试试,可以把对 gobject.timeout_add 的调用替换为简单地调用 drawWindow(win)

请注意,这种方法保存的是 GTK 窗口的图像,而不是窗口的边框(所以没有标题栏)。

另外,作为一个附带说明,当定义一个通过 gobject.timeout_add 调用的函数时,你实际上并不需要明确使用 return False,只要它的返回值等于 0 就可以。Python 默认情况下如果函数没有 return 语句,会返回 None,所以默认情况下这个函数只会被调用一次。如果你希望这个函数在另一个时间间隔后再次被调用,可以返回 True

使用信号

正如 liberforce 所提到的,使用信号而不是超时会是一个更好的方法,这样可以连接到 GTK 用于通信事件(以及状态变化)的信号。

任何继承自 gobject.GObject 的东西(基本上所有的控件都是)都有一个 connect() 函数,用于注册一个回调与特定信号。我试了一些信号,似乎我们想用的是 expose-event,这个信号在任何控件需要重绘时都会发生(包括第一次绘制时)。因为我们希望窗口在我们的回调发生之前被绘制,所以我们会使用 connect_after() 变体。

这是修订后的代码:

import gtk

# Don't use globals in a real application,
# better to encapsulate everything in a class
handlerId = 0

def main():
    button = gtk.Button("Hello")
    scroll_win = gtk.ScrolledWindow()
    scroll_win.add(button)
    win = gtk.Window(gtk.WINDOW_TOPLEVEL)
    win.add(scroll_win)

    # Connect to expose signal to allow time
    # for the window to be drawn
    global handlerId 
    handlerId = win.connect_after('expose-event', drawWindow)

    win.show_all()

def drawWindow(win, e):
    width, height = win.get_size()
    pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, width, height)

    # Retrieve the pixel data from the gdk.window attribute (win.window)
    # of the gtk.window object
    screenshot = pixbuf.get_from_drawable(win.window, win.get_colormap(), 
                                          0, 0, 0, 0, width, height)
    screenshot.save('screenshot.png', 'png')

    # Disconnect this handler so that it isn't
    # repeated when the screen needs to be redrawn again
    global handlerId
    win.disconnect(handlerId)

if __name__ == '__main__':
    main()
    gtk.main()

撰写回答