Без опису

valera e13bc0dbc5 first commit 6 місяців тому
img e13bc0dbc5 first commit 6 місяців тому
readme.md e13bc0dbc5 first commit 6 місяців тому

readme.md

Создание оконного приложения на Python с использовением Kivy/KivyMD


Установка Kivy/KivyMD на Windows


Для установки нам потребуется всего две команды:

pip install kivy
pip install kivymd

Установка Kivy/KivyMD на MacOS


Для установки Kivy на Mac, нам нужно установить версию языка Python от 3.6 до 3.9, так как начиная с версии 3.10, Kivy перестает работать на MacOS. Эта проблема затрагивает только пользователей MacOS, с Windows же таких проблем нет.


pip install kivy
pip install kivymd

Скачать Python любой версии можно с официального сайта.


В чем же отличие Kivy от KivyMD?


Kivy:

  • Базовый фреймворк: Kivy - основа, которая предоставляет все основные инструменты для создания UI, обработки событий, работы с графикой, анимацией и т.д.
  • Собственный стиль: Kivy имеет свой собственный, довольно минималистичный стиль виджетов.
  • Гибкость: Kivy предоставляет большую гибкость в настройке внешнего вида приложения. Вы можете создавать свои собственные виджеты и стилизовать их как угодно. ***

KivyMD:

  • Надстройка над Kivy: KivyMD - это коллекция виджетов, созданная поверх Kivy, которая реализует Material Design от Google.
  • Современный внешний вид: Виджеты KivyMD соответствуют гайдлайнам Material Design, что обеспечивает современный и привлекательный внешний вид приложения.
  • Готовые компоненты: KivyMD предоставляет широкий набор готовых к использованию компонентов Material Design, что упрощает разработку.
  • Меньше гибкости: KivyMD предлагает меньше возможностей для кастомизации, чем Kivy, так как он следует определенным правилам Material Design.


    Вывод:

  • Если вам нужна максимальная гибкость в настройке дизайна, и вы готовы создавать свой собственный стиль, выбирайте Kivy.

  • Если вам нужен современный дизайн в стиле Material Design, и вы хотите использовать готовые компоненты для ускорения разработки, выбирайте KivyMD.


    Перейдем к созданию приложеяния нa KivyMD


    Напишем обычное окно с тектом.

Для начала нам нужно импортировать все необходимые для нас ресурсы, для создания приложения.


  • импортируем класс MDApp, который является основой для приложений KivyMD.

    from kivymd.app import MDApp
    

  • импортируем класс MDLabel для создания текстовой метки в стиле Material Design.

    from kivymd.uix.label import MDLabel
    

    Создание класса приложения

  • создаем класс MyMDApp, наследуемый от MDApp.

    class MyMDApp(MDApp):
    

Метод build()

  • определяет содержимое окна приложения

    def build(self):
    
  • создаем объект MDLabel с текстом, выровненным по центру, и возвращаем его.

    return MDLabel(text="Любой другой текст", halign="center")
    

Запуск приложения

  • стандартная проверка для запуска скрипта.

    if __name__ == '__main__':
    
  • создаем экземпляр класса MyMDApp и запускаем приложение.

    MyMDApp().run()
    

    Полный код выглядит таким образом:

    from kivymd.app import MDApp
    from kivymd.uix.label import MDLabel
    
    class MyMDApp(MDApp):
    def build(self):
        return MDLabel(text="Любой другой текст", halign="center")
    
    if __name__ == '__main__':
    MyMDApp().run()
    


Совсем забыл сказать, что в Kivy мы используем ООП (Объектно Ориентированное Программирование). Если вы с ним не знакомы, советую прочитать познавательную статью на эту тему.


Думаю суть уловили, буду приступать к своей работе.

Импорт главного окна приложения

from kivymd.app import MDApp

Импорт бокслояута для добавления в него виджетов

from kivy.uix.boxlayout import BoxLayout

Импорт для изображения

from kivy.uix.image import Image

  • главный класс

    class SearchBoxLayout(BoxLayout):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.orientation = 'vertical'
        self.padding = [50, 0, 50, 50]
    

