Исправления:

- Добавлен столбец 'Разбавление (x)' для каждого реагента
- Переработана логика расчёта с учётом разбавления
- Исправлен расчёт количества растворителя
- Растворитель отображается в первой строке таблицы
- Поддержка дробных значений разбавления
This commit is contained in:
2026-05-05 22:31:08 +05:00
parent cde52d1123
commit 6798fd5f63
5 changed files with 467 additions and 277 deletions
+109 -56
View File
@@ -1,100 +1,120 @@
from PyQt5.QtWidgets import QFileDialog, QMessageBox, QTableWidgetItem, QComboBox
from model import Reagent
from PyQt5.QtWidgets import QMessageBox, QFileDialog, QTableWidgetItem, QComboBox, QDoubleSpinBox, QLineEdit
from PyQt5.QtCore import Qt
from model import Model
from view import MainWindow
import json
from reagent import Reagent
class Controller:
def __init__(self, view, model):
self.view = view
self.model = model
self.setup_connections()
def __init__(self):
self.model = Model()
self.view = MainWindow()
self._connect_signals()
def setup_connections(self):
"""Подключает сигналы виджетов к методам контроллера"""
def _connect_signals(self):
"""Подключает обработчики событий интерфейса"""
self.view.add_row_btn.clicked.connect(self.add_reagent_row)
self.view.remove_row_btn.clicked.connect(self.remove_reagent_row)
self.view.calculate_btn.clicked.connect(self.calculate)
self.view.calculate_btn.clicked.connect(self._perform_calculation)
self.view.save_btn.clicked.connect(self.save_composition)
self.view.load_btn.clicked.connect(self.load_composition)
# Авторасчёт при изменении количества среды
self.view.amount_input.valueChanged.connect(self.calculate)
# Авторасчёт при смене единицы измерения
self.view.amount_unit_combo.currentTextChanged.connect(self.calculate)
# Подключаем обновление названия растворителя в таблице при изменении поля
self.view.solvent_input.textChanged.connect(self.view.update_solvent_name)
def add_reagent_row(self):
"""Добавляет новую строку в таблицу реагентов с предустановленными значениями"""
row = self.view.table.rowCount()
self.view.table.insertRow(row)
self.view.table.setItem(row, 0, QTableWidgetItem("Реагент"))
self.view.table.setItem(row, 1, QTableWidgetItem("1.0"))
unit_combo = QComboBox()
unit_combo.addItems(["нг", "мкг", "мг", "г", "кг", "нл", "мкл", "мл", "л"])
unit_combo.setCurrentText("мг")
self.view.table.setCellWidget(row, 2, unit_combo)
self.view.table.setItem(row, 3, QTableWidgetItem("1.0"))
self.view.table.setItem(row, 4, QTableWidgetItem(""))
"""Добавляет новую строку для реагента"""
self.view.add_new_row()
def remove_reagent_row(self):
"""Удаляет выделенную строку из таблицы реагентов и из модели"""
current_row = self.view.table.currentRow()
if current_row >= 0:
self.view.table.removeRow(current_row)
if 0 <= current_row < len(self.model.reagents):
del self.model.reagents[current_row]
"""Удаляет выбранную строку реагента"""
self.view.remove_selected_row()
def calculate(self):
"""Выполняет расчёт количеств реагентов и растворителя"""
def _perform_calculation(self):
"""Выполняет расчёт и обновляет интерфейс"""
try:
self._update_model_from_view()
results = self.model.calculate_amounts()
results, solvent_amount, solvent_percentage = self.model.calculate_amounts()
self.view.update_results(results)
# Обновляем информацию о растворителе в первой строке таблицы
self.view.update_solvent_result(solvent_amount, self.model.amount_unit)
self.view.update_solvent_percent(solvent_percentage)
except ValueError as e:
self.view.show_error(f"Ошибка в данных: {str(e)}")
except Exception as e:
self.view.show_error(f"Неожиданная ошибка: {str(e)}")
def _update_model_from_view(self):
"""Обновляет модель данными из интерфейса"""
"""Обновляет модель данными из интерфейса (только реагенты, без растворителя)"""
# Очищаем список реагентов в модели
self.model.reagents.clear()
# Обновляем общее количество и единицу измерения
self.model.total_amount = self.view.amount_input.value()
self.model.amount_unit = self.view.amount_unit_combo.currentText()
self.model.solvent = self.view.solvent_input.text()
for row in range(self.view.table.rowCount()):
# Заполняем реагенты из таблицы (начиная с 1 строки, пропуская растворитель)
for row in range(1, self.view.table.rowCount()):
name_item = self.view.table.item(row, 0)
percentage_item = self.view.table.item(row, 1)
unit_widget = self.view.table.cellWidget(row, 2)
conversion_item = self.view.table.item(row, 3)
dilution_widget = self.view.table.cellWidget(row, 4)
# Пропускаем строку, если какие-то обязательные поля отсутствуют
if not all([name_item, percentage_item, conversion_item]):
continue
try:
name = name_item.text()
percentage = float(percentage_item.text())
unit = unit_widget.currentText()
unit = unit_widget.currentText() if unit_widget else "мг"
conversion_factor = float(conversion_item.text())
reagent = Reagent(name, percentage, unit, conversion_factor)
self.model.add_reagent(reagent)
# Получаем коэффициент разбавления (поддерживаем QDoubleSpinBox и QLineEdit)
dilution_factor = 1.0
if dilution_widget:
if isinstance(dilution_widget, QDoubleSpinBox):
dilution_factor = dilution_widget.value()
elif isinstance(dilution_widget, QLineEdit):
try:
dilution_factor = float(dilution_widget.text())
except ValueError:
dilution_factor = 1.0
# Добавляем реагент в модель
self.model.add_reagent(name, percentage, unit, conversion_factor, dilution_factor)
except ValueError as e:
raise ValueError(f"Ошибка в строке {row + 1}: {str(e)}")
def _update_view_from_model(self):
"""Обновляет интерфейс данными из модели"""
self.view.table.setRowCount(0)
# Очищаем таблицу, но сохраняем строку растворителя
while self.view.table.rowCount() > 1:
self.view.table.removeRow(1)
# Если нет строки растворителя, добавляем её
if self.view.table.rowCount() == 0:
self.view.add_solvent_row()
# Устанавливаем общее количество и единицу измерения
self.view.amount_input.setValue(self.model.total_amount)
index = self.view.amount_unit_combo.findText(self.model.amount_unit)
if index >= 0:
self.view.amount_unit_combo.setCurrentIndex(index)
self.view.solvent_input.setText(self.model.solvent)
# Устанавливаем название растворителя и обновляем его в таблице
self.view.solvent_input.setText(self.model.solvent)
self.view.update_solvent_name()
# Заполняем таблицу реагентами из модели
for reagent in self.model.reagents:
row = self.view.table.rowCount()
self.view.table.insertRow(row)
self.view.table.setItem(row, 0, QTableWidgetItem(reagent.name))
self.view.table.setItem(row, 1, QTableWidgetItem(f"{reagent.percentage:.2f}"))
@@ -102,21 +122,34 @@ class Controller:
unit_combo.addItems(["нг", "мкг", "мг", "г", "кг", "нл", "мкл", "мл", "л"])
unit_combo.setCurrentText(reagent.unit)
self.view.table.setCellWidget(row, 2, unit_combo)
self.view.table.setItem(row, 3, QTableWidgetItem(f"{reagent.conversion_factor:.2f}"))
self.view.table.setItem(row, 4, QTableWidgetItem(""))
self.view.table.setItem(row, 3, QTableWidgetItem(f"{reagent.conversion_factor:.2f}"))
# Создаём поле для разбавления (QLineEdit для ручного ввода)
dilution_edit = QLineEdit()
dilution_edit.setText(f"{getattr(reagent, 'dilution_factor', 1.0):.3f}")
dilution_edit.setAlignment(Qt.AlignRight)
dilution_edit.setToolTip("Во сколько раз разбавить (1 = без разбавления)")
self.view.table.setCellWidget(row, 4, dilution_edit)
self.view.table.setItem(row, 5, QTableWidgetItem(""))
# Очищаем результаты
self.view.clear_results()
def save_composition(self):
"""Сохраняет состав среды в JSONфайл"""
"""Сохраняет состав среды в JSON-файл"""
filename, _ = QFileDialog.getSaveFileName(
self.view,
"Сохранить состав среды",
"",
"JSON Files (*.json);;All Files (*)"
)
if filename:
if not filename.lower().endswith('.json'):
filename += '.json'
try:
self._update_model_from_view()
self.model.save_to_file(filename)
@@ -125,18 +158,38 @@ class Controller:
self.view.show_error(f"Ошибка сохранения: {str(e)}")
def load_composition(self):
"""Загружает состав среды из JSONфайла"""
"""Загружает состав среды из JSON-файла"""
filename, _ = QFileDialog.getOpenFileName(
self.view,
"Загрузить состав среды",
"",
"JSON Files (*.json);;All Files (*)"
)
if filename:
try:
self.model.load_from_file(filename)
self._update_view_from_model() # Исправлено: добавлены скобки ()
self.calculate()
QMessageBox.information(self.view, "Успех", "Состав среды успешно загружен!")
self._update_view_from_model()
QMessageBox.information(
self.view,
"Успех",
"Состав среды успешно загружен"
)
except FileNotFoundError:
QMessageBox.critical(
self.view,
"Ошибка",
f"Файл не найден: {filename}"
)
except json.JSONDecodeError as e:
QMessageBox.critical(
self.view,
"Ошибка",
f"Неверный формат JSON-файла: {str(e)}"
)
except Exception as e:
self.view.show_error(f"Ошибка загрузки: {str(e)}")
QMessageBox.critical(
self.view,
"Ошибка",
f"Ошибка при загрузке состава: {str(e)}"
)
+7 -22
View File
@@ -1,34 +1,19 @@
# main.py
import sys
from PyQt5.QtWidgets import QApplication
from model import Model
from view import MainWindow
from controller import Controller
def main():
# Создаём приложение Qt
app = QApplication(sys.argv)
# Инициализируем компоненты MVC
model = Model() # Модель данных
view = MainWindow() # Графический интерфейс
controller = Controller(view, model) # Контроллер, связывающий модель и представление
# Создаём контроллер - он сам создаст модель и представление
controller = Controller()
# Связываем модель с представлением — критически важный шаг для устранения ошибки «Модель не установлена»
view.set_model(model)
# Показываем главное окно
controller.view.show()
# Настраиваем контроллер (подключаем обработчики событий к кнопкам)
controller.setup_connections()
# Отображаем главное окно приложения
view.show()
# Запускаем главный цикл обработки событий Qt и ожидаем завершения приложения
# Запускаем цикл обработки событий
sys.exit(app.exec_())
if __name__ == '__main__':
if __name__ == "__main__":
main()
+133 -93
View File
@@ -1,5 +1,5 @@
import json
from reagent import Reagent
VOLUME_UNITS = {
'нл': 0.001, # 1 нл = 0.001 мкл
@@ -16,135 +16,175 @@ MASS_UNITS = {
'кг': 1000000.0 # 1 кг = 1 000 000 000 мкг
}
class Reagent:
def __init__(self, name: str, percentage: float, unit: str, conversion_factor: float = 1.0):
self.name = name
self.percentage = percentage
self.unit = unit
self.conversion_factor = conversion_factor
def to_dict(self):
"""Преобразует реагент в словарь для сохранения в JSON"""
return {
'name': self.name,
'percentage': self.percentage,
'unit': self.unit,
'conversion_factor': self.conversion_factor
}
@classmethod
def from_dict(cls, data):
"""Создаёт реагент из словаря, загруженного из JSON"""
return cls(
name=data['name'],
percentage=data['percentage'],
unit=data['unit'],
conversion_factor=data['conversion_factor']
)
class Model:
def __init__(self):
self.reagents = []
self.total_amount = 1000.0 # по умолчанию 1000 мл
self.amount_unit = "мл"
self.solvent = "Вода"
def convert_amount(self, amount_mkl_or_mkg: float, target_unit: str, is_volume: bool) -> float:
self.total_amount = 100.0 # общее количество среды (по умолчанию — 1000 мл)
self.amount_unit = 'мл' # единица измерения общего количества
self.solvent = 'Вода' # растворитель по умолчанию
self.reagents = [] # список реагентов
def convert_amount(self, amount_base: float, target_unit: str, is_volume: bool) -> float:
"""
Пересчитывает количество из базовых единиц (мкл/мкг) в целевую единицу.
Args:
amount_mkl_or_mkg: количество в базовых единицах (мкл или мкг)
target_unit: целевая единица измерения
is_volume: True — объём, False — масса
Returns:
Количество в целевой единице
"""
if is_volume:
conversion_factor = VOLUME_UNITS.get(target_unit, 1.0)
else:
conversion_factor = MASS_UNITS.get(target_unit, 1.0)
return amount_mkl_or_mkg / conversion_factor
return amount_base / conversion_factor
def add_reagent(self, reagent: Reagent):
"""Добавляет реагент в список"""
self.reagents.append(reagent)
def calculate_amounts(self) -> list:
def calculate_amounts(self) -> tuple:
"""
Рассчитывает абсолютное количество каждого реагента с учётом единиц измерения.
Возвращает список количеств в единицах измерения реагента.
Рассчитывает:
1. Исходное количество реагента для неразбавленного состава
2. Количество разбавленного реагента, которое нужно взять
3. Количество растворителя с учётом добавленных разбавленных реагентов
Возвращает: (список количеств разбавленных реагентов, количество растворителя, процент растворителя)
"""
results = []
# Проверяем, есть ли реагенты
if not self.reagents:
return results, self.total_amount, 100.0
# Суммируем проценты всех реагентов
total_percentage = sum(reagent.percentage for reagent in self.reagents)
# Проверяем, что сумма процентов не превышает 100
if total_percentage > 100:
raise ValueError(f"Сумма процентов реагентов ({total_percentage:.2f}%) превышает 100%")
# Шаг 1: Рассчитываем общий объём среды в базовых единицах (мкл)
total_in_base = self.total_amount * VOLUME_UNITS[self.amount_unit]
# Шаг 2: Рассчитываем количество каждого реагента для неразбавленного состава
undiluted_amounts = []
for reagent in self.reagents:
# Базовый расчёт в процентах от общего количества
base_amount = (reagent.percentage / 100) * self.total_amount
# Расчёт количества реагента в базовых единицах
amount_in_base = (reagent.percentage / 100) * total_in_base
# Определяем, объём или масса
# Определение типа единицы измерения реагента
is_volume = reagent.unit in VOLUME_UNITS
# Применение коэффициента пересчёта
adjusted_amount = amount_in_base * reagent.conversion_factor
# Пересчёт в целевую единицу измерения
final_amount = self.convert_amount(adjusted_amount, reagent.unit, is_volume)
undiluted_amounts.append(final_amount)
# Шаг 3: Рассчитываем количество разбавленного реагента, которое нужно взять
# и общий объём вносимых разбавленных реагентов (в базовых единицах)
diluted_amounts = []
total_diluted_volume_base = 0
for i, reagent in enumerate(self.reagents):
dilution_factor = getattr(reagent, 'dilution_factor', 1.0)
if dilution_factor <= 0:
dilution_factor = 1.0
# Количество разбавленного реагента = исходное количество / фактор разбавления
diluted_amount = undiluted_amounts[i] * dilution_factor
diluted_amounts.append(diluted_amount)
# Вычисляем объём разбавленного реагента в базовых единицах (мкл)
is_volume = reagent.unit in VOLUME_UNITS
is_mass = reagent.unit in MASS_UNITS
if is_volume:
# Переводим общее количество среды в мкл для расчёта
total_in_mkl = self.total_amount * VOLUME_UNITS[self.amount_unit]
# Расчёт в мкл
amount_in_mkl = (reagent.percentage / 100) * total_in_mkl
# Переводим обратно в единицы реагента
final_amount = self.convert_amount(amount_in_mkl, reagent.unit, True)
elif is_mass:
# Переводим общее количество среды в мкг для расчёта (если среда в единицах объёма,
# предполагаем плотность = 1 г/мл)
if self.amount_unit in VOLUME_UNITS:
# Предполагаем плотность 1 г/мл: 1 мл ≈ 1 г
total_in_mkg = self.total_amount * VOLUME_UNITS[self.amount_unit] # в мкл
total_in_mkg *= 1000 # переводим в мкг (1 мкл воды ≈ 1 мкг)
# Если реагент в объёмных единицах, его объём = diluted_amount
reagent_volume_base = diluted_amount * VOLUME_UNITS[reagent.unit]
else:
total_in_mkg = self.total_amount * MASS_UNITS[self.amount_unit]
# Если реагент в массовых единицах, считаем, что он вносит пренебрежимый объём
# или можно добавить коэффициент плотности, пока игнорируем
reagent_volume_base = 0
# Расчёт в мкг
amount_in_mkg = (reagent.percentage / 100) * total_in_mkg
# Переводим в единицы реагента
final_amount = self.convert_amount(amount_in_mkg, reagent.unit, False)
else:
# Если единица неизвестна, возвращаем базовый расчёт
final_amount = base_amount
total_diluted_volume_base += reagent_volume_base
results.append(final_amount)
return results
# Шаг 4: Рассчитываем количество растворителя
# Растворитель = общий объём - объём всех разбавленных реагентов
solvent_volume_base = total_in_base - total_diluted_volume_base
# Переводим количество растворителя в целевую единицу измерения
solvent_amount = solvent_volume_base / VOLUME_UNITS[self.amount_unit]
def get_solvent_info(self) -> dict:
"""Возвращает информацию о растворителе: процент и количество"""
total_percentage = sum(r.percentage for r in self.reagents)
solvent_percentage = max(0, 100 - total_percentage) # не меньше 0%
solvent_amount = (solvent_percentage / 100) * self.total_amount
return {
'percentage': solvent_percentage,
'amount': solvent_amount,
'unit': self.amount_unit
}
# Если объём реагентов превышает общий объём, корректируем
if solvent_amount < 0:
solvent_amount = 0
# Процент растворителя в неразбавленном составе
solvent_percentage = 100 - total_percentage
return diluted_amounts, solvent_amount, solvent_percentage
def save_to_file(self, filename: str):
"""Сохраняет состав среды в JSONфайл"""
"""Сохраняет модель в JSON-файл"""
data = {
'total_amount': self.total_amount,
'amount_unit': self.amount_unit,
'solvent': self.solvent,
'reagents': [r.to_dict() for r in self.reagents]
'reagents': [
{
'name': r.name,
'percentage': r.percentage,
'unit': r.unit,
'conversion_factor': r.conversion_factor,
'dilution_factor': getattr(r, 'dilution_factor', 1.0)
} for r in self.reagents
]
}
with open(filename, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
json.dump(data, f, ensure_ascii=False, indent=4)
def load_from_file(self, filename: str):
"""Загружает состав среды из JSONфайла"""
"""Загружает модель из JSON-файла"""
with open(filename, 'r', encoding='utf-8') as f:
data = json.load(f)
# Восстанавливаем данные модели
self.total_amount = data['total_amount']
self.amount_unit = data['amount_unit']
self.solvent = data['solvent']
# Очищаем и заполняем список реагентов
self.reagents.clear()
for reagent_data in data['reagents']:
reagent = Reagent.from_dict(reagent_data)
for r_data in data['reagents']:
reagent = Reagent(
name=r_data['name'],
percentage=r_data['percentage'],
unit=r_data['unit'],
conversion_factor=r_data.get('conversion_factor', 1.0)
)
# Добавляем коэффициент разбавления
reagent.dilution_factor = r_data.get('dilution_factor', 1.0)
self.reagents.append(reagent)
def add_reagent(self, name: str, percentage: float, unit: str, conversion_factor: float = 1.0,
dilution_factor: float = 1.0):
"""Добавляет новый реагент в модель"""
reagent = Reagent(name, percentage, unit, conversion_factor)
reagent.dilution_factor = dilution_factor
self.reagents.append(reagent)
def remove_reagent(self, index: int):
"""Удаляет реагент по индексу"""
if 0 <= index < len(self.reagents):
del self.reagents[index]
def clear_reagents(self):
"""Очищает список реагентов"""
self.reagents.clear()
def get_reagent_count(self) -> int:
"""Возвращает количество реагентов в модели"""
return len(self.reagents)
def set_total_amount(self, amount: float, unit: str):
"""Устанавливает общее количество среды и единицу измерения"""
self.total_amount = amount
self.amount_unit = unit
def set_solvent(self, solvent_name: str):
"""Устанавливает название растворителя"""
self.solvent = solvent_name
+7
View File
@@ -0,0 +1,7 @@
class Reagent:
def __init__(self, name: str, percentage: float, unit: str, conversion_factor: float = 1.0):
self.name = name
self.percentage = percentage
self.unit = unit
self.conversion_factor = conversion_factor # коэффициент пересчёта
self.dilution_factor = 1.0 # во сколько раз разбавляем (1 = без разбавления)
+191 -86
View File
@@ -1,134 +1,239 @@
from PyQt5.QtWidgets import (
QMainWindow, QVBoxLayout, QHBoxLayout, QWidget,
QLabel, QDoubleSpinBox, QComboBox, QPushButton,
QTableWidget, QTableWidgetItem, QLineEdit, QMessageBox
)
from PyQt5.QtWidgets import (QMainWindow, QVBoxLayout, QHBoxLayout,
QTableWidget, QTableWidgetItem, QPushButton,
QLabel, QDoubleSpinBox, QComboBox, QLineEdit,
QWidget, QMessageBox)
from PyQt5.QtCore import Qt
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Калькулятор питательных сред")
self.setGeometry(100, 100, 800, 600)
self.setGeometry(100, 100, 1000, 600)
self._init_ui()
def _init_ui(self):
"""Инициализирует все виджеты интерфейса"""
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QVBoxLayout()
layout = QVBoxLayout(central_widget)
# Секция параметров среды
params_layout = QHBoxLayout()
# Общее количество среды
amount_layout = QHBoxLayout()
amount_layout.addWidget(QLabel("Общее количество:"))
# Верхняя панель: параметры среды
top_layout = QHBoxLayout()
top_layout.addWidget(QLabel("Общее количество:"))
self.amount_input = QDoubleSpinBox()
self.amount_input.setRange(0.001, 1000000)
self.amount_input.setRange(0.001, 1000000.0)
self.amount_input.setValue(1000.0)
self.amount_input.setSingleStep(10)
amount_layout.addWidget(self.amount_input)
top_layout.addWidget(self.amount_input)
# Единица измерения
self.amount_unit_combo = QComboBox()
self.amount_unit_combo.addItems(["нг", "мкг", "мг", "г", "кг", "нл", "мкл", "мл", "л"])
self.amount_unit_combo.addItems(["нл", "мкл", "мл", "л"])
self.amount_unit_combo.setCurrentText("мл")
amount_layout.addWidget(self.amount_unit_combo)
top_layout.addWidget(self.amount_unit_combo)
# Растворитель
solvent_layout = QHBoxLayout()
solvent_layout.addWidget(QLabel("Растворитель:"))
self.solvent_input = QLineEdit()
self.solvent_input.setText("Вода")
solvent_layout.addWidget(self.solvent_input)
top_layout.addWidget(QLabel("Растворитель:"))
self.solvent_input = QLineEdit("Вода")
top_layout.addWidget(self.solvent_input)
layout.addLayout(top_layout)
params_layout.addLayout(amount_layout)
params_layout.addSpacing(20)
params_layout.addLayout(solvent_layout)
params_layout.addStretch()
layout.addLayout(params_layout)
# Таблица реагентов
# Таблица реагентов (с растворителем в первой строке)
layout.addWidget(QLabel("Состав среды:"))
self.table = QTableWidget()
self.table.setColumnCount(5)
self.table.setHorizontalHeaderLabels([
"Название реагента",
"Процент (%)",
"Единица измерения",
"Коэффициент пересчёта",
"Результат"
])
self.table.setColumnCount(6) # Увеличиваем до 6 колонок
self.table.setHorizontalHeaderLabels(["Название", "%", "Единица", "Коэфф.", "Разбавление (x)", "Результат"])
layout.addWidget(self.table)
# Кнопки управления
buttons_layout = QHBoxLayout()
btn_layout = QHBoxLayout()
self.add_row_btn = QPushButton("Добавить реагент")
self.remove_row_btn = QPushButton("Удалить реагент")
self.calculate_btn = QPushButton("Рассчитать")
self.save_btn = QPushButton("Сохранить")
self.load_btn = QPushButton("Загрузить")
buttons_layout.addWidget(self.add_row_btn)
buttons_layout.addWidget(self.remove_row_btn)
buttons_layout.addWidget(self.calculate_btn)
buttons_layout.addWidget(self.save_btn)
buttons_layout.addWidget(self.load_btn)
buttons_layout.addStretch()
btn_layout.addWidget(self.add_row_btn)
btn_layout.addWidget(self.remove_row_btn)
btn_layout.addWidget(self.calculate_btn)
btn_layout.addWidget(self.save_btn)
btn_layout.addWidget(self.load_btn)
layout.addLayout(btn_layout)
layout.addLayout(buttons_layout)
central_widget.setLayout(layout)
self.add_initial_rows()
# Добавляем начальную строку
self.add_initial_row()
def add_initial_rows(self):
"""Добавляет начальные строки: растворитель и первый реагент"""
# Добавляем строку растворителя (первая, нередактируемая)
self.add_solvent_row()
# Добавляем строку для первого реагента
self.add_new_row()
def add_initial_row(self):
"""Добавляет начальную строку в таблицу"""
self.table.insertRow(0)
self.table.setItem(0, 0, QTableWidgetItem("Реагент"))
self.table.setItem(0, 1, QTableWidgetItem("1.0"))
def add_solvent_row(self):
"""Добавляет строку растворителя (нередактируемая)"""
row_count = self.table.rowCount()
self.table.insertRow(row_count)
# Название растворителя (берём из поля ввода)
solvent_name = self.solvent_input.text()
solvent_item = QTableWidgetItem(solvent_name)
solvent_item.setFlags(solvent_item.flags() & ~Qt.ItemIsEditable)
solvent_item.setBackground(Qt.lightGray)
self.table.setItem(row_count, 0, solvent_item)
# Процент (будет рассчитан автоматически)
percent_item = QTableWidgetItem("")
percent_item.setFlags(percent_item.flags() & ~Qt.ItemIsEditable)
percent_item.setBackground(Qt.lightGray)
self.table.setItem(row_count, 1, percent_item)
# Единица измерения (не используется для растворителя)
unit_item = QTableWidgetItem("-")
unit_item.setFlags(unit_item.flags() & ~Qt.ItemIsEditable)
unit_item.setBackground(Qt.lightGray)
self.table.setItem(row_count, 2, unit_item)
# Коэффициент (не используется для растворителя)
coeff_item = QTableWidgetItem("-")
coeff_item.setFlags(coeff_item.flags() & ~Qt.ItemIsEditable)
coeff_item.setBackground(Qt.lightGray)
self.table.setItem(row_count, 3, coeff_item)
# Разбавление (не используется для растворителя)
dilution_item = QTableWidgetItem("-")
dilution_item.setFlags(dilution_item.flags() & ~Qt.ItemIsEditable)
dilution_item.setBackground(Qt.lightGray)
self.table.setItem(row_count, 4, dilution_item)
# Результат (будет заполнен при расчёте)
result_item = QTableWidgetItem("")
result_item.setFlags(result_item.flags() & ~Qt.ItemIsEditable)
result_item.setBackground(Qt.lightGray)
self.table.setItem(row_count, 5, result_item)
def update_solvent_name(self):
"""Обновляет название растворителя в первой строке таблицы"""
solvent_name = self.solvent_input.text()
name_item = self.table.item(0, 0)
if name_item:
name_item.setText(solvent_name)
def add_new_row(self):
"""Добавляет новую строку для реагента"""
row_count = self.table.rowCount()
self.table.insertRow(row_count)
self.table.setItem(row_count, 0, QTableWidgetItem(f"Реагент_{row_count}"))
self.table.setItem(row_count, 1, QTableWidgetItem("0.0"))
unit_combo = QComboBox()
unit_combo.addItems(["нг", "мкг", "мг", "г", "кг", "нл", "мкл", "мл", "л"])
unit_combo.setCurrentText("мг")
self.table.setCellWidget(0, 2, unit_combo)
unit_combo.setCurrentText("г")
self.table.setCellWidget(row_count, 2, unit_combo)
self.table.setItem(0, 3, QTableWidgetItem("1.0"))
self.table.setItem(0, 4, QTableWidgetItem(""))
self.table.setItem(row_count, 3, QTableWidgetItem("1.0"))
def set_model(self, model):
"""Устанавливает связь с моделью"""
self.model = model
# Разбавление - обычное текстовое поле
dilution_edit = QLineEdit("1.0")
dilution_edit.setAlignment(Qt.AlignRight)
dilution_edit.setToolTip("Во сколько раз разбавить (1 = без разбавления)")
self.table.setCellWidget(row_count, 4, dilution_edit)
def update_results(self, results: list):
"""Обновляет столбец результатов в таблице"""
for row, amount in enumerate(results):
if row < self.table.rowCount():
# Округление до 4 знаков после запятой
self.table.setItem(row, 4, QTableWidgetItem(f"{amount:.4f}"))
self.table.setItem(row_count, 5, QTableWidgetItem(""))
def remove_selected_row(self):
"""Удаляет выделенную строку из таблицы (кроме строки растворителя)"""
selected_rows = set()
for item in self.table.selectedItems():
selected_rows.add(item.row())
def show_error(self, message: str):
"""Показывает диалоговое окно с ошибкой"""
QMessageBox.critical(self, "Ошибка", message)
# Удаляем строки в обратном порядке, пропуская строку растворителя (индекс 0)
for row in sorted(selected_rows, reverse=True):
if row > 0: # Не удаляем строку растворителя
self.table.removeRow(row)
def get_table_data(self) -> list:
"""Возвращает данные из таблицы для отладки"""
"""Возвращает данные таблицы в виде списка списков (только реагенты, без растворителя)"""
data = []
for row in range(self.table.rowCount()):
# Начинаем с 1 строки (пропускаем растворитель)
for row in range(1, self.table.rowCount()):
row_data = []
for col in range(self.table.columnCount() - 1): # не берём столбец результатов
item = self.table.item(row, col)
if item:
row_data.append(item.text())
# Название (колонка 0)
name_item = self.table.item(row, 0)
row_data.append(name_item.text() if name_item else "")
# Процент (колонка 1)
percent_item = self.table.item(row, 1)
row_data.append(percent_item.text() if percent_item else "0")
# Единица измерения (колонка 2 - комбобокс)
unit_widget = self.table.cellWidget(row, 2)
if unit_widget and isinstance(unit_widget, QComboBox):
row_data.append(unit_widget.currentText())
else:
widget = self.table.cellWidget(row, col)
if widget and isinstance(widget, QComboBox):
row_data.append(widget.currentText())
row_data.append("мг")
# Коэффициент (колонка 3)
coeff_item = self.table.item(row, 3)
row_data.append(coeff_item.text() if coeff_item else "1.0")
# Разбавление (колонка 4 - spinbox)
dilution_widget = self.table.cellWidget(row, 4)
if dilution_widget and isinstance(dilution_widget, QDoubleSpinBox):
dilution_factor = dilution_widget.value()
row_data.append(dilution_factor)
else:
row_data.append("")
row_data.append(1.0)
data.append(row_data)
return data
def update_solvent_percent(self, solvent_percent: float):
"""Обновляет процент растворителя в первой строке"""
percent_item = self.table.item(0, 1)
if percent_item:
percent_item.setText(f"{solvent_percent:.2f}")
def show_error(self, message: str):
"""Показывает сообщение об ошибке"""
QMessageBox.critical(self, "Ошибка", message)
def update_results(self, results: list):
"""Обновляет столбец результатов (индекс 5) в таблице"""
# Начинаем с 1 строки (реагенты), 0 строка - растворитель
for row, amount in enumerate(results, start=1):
if row < self.table.rowCount():
formatted_amount = f"{amount:.4f}"
self.table.setItem(row, 5, QTableWidgetItem(formatted_amount))
def update_solvent_result(self, solvent_amount: float, unit: str):
"""Обновляет результат для растворителя в первой строке"""
formatted_amount = f"{solvent_amount:.4f}"
result_item = self.table.item(0, 5)
if result_item:
result_item.setText(formatted_amount)
# Также обновляем единицу измерения в колонке 2 для информации
unit_item = self.table.item(0, 2)
if unit_item:
unit_item.setText(unit)
def update_display(self, solvent: str, total_amount: float, amount_unit: str):
"""Обновляет отображение растворителя и общего количества среды"""
self.solvent_input.setText(solvent)
self.update_solvent_name()
self.amount_input.setValue(total_amount)
self.amount_unit_combo.setCurrentText(amount_unit)
def clear_results(self):
"""Очищает столбец результатов для всех строк"""
for row in range(self.table.rowCount()):
self.table.setItem(row, 5, QTableWidgetItem(""))
# Очищаем процент растворителя
percent_item = self.table.item(0, 1)
if percent_item:
percent_item.setText("")
# Очищаем единицу измерения растворителя
unit_item = self.table.item(0, 2)
if unit_item:
unit_item.setText("-")