diff --git a/GSS2/ViewModels/AmineContent/SampleCalibrationViewModel.cs b/GSS2/ViewModels/AmineContent/SampleCalibrationViewModel.cs
index 6fbb1ac..c3fbdf6 100644
--- a/GSS2/ViewModels/AmineContent/SampleCalibrationViewModel.cs
+++ b/GSS2/ViewModels/AmineContent/SampleCalibrationViewModel.cs
@@ -1,3 +1,4 @@
+using System.Data;
using System.Collections.ObjectModel;
using Microsoft.EntityFrameworkCore;
@@ -8,9 +9,10 @@ using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
-using GSS2.Core;
using GSS2.Core.Analysis.AmineContent;
-using GSS2.Core.Analysis.AmineContent.Database.Calibration;
+using GSS2.Core.Analysis.AmineContent.Database;
+using GSS2.Core.Extensions;
+
using GSS2.UI.Core.ViewModels;
using GSS2.UI.Core.Services;
@@ -18,294 +20,711 @@ namespace GSS2.ViewModels.AmineContent;
public partial class SampleCalibrationViewModel : ViewModelBase
{
+ ///
+ /// Внутренняя модель элементов меню
+ ///
+ public partial class MenuItemViewModel : ViewModelBase
+ {
+ [ObservableProperty] public partial int Id { get; set; }
+ [ObservableProperty] public partial string SampleName { get; set; }
+
+ public MenuItemViewModel(int id, string sampleName)
+ {
+ Id = id;
+ SampleName = sampleName;
+ }
+ }
+
+ ///
+ /// Внутренняя модель элементов выпадающего списка марок
+ ///
+ public partial class BrandComboBoxItemViewModel : ViewModelBase
+ {
+ [ObservableProperty] public partial int Id { get; set; }
+ [ObservableProperty] public partial string BrandName { get; set; }
+ [ObservableProperty] public partial string MixtureName { get; set; }
+
+ public BrandComboBoxItemViewModel(int id, string brandName, string mixtureName)
+ {
+ Id = id;
+ BrandName = brandName;
+ MixtureName = mixtureName;
+
+ }
+ }
+
+ ///
+ /// Логгер
+ ///
private readonly ILogger _logger;
- private readonly CalibrationContext _context;
- private readonly ImageCapturerService _imageCapturer;
- private readonly ImageStorageService _imageStorage;
+ ///
+ /// Контекст базы данных
+ ///
+ private readonly Context _context;
+ ///
+ /// Сервис для получения изображений с камеры
+ ///
+ private readonly IImageCapturerService _imageCapturer;
+ ///
+ /// ViewLocator для получения других View по их моделям
+ ///
private readonly DependencyInjectionViewLocator _viewLocator;
+ ///
+ /// Сервис навигации между экранами
+ ///
private readonly NavigationService _navigation;
+ ///
+ /// Задача съёмки изображений
+ ///
+ private Task? _сaptureImagesTask = null;
+ ///
+ /// Токен отмены задачи съёмки изображений
+ ///
private CancellationTokenSource? _captureImagesTaskCts = null;
+ ///
+ /// выбранного элемента
+ ///
+ private int _selectedBrandId = 0;
+ ///
+ /// Данные выбранного элемента
+ ///
+ private string? _selectedImageData = null;
+ ///
+ /// Данные выбранного элемента
+ ///
+ private List _selectedFeaturesData = new();
+
+ ///
+ /// Меню открыто
+ ///
[ObservableProperty] public partial bool MenuOpened { get; set; } = false;
- [ObservableProperty] public partial ObservableCollection Records { get; set; }
+ ///
+ /// Элементы меню
+ ///
+ [ObservableProperty] public partial ObservableCollection MenuItems { get; set; } = new();
+ ///
+ /// Текущий выбранный элемент меню. Равен -1 если элемент не выбран
+ ///
+ [ObservableProperty] public partial int MenuSelectedIndex { get; set; } = -1;
- [ObservableProperty] public partial SampleRecord? SelectedRecord { get; set; }
- [ObservableProperty] public partial SampleRecord EditingRecord { get; set; }
+ ///
+ /// выбранного элемента
+ ///
+ [ObservableProperty] public partial int SelectedId { get; set; } = 0;
+ ///
+ /// выбранного элемента
+ ///
+ [ObservableProperty] public partial string SelectedUuid { get; set; } = Guid.NewGuid().ToString();
+ ///
+ /// выбранного элемента
+ ///
+ [ObservableProperty] public partial string SelectedSampleName { get; set; } = "";
+ ///
+ /// выбранного элемента
+ ///
+ [ObservableProperty] public partial string SelectedSampleComment { get; set; } = "";
+ ///
+ /// выбранного элемента
+ ///
+ [ObservableProperty] public partial DateTime SelectedSamplingDateTime { get; set; } = DateTime.UtcNow;
+ ///
+ /// выбранного элемента
+ ///
+ [ObservableProperty] public partial string SelectedSamplingPlace { get; set; } = "";
+ ///
+ /// выбранного элемента
+ ///
+ [ObservableProperty] public partial double SelectedMixtureActualRate { get; set; } = -1;
+ ///
+ /// выбранного элемента
+ ///
+ [ObservableProperty] public partial double SelectedMeasuredContent { get; set; } = -1;
+ ///
+ /// Представление выбранного элемента
+ ///
+ [ObservableProperty] public partial AsyncImageRecordViewModel SelectedImage { get; set; } = new();
+ ///
+ /// Список элементов выпадающего списка марок
+ ///
+ [ObservableProperty] public partial ObservableCollection BrandComboBoxItems { get; set; } = new();
+ ///
+ /// Текущий выбранный элемент выпадающего списка марок. Равен -1 если элемент не выбран
+ ///
+ [ObservableProperty] public partial int BrandComboBoxSelectedIndex { get; set; } = -1;
+
+ ///
+ /// Текущий экран главной секции
+ ///
[ObservableProperty] public partial int MainSectionCurrentIndex { get; set; } = 0;
+ ///
+ /// Можно ли переходить на следующий экран главной секции
+ ///
[ObservableProperty] public partial bool MainSectionCanScrollBackward { get; set; } = false;
- [ObservableProperty] public partial bool MainSectionCanScrollForward { get; set; } = true;
- [ObservableProperty] public partial bool MainSectionIsScrolling { get; set; } = true;
+ ///
+ /// Можно ли переходить на предыдущий экран главной секции
+ ///
+ [ObservableProperty] public partial bool MainSectionCanScrollForward { get; set; } = false;
+ ///
+ /// Происходит ли переход между экранами главной секции
+ ///
+ [ObservableProperty] public partial bool MainSectionIsScrolling { get; set; } = false;
+
+ ///
+ /// Текущий экран информационной секции
+ ///
[ObservableProperty] public partial int InfoSectionCurrentIndex { get; set; } = 0;
+ ///
+ /// Можно ли переходить на следующий экран информационной секции
+ ///
[ObservableProperty] public partial bool InfoSectionCanScrollBackward { get; set; } = false;
- [ObservableProperty] public partial bool InfoSectionCanScrollForward { get; set; } = true;
- [ObservableProperty] public partial bool InfoSectionIsScrolling { get; set; } = true;
-
- [ObservableProperty] public partial Task? CaptureImagesTask { get; set; } = null;
+ ///
+ /// Можно ли переходить на предыдущий экран информационной секции
+ ///
+ [ObservableProperty] public partial bool InfoSectionCanScrollForward { get; set; } = false;
+ ///
+ /// Происходит ли переход между экранами информационной секции
+ ///
+ [ObservableProperty] public partial bool InfoSectionIsScrolling { get; set; } = false;
+ ///
+ /// Проводится ли съёмка изображений
+ ///
[ObservableProperty] public partial bool ImagesIsCapturing { get; set; } = false;
+ ///
+ /// Прогресс съёмки изображений
+ ///
[ObservableProperty] public partial string ImagesCapturingProgress { get; set; } = "";
- [ObservableProperty] public partial AsyncImageRecordViewModel? Image { get; set; } = null;
-
+ ///
+ /// Можно ли производить перемещение между экранами
+ ///
+ [ObservableProperty] public partial bool CanScroll { get; set; }
public SampleCalibrationViewModel(
ILogger logger,
- CalibrationContext context,
- ImageCapturerService imageCapturer,
- ImageStorageService imageStorage,
+ Context context,
+ IImageCapturerService imageCapturer,
DependencyInjectionViewLocator viewLocator,
NavigationService navigation
)
{
+ // Устанавливаем readonly поля
_logger = logger;
_context = context;
_imageCapturer = imageCapturer;
- _imageStorage = imageStorage;
_viewLocator = viewLocator;
_navigation = navigation;
_logger.LogInformation("Инициализация");
- // Получаем записи из базы данных
- Records = new ObservableCollection(_context.SampleRecords);
+ // Получаем записи меню из базы данных
+ MenuItems = new ObservableCollection(_context.SampleRecords.Select(r => new MenuItemViewModel(r.Id, r.SampleName)));
+ if (MenuItems.Count() > 0) // Если записи есть,
+ MenuSelectedIndex = 0; // Выбираем первую запись
+ else // Если записей нет,
+ MenuSelectedIndex = -1; // Устанавливаем, что запись не выбрана
- // Берём первую запись
- SelectedRecord = Records.FirstOrDefault();
- // Если записей нет, то создаём пустую
- // на самом деле это излишне, потому что при изменении SelectedRecord
- // вызовется OnSelectedRecordChanged который сделает то же самое, но сделано это для избавления от предупреждений
- if (SelectedRecord is null)
- EditingRecord = new()
+ // Получаем записи выпадающего списка марок из базы данных
+ BrandComboBoxItems = new ObservableCollection(_context.BrandRecords.Select(r => new BrandComboBoxItemViewModel(r.Id, r.BrandName, r.MixtureName)));
+ // Подписываемся на изменения базы данных для отслеживания изменений списка марок
+ _context.ChangeTracker.StateChanged += (_, ea) =>
+ {
+ // Если запись не интересует нас, то ничего не делаем
+ if (ea.Entry.Entity is not BrandRecord brandRecord)
+ return;
+
+ // Если запись добавлена
+ if (ea.NewState is EntityState.Added)
{
- SampleName = "",
- SampleComment = "",
- SampleBrand = "",
- SampleMass = -1.0,
- SamplingDateTime = DateTime.UtcNow,
- SamplingPlace = "",
- MixtureName = "",
- MixtureAmineContent = -1.0,
- MixtureNormalRate = -1.0,
- MixtureActualRate = -1.0,
- MeasuredContent = -1.0,
- Image = "",
- VectorRecords = new()
- };
- else
- EditingRecord = CopyRecord(SelectedRecord);
+ // Добавляем запись
+ BrandComboBoxItems.Add(new BrandComboBoxItemViewModel(brandRecord.Id, brandRecord.BrandName, brandRecord.MixtureName));
+ }
+ // Если запись удалена
+ else if (ea.NewState is EntityState.Deleted)
+ {
+ // Ищем удалённую запись в выпадающем списке марок
+ var deletedComboBoxItem = BrandComboBoxItems.FirstOrDefault(r => r.Id == brandRecord.Id);
+ // Если запись не найдена в выпадающем списке марок, то логгируем как ошибку
+ if (deletedComboBoxItem is null)
+ {
+ _logger.LogError("Не удалось найти элемент с Id({}) в списке {}", brandRecord.Id, nameof(BrandComboBoxItems));
+ return;
+ }
+ // Берём индекс удалённого элемента в выпадающем списке марок
+ var deletedComboBoxIndex = BrandComboBoxItems.IndexOf(deletedComboBoxItem);
+ // Если текущий индекс равен удаляемому, то устанавливаем, что элемент не выбран
+ if (BrandComboBoxSelectedIndex == deletedComboBoxIndex)
+ BrandComboBoxSelectedIndex = -1;
+ // Удаляем элемент из выпадающего списка марок
+ BrandComboBoxItems.RemoveAt(deletedComboBoxIndex);
+ }
+ // Если запись изменилась
+ else if (ea.NewState is EntityState.Modified)
+ {
+ // Ищем изменённую запись в выпадающем списке марок
+ var editedComboBoxItem = BrandComboBoxItems.FirstOrDefault(r => r.Id == brandRecord.Id);
+ // Если запись не найдена в выпадающем списке марок, то логгируем как ошибку
+ if (editedComboBoxItem is null)
+ {
+ _logger.LogError("Не удалось найти элемент с Id({}) в списке {}", brandRecord.Id, nameof(BrandComboBoxItems));
+ return;
+ }
+ // Обновляем элемент из выпадающего списка марок
+ editedComboBoxItem.BrandName = brandRecord.BrandName;
+ editedComboBoxItem.MixtureName = brandRecord.MixtureName;
+ }
+ };
+
+ // Подписываемся на изменения свойств для обновления CanScroll
+ PropertyChanged += (_, ea) =>
+ {
+ if (ea.PropertyName == nameof(MenuOpened) ||
+ ea.PropertyName == nameof(MainSectionIsScrolling) ||
+ ea.PropertyName == nameof(InfoSectionIsScrolling))
+ UpdateCanScroll();
+ };
_logger.LogInformation("Инициализировано");
}
///
- /// Вызывается при изменении .
- /// Копирует в или издаёт пустую .
+ /// Вызывается при изменении
///
- partial void OnSelectedRecordChanged(SampleRecord? value)
+ partial void OnMenuSelectedIndexChanged(int value)
{
- // Если выбранная запись отсутствует (в базе данных нет записей или создана новая запись)
- // то создаём пустую запись
- if (SelectedRecord is null)
+ // Если значение выходит за верхнюю границу списка элементов меню, то логгируем как ошибку
+ if (value >= MenuItems.Count())
+ _logger.LogError("Значение {}({}) больше количества элементов {}({})", nameof(MenuSelectedIndex), MenuSelectedIndex, nameof(MenuItems), MenuItems.Count());
+ // Если значение больше или равно 0, то значит выбран один из элементов меню
+ if (value >= 0 && value < MenuItems.Count())
{
- EditingRecord = new()
+ _logger.LogTrace("Выбран элемент из списка: {}", value);
+ // Находим запись в базе данных
+ var selectedRecord = _context.SampleRecords.Find(MenuItems[value].Id);
+ // Если запись найдена, то заполняем поля из базы данных
+ if (selectedRecord is not null)
{
- SampleName = "",
- SampleComment = "",
- SampleBrand = "",
- SampleMass = -1.0,
- SamplingDateTime = DateTime.UtcNow,
- SamplingPlace = "",
- MixtureName = "",
- MixtureAmineContent = -1.0,
- MixtureNormalRate = -1.0,
- MixtureActualRate = -1.0,
- MeasuredContent = -1.0,
- Image = "",
- VectorRecords = new()
- };
+ // Заполняем простые поля
+ SelectedId = selectedRecord.Id;
+ SelectedUuid = selectedRecord.Uuid;
+ SelectedSampleName = selectedRecord.SampleName;
+ SelectedSampleComment = selectedRecord.SampleComment;
+ SelectedSamplingDateTime = selectedRecord.SamplingDateTime;
+ SelectedSamplingPlace = selectedRecord.SamplingPlace;
+ SelectedMixtureActualRate = selectedRecord.MixtureActualRate;
+ SelectedMeasuredContent = selectedRecord.MeasuredContent;
+ _selectedBrandId = selectedRecord.BrandId;
+
+ // Ищем изображение в базе данных
+ var imageRecord = _context.ImageRecords.Find(selectedRecord.ImageId);
+ // Если изображение не найдено в базе данных, то логгируем как ошибку
+ if (imageRecord is null)
+ _logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", selectedRecord.ImageId, nameof(_context.ImageRecords));
+ // В любом случае заполняем изображение, оно заполнится null если запись не найдена
+ _selectedImageData = imageRecord?.Base64;
+ SelectedImage.Data = _selectedImageData;
+
+ // Ищем марку в базе данных
+ var brandRecord = _context.BrandRecords.Find(_selectedBrandId);
+ // Если марка не найдена в базе данных
+ if (brandRecord is null)
+ {
+ // Логгируем как ошибку
+ _logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", _selectedBrandId, nameof(_context.BrandRecords));
+ // Устанавливаем, что элемент не выбран
+ BrandComboBoxSelectedIndex = -1;
+ }
+ // Если марка найдена в базе данных
+ else
+ {
+ // Ищем марку в выпадающем списке марок
+ var brandComboBoxItem = BrandComboBoxItems.FirstOrDefault(r => r.Id == brandRecord.Id);
+ // Если марка не найдена в выпадающем списке марок, то логгируем как ошибку
+ if (brandComboBoxItem is null)
+ _logger.LogError("Не удалось найти элемент с Id({}) в списке {}", brandRecord.Id, nameof(BrandComboBoxItems));
+ // Если марка найдена в выпадающем списке марок, выбираем её
+ else
+ BrandComboBoxSelectedIndex = BrandComboBoxItems.IndexOf(brandComboBoxItem);
+ }
+
+ // Ищем все признаки в базе данных
+ _selectedFeaturesData = _context.FeatureRecords
+ .Where(r => r.SampleRecordId == SelectedId)
+ .Select(r => r.Values.ToArray())
+ .ToList();
+
+ return;
+ }
+ // Если значение не найдено в базе данных
+ else
+ {
+ // Логгируем как ошибку
+ _logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", MenuItems[value].Id, nameof(_context.BrandRecords));
+ var nonExistingIndex = MenuSelectedIndex; // Сохраняем несуществующий индекс
+ // Устанавливаем, что запись не выбрана, этот метод вызовется повторно и заполнит поля пустыми значениями
+ MenuSelectedIndex = -1;
+ // Удаляем несуществующий элемент из списка
+ MenuItems.RemoveAt(nonExistingIndex);
+ return;
+ }
}
+
+ // Если элемент не выбран, заполняем поля пустыми значениями
+ _logger.LogTrace("Выбран новый элемент");
+ SelectedId = 0;
+ SelectedUuid = Guid.NewGuid().ToString();
+ SelectedSampleName = "";
+ SelectedSampleComment = "";
+ SelectedSamplingDateTime = DateTime.UtcNow;
+ SelectedSamplingPlace = "";
+ SelectedMixtureActualRate = -1;
+ SelectedMeasuredContent = -1;
+ SelectedImage.Data = null;
+ BrandComboBoxSelectedIndex = -1;
+ }
+
+ ///
+ /// Вызывается при изменении
+ ///
+ partial void OnBrandComboBoxSelectedIndexChanged(int value)
+ {
+ if (value >= 0 && value < BrandComboBoxItems.Count())
+ _selectedBrandId = BrandComboBoxItems[value].Id;
+ }
+
+ ///
+ /// Обновить значение
+ ///
+ private void UpdateCanScroll()
+ {
+ CanScroll = !MenuOpened;
+ CanScroll &= !MainSectionIsScrolling;
+ CanScroll &= !InfoSectionIsScrolling;
+ CanScroll &= !ImagesIsCapturing;
+ }
+
+ // Команды управления меню
+ ///
+ /// Открыть меню
+ ///
+ [RelayCommand] private void OpenMenu() => MenuOpened = true;
+ ///
+ /// Закрыть меню
+ ///
+ [RelayCommand] private void CloseMenu() => MenuOpened = false;
+ ///
+ /// Переключить видимость меню
+ ///
+ [RelayCommand] private void ToggleMenu() => MenuOpened = !MenuOpened;
+
+ ///
+ /// Переключить главную секцию информация <-> изображения
+ ///
+ [RelayCommand] private void SwitchMainSection() => MainSectionCurrentIndex = MainSectionCurrentIndex == 0 ? 1 : 0;
+ ///
+ /// Переключить информационную секцию на следующую
+ ///
+ [RelayCommand] private void InfoSectionPrevious() => InfoSectionCurrentIndex--;
+ ///
+ /// Переключить информационную секцию на предыдущую
+ ///
+ [RelayCommand] private void InfoSectionNext() => InfoSectionCurrentIndex++;
+
+ ///
+ /// Создать новую запись
+ ///
+ [RelayCommand]
+ private void CreateRecord()
+ {
+ _logger.LogInformation("Создание новой записи");
+ // Если уже создана новая запись, то MenuSelectedIndex может быть равен -1 и
+ // при простом присвоении MenuSelectedIndex = -1 событие OnMenuSelectedIndexChanged не вызовется,
+ // потому что событие срабатывает только при изменении значения, а значение было -1 и привилось ему -1,
+ // поэтому если SelectedRecord != -1, то нужно явно вызвать метод OnMenuSelectedIndexChanged
+ if (MenuSelectedIndex != -1)
+ MenuSelectedIndex = -1;
else
+ OnMenuSelectedIndexChanged(-1);
+ }
+
+ ///
+ /// Удалить выбранную запись из базы данных или очистить значения, если запись не выбрана
+ ///
+ [RelayCommand]
+ private void DeleteRecord()
+ {
+ // TODO: Добавить определение и отображение количества затрагиваемых, записей которые будут повреждены при удалении этой записи
+ try
{
- // Иначе копируем запись, отвязывая её от базы данных
- EditingRecord = CopyRecord(SelectedRecord);
+ _logger.LogTrace("Удаление записи: {}={}, {}={}", nameof(SampleRecord.Id), SelectedId, nameof(SampleRecord.SampleName), SelectedSampleName);
+ // Если запись не выбрана, то просто создаём новую запись
+ if (MenuSelectedIndex < 0)
+ {
+ _logger.LogTrace("Запись не выбрана, создаём новую");
+ CreateRecord();
+ return;
+ }
+
+ // Создаём модель для экрана подтверждения
+ var confirmViewModel = new DeleteConfirmViewModel(_navigation)
+ {
+ Question = "Вы уверены, что хотите удалить запись?"
+ };
+
+ // Создаём коллбек который вызовется при экрана подтверждения
+ void OnConfirmCloses(object? sender, bool result)
+ {
+ // Если пользователь отменил удаление, то выходим из коллбека
+ if (!result)
+ {
+ _logger.LogTrace("Пользователь отменил удаление");
+ return;
+ }
+
+ // Если запись не выбрана, то просто создаём новую запись
+ if (MenuSelectedIndex < 0)
+ {
+ _logger.LogTrace("Запись не выбрана, создаём новую");
+ CreateRecord();
+ return;
+ }
+
+ // Сохраняем индекс удалённый записи и находим её в базе данных
+ var deletedMenuIndex = MenuSelectedIndex;
+ var deletedRecordId = SelectedId;
+ var deletedRecord = _context.SampleRecords.Find(deletedRecordId);
+ // Создаём новую запись
+ CreateRecord();
+
+ // Удаляем запись из списка
+ MenuItems.RemoveAt(deletedMenuIndex);
+ // Если удалённая запись не найдена в базе данных, то логгируем как ошибку
+ if (deletedRecord is null)
+ {
+ _logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", deletedRecordId, nameof(_context.BrandRecords));
+ return;
+ }
+
+ // Удаляем запись из базы данных и сохраняем изменения в базе данных
+ _context.SampleRecords.Remove(deletedRecord);
+ _context.SaveChanges();
+ _logger.LogTrace("Запись удалена");
+
+ // Отписываемся от события закрытия экрана подтверждения
+ confirmViewModel.Closes -= OnConfirmCloses;
+ }
+
+ // Создаём View экрана подтверждения
+ var confirmView = _viewLocator.Build(confirmViewModel);
+ // Если View не найден, то логгируем как ошибку
+ if (confirmView is null)
+ {
+ _logger.LogError("Не удалось найти View для экрана подтверждения");
+ return;
+ }
+
+ _logger.LogTrace("Задаём вопрос");
+ // Подписываемся на событие закрытия экрана подтверждения
+ confirmViewModel.Closes += OnConfirmCloses;
+ // Преходим на экран подтверждения, при закрытии он сам вернёт экран обратно на текущий
+ _navigation.NavigateTo(confirmView);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Исключение при удалении записи");
}
}
///
- /// Вызывается при изменении .
- /// Изменяет изображением из .
+ /// Сохранить текущие значения в новую запись или перезаписать значения в выдранной записи
///
- partial void OnEditingRecordChanged(SampleRecord value)
- {
- var imagePath = _imageStorage.GetFullPath(value.Image, "calibration-sample");
- if (!File.Exists(imagePath))
- {
- Image = null;
- return;
- }
-
- if (Image is null)
- Image = new AsyncImageRecordViewModel(imagePath);
- else
- Image.Path = imagePath;
- }
-
- // Просто команды управления меню
- [RelayCommand] private void OpenMenu() => MenuOpened = true;
- [RelayCommand] private void CloseMenu() => MenuOpened = false;
- [RelayCommand] private void ToggleMenu() => MenuOpened = !MenuOpened;
-
- // Команда создания новой записи
- // вызывает цепочку действий по изменению EditingRecord и Images
- [RelayCommand]
- private void CreateRecord()
- {
- // Если уже создана новая запись, то SelectedRecord == null и
- // при простом присвоении SelectedRecord = null событие SelectedRecordChanged не вызовется,
- // потому что событие срабатывает только при изменении значения, а значение было null и присвоилось ему null,
- // поэтому если SelectedRecord == null, то нужно явно вызвать метод OnSelectedRecordChanged
- if (SelectedRecord is not null)
- SelectedRecord = null;
- else
- OnSelectedRecordChanged(null);
- }
-
- // Команда удаления записи, создаёт новую запись и удаляет старую из списка и базы данных
- [RelayCommand]
- private void DeleteRecord()
- {
- var confirmViewModel = new DeleteConfirmViewModel(_navigation)
- {
- Question = "Вы уверены, что хотиите удаить запись?"
- };
- if (SelectedRecord is null)
- {
- CreateRecord();
- return;
- }
- confirmViewModel.Closes += (_, ea) =>
- {
- if (ea != true)
- return;
-
- // Сохраняем текущую запись
- var deletedRecord = SelectedRecord;
- // Создаём новую запись
- CreateRecord();
-
- // Если запись на была сохранена в базу данных, то ничего не делаем
- if (deletedRecord is null)
- return;
-
- // Если запись была в без данных, то
- // она была и в Records, удаляем её из Records
- Records.Remove(deletedRecord);
- // Удаляем запись из базы данных и сохраняем изменения
- // TODO: Подумать над тем, что возможно стоит не удалять запись полностью, а добавить флаг удаления,
- // но тогда нужно будет сохранять и изображения этой записи, как в базе данных, так и на диске,
- // тогда удалённые записи будут заполнять диск
- // TODO: Подумать над тем, чтобы сразу удалять изображения из хранилища
- _context.SampleRecords.Remove(deletedRecord);
- _context.SaveChanges();
- };
-
- var convfirmView = _viewLocator.Build(confirmViewModel);
- if (convfirmView is not null)
- _navigation.NavigateTo(convfirmView);
- }
-
- // // Команда сохранения записи, сохраняет новую запись или изменения в отредактированной
[RelayCommand]
private void SaveRecord()
{
- // Если это новая запись
- if (SelectedRecord is null)
+ try
{
- // Копируем её
- var newRecord = CopyRecord(EditingRecord);
-
- // Для каждого вектора
- for (int i = 0; i < newRecord.VectorRecords.Count(); i++)
+ _logger.LogTrace("Сохранение записи: {}={}, {}={}", nameof(SampleRecord.Id), SelectedId, nameof(SampleRecord.SampleName), SelectedSampleName);
+ // Если запись не выбрана
+ if (MenuSelectedIndex < 0)
{
- var vector = newRecord.VectorRecords[i];
+ // Создаём запись изображения
+ _logger.LogTrace("Создаём новую запись изображения");
+ // Заполняем запись изображения
+ var imageRecord = new ImageRecord(base64: _selectedImageData ?? "");
+ // Добавляем запись изображения в базу данных
+ var attachedImageRecord = _context.ImageRecords.Attach(imageRecord);
+ // Сохраняем изменения в базе данных
+ _context.SaveChanges();
+ _logger.LogTrace("Запись изображения сохранена");
- // Добавляем вектор в базу
- var attachedRecord = _context.VectorRecords.Add(vector);
- // Удаляем отвязанный от базы данных вектор из записи
- newRecord.VectorRecords.RemoveAt(i);
- // Добавляем привязанный к базе данных вектор в запись
- newRecord.VectorRecords.Insert(i, attachedRecord.Entity);
- }
-
- {
- // То добавляем запись в базу
+ _logger.LogTrace("Создаём новую запись");
+ // Заполняем запись
+ var newRecord = new SampleRecord(
+ uuid: SelectedUuid,
+ sampleName: SelectedSampleName,
+ sampleComment: SelectedSampleComment,
+ samplingDateTime: SelectedSamplingDateTime,
+ brandId: _selectedBrandId,
+ samplingPlace: SelectedSamplingPlace,
+ mixtureActualRate: SelectedMixtureActualRate,
+ measuredContent: SelectedMeasuredContent,
+ imageId: attachedImageRecord.Entity.Id,
+ featureIds: []
+ );
+ // Добавляем запись в базу данных
var attachedRecord = _context.SampleRecords.Add(newRecord);
- // Заменяем текущую запись на привязанную к базе данных
- newRecord = attachedRecord.Entity;
+ // Сохраняем изменения в базе данных
+ _context.SaveChanges();
+ _logger.LogTrace("Запись сохранена");
+
+ _logger.LogTrace("Создаём новые записи признаков");
+ // Заполняем записи признаков и добавляем их в базу данных
+ var attachedFeatureRecords = new List();
+ foreach (var feature in _selectedFeaturesData)
+ {
+ var featureRecord = new FeatureRecord(
+ values: feature,
+ sampleRecordId: attachedRecord.Entity.Id
+ );
+ var attachedFeatureRecord = _context.FeatureRecords.Attach(featureRecord);
+ attachedFeatureRecords.Add(attachedFeatureRecord.Entity);
+ }
+ // Сохраняем изменения в базе данных
+ _context.SaveChanges();
+ _logger.LogTrace("Записи признаков сохранены");
+
+ _logger.LogTrace("Заполняем признаки в сохранённой записи");
+ // Добавляем идентификаторы признаков в запись
+ attachedRecord.Entity.FeatureIds.AddRange(attachedFeatureRecords.Select(r => r.Id));
+ // Сохраняем изменения в базе данных
+ _context.SaveChanges();
+ _logger.LogTrace("Сохранение записи завершено");
+
+ // Добавляем новый элемент в меню
+ MenuItems.Add(new MenuItemViewModel(attachedRecord.Entity.Id, attachedRecord.Entity.SampleName));
+ // Выбираем последний элемент из меню
+ MenuSelectedIndex = MenuItems.Count() - 1;
}
-
- // Сохраняем изменения в базе данных
- _context.SaveChanges();
- // Добавляем запись в список
- Records.Add(newRecord);
- // Выбираем запись
- SelectedRecord = newRecord;
- }
- // Если эта запись есть в базе
- else
- {
- // Переносим данные из редактируемой записи в выбранную
- SelectedRecord.SampleName = EditingRecord.SampleName;
- SelectedRecord.SampleComment = EditingRecord.SampleComment;
- SelectedRecord.SampleBrand = EditingRecord.SampleBrand;
- SelectedRecord.SampleMass = EditingRecord.SampleMass;
- SelectedRecord.SamplingDateTime = EditingRecord.SamplingDateTime;
- SelectedRecord.SamplingPlace = EditingRecord.SamplingPlace;
- SelectedRecord.MixtureName = EditingRecord.MixtureName;
- SelectedRecord.MixtureAmineContent = EditingRecord.MixtureAmineContent;
- SelectedRecord.MixtureNormalRate = EditingRecord.MixtureNormalRate;
- SelectedRecord.MixtureActualRate = EditingRecord.MixtureActualRate;
- SelectedRecord.MeasuredContent = EditingRecord.MeasuredContent;
- SelectedRecord.Image = EditingRecord.Image;
- SelectedRecord.VectorRecords = EditingRecord.VectorRecords;
-
- // Для каждого вектора в выбранной записи
- for (int i = 0; i < SelectedRecord.VectorRecords.Count(); i++)
+ // Если запись выбрана
+ else
{
- var vector = SelectedRecord.VectorRecords[i];
- // Если вектор есть в базе данных, то пропускаем его
- if (_context.Entry(vector).State is not EntityState.Detached)
- continue;
+ // Находим запись в базе данных
+ var attachedRecord = _context.SampleRecords.Find(SelectedId);
+ // Если запись не найдена, то логгируем как ошибку
+ if (attachedRecord is null)
+ {
+ _logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", SelectedId, nameof(_context.SampleRecords));
+ return;
+ }
- // Если вектора нет в базе данных, то добавляем его в базу
- var attachedRecord = _context.VectorRecords.Add(vector);
- // Удаляем отвязанный от базы данных вектор из записи
- SelectedRecord.VectorRecords.RemoveAt(i);
- // Добавляем привязанный вектор к записи
- SelectedRecord.VectorRecords.Insert(i, attachedRecord.Entity);
+ _logger.LogTrace("Записываем изменения в базу данных");
+
+ // Ищем запись изображения в базе данных
+ var attachedImageRecord = _context.ImageRecords.Find(attachedRecord.ImageId);
+ // Если запись не найдена
+ if (attachedImageRecord is null)
+ {
+ // Логгируем как ошибку
+ _logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", attachedRecord.ImageId, nameof(_context.ImageRecords));
+ _logger.LogTrace("Создаём новую запись изображения");
+ // Заполняем запись изображения
+ var imageRecord = new ImageRecord(base64: _selectedImageData ?? "");
+ // Добавляем запись изображения в базу данных
+ attachedImageRecord = _context.ImageRecords.Attach(imageRecord).Entity;
+ // Сохраняем изменения в базе данных
+ _context.SaveChanges();
+ _logger.LogTrace("Запись изображения сохранена");
+ }
+
+ // Ищем записи признаков в базе данных
+ var attachedFeatureRecords = _context.FeatureRecords.Where(r => r.SampleRecordId == SelectedId);
+ // Если количество признаков в базе данных больше чем текущее
+ if (attachedFeatureRecords.Count() > _selectedFeaturesData.Count())
+ {
+ // Находим количество признаков которое нужно удалить
+ var featuresRecordsToRemove = attachedFeatureRecords.Count() - _selectedFeaturesData.Count();
+ // Логгируем
+ _logger.LogTrace("Количество записей признаков в базе данных больше чем текущее, удаление лишних записей: {} ({} - {})", featuresRecordsToRemove, attachedFeatureRecords.Count(), _selectedFeaturesData.Count());
+ // Удаляем необходимое количество признаков из конца
+ attachedFeatureRecords.TakeLast(featuresRecordsToRemove);
+ }
+
+ // Заполняем все имеющиеся признаки в базе данных новыми значениями
+ foreach (var (i, attachedFeatureRecord) in attachedFeatureRecords.Enumerate())
+ attachedFeatureRecord.Values = _selectedFeaturesData[i];
+
+ // Если количество признаков в безе данных меньше чем текущее
+ if (attachedFeatureRecords.Count() < _selectedFeaturesData.Count())
+ {
+ // Находим количество признаков которое нужно добавить
+ var featuresRecordsToAdd = _selectedFeaturesData.Count() - attachedFeatureRecords.Count();
+ // Логгируем
+ _logger.LogTrace("Количество записей признаков в базе данных меньше чем текущее, добавление недостающих записей: {} ({} - {})", featuresRecordsToAdd, _selectedFeaturesData.Count(), attachedFeatureRecords.Count());
+ // Заполняем записи недостающих признаков и добавляем их в базу данных
+ for (var i = attachedFeatureRecords.Count(); i < _selectedFeaturesData.Count(); i++)
+ {
+ var featureRecord = new FeatureRecord(
+ values: _selectedFeaturesData[i],
+ sampleRecordId: attachedRecord.Id
+ );
+ var attachedFeatureRecord = _context.FeatureRecords.Attach(featureRecord);
+ attachedFeatureRecords.Append(attachedFeatureRecord.Entity);
+ }
+ }
+
+ _context.SaveChanges();
+ _logger.LogTrace("Записи признаков сохранена");
+
+ // Если в данных есть изменения, то записываем их
+ if (attachedRecord.SampleName != SelectedSampleName)
+ attachedRecord.SampleName = SelectedSampleName;
+ if (attachedRecord.SampleComment != SelectedSampleComment)
+ attachedRecord.SampleComment = SelectedSampleComment;
+ if (attachedRecord.SamplingDateTime != SelectedSamplingDateTime)
+ attachedRecord.SamplingDateTime = SelectedSamplingDateTime;
+ if (attachedRecord.BrandId != _selectedBrandId)
+ attachedRecord.BrandId = _selectedBrandId;
+ if (attachedRecord.SamplingPlace != SelectedSamplingPlace)
+ attachedRecord.SamplingPlace = SelectedSamplingPlace;
+ if (attachedRecord.MixtureActualRate != SelectedMixtureActualRate)
+ attachedRecord.MixtureActualRate = SelectedMixtureActualRate;
+ if (attachedRecord.MeasuredContent != SelectedMeasuredContent)
+ attachedRecord.MeasuredContent = SelectedMeasuredContent;
+ if (attachedRecord.ImageId != attachedImageRecord.Id)
+ attachedRecord.ImageId = attachedImageRecord.Id;
+ // Для признаков особый случай, они в любом случае будут перезаписаны, потому что так быстрее
+ attachedRecord.FeatureIds = attachedFeatureRecords.Select(r => r.Id).ToList();
+
+ // Сохраняем изменения в базе данных
+ _context.SaveChanges();
+ _logger.LogTrace("Запись сохранена");
+
+ // Записываем изменения в меню
+ _logger.LogTrace("Записываем изменения в меню");
+ MenuItems[MenuSelectedIndex].SampleName = SelectedSampleName;
}
-
- // Сохраняем изменения в базе данных
- _context.SaveChanges();
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Исключение при сохранении записи");
}
}
- // Команда изменения основной секции (информация <-> изображения)
- [RelayCommand] private void SwitchMainSection() => MainSectionCurrentIndex = MainSectionCurrentIndex == 0 ? 1 : 0;
-
- // Команда навигации по информации
- [RelayCommand] private void InfoSectionPrevious() => InfoSectionCurrentIndex--;
- [RelayCommand] private void InfoSectionNext() => InfoSectionCurrentIndex++;
-
- // Команда съёмки изображений
+ ///
+ /// Снять изображения
+ ///
[RelayCommand]
private void CaptureImage()
{
- // Если съёмка уже начата, то ничего не делаем
- if (CaptureImagesTask is not null)
+ // Если съёмка уже начата, то логгируем как ошибку
+ if (_сaptureImagesTask is not null)
+ {
+ _logger.LogError("Задача съёмки уже запущена");
return;
+ }
// Создаём токен отмены задачи
_captureImagesTaskCts = new CancellationTokenSource();
@@ -316,8 +735,8 @@ public partial class SampleCalibrationViewModel : ViewModelBase
// а изменения данных в интерфейсе могут быть вызваны только из потока в котором интерфейс был создан
_ = Dispatcher.UIThread.InvokeAsync(() => ImagesCapturingProgress = $"Получение изображений: 0 / {ImageCapturerService.IlluminatorIntensities.Count()}");
- // Запускаем получение изображений асинхронно
- CaptureImagesTask = Task.Run(async () =>
+ // Запускаем задачу получения изображений
+ _сaptureImagesTask = Task.Run(async () =>
{
// Список полученных изображений
var imagesData = new List>();
@@ -327,12 +746,12 @@ public partial class SampleCalibrationViewModel : ViewModelBase
// а изменения данных в интерфейсе могут быть вызваны только из потока в котором интерфейс был создан
_ = Dispatcher.UIThread.InvokeAsync(() => ImagesIsCapturing = true);
- // Получаем изображения
+ // Создаём задачу получения изображений
var captureTask = _imageCapturer.CaptureImages(async (data) =>
{
// Распаковываем данные
var (i, visible, uv365, uv254, image) = data;
- // Выбрасываем исключение если задача отменена
+ // Выбрасываем исключение, если задача отменена
cancellationToken.ThrowIfCancellationRequested();
// Обновляем данные в интерфейсе
@@ -340,142 +759,179 @@ public partial class SampleCalibrationViewModel : ViewModelBase
// а изменения данных в интерфейсе могут быть вызваны только из потока в котором интерфейс был создан
_ = Dispatcher.UIThread.InvokeAsync(() => ImagesCapturingProgress = $"Получение изображений: {i + 1} / {ImageCapturerService.IlluminatorIntensities.Count()}");
+ // Создаём задачу сохранения изображения
var imagesDataTask = Task.Run(() =>
{
- // Сохраняем png изображение во временную папку
+ // Выбрасываем исключение, если задача отменена
+ cancellationToken.ThrowIfCancellationRequested();
+ // Сохраняем PNG изображение во временную папку
var path = Path.Join(Path.GetTempPath(), $"gss2-calibration-sample-{visible}-{uv365}-{uv254}.png");
image.ImWrite(path);
// Освобождаем память изображения
- image.Release();
+ image.Dispose();
// Добавляем данные изображения в список
return new ImageData(path, visible, uv365, uv254);
- });
+ }, cancellationToken);
+ // Добавляем задачу сохранения изображения в список
imagesData.Add(imagesDataTask);
}, cancellationToken);
- // Обрабатываем изображения
- var postTask = Task.WhenAll([captureTask, .. imagesData]).ContinueWith(async t =>
+ // Создаём задачу обработки изображений,
+ // эта задача начнётся только после того как завершится
+ // задача съёмки изображений и все задачи сохранения изображений
+ var processTask = Task.WhenAll([captureTask, .. imagesData]).ContinueWith(async t =>
{
+ // Выбрасываем исключение, если задача отменена
+ cancellationToken.ThrowIfCancellationRequested();
+
// Обновляем данные в интерфейсе
// Dispatcher.UIThread.Invoke нужен потому-что этот метод может (и скорее всего будет) вызван в другом потоке,
// а изменения данных в интерфейсе могут быть вызваны только из потока в котором интерфейс был создан
_ = Dispatcher.UIThread.InvokeAsync(() => ImagesCapturingProgress = $"Обработка изображений");
- // Проверяем исключения задачи получения изображений
- if (t.Exception is not null)
+
+ // Если во время выполнения задачи съёмки изображений произошло исключение,
+ // то логгируем все исключения и выходим
+ if (captureTask.Exception is not null)
{
- _logger.LogError(t.Exception, "Исключение во время получения изображений");
- foreach (var ex in t.Exception.InnerExceptions ?? [])
- _logger.LogError(ex, "Исключение во время получения изображений");
+ _logger.LogError(captureTask.Exception, "Исключение во время получения изображения");
+ foreach (var ex in captureTask.Exception.InnerExceptions ?? [])
+ _logger.LogError(ex, "Исключение во время получения изображения");
return;
}
- // Проверяем исключения задач сохранения изображений
+
+ // Если во время выполнения задач сохранения изображений произошло исключение,
+ // то логгируем все исключения и выходим
+ bool anyImageDataException = false;
foreach (var imageData in imagesData)
+ {
if (imageData.Exception is not null)
{
- _logger.LogError(imageData.Exception, "Исключение во время получения изображения");
+ _logger.LogError(imageData.Exception, "Исключение во время сохранения изображения");
foreach (var ex in imageData.Exception.InnerExceptions ?? [])
- _logger.LogError(ex, "Исключение во время получения изображения");
- return;
+ _logger.LogError(ex, "Исключение во время сохранения изображения");
+ anyImageDataException = true;
}
+ }
+ if (anyImageDataException)
+ return;
+
+ // Если во время выполнения произошло исключение,
+ // то логгируем все исключения и выходим
+ if (t.Exception is not null)
+ {
+ _logger.LogError(t.Exception, "Исключение во время обработки изображений");
+ foreach (var ex in t.Exception.InnerExceptions ?? [])
+ _logger.LogError(ex, "Исключение во время обработки изображений");
+ return;
+ }
// Вычисляем вектор признаков
var result = AnalyzerService.GetFeatureVectors(imagesData.Select(i => i.Result));
- // Если вектор признаков вычислен
- if (result is not null)
+ // Если вектор признаков не вычислен, то логгируем как ошибку
+ if (result is null)
{
- // Распаковываем результат
- var (avgImage, roi, features) = result.Value;
- _logger.LogInformation($"Длинна вектора признаков: {features.Count()}");
-
- // Обрабатываем среднее изображение
- avgImage = new OpenCvSharp.Mat(avgImage, roi);
- OpenCvSharp.Cv2.Resize(
- avgImage,
- avgImage,
- new OpenCvSharp.Size(
- AnalyzerService.ANALYSIS_IMAGE_SIZE,
- AnalyzerService.ANALYSIS_IMAGE_SIZE
- )
- );
-
- // Кодируем и сохраняем среднее изображение
- OpenCvSharp.Cv2.ImEncode(".png", avgImage, out var avgData);
- string avgPath;
- using (var stream = new MemoryStream(avgData))
- avgPath = await _imageStorage.SaveAsync(stream, ".png", "calibration-sample");
-
- // Заполняем поля в редактируемой записи
- EditingRecord.Image = avgPath;
- EditingRecord.VectorRecords = features.Select(f => new VectorRecord { Values = f }).ToList();
-
- // Обновляем данные в редактируемой записи
- // Так же Dispatcher.UIThread.Invoke нужен потому-что этот метод может (и скорее всего будет) вызван в другом потоке,
- // а изменения данных в интерфейсе могут быть вызваны только из потока в котором интерфейс был создан
- _ = Dispatcher.UIThread.InvokeAsync(() => OnEditingRecordChanged(EditingRecord));
- }
- else
- {
- _logger.LogError("Ошибка при вычислении векторов признаков");
+ _logger.LogError("Не удалось вычислить вектор признаков");
+ return;
}
+
+ // Распаковываем результат
+ var (avgImage, roi, features) = result.Value;
+
+ // Сохраняем вектор признаков
+ _selectedFeaturesData = features;
+
+ // Обрабатываем среднее изображение
+ avgImage = new OpenCvSharp.Mat(avgImage, roi);
+ OpenCvSharp.Cv2.Resize(
+ avgImage,
+ avgImage,
+ new OpenCvSharp.Size(
+ AnalyzerService.ANALYSIS_IMAGE_SIZE,
+ AnalyzerService.ANALYSIS_IMAGE_SIZE
+ )
+ );
+
+ // Кодируем среднее изображение
+ OpenCvSharp.Cv2.ImEncode(".png", avgImage, out var avgData);
+ _selectedImageData = Convert.ToBase64String(avgData);
+
+ // Очищаем память среднего изображения
+ avgImage.Dispose();
+
+ // Обновляем данные в интерфейсе
+ // Dispatcher.UIThread.Invoke нужен потому-что этот метод может (и скорее всего будет) вызван в другом потоке,
+ // а изменения данных в интерфейсе могут быть вызваны только из потока в котором интерфейс был создан
+ _ = Dispatcher.UIThread.InvokeAsync(() => SelectedImage.Data = _selectedImageData);
}, cancellationToken);
- // Сбрасываем состояние задачи съёмки изображений
- _ = Task.WhenAll([captureTask, .. imagesData, postTask]).ContinueWith(t =>
+ // Создаём задачу которая выполнится после завершения всех других
+ _ = Task.WhenAll([captureTask, .. imagesData, processTask]).ContinueWith(t =>
{
+ // Если во время выполнения задачи съёмки изображений произошло исключение,
+ // то логгируем все исключения и выходим
+ if (captureTask.Exception is not null)
+ {
+ _logger.LogError(captureTask.Exception, "Исключение во время получения изображения");
+ foreach (var ex in captureTask.Exception.InnerExceptions ?? [])
+ _logger.LogError(ex, "Исключение во время получения изображения");
+ return;
+ }
+
+ // Если во время выполнения задач сохранения изображений произошло исключение,
+ // то логгируем все исключения и выходим
+ bool anyImageDataException = false;
+ foreach (var imageData in imagesData)
+ {
+ if (imageData.Exception is not null)
+ {
+ _logger.LogError(imageData.Exception, "Исключение во время сохранения изображения");
+ foreach (var ex in imageData.Exception.InnerExceptions ?? [])
+ _logger.LogError(ex, "Исключение во время сохранения изображения");
+ anyImageDataException = true;
+ }
+ }
+ if (anyImageDataException)
+ return;
+
+ // Если во время выполнения задачи обработки изображений произошло исключение,
+ // то логгируем все исключения и выходим
+ if (processTask.Exception is not null)
+ {
+ _logger.LogError(processTask.Exception, "Исключение во время обработки изображений");
+ foreach (var ex in processTask.Exception.InnerExceptions ?? [])
+ _logger.LogError(ex, "Исключение во время обработки изображений");
+ return;
+ }
+
+ // Если во время выполнения произошло исключение,
+ // то логгируем все исключения и выходим
+ if (t.Exception is not null)
+ {
+ _logger.LogError(t.Exception, "Исключение во время обработки изображений");
+ foreach (var ex in t.Exception.InnerExceptions ?? [])
+ _logger.LogError(ex, "Исключение во время обработки изображений");
+ return;
+ }
+
// Обновляем данные в интерфейсе
// Dispatcher.UIThread.Invoke нужен потому-что этот метод может (и скорее всего будет) вызван в другом потоке,
// а изменения данных в интерфейсе могут быть вызваны только из потока в котором интерфейс был создан
_ = Dispatcher.UIThread.InvokeAsync(() =>
{
+ // Сбрасываем состояние задачи съёмки изображений
ImagesIsCapturing = false;
ImagesCapturingProgress = "";
- CaptureImagesTask = null;
+ _сaptureImagesTask = null;
});
});
});
}
- // Команда отмены задачи съёмки изображений
+ ///
+ /// Отменить задачу съёмки изображения
+ ///
[RelayCommand] private void CancelImageCapturing() => _captureImagesTaskCts?.Cancel();
- ///
- /// Копирует , отвязывая её от базы данных
- ///
- /// Скопированная
- private static SampleRecord CopyRecord(SampleRecord source)
- {
- return new SampleRecord
- {
- Id = source.Id,
- SampleName = source.SampleName,
- SampleComment = source.SampleComment,
- SampleBrand = source.SampleBrand,
- SampleMass = source.SampleMass,
- SamplingDateTime = source.SamplingDateTime,
- SamplingPlace = source.SamplingPlace,
- MixtureName = source.MixtureName,
- MixtureAmineContent = source.MixtureAmineContent,
- MixtureNormalRate = source.MixtureNormalRate,
- MixtureActualRate = source.MixtureActualRate,
- MeasuredContent = source.MeasuredContent,
- Image = source.Image,
- VectorRecords = source.VectorRecords
- .Select(CopyRecord)
- .ToList()
- };
- }
- ///
- /// Копирует , отвязывая её от базы данных
- ///
- /// Скопированная
- private static VectorRecord CopyRecord(VectorRecord source)
- {
- return new VectorRecord
- {
- Id = source.Id,
- Values = (float[])source.Values.Clone()
- };
- }
}
diff --git a/GSS2/Views/AmineContent/SampleCalibrationView.axaml b/GSS2/Views/AmineContent/SampleCalibrationView.axaml
index d779c3f..c28e83a 100644
--- a/GSS2/Views/AmineContent/SampleCalibrationView.axaml
+++ b/GSS2/Views/AmineContent/SampleCalibrationView.axaml
@@ -1,107 +1,53 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ xmlns:vm="using:GSS2.ViewModels.AmineContent"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns="https://github.com/avaloniaui"
+>
-
+
+
+
+
+
+
+
+
+ 5
-
+
-
-
-
-
-
+ 5
-
+ Orientation="Vertical"
+ VerticalAlignment="Stretch"
+ >
-
-
-
-
-
-
+
@@ -109,135 +55,257 @@
+ 5
+
+
+
+
+
+
+ 5
+
-
-
+
+ 5
+
-
+
+
+
+
+
+
-
+
+
-
+
-
-
+
-
-
-
-
-
+
+
-
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
-
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
-
+ >
-
+ >
@@ -342,22 +454,37 @@
-
-
+
+
-
-
+
+
-
-
+
+ Grid.Column="2"
+ Grid.Row="1"
+ HorizontalAlignment="Stretch"
+ IsVisible="{Binding MainSectionCurrentIndex, Converter={StaticResource EqualsConverter}, ConverterParameter=1}"
+ >
@@ -370,12 +497,15 @@
-
-
+
+ Grid.Column="2"
+ Grid.Row="1"
+ HorizontalAlignment="Stretch"
+ IsVisible="{Binding MainSectionCurrentIndex, Converter={StaticResource EqualsConverter}, ConverterParameter=0}"
+ >
@@ -391,7 +521,11 @@
-
+
@@ -400,68 +534,109 @@
-
+
+
-
+
+
-
+
+
-
+
+
-
+
-
-
+
+
-
+ 10
+
+
+
-
+
+
+
+
+
+
+
+
-
+
-
+