def __init__(self, **kwargs): # Инициализация класса

super().__init__(**kwargs) # Вызов конструктора родительского класса BoxLayout

self.orientation = 'vertical' # Установка ориентации элементов на вертикальную

self.padding = [50, 0, 50, 50] # Отступы вокруг элементов


  • добавление изображения

    self.image = Image(
            source='net.jpg',
            size_hint_y=0.3
        )
        self.add_widget(self.image)
    

self.image = Image() # Создание объекта Image

source='net.jpg', # Путь к изображению

size_hint_y=0.3 # Относительный размер изображения по вертикали

self.add_widget(self.image) # Добавление изображения в бокслояут

Основной код выглядит так:

from kivymd.app import MDApp
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.image import Image

class SearchBoxLayout(BoxLayout):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.orientation = 'vertical'
        self.padding = [50, 0, 50, 50]

        # Импорт фотографии
        self.image = Image(
            source='net.jpg',
            size_hint_y=0.3
        )
        self.add_widget(self.image)

class MyApp(MDApp):
    def build(self):
        return SearchBoxLayout()

if __name__ == '__main__':
    MyApp().run()


  • создание таблицы и импорт данных из json

это создание обычной таблицы

self.data_table = MDDataTable(
            size_hint=(0.9, 0.9),
            rows_num=10,
            column_data=[
                ("Название", dp(30)),
                ("Год", dp(20)),
                ("Цена", dp(20)),
                ("Цвет", dp(20)),
                ("Повреждения", dp(30)),
                ("Дата СТО", dp(30))
            ],
            row_data=[
                ("Toyota Trueno AE86", "1995", "15000", "white", "True", "19/04/2024"),
                ("Toyota Supra A80", "1996", "30000", "black", "False", "09/02/2024"),
                ("Nissal Skyline R34", "1996", "25000", "orange", "False", "10/07/2024"),
                ("Nissan Silvia S15", "1999", "20000", "gray", "False", "20/03/2024"),
                ("Toyota Camry 3.5", "2020", "20000", "blue", "False", "19/04/2024"),
                ("Audi RS 6", "2016", "30000", "lightblue", "True", "19/04/2024"),
                ("Трактор LOVOL TE354 HT", "2024", "66666", "brown", "False", "19/04/2024"),
                ("BMW M5 F90", "2019", "30000", "black", "True", "19/04/2024"),
                ("BMW E36", "2006", "35000", "red", "False", "19/04/2024"),
                ("Daewoo Matiz", "2010", "20000", "pink", "True", "19/04/2024"),
            ]
        )

Это создание таблицы с json

        # Прокрутка для таблицы
        self.scrollview = ScrollView(size_hint_y=0.7)# Создание прокручиваемого контейнера для таблицы
        self.add_widget(self.scrollview)# Добавление прокрутки в основной бокслояут

        # Создание таблицы
        self.data_table = MDDataTable(
            size_hint=(1, 1),
            use_pagination=True,
            rows_num=10,
            column_data=[
                ("Название", dp(30)),
                ("Год", dp(15)),
                ("Цена", dp(15)),
                ("Цвет", dp(15)),
                ("Повреждения", dp(25)),
                ("Дата СТО", dp(30))
            ],
            # Инициализируем row_data пустым списком, данные будут загружены из JSON
            row_data=[]
        )
        self.scrollview.add_widget(self.data_table)

        # Загрузка данных из JSON файла
        self.load_data_from_json('package.json')

    def load_data_from_json(self, json_path):
        # Загрузка данных из JSON файла и обновление таблицы
        with open(json_path, 'r', encoding='utf-8') as json_file:
            data = json.load(json_file)
            # Предполагаем, что данные в JSON файле имеют формат списка словарей
            row_data = [(d['Название'], d['Год'], d['Цена'], d['Цвет'], d['Повреждения'], d['Дата СТО']) for d in data]
            self.data_table.row_data = row_data
            self.original_row_data = row_data
        self.original_row_data = self.data_table.row_data

json файл:

