valera e13bc0dbc5 first commit | 6 hónapja | |
---|---|---|
img | 6 hónapja | |
readme.md | 6 hónapja |
Создание оконного приложения на 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:
KivyMD:
Меньше гибкости: 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'
.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