Вопрос или проблема
Я пытаюсь сделать выпадающий список в приложении GTK4, но у меня возникают проблемы с тем, как его заставить работать.
Я нашел и использую этот пример выпадающего списка GTK4 на Python, и он сначала работает, но когда dict
, используемый списком, обновляется, выпадающий список не изменяется.
Поскольку я не уверен, что подхожу к проблеме правильно с самого начала, я собираюсь представить упрощенную версию самой проблемы.
@dataclass
class Coffee:
"""
Представляет запись о кофе в базе данных
"""
coffee_id:int
name:str
description:str
# дата/время добавления (в виде unix времени)
date_added:datetime
Мое окно приложения хранит «список кофе», используя self.coffees:list[Coffees]
, и отслеживает «в настоящий момент активное» кофе, используя self.curr_coffee:Coffee
. Список обновляется другими частями приложения.
Моя цель – Gtk.Dropdown
, состоящий из name
ов Coffee
в этом списке, и когда пользователь делает выбор, self.curr_coffee
обновляется.
Я использую упрощенный пример выпадающего списка с небольшими модификациями.
Затем я создаю dict
, self.coffees_dict:dict
, который передается в выпадающий список. Этот словарь обновляется при каждом обновлении self.coffees
. (да, я знаю, что это не идеально; это был хак, чтобы оно сначала заработало)
for c in self.coffees:
self.coffees_dict[c.coffee_id] = c.name
Затем выпадающий список создается с использованием класса примера следующим образом:
self.coffee_setting_dropdown = DropDown(data=self.appstate.localDB.coffees_dict,
itemSelectNotifyMethod=self._on_coffee_changed_notify)
self.dropDownBox = self.coffee_setting_dropdown.getDropDownElementWithBoxAndLabel("Кофе:")
Однако, несмотря на обновление coffee_dict
, новые добавления в базу данных не отражаются в выпадающем списке. Должен быть более простой способ сделать это – Как я могу создать выпадающий список GTK4 из названий кофе и сделать так, чтобы он обновлялся, когда я добавляю новые?
Ниже приведен работающий пример одного файла базового случая, адаптированный из приведенного выше примера.
import gi
from dataclasses import dataclass
gi.require_version("Adw", "1")
gi.require_version("Gtk", "4.0")
from gi.repository import Adw, Gio, GObject, Gtk
@dataclass
class Coffee:
"""
Представляет запись о кофе в базе данных
"""
coffee_id: int
name: str
description: str
# используется для генерации примера list[Coffee]
EXAMPLE_COFFEES_LIST = [
"Эспрессо",
"Американо",
"Латте",
"Капучино",
"Мокка",
"Марчиато",
"Флэт Уайт",
"Аффогато",
"Холодный заварочный",
"Нитро кофе",
"Турецкий кофе",
"Ирландский кофе",
"Ристретто",
"Доппио",
"Кафе о ле",
"Венский кофе",
"Кафе кон Лече",
"Кофе со льдом",
"Бреве",
"Кофейное молоко",
]
# Классы выпадающего списка взяты из
# https://github.com/ksaadDE/GTK4PythonExamples/blob/main/DropDownSimplified.md
class DataDropDownChild(GObject.Object):
__gtype_name__ = "DataDropDownChild"
def __init__(self, key, value):
super().__init__()
self.k = key
self.v = value
@GObject.Property
def key(self):
return self.k
@GObject.Property
def value(self):
return self.v
def getKey(
self,
):
return self.k
def getValue(
self,
):
return self.v
class DropDown:
def __init__(
self,
data=[],
factoryBindSetupMethod=None,
factoryBindMethod=None,
itemSelectNotifyMethod=None,
):
self.box = None
self.data = data
self.model = Gio.ListStore(item_type=DataDropDownChild)
for k in data.keys():
self.model.append(DataDropDownChild(key=k, value=data[k]))
# Настройка фабрики
self.factory = Gtk.SignalListItemFactory()
if factoryBindSetupMethod is None:
self.factory.connect("setup", self._on_factory_setup)
else:
self.factory.connect("setup", factoryBindSetupMethod)
if factoryBindMethod is None:
self.factory.connect("bind", self._on_factory_bind)
else:
self.factory.connect("bind", factoryBindMethod)
self.dd = Gtk.DropDown(model=self.model, factory=self.factory, hexpand=True)
if itemSelectNotifyMethod is None:
self.dd.connect("notify::selected-item", self._on_selected_item_notify)
else:
self.dd.connect("notify::selected-item", itemSelectNotifyMethod)
def getDropDownElement(
self,
):
return self.dd
def getDropDownElementWithBox(self, labelText="Выберите объект:"):
self.box = Gtk.Box(spacing=12, hexpand=True, vexpand=True)
self.box.props.margin_start = 12
self.box.props.margin_end = 12
# self.box.props.margin_top = 6
# self.box.props.margin_bottom = 6
# self.box.append(Gtk.Label(label=labelText))
self.box.append(self.getDropDownElement())
self.box.set_vexpand(False)
return self.box
# Настройка дочернего элемента списка; это может быть произвольно
# сложный виджет, но сейчас мы используем простую метку
def _on_factory_setup(self, factory, list_item):
label = Gtk.Label()
list_item.set_child(label)
# Привязка элемента в модели к строке виджета; опять же, поскольку
# объект в модели может содержать несколько свойств, и
# элемент списка может содержать любой произвольно сложный виджет, мы
# можем иметь очень сложные строки вместо простых рендереров ячеек
# в GtkComboBox
def _on_factory_bind(self, factory, list_item):
label = list_item.get_child()
item = list_item.get_item()
label.set_text(str(item.v))
# Сигнал уведомления срабатывает, когда выбор меняется
def _on_selected_item_notify(self, dropdown, _):
item = dropdown.get_selected_item()
print(item.getKey(), item.getValue())
def update_liststore(self, data):
# наивный (и довольно глупый) способ обновления элементов в выпадающем списке. не будет масштабироваться
self.data = data
self.model = Gio.ListStore(item_type=DataDropDownChild)
for k in data.keys():
self.model.append(DataDropDownChild(key=k, value=data[k]))
class ExampleWindow(Gtk.ApplicationWindow):
def __init__(self, app):
super().__init__(application=app, title="Выпадающий список")
# генерируем пример данных
self.coffees: list[Coffee] = []
for idx, item in enumerate(EXAMPLE_COFFEES_LIST):
self.coffees.append(Coffee(idx, item, ""))
self.coffees_dict: dict = {}
for c in self.coffees:
self.coffees_dict[c.coffee_id] = c.name
# не используется в этом примере, здесь для справки
self.curr_coffee:Coffee = None
self.dd = DropDown(
data=self.coffees_dict, itemSelectNotifyMethod=self.onItemChangeNotify
)
self.dropDownBox = self.dd.getDropDownElementWithBox(labelText="Кофе:")
self.set_child(self.dropDownBox)
def onItemChangeNotify(self, dropdown, _):
item = dropdown.get_selected_item()
print("22:", item.getKey(), item.getValue())
class ExampleApp(Adw.Application):
def __init__(self):
super().__init__()
self.window = None
def do_activate(self):
if self.window is None:
self.window = ExampleWindow(self)
self.window.present()
app = ExampleApp()
app.run([])
Итак, у вас есть выпадающий список для выбора кофе, и вы хотите, чтобы он обновлялся, если новые кофе добавляются в словарь? Поскольку вы можете определить, когда он изменяется, возможно, просто переопределить всю переменную Dropdown с новым обновленным списком?
Ответ или решение
Для автоматического обновления выпадающего списка в приложении на GTK4, когда изменяется список объектов, вам нужно интегрировать возможность динамического обновления виджета Dropdown на основе изменений в вашем списке. Я опишу подробный план, как это можно реализовать.
Шаги для реализации динамического обновления выпадающего списка:
1. Создание структуры данных
Во-первых, вам необходимо продолжить использование вашей структуры данных, которую вы уже определили для объектов Coffee
. Убедитесь, что ваш класс Coffee
правильно создан, и вы используете его для отслеживания списка кофе.
@dataclass
class Coffee:
coffee_id: int
name: str
description: str
2. Модель данных (ListStore)
Как вы правильно заметили, необходимо обновить вашу модель ListStore
, чтобы автоматически отображать изменения. Вместо создания отдельного словаря self.coffees_dict
, рекомендуется использовать сам ListStore
напрямую:
self.model = Gio.ListStore(item_type=DataDropDownChild)
Это позволит вам более эффективно управлять объектами и их представлениями в выпадающем списке.
3. Функция для обновления данных
Добавьте метод для обновления вашего ListStore
. Он будет очищать старые элементы и добавлять новые:
def update_coffee_list(self):
self.model.remove_all() # очищаем модель
for coffee in self.coffees:
self.model.append(DataDropDownChild(key=coffee.coffee_id, value=coffee.name))
4. Изменения в классе DropDown
Обновите класс DropDown
для добавления метода обновления модели:
class DropDown:
# ... ваш код ...
def update_model(self, data):
self.model.remove_all() # очищаем модель
for k, v in data.items():
self.model.append(DataDropDownChild(key=k, value=v))
5. Вызов обновления при изменении списка
Теперь вам нужно убедиться, что функция update_coffee_list
вызывается каждый раз, когда список self.coffees
обновляется. Например:
def add_coffee(self, coffee: Coffee):
self.coffees.append(coffee) # добавляем новый объект Coffee
self.update_coffee_list() # обновляем выпадающий список
6. Пример применения
Имплементируйте новый метод в вашем основном классе окна:
class ExampleWindow(Gtk.ApplicationWindow):
# ... ваш код ...
def onItemChangeNotify(self, dropdown, _):
item = dropdown.get_selected_item()
self.curr_coffee = item # обновляем текущую выбранную кофейню
print("Выбранная кофейня:", item.getKey(), item.getValue())
def add_coffee_example(self):
# пример добавления новой кофейны
new_coffee = Coffee(coffee_id=len(self.coffees), name="Новый кофе", description="")
self.add_coffee(new_coffee) # добавление новой кофейны
Заключение
Следуя вышеперечисленным шагам, вы сможете создать выпадающий список, который будет автоматически обновляться при изменении списка элементов. Это позволит избежать использования промежуточных данных, таких как словари, и упростит вашу архитектуру.
Это решение не только улучшит взаимодействие пользователя с вашим приложением, но и обеспечит более чистую и эффективную реализацию.