[
    {
        "Название": "Toyota Trueno AE86",
        "Год": 1995,
        "Цена": 15000,
        "Цвет": "white",
        "Повреждения": "True",
        "Дата СТО": "19/04/2024",
    },
]

Строка поиска и кнопки фильтрации

# Панель поиска и фильтрации
        search_filter_bar = BoxLayout(size_hint_y=None, height=dp(40), spacing=10) # Создание бокслояута для панели поиска и фильтрации
        self.add_widget(search_filter_bar) # Добавление панели в основной бокслояут

        # Строка поиска
        self.search_field = MDTextField( # Создание строки ввода
            hint_text="Search...", # Текст подсказки
            size_hint_x=0.7 # Относительный размер строки ввода по горизонтали
        )
        self.search_field.bind(text=self.filter_table) # Привязка функции filter_table к изменению текста в строке поиска
        search_filter_bar.add_widget(self.search_field) # Добавление строки поиска на панель

        # Кнопки фильтрации
        self.asc_button = MDIconButton(icon="arrow-up-drop-circle") # Создание кнопки с иконкой для сортировки по возрастанию
        self.asc_button.bind(on_release=self.sort_ascending) # Привязка функции sort_ascending к нажатию на кнопку
        search_filter_bar.add_widget(self.asc_button) # Добавление кнопки на панель

        self.desc_button = MDIconButton(icon="arrow-down-drop-circle") # Создание кнопки с иконкой для сортировки по убыванию
        self.desc_button.bind(on_release=self.sort_descending) # Привязка функции sort_descending к нажатию на кнопку
        search_filter_bar.add_widget(self.desc_button) # Добавление кнопки на панель

Выпадающий список с фильтрацией

# Выпадающий список для выбора машины
        self.spinner_car = Spinner( # Создание выпадающего списка для выбора машины
            text='Выберите машину', # Начальный текст
            values=('Toyota Trueno AE86', 'Toyota Supra A80', 'Nissan Skyline R34', 'Nissan Silvia S15', 'Toyota Camry 3.5', 'Audi RS 6', 'Трактор LOVOL TE354 HT', 'BMW M5 F90', 'BMW E36', 'Daewoo Matiz'), # Список значений
            size_hint=(None, None), # Отключение автоматического расчета размера
            size=(dp(200), dp(44)), # Фиксированный размер
            pos_hint={'center_x': 0.5} # Позиционирование по центру по горизонтали
        )
        self.spinner_car.bind(text=self.filter_table_by_name) # Привязка функции filter_table_by_name к изменению значения в списке
        search_filter_bar.add_widget(self.spinner_car) # Добавление списка на панель

        # Выпадающий список для выбора ценового диапазона
        self.spinner_price = Spinner( # Создание выпадающего списка для выбора ценового диапазона
            text='Выберите цену', # Начальный текст
            values=('10000 - 20000', '21000 - 30000', '31000 - 70000'), # Список значений
            size_hint=(None, None), # Отключение автоматического расчета размера
            size=(dp(200), dp(44)), # Фиксированный размер
            pos_hint={'center_x': 0.5} # Позиционирование по центру по горизонтали
        )
        self.spinner_price.bind(text=self.filter_table_by_price) # Привязка функции filter_table_by_price к изменению значения в списке
        search_filter_bar.add_widget(self.spinner_price) # Добавление списка на панель

Методы для всех фильтрациий и поисков (писать в конец кода)

    def filter_table(self, instance, text):
        # Фильтрация таблицы по поиску
        filtered_data = []
        for row in self.original_row_data:
            if text.lower() in row[0].lower():
                filtered_data.append(row)
        self.data_table.row_data = filtered_data

    def filter_table_by_name(self, spinner, text):
        # Фильтрация таблицы по названию машины
        if text != 'Выберите машину':
            filtered_data = [row for row in self.original_row_data if row[0] == text]
            self.data_table.row_data = filtered_data

    def filter_table_by_price(self, spinner, text):
        # Фильтрация таблицы по ценовому диапазону
        if text != 'Выберите цену':
            price_range = text.split(' - ')
            min_price, max_price = int(price_range[0]), int(price_range[1])
            filtered_data = [row for row in self.original_row_data if min_price <= int(row[2]) <= max_price]
            self.data_table.row_data = filtered_data

    def sort_ascending(self, instance):
        # Сортировка таблицы по цене в порядке возрастания
        self.data_table.row_data = sorted(self.original_row_data, key=lambda row: int(row[2]))

    def sort_descending(self, instance):
        # Сортировка таблицы по цене в порядке убывания
        self.data_table.row_data = sorted(self.original_row_data, key=lambda row: int(row[2]), reverse=True)


