Python 创建gtk.窗口的屏幕截图

Python 创建gtk.窗口的屏幕截图,python,gtk,pygtk,screenshot,Python,Gtk,Pygtk,Screenshot,出于测试和文档的目的,我想创建一个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

出于测试和文档的目的,我想创建一个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.Drawable继承的gtk.gdk.Window

我的问题是:

  • 是否有其他方法使Pixbuf对象捕获窗口
  • 我可以将gtk.Window设置为gtk.gdk.Window还是在gtk.Window中找到gtk.gdk.Window功能

要理解的关键区别在于,
gtk.gdk.Window
不是大多数人想象中的“窗口”。它不是一个GUI元素,它只是屏幕的一部分,作为逻辑显示区域,如图所示。这样,它更像是一个低级对象

gtk.Window
对象(传统的“Window”)包含一个
Window
属性,该属性是
gtk.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\u add
的调用替换为对
drawWindow(win)
的简单调用

请注意,此方法保存GTK窗口的图像,但不保存窗口的边框(因此,没有标题栏)

另外,作为补充,在定义使用
gobject.timeout\u add
调用的函数时,实际上不必显式地使用
return False
,只需将其求值为等于
0
的值即可。如果函数中没有
return
语句,则默认情况下Python return
None
,因此默认情况下只调用一次函数。如果希望在另一个间隔后再次调用该函数,请返回
True

使用信号 正如liberforce所指出的,与使用超时不同,更好的方法是连接到GTK用来传递事件(以及一般状态变化)的信号

任何继承自
gobject.gobject
(所有小部件基本上都是这么做的)的东西都有一个
connect()
函数,用于用给定信号注册回调。我尝试了几个信号,似乎我们想要使用的是
expose event
,它在小部件需要重新绘制时(包括第一次绘制时)发生。因为我们希望在回调发生之前绘制窗口,所以我们将使用
connect\u 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()

回答得很好。你解释的细节超出了我的要求;你还加了一些其他有用的东西。非常感谢。当我们确定gtk.gdk.Window已经创建时,超时应该由与信号的连接来代替。可能是
实现
映射事件
,或类似的东西。@liberforce:是的,这是一个很好的观点。我更新了答案,加入了更多关于信号的信息。谢谢@voithos:我用了你的代码,但它不适用于alpha通道。请参阅我的问题:请注意,您不需要全局变量-只需使用lambda将
handlerId
传递给回调即可获得后期绑定。例如:
handlerId=win.connect\u在('expose-event',drawWindow,lambda:handlerId)
之后。
drawWindow
然后获得一个额外的
getId
参数,并以
win.disconnect(getId())
结束。这避免了全局变量,而不需要整个专用类。