Создание скриншота gtk.Window - PullRequest
       12

Создание скриншота gtk.Window

6 голосов
/ 22 сентября 2011

Для тестирования и документирования я хотел бы создать скриншот объекта 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.gdk.Window в gtk.Window?

1 Ответ

11 голосов
/ 22 сентября 2011

Ключевым отличием для понимания является то, что gtk.gdk.Window не является «окном» в том смысле, о котором думает большинство людей. Это не элемент графического интерфейса, это просто часть экрана, которая действует как логическая область отображения, как объяснено в документации 1003 *. Таким образом, это скорее низкоуровневый объект.

Объект 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 возвращает None по умолчанию, если в функции нет оператора return, поэтому функция будет вызываться только один раз по умолчанию. Если вы хотите, чтобы функция вызывалась снова через другой интервал, верните 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()
...