PyGObject (GTK4) – Как автоматически обновлять выпадающие списки при изменениях в списке элементов?

Вопрос или проблема

Я пытаюсь сделать выпадающий список в приложении 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)  # добавление новой кофейны

Заключение

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

Это решение не только улучшит взаимодействие пользователя с вашим приложением, но и обеспечит более чистую и эффективную реализацию.

Оцените материал
Добавить комментарий

Капча загружается...