Метод filter_table(self, instance, text):**

  • filtered_data = []: создаёт пустой список для хранения отфильтрованных данных.
  • Цикл for row in self.original_row_data::
    • Проходит по каждой строке row в исходных данных.
  • if text.lower() in row[0].lower()::
    • Проверяет, содержится ли текст поиска (text) в названии автомобиля (row[0]), игнорируя регистр.
    • Если содержится, добавляет строку в filtered_data.
  • self.data_table.row_data = filtered_data: - Обновляет данные в таблице, отображая только отфильтрованные строки.

Метод filter_table_by_name(self, spinner, text): - Фильтрует таблицу по названию машины, выбранному в выпадающем списке.

  • if text != 'Выберите машину':: проверяет, выбрана ли какая-то машина.

  • filtered_data = [row for row in self.original_row_data if row[0] == text]: - Создаёт список, содержащий только строки, где название машины совпадает с выбранным.

  • self.data_table.row_data = filtered_data: - Обновляет таблицу отфильтрованными данными.

Метод filter_table_by_price(self, spinner, text): - Фильтрует таблицу по ценовому диапазону, выбранному в выпадающем списке.

  • if text != 'Выберите цену':: проверяет, выбран ли ценовой диапазон.

  • price_range = text.split(' - '): разделяет текст диапазона на минимальную и максимальную цену.

  • min_price, max_price = int(price_range[0]), int(price_range[1]): преобразует цены в числа.

  • filtered_data = [row for row in self.original_row_data if min_price <= int(row[2]) <= max_price]:- Создаёт список, содержащий строки, где цена находится в заданном диапазоне.

  • self.data_table.row_data = filtered_data:- Обновляет таблицу отфильтрованными данными.

Метод sort_ascending(self, instance): - Сортирует таблицу по цене в порядке возрастания.

  • self.data_table.row_data = sorted(self.original_row_data, key=lambda row: int(row[2])):: - Сортирует строки

  • self.original_row_data по цене (row[2]), преобразуя её в число.

Метод sort_descending(self, instance): - Сортирует таблицу по цене в порядке убывания.

  • self.data_table.row_data = sorted(self.original_row_data, key=lambda row: int(row[2]), reverse=True):: - Сортирует строки

  • self.original_row_data по цене (row[2]), преобразуя её в число, с обратным порядком (reverse=True).


Замена таблицы на разметку с изображениями и текстом

self.spinner_price.bind(text=self.filter_items)
        search_filter_bar.add_widget(self.spinner_price)

        # Прокрутка для таблицы
        self.scrollview = ScrollView()
        self.add_widget(self.scrollview)

        # Сетчатая разметка для хранения элементов автомобиля
        self.car_grid = GridLayout(cols=1, spacing=10, size_hint_y=None)
        self.car_grid.bind(minimum_height=self.car_grid.setter('height'))
        self.scrollview.add_widget(self.car_grid)

        # Загрузка данных из JSON
        self.car_data = self.load_data_from_json('package.json')
        self.original_car_data = self.car_data.copy()
        self.populate_car_grid()

        self.spinner_car.values = list({car['Название'] for car in self.car_data})

    def load_data_from_json(self, filename):
        with open(filename, 'r', encoding='utf-8') as f:
            data = json.load(f)
        return data

    def populate_car_grid(self):
        # Заполнение сетки виджетов
        self.car_grid.clear_widgets()
        for car in self.car_data:
            car_item = CarItem(car)
            self.car_grid.add_widget(car_item)

