最終更新: 2026-07-29
対象バージョン: BlinkGTK v1.1.0 以降
レベル: 初級〜中級
言語: 日本語 |
このチュートリアルでは、Pythonでイベント駆動プログラミングを行う方法を学びます。
import gi
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk
import blink_gtk
class BrowserWindow(Gtk.ApplicationWindow):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.set_title("Signal Example")
self.set_default_size(800, 600)
self.web_view = blink_gtk.WebView.new()
self.set_child(self.web_view)
# シグナル接続
self.web_view.connect('load-changed', self.on_load_changed)
def on_load_changed(self, web_view, load_event):
"""ロード状態変化"""
events = {
blink_gtk.LoadEvent.STARTED: "開始",
blink_gtk.LoadEvent.REDIRECTED: "リダイレクト",
blink_gtk.LoadEvent.COMMITTED: "コミット",
blink_gtk.LoadEvent.FINISHED: "完了",
}
print(f"[load-changed] {events.get(load_event, '不明')}")# 簡単なログ出力
web_view.connect('load-changed',
lambda w, e: print(f"Load event: {e}"))
# タイトル更新
web_view.connect('title-changed',
lambda w, title: self.set_title(f"{title} - Browser"))def on_load_changed(self, web_view, load_event):
"""ロード状態変化"""
if load_event == blink_gtk.LoadEvent.STARTED:
self.progress_bar.set_fraction(0.0)
self.progress_bar.set_visible(True)
self.status_label.set_text("読み込み中...")
elif load_event == blink_gtk.LoadEvent.COMMITTED:
self.progress_bar.set_fraction(0.5)
elif load_event == blink_gtk.LoadEvent.FINISHED:
self.progress_bar.set_fraction(1.0)
self.progress_bar.set_visible(False)
self.status_label.set_text("完了")
# ナビゲーションボタン更新
self.back_button.set_sensitive(web_view.can_go_back())
self.forward_button.set_sensitive(web_view.can_go_forward())def on_load_failed(self, web_view, failing_url, error_code):
"""ロード失敗"""
error_messages = {
-2: "読み込み失敗",
-7: "タイムアウト",
-105: "サーバーが見つかりません",
-106: "インターネット未接続",
}
error_msg = error_messages.get(error_code, f"不明なエラー (コード: {error_code})")
# エラーダイアログ
dialog = Gtk.MessageDialog(
transient_for=self,
modal=True,
message_type=Gtk.MessageType.ERROR,
buttons=Gtk.ButtonsType.OK,
text="読み込みエラー"
)
dialog.format_secondary_text(f"{error_msg}\n\nURL: {failing_url}")
dialog.connect('response', lambda d, r: d.destroy())
dialog.show()def on_title_changed(self, web_view, title):
"""タイトル変更"""
self.set_title(f"{title} - Python Browser")
print(f"[title-changed] {title}")def on_uri_changed(self, web_view, uri):
"""URI変更"""
self.url_entry.set_text(uri)
# セキュリティアイコン更新
if uri.startswith("https://"):
self.security_icon.set_from_icon_name("dialog-password")
self.security_icon.set_tooltip_text("このページは安全です")
else:
self.security_icon.set_from_icon_name("dialog-warning")
self.security_icon.set_tooltip_text("このページは保護されていません")from functools import wraps
from typing import Callable
def signal_handler(signal_name: str) -> Callable:
"""シグナルハンドラデコレータ"""
def decorator(func: Callable) -> Callable:
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"Error in {signal_name} handler: {e}")
return wrapper
return decorator
class BrowserWindow(Gtk.ApplicationWindow):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.web_view = blink_gtk.WebView.new()
self.set_child(self.web_view)
# デコレータ付きハンドラを接続
self.web_view.connect('load-changed', self.on_load_changed)
self.web_view.connect('load-failed', self.on_load_failed)
@signal_handler('load-changed')
def on_load_changed(self, web_view, load_event):
if load_event == blink_gtk.LoadEvent.FINISHED:
print("読み込み完了")
@signal_handler('load-failed')
def on_load_failed(self, web_view, failing_url, error_code):
print(f"エラー: {error_code}")from typing import Dict, List, Callable
class SignalManager:
"""シグナルを管理するクラス"""
def __init__(self, web_view: blink_gtk.WebView):
self.web_view = web_view
self.handlers: Dict[str, List[int]] = {}
def connect(self, signal_name: str, handler: Callable) -> int:
"""シグナルを接続"""
handler_id = self.web_view.connect(signal_name, handler)
if signal_name not in self.handlers:
self.handlers[signal_name] = []
self.handlers[signal_name].append(handler_id)
return handler_id
def disconnect(self, signal_name: str) -> None:
"""特定のシグナルのハンドラをすべて切断"""
if signal_name in self.handlers:
for handler_id in self.handlers[signal_name]:
self.web_view.disconnect(handler_id)
del self.handlers[signal_name]
def disconnect_all(self) -> None:
"""すべてのシグナルハンドラを切断"""
for signal_name in list(self.handlers.keys()):
self.disconnect(signal_name)
# 使用例
signal_manager = SignalManager(web_view)
signal_manager.connect('load-changed', on_load_changed)
signal_manager.connect('title-changed', on_title_changed)
# 後でクリーンアップ
signal_manager.disconnect_all()import asyncio
from gi.repository import GLib
class AsyncBrowserWindow(Gtk.ApplicationWindow):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.web_view = blink_gtk.WebView.new()
self.set_child(self.web_view)
# イベント
self.load_finished_event = asyncio.Event()
self.web_view.connect('load-changed', self.on_load_changed)
def on_load_changed(self, web_view, load_event):
if load_event == blink_gtk.LoadEvent.FINISHED:
# イベントをセット
self.load_finished_event.set()
async def load_url_async(self, url: str) -> None:
"""URLを非同期で読み込む"""
self.load_finished_event.clear()
self.web_view.load_uri(url)
await self.load_finished_event.wait()
print(f"読み込み完了: {url}")
# 使用例
async def main():
window = AsyncBrowserWindow()
# 複数URLを順次読み込み
urls = [
"https://example.com",
"https://www.chromium.org",
"https://www.python.org",
]
for url in urls:
await window.load_url_async(url)
await asyncio.sleep(2) # 2秒待機import asyncio
from typing import List
class MultiTabBrowser:
"""複数タブを持つブラウザ"""
def __init__(self):
self.tabs: List[blink_gtk.WebView] = []
async def load_multiple_urls(self, urls: List[str]) -> None:
"""複数URLを並行して読み込む"""
tasks = []
for url in urls:
web_view = blink_gtk.WebView.new()
self.tabs.append(web_view)
# 非同期タスクを作成
task = asyncio.create_task(self._load_url_async(web_view, url))
tasks.append(task)
# すべて完了まで待機
await asyncio.gather(*tasks)
print(f"{len(urls)}個のURLの読み込み完了")
async def _load_url_async(self, web_view: blink_gtk.WebView, url: str) -> None:
"""単一URLを非同期で読み込む"""
event = asyncio.Event()
def on_load_changed(w, load_event):
if load_event == blink_gtk.LoadEvent.FINISHED:
event.set()
web_view.connect('load-changed', on_load_changed)
web_view.load_uri(url)
await event.wait()import asyncio
async def load_with_timeout(
web_view: blink_gtk.WebView,
url: str,
timeout: float = 30.0
) -> bool:
"""タイムアウト付きでURLを読み込む"""
event = asyncio.Event()
def on_load_changed(w, load_event):
if load_event == blink_gtk.LoadEvent.FINISHED:
event.set()
def on_load_failed(w, failing_url, error_code):
event.set()
web_view.connect('load-changed', on_load_changed)
web_view.connect('load-failed', on_load_failed)
web_view.load_uri(url)
try:
await asyncio.wait_for(event.wait(), timeout=timeout)
return True
except asyncio.TimeoutError:
print(f"タイムアウト: {url}")
web_view.stop()
return False#!/usr/bin/env python3
"""完全な機能を持つブラウザ"""
import gi
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk
import blink_gtk
class CompleteBrowser(Gtk.ApplicationWindow):
"""完全な機能を持つブラウザ"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.set_title("Complete Browser (Python)")
self.set_default_size(1200, 800)
self._build_ui()
self._connect_signals()
def _build_ui(self):
"""UIを構築"""
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
# ツールバー
toolbar = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
toolbar.set_margin_start(5)
toolbar.set_margin_end(5)
toolbar.set_margin_top(5)
toolbar.set_margin_bottom(5)
self.back_button = Gtk.Button.new_from_icon_name("go-previous")
self.forward_button = Gtk.Button.new_from_icon_name("go-next")
self.reload_button = Gtk.Button.new_from_icon_name("view-refresh")
self.stop_button = Gtk.Button.new_from_icon_name("process-stop")
self.security_icon = Gtk.Image.new_from_icon_name("dialog-warning")
self.url_entry = Gtk.Entry()
self.stop_button.set_visible(False)
self.back_button.set_sensitive(False)
self.forward_button.set_sensitive(False)
self.url_entry.set_hexpand(True)
for widget in [self.back_button, self.forward_button,
self.reload_button, self.stop_button,
self.security_icon, self.url_entry]:
toolbar.append(widget)
# Settings
settings_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
settings_box.set_margin_start(5)
settings_box.set_margin_end(5)
settings_box.set_margin_bottom(5)
self.js_switch = Gtk.Switch()
self.images_switch = Gtk.Switch()
self.storage_switch = Gtk.Switch()
for switch in [self.js_switch, self.images_switch, self.storage_switch]:
switch.set_active(True)
settings_box.append(Gtk.Label(label="JavaScript:"))
settings_box.append(self.js_switch)
settings_box.append(Gtk.Label(label="画像:"))
settings_box.append(self.images_switch)
settings_box.append(Gtk.Label(label="ストレージ:"))
settings_box.append(self.storage_switch)
# プログレスバー
self.progress_bar = Gtk.ProgressBar()
self.progress_bar.set_visible(False)
# WebView
self.web_view = blink_gtk.WebView.new()
# ステータスバー
self.status_label = Gtk.Label(label="準備完了")
self.status_label.set_halign(Gtk.Align.START)
self.status_label.set_margin_start(5)
self.status_label.set_margin_bottom(2)
# レイアウト
vbox.append(toolbar)
vbox.append(settings_box)
vbox.append(self.progress_bar)
vbox.append(self.web_view)
vbox.append(self.status_label)
self.web_view.set_vexpand(True)
self.set_child(vbox)
def _connect_signals(self):
"""シグナルを接続"""
# WebViewシグナル
self.web_view.connect('load-changed', self.on_load_changed)
self.web_view.connect('load-failed', self.on_load_failed)
self.web_view.connect('title-changed', self.on_title_changed)
self.web_view.connect('uri-changed', self.on_uri_changed)
# ボタン
self.back_button.connect('clicked', lambda b: self.web_view.go_back())
self.forward_button.connect('clicked', lambda b: self.web_view.go_forward())
self.reload_button.connect('clicked', lambda b: self.web_view.reload())
self.stop_button.connect('clicked', lambda b: self.web_view.stop())
self.url_entry.connect('activate', self.on_url_activate)
# Settings
self.js_switch.connect('notify::active',
lambda s, _: self.web_view.set_enable_javascript(s.get_active()))
self.images_switch.connect('notify::active',
lambda s, _: self.web_view.set_auto_load_images(s.get_active()))
self.storage_switch.connect('notify::active',
lambda s, _: self.web_view.set_enable_local_storage(s.get_active()))
def on_load_changed(self, web_view, load_event):
"""ロード状態変化"""
if load_event == blink_gtk.LoadEvent.STARTED:
self.progress_bar.set_fraction(0.0)
self.progress_bar.set_visible(True)
self.stop_button.set_visible(True)
self.reload_button.set_visible(False)
self.status_label.set_text("読み込み中...")
elif load_event == blink_gtk.LoadEvent.COMMITTED:
self.progress_bar.set_fraction(0.5)
elif load_event == blink_gtk.LoadEvent.FINISHED:
self.progress_bar.set_fraction(1.0)
self.progress_bar.set_visible(False)
self.stop_button.set_visible(False)
self.reload_button.set_visible(True)
self.status_label.set_text("完了")
self.back_button.set_sensitive(web_view.can_go_back())
self.forward_button.set_sensitive(web_view.can_go_forward())
def on_load_failed(self, web_view, failing_url, error_code):
"""ロード失敗"""
error_messages = {
-2: "読み込み失敗",
-7: "タイムアウト",
-105: "サーバーが見つかりません",
-106: "インターネット未接続",
}
error_msg = error_messages.get(error_code, f"不明なエラー (コード: {error_code})")
self.status_label.set_text(f"{error_msg} (コード: {error_code})")
# エラーダイアログ
dialog = Gtk.MessageDialog(
transient_for=self,
modal=True,
message_type=Gtk.MessageType.ERROR,
buttons=Gtk.ButtonsType.OK,
text="読み込みエラー"
)
dialog.format_secondary_text(f"{error_msg}\n\nURL: {failing_url}\nエラーコード: {error_code}")
dialog.connect('response', lambda d, r: d.destroy())
dialog.show()
self.progress_bar.set_visible(False)
self.stop_button.set_visible(False)
self.reload_button.set_visible(True)
def on_title_changed(self, web_view, title):
"""タイトル変更"""
self.set_title(f"{title} - Complete Browser")
def on_uri_changed(self, web_view, uri):
"""URI変更"""
self.url_entry.set_text(uri)
if uri.startswith("https://"):
self.security_icon.set_from_icon_name("dialog-password")
self.security_icon.set_tooltip_text("このページは安全です")
else:
self.security_icon.set_from_icon_name("dialog-warning")
self.security_icon.set_tooltip_text("このページは保護されていません")
def on_url_activate(self, entry):
"""URLエントリでEnter"""
url = entry.get_text()
self.web_view.load_uri(url)
def on_activate(app):
window = CompleteBrowser(application=app)
window.web_view.load_uri("https://www.chromium.org")
window.present()
if __name__ == '__main__':
blink_gtk.init()
app = Gtk.Application(application_id='org.example.complete')
app.connect('activate', on_activate)
app.run()
blink_gtk.shutdown()このチュートリアルでは、PythonでBlinkGTKのイベント処理を行う方法を学びました。
作成者: BlinkGTK開発チーム
ライセンス: BSD 3-Clause
フィードバック: daisy19@gmail.com