2012-06-14 5 views
4

У меня есть код Gtk+, написанный на C, который делает некоторую анимацию с использованием Cairo и таймера. Большую часть времени, когда я нажимаю на значок закрытия приложения, я получаю следующее сообщение на терминале:Как определить, был ли виджет GTK уничтожен

Gtk-ВАЖНЫЙ **: gtk_widget_queue_draw: утверждение `GTK_IS_WIDGET (виджета)» не удалось

Теперь я предполагаю, что это происходит, потому что на момент закрытия приложения таймер запускается, а виджет главного окна доступен, но с тех пор был уничтожен. Каков обычный метод определения, действительно ли виджет Gtk действителен и на который можно ссылаться?

Нарушитель код здесь:

gboolean rotate_cb(void *degrees) 
{ 
    rotation += DegreesToRadians((*(int*)(degrees))); 
    // Tell our window that it should repaint itself (ie. emit an expose event) 
    /* need to only call gtk_widget_queue_draw() if window is still valid/exists */ 
    gtk_widget_queue_draw(window); 
    return(TRUE); 
} 

Я предполагаю, что должен быть какой-то способ, чтобы проверить, является ли или нет window все еще активен и действует?

ответ

9

Ваша проблема довольно тонкая. Это обычно происходит из-за правил владения и уничтожения GObject/GtkObject. Напомню им:

  • GObject s просто ссылаются на счет. Они уничтожаются, когда счетчик достигает 0. У вновь созданного объекта есть счет 1.
  • GInitiallyUnowned s также ссылаются на счет, и они также уничтожаются, когда счетчик достигает 0. Но вновь созданный объект имеет плавающий счет. Это означает, что в первый раз, когда счетчик должен увеличиваться, он фактически не увеличивается, но плавающий счетчик равен , затонувший, то есть преобразованный в нормальную ссылку.

GtkObject s являютсяGInitiallyUnowned объекты, поэтому у них есть магия плавающей подсчет.

Но вы, наверное, знаете, все это ... Так вот, мой вопрос:

Кто владеет счетчик видимой основной GtkWindow?

Это действительно просто, структура GTK имеет их список и сохраняет ссылку на все видимые GtkWindow.Но затем еще один вопрос:

Когда фрейм GTK освобождает ссылки на его GtkWindow?

Вы помните функцию gtk_widget_destroy() и сигнал destroy? Они предназначены именно для этого: когда вы хотите удалить toplevel GtkWindow, вы вызываете gtk_widget_destroy(), он активирует сигнал destroy, который принимается рамкой GTK, которая удаляет фактическое окно и освобождает его ссылку на объект.

И вот причина вашей проблемы: если структура GTK сохраняет только существующую ссылку на GtkWindow, объект фактически освобождается. Если тогда ваш таймер попытается получить к нему доступ, он не сработает, потому что окна больше нет.

И вот, наконец, приходит (надеюсь) решение:

  • Вызов g_object_ref()/g_object_ref_sink() на окне при запуске таймера. Также зарегистрируйте обработчик сигнала destroy окна.
  • В обработчике сигнала destroy окна: вызовите g_object_unref() на окно и остановите таймер.

Естественно, это частичное решение должно работать, потому что окно не будет уничтожено без отправки destroy сигнала:

  • Регистрация обработчика для destroy сигнала окна.
  • В обработчике сигнала destroy окна: остановить таймер.

Но считается хорошей практикой приращения рефректора объектов, когда вы на самом деле держите указатель на них.

+0

Спасибо, Родриго за ваш очень подробный ответ. Я многому научился и ценю это. – Chimera

+1

Вау, какой отличный ответ. –

+0

Очень воспитательный ответ, +1! – Nelson

2

Нет никакого способа, поскольку вы запрашиваете состояние объекта, который больше не находится в памяти, то есть какой-либо неопределенной части памяти. Такая функция может выйти из строя, например. если есть другой виджет, созданный по тому же адресу памяти. В принципе, вы можете просто использовать этот макрос GTK_IS_WIDGET(), но не гарантируется, чтобы работать корректно, даже если чаще всего это будет.

Правильный способ решить вашу проблему вместо этого удалить обратный вызов rotate_cb(), когда window будет уничтожен. Для этого есть «уничтожить» сигнал в классе GtkWidget. Итак, вы должны подключиться к «destroy» и в этом обработчике удалить rotate_cb() (или установить некоторый флаг, который делает rotate_cb() ничего не делать).

+0

Спасибо двойной. – Chimera

3

g_object_weak_ref() также полезно для таких случаев

Слабые ссылки используются для уведомления, когда объект будет завершен.

Вы можете подключить слабый реф обратный вызов (экс. widget_destroy_cb), чтобы отключить rotate_cb от виджета, прежде чем она будет уничтожена, так rotate_cb не должен вызываться один раз виджет разрушается.

Смежные вопросы