self.spinner_price.bind(text=self.filter_items):

  • Эта строка связывает изменение текста в выпадающем списке self.spinner_price с методом self.filter_items.
  • То есть, когда пользователь выбирает новое значение в self.spinner_price, Kivy автоматически вызывает self.filter_items.

search_filter_bar.add_widget(self.spinner_price):

  • Добавляет выпадающий список self.spinner_price в контейнер search_filter_bar.
  • search_filter_bar - это, скорее всего, BoxLayout, или другой контейнер, который хранит элементы панели поиска и фильтрации.

self.scrollview = ScrollView():

  • Создаёт экземпляр класса ScrollView.
  • ScrollView - это контейнер, который позволяет прокручивать содержимое, если оно не помещается в видимой области.

self.add_widget(self.scrollview):

  • Добавляет self.scrollview в главный контейнер экрана.
  • То есть, теперь self.scrollview будет отображаться на экране, и все его дочерние элементы тоже.

self.car_grid = GridLayout(cols=1, spacing=10, size_hint_y=None):

  • Создаёт экземпляр класса GridLayout.
  • cols=1: сетка будет иметь одну колонку.
  • spacing=10: расстояние между элементами сетки будет 10 пикселей.
  • size_hint_y=None: сетка не будет масштабироваться по вертикали, её высота будет определяться содержимым.

self.car_grid.bind(minimum_height=self.car_grid.setter('height')):

  • Эта строка гарантирует, что высота сетки self.car_grid будет всегда достаточной для отображения всех элементов.

self.scrollview.add_widget(self.car_grid):

  • Добавляет сетку self.car_grid в self.scrollview.
  • Теперь содержимое self.car_grid можно будет прокручивать, если оно не помещается в видимой области.

self.car_data = self.load_data_from_json('package.json'):

  • Вызывает метод self.load_data_from_json, передавая ему имя файла 'package.json'.
  • Метод загружает данные из JSON файла и возвращает их.
  • Результат сохраняется в self.car_data.

self.original_car_data = self.car_data.copy():

  • Создает копию данных из self.car_data и сохраняет ее в self.original_car_data.
  • Это делается для того, чтобы можно было фильтровать и сортировать данные, не изменяя исходный набор. self.populate_car_grid():
  • Вызывает метод self.populate_car_grid, который отвечает за заполнение сетки self.car_grid виджетами, представляющими автомобили.
  • Предполагается, что у вас есть отдельный класс (например, CarItem) для создания виджетов, отображающих информацию об автомобиле.

self.spinner_car.values = list({car['Название'] for car in self.car_data}):

  • Эта строка задает значения для выпадающего списка self.spinner_car, который, вероятно, используется для фильтрации по названию машины.
  • {car['Название'] for car in self.car_data}: создает множество уникальных названий машин из self.car_data.
  • list(...): преобразует множество в список, так как spinner.values ожидает список значений.


    json file

    [
    {
        "Название": "Toyota Trueno AE86",
        "Год": 1995,
        "Цена": 15000,
        "Цвет": "white",
        "Повреждения": "True",
        "Дата СТО": "19/04/2024",
        "image": "tru.jpg"
    },
    ]
    

все импорты, которые нам понадобились:

from kivymd.app import MDApp # Импорт главного окна приложения
from kivy.uix.boxlayout import BoxLayout # Импорт бокслояута для добавления в него виджетов
from kivy.uix.gridlayout import GridLayout # Импорт гридлояута для добавления элементов в виде сетки
from kivy.uix.spinner import Spinner # Импорт для выпадающего списка
from kivymd.uix.textfield import MDTextField # Импорт для таблицы
from kivymd.uix.button import MDIconButton # Импорт для иконок кнопок
from kivy.uix.image import Image # Импорт для изменения размера
from kivy.uix.label import Label # Импорт Label для отображения текста
from kivy.uix.scrollview import ScrollView # Импорт для прокрутки таблицы
from kivy.metrics import dp # Импорт для изменения размера
from kivy.utils import get_color_from_hex # Импорт для использования шестнадцатеричных цветовых кодов

import json