forked from amkovkov/GranuSightSoftware2
comprehensive update
1) remove unused components: camera view (due it causes segmentation faults), compute resources and all related, illuminator controller (due in useless without camera view), image storage service, temperature and humidity service 2) database schema - reduce tables references, features storing in records themselves in compressed form, add records creation and editing date and time, add separator comment column 3) analysis - rework of pipeline and ui, now database storing only raw data and all display values calculated from it 4) lights service - add reconnection if disconnected 5) add width and height command line arguments 6) fix some typos and other issues
This commit is contained in:
@@ -3,10 +3,69 @@
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:GSS2"
|
||||
xmlns:converters="using:GSS2.UI.Core.Converters"
|
||||
RequestedThemeVariant="Dark">
|
||||
|
||||
<Application.Resources>
|
||||
<converters:NotEqualsConverter x:Key="NotEqualsConverter" />
|
||||
<converters:EqualsConverter x:Key="EqualsConverter" />
|
||||
<converters:GreaterThanConverter x:Key="GreaterThanConverter" />
|
||||
<converters:GreaterThanOrEqualConverter x:Key="GreaterThanOrEqualConverter" />
|
||||
<converters:LessThanConverter x:Key="LessThanConverter" />
|
||||
<converters:LessThanOrEqualConverter x:Key="LessThanOrEqualConverter" />
|
||||
<converters:DateTimeConverter x:Key="DateTimeConverter" />
|
||||
|
||||
<SolidColorBrush x:Key="MenuBackground" Color="#00357a" />
|
||||
<SolidColorBrush x:Key="MenuBorder" Color="#0865b2" />
|
||||
<SolidColorBrush x:Key="Overlay" Color="#0865b23f" />
|
||||
<SolidColorBrush x:Key="ButtonBackground" Color="#0865b2" />
|
||||
<SolidColorBrush x:Key="ButtonDangerBackground" Color="#b20808" />
|
||||
<SolidColorBrush x:Key="ButtonForeground" Color="#ffffff" />
|
||||
<SolidColorBrush x:Key="MainBackground" Color="#001e46" />
|
||||
</Application.Resources>
|
||||
|
||||
<Application.Styles>
|
||||
<FluentTheme />
|
||||
|
||||
<Style Selector="Button">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="Background" Value="{StaticResource ButtonBackground}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource ButtonForeground}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.danger">
|
||||
<Setter Property="Background" Value="{StaticResource ButtonDangerBackground}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.vertical">
|
||||
<Setter Property="MinWidth" Value="40" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBox">
|
||||
<Setter Property="FontSize" Value="20"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBox.singleline">
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBox.multiline">
|
||||
<Setter Property="VerticalContentAlignment" Value="Top" />
|
||||
<Setter Property="AcceptsReturn" Value="True" />
|
||||
<Setter Property="TextWrapping" Value="Wrap" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBlock">
|
||||
<Setter Property="FontSize" Value="20"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBlock.mini">
|
||||
<Setter Property="FontSize" Value="16"/>
|
||||
<Setter Property="Margin" Value="0,0,0,0" />
|
||||
</Style>
|
||||
|
||||
</Application.Styles>
|
||||
|
||||
</Application>
|
||||
|
||||
@@ -18,6 +18,8 @@ public partial class Application : Avalonia.Application
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly bool _fullscreen;
|
||||
private readonly double _width;
|
||||
private readonly double _height;
|
||||
private readonly ILogger<Application> _logger;
|
||||
private readonly DependencyInjectionViewLocator _viewLocator;
|
||||
|
||||
@@ -25,10 +27,12 @@ public partial class Application : Avalonia.Application
|
||||
|
||||
public NavigationService? Navigation { get; private set; } = null;
|
||||
|
||||
public Application(IServiceProvider services, bool fullscreen = false)
|
||||
public Application(IServiceProvider services, bool fullscreen = false, int width = 0, int height = 0)
|
||||
{
|
||||
_services = services;
|
||||
_fullscreen = fullscreen;
|
||||
_width = width <= 0 ? double.NaN : width;
|
||||
_height = height <= 0 ? double.NaN : height;
|
||||
|
||||
// Создаём AvaloniaLogSink, который перенаправляет логи из Avalonia в хост
|
||||
var loggerFactory = _services.GetRequiredService<ILoggerFactory>();
|
||||
@@ -124,7 +128,9 @@ public partial class Application : Avalonia.Application
|
||||
{
|
||||
WindowDecorations = _fullscreen ? Avalonia.Controls.WindowDecorations.None : Avalonia.Controls.WindowDecorations.Full,
|
||||
Topmost = _fullscreen,
|
||||
WindowState = _fullscreen ? Avalonia.Controls.WindowState.FullScreen : Avalonia.Controls.WindowState.Normal
|
||||
WindowState = _fullscreen ? Avalonia.Controls.WindowState.FullScreen : Avalonia.Controls.WindowState.Normal,
|
||||
Width = _width,
|
||||
Height = _height
|
||||
},
|
||||
};
|
||||
desktop.MainWindow = mainWindow;
|
||||
|
||||
+23
-7
@@ -28,6 +28,8 @@ public static class UI
|
||||
public static readonly Option<RenderingModes> RenderingMode;
|
||||
public static readonly Option<bool> HideConsole;
|
||||
public static readonly Option<bool> Mock;
|
||||
public static readonly Option<int> Width;
|
||||
public static readonly Option<int> Height;
|
||||
|
||||
static UI()
|
||||
{
|
||||
@@ -56,6 +58,16 @@ public static class UI
|
||||
Description = "Запуск в фиктивном режиме без инициализации и использования периферии",
|
||||
DefaultValueFactory = (result) => false
|
||||
};
|
||||
Width = new("--width")
|
||||
{
|
||||
Description = "Ширина окна, если меньше или равна 0, то устанавливается ширина по-умолчанию",
|
||||
DefaultValueFactory = (result) => 0
|
||||
};
|
||||
Height = new("--height")
|
||||
{
|
||||
Description = "Высота окна, если меньше или равна 0, то устанавливается высота по-умолчанию",
|
||||
DefaultValueFactory = (result) => 0
|
||||
};
|
||||
|
||||
// Изменяем описания стандартных опций
|
||||
Helper.TranslateDefaultOptionDescriptions(RootCommand);
|
||||
@@ -65,12 +77,16 @@ public static class UI
|
||||
RootCommand.Add(RenderingMode);
|
||||
RootCommand.Add(HideConsole);
|
||||
RootCommand.Add(Mock);
|
||||
RootCommand.Add(Width);
|
||||
RootCommand.Add(Height);
|
||||
RootCommand.SetAction(CommandAction);
|
||||
|
||||
Command.Add(Fullscreen);
|
||||
Command.Add(RenderingMode);
|
||||
Command.Add(HideConsole);
|
||||
Command.Add(Mock);
|
||||
Command.Add(Width);
|
||||
Command.Add(Height);
|
||||
Command.SetAction(CommandAction);
|
||||
}
|
||||
|
||||
@@ -92,6 +108,8 @@ public static class UI
|
||||
var renderingMode = result.GetValue(RenderingMode);
|
||||
var hideConsole = result.GetValue(HideConsole);
|
||||
var mock = result.GetValue(Mock);
|
||||
var width = result.GetValue(Width);
|
||||
var height = result.GetValue(Height);
|
||||
|
||||
// Если выбран режим рендеринга DRM, то приложение будет рисоваться вместе с консолью,
|
||||
// поэтому если не перехватить консоль, то вывод в неё будет рисоваться вместе с интерфейсом
|
||||
@@ -105,7 +123,7 @@ public static class UI
|
||||
_ = host.RunAsync();
|
||||
|
||||
// Собираем графическое приложение
|
||||
var ui = BuildApplication(host, fullscreen);
|
||||
var ui = BuildApplication(host, fullscreen, width, height);
|
||||
switch (renderingMode)
|
||||
{
|
||||
case RenderingModes.X11:
|
||||
@@ -160,15 +178,13 @@ public static class UI
|
||||
builder.Services.AddAmineContentContext(builder.Configuration);
|
||||
|
||||
// Добавляем сервисы аппаратной части и прочее
|
||||
builder.Services.AddLightsService("Hardware:Lights");
|
||||
if (!mock)
|
||||
{
|
||||
builder.Services.AddLightsService("Hardware:Lights");
|
||||
builder.Services.AddTemperatureHumidityService("Hardware:TemperatureHumidity");
|
||||
// builder.Services.AddTemperatureHumidityService("Hardware:TemperatureHumidity");
|
||||
builder.Services.AddIlluminatorService("Hardware:Illuminator");
|
||||
builder.Services.AddCameraService("Hardware:Camera", true);
|
||||
builder.Services.AddComputeResourcesService();
|
||||
}
|
||||
builder.Services.AddImageStorageService(new DirectoryInfo(builder.Configuration.GetValue<string>("ImageStorage") ?? "images"));
|
||||
builder.Services.AddAmineContentImageCapturerService(mock);
|
||||
builder.Services.AddAmineContentAnalyzerService();
|
||||
|
||||
@@ -193,8 +209,8 @@ public static class UI
|
||||
return host;
|
||||
}
|
||||
|
||||
private static AppBuilder BuildApplication(IHost host, bool fullscreen) =>
|
||||
AppBuilder.Configure(() => new Application(host.Services, fullscreen))
|
||||
private static AppBuilder BuildApplication(IHost host, bool fullscreen, int width, int height) =>
|
||||
AppBuilder.Configure(() => new Application(host.Services, fullscreen, width, height))
|
||||
.UsePlatformDetect()
|
||||
.UseSkia()
|
||||
.LogToTrace()
|
||||
|
||||
@@ -27,10 +27,6 @@ public static class DependencyInjectionHelper
|
||||
.AddSingleton<AmineContentView>()
|
||||
// Core
|
||||
.AddSingleton<GSS2.UI.Core.ViewModels.MainWindowViewModel>()
|
||||
.AddSingleton<GSS2.UI.Core.ViewModels.CameraViewModel>()
|
||||
.AddSingleton<GSS2.UI.Core.ViewModels.IlluminatorControllerViewModel>()
|
||||
.AddSingleton<GSS2.UI.Core.Views.CameraView>()
|
||||
.AddSingleton<GSS2.UI.Core.Views.IlluminatorControllerView>()
|
||||
// База
|
||||
.AddSingleton<ViewModels.DeleteConfirmViewModel>()
|
||||
.AddSingleton<ViewModels.NumericKeyboardViewModel>()
|
||||
@@ -38,16 +34,18 @@ public static class DependencyInjectionHelper
|
||||
.AddSingleton<Views.NumericKeyboardView>()
|
||||
// Общие
|
||||
.AddSingleton<ViewModels.Common.SystemViewModel>()
|
||||
.AddSingleton<ViewModels.Common.CameraViewModel>()
|
||||
.AddSingleton<Views.Common.SystemView>()
|
||||
.AddSingleton<Views.Common.CameraView>()
|
||||
// .AddSingleton<ViewModels.Common.CameraViewModel>()
|
||||
// .AddSingleton<Views.Common.SystemView>()
|
||||
// .AddSingleton<Views.Common.CameraView>()
|
||||
// AmineContent
|
||||
.AddSingleton<ViewModels.AmineContent.StartViewModel>()
|
||||
.AddSingleton<ViewModels.AmineContent.AnalysisViewModel>()
|
||||
.AddSingleton<ViewModels.AmineContent.SampleCalibrationViewModel>()
|
||||
.AddSingleton<ViewModels.AmineContent.SeparatorCalibrationViewModel>()
|
||||
.AddSingleton<ViewModels.AmineContent.BrandCalibrationViewModel>()
|
||||
.AddSingleton<Views.AmineContent.StartView>()
|
||||
.AddSingleton<Views.AmineContent.AnalysisView>()
|
||||
.AddSingleton<Views.AmineContent.SampleCalibrationView>()
|
||||
.AddSingleton<Views.AmineContent.SeparatorCalibrationView>();
|
||||
.AddSingleton<Views.AmineContent.SeparatorCalibrationView>()
|
||||
.AddSingleton<Views.AmineContent.BrandCalibrationView>();
|
||||
}
|
||||
|
||||
+6
-11
@@ -4,15 +4,7 @@ public partial class Program
|
||||
{
|
||||
// Аргументы командной строки
|
||||
private static List<string> _args = new List<string>();
|
||||
public static string[] Args
|
||||
{
|
||||
get
|
||||
{
|
||||
var copy = new string[_args.Count()];
|
||||
_args.CopyTo(copy);
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
public static string[] Args => _args.ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Точка входа
|
||||
@@ -60,8 +52,11 @@ public partial class Program
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine("Ошибка при выполнении программы:");
|
||||
Console.Write(ex.Message);
|
||||
Console.Write(ex.StackTrace);
|
||||
Console.Error.WriteLine(ex.Message);
|
||||
if (ex.InnerException is not null)
|
||||
Console.Error.WriteLine($"Внутреннее исключение: {ex.InnerException}");
|
||||
Console.Error.WriteLine(ex.StackTrace);
|
||||
Environment.Exit(1);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -31,6 +31,45 @@ public partial class BrandCalibrationViewModel : ViewModelBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Внутренняя модель записи
|
||||
/// </summary>
|
||||
public partial class RecordViewModel : ViewModelBase
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="BrandRecord.Id"/>
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial int Id { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// <see cref="BrandRecord.Uuid"/>
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial string Uuid { get; set; } = Guid.NewGuid().ToString();
|
||||
/// <summary>
|
||||
/// <see cref="SampleRecord.CreateDateTime"/>
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial DateTime CreateDateTime { get; set; } = DateTime.UtcNow;
|
||||
/// <summary>
|
||||
/// <see cref="SampleRecord.EditDateTime"/>
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial DateTime EditDateTime { get; set; } = DateTime.UtcNow;
|
||||
/// <summary>
|
||||
/// <see cref="BrandRecord.BrandName"/>
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial string BrandName { get; set; } = "";
|
||||
/// <summary>
|
||||
/// <see cref="BrandRecord.MixtureName"/>
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial string MixtureName { get; set; } = "";
|
||||
/// <summary>
|
||||
/// <see cref="BrandRecord.MixtureNormalRate"/>
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial double MixtureNormalRate { get; set; } = -1;
|
||||
/// <summary>
|
||||
/// <see cref="BrandRecord.Comment"/>
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial string Comment { get; set; } = "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Логгер
|
||||
/// </summary>
|
||||
@@ -60,30 +99,33 @@ public partial class BrandCalibrationViewModel : ViewModelBase
|
||||
/// Текущий выбранный элемент меню. Равен -1 если элемент не выбран
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial int MenuSelectedIndex { get; set; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="BrandRecord.Id"/> выбранного элемента
|
||||
/// Запись
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial int SelectedId { get; set; } = 0;
|
||||
[ObservableProperty] public partial RecordViewModel Record { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="BrandRecord.Uuid"/> выбранного элемента
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial string SelectedUuid { get; set; } = Guid.NewGuid().ToString();
|
||||
/// Текущий экран информационной секции
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial int InfoSectionCurrentIndex { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// <see cref="BrandRecord.BrandName"/> выбранного элемента
|
||||
/// Можно ли переходить на следующий экран информационной секции
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial string SelectedBrandName { get; set; } = "";
|
||||
[ObservableProperty] public partial bool InfoSectionCanScrollBackward { get; set; } = false;
|
||||
/// <summary>
|
||||
/// <see cref="BrandRecord.MixtureName"/> выбранного элемента
|
||||
/// Можно ли переходить на предыдущий экран информационной секции
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial string SelectedMixtureName { get; set; } = "";
|
||||
[ObservableProperty] public partial bool InfoSectionCanScrollForward { get; set; } = false;
|
||||
/// <summary>
|
||||
/// <see cref="BrandRecord.MixtureNormalRate"/> выбранного элемента
|
||||
/// Происходит ли переход между экранами информационной секции
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial double SelectedMixtureNormalRate { get; set; } = -1;
|
||||
[ObservableProperty] public partial bool InfoSectionIsScrolling { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Можно ли производить перемещение между экранами
|
||||
/// Можно ли взаимодействовать с объектом
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial bool CanScroll { get; set; } = true;
|
||||
[ObservableProperty] public partial bool CanInteract { get; set; } = true;
|
||||
|
||||
public BrandCalibrationViewModel(
|
||||
ILogger<BrandCalibrationViewModel> logger,
|
||||
@@ -109,8 +151,9 @@ public partial class BrandCalibrationViewModel : ViewModelBase
|
||||
// Подписываемся на изменения свойств для обновления CanScroll
|
||||
PropertyChanged += (_, ea) =>
|
||||
{
|
||||
if (ea.PropertyName == nameof(MenuOpened))
|
||||
UpdateCanScroll();
|
||||
if (ea.PropertyName == nameof(MenuOpened) ||
|
||||
ea.PropertyName == nameof(InfoSectionIsScrolling))
|
||||
UpdateCanInteract();
|
||||
};
|
||||
|
||||
_logger.LogInformation("Инициализировано");
|
||||
@@ -133,11 +176,14 @@ public partial class BrandCalibrationViewModel : ViewModelBase
|
||||
// Если запись найдена, то заполняем поля из базы данных
|
||||
if (selectedRecord is not null)
|
||||
{
|
||||
SelectedId = selectedRecord.Id;
|
||||
SelectedUuid = selectedRecord.Uuid;
|
||||
SelectedBrandName = selectedRecord.BrandName;
|
||||
SelectedMixtureName = selectedRecord.MixtureName;
|
||||
SelectedMixtureNormalRate = selectedRecord.MixtureNormalRate;
|
||||
Record.Id = selectedRecord.Id;
|
||||
Record.Uuid = selectedRecord.Uuid;
|
||||
Record.CreateDateTime = selectedRecord.CreateDateTime;
|
||||
Record.EditDateTime = selectedRecord.EditDateTime;
|
||||
Record.BrandName = selectedRecord.BrandName;
|
||||
Record.MixtureName = selectedRecord.MixtureName;
|
||||
Record.MixtureNormalRate = selectedRecord.MixtureNormalRate;
|
||||
Record.Comment = selectedRecord.Comment;
|
||||
return;
|
||||
}
|
||||
// Если значение не найдено в базе данных
|
||||
@@ -156,19 +202,24 @@ public partial class BrandCalibrationViewModel : ViewModelBase
|
||||
|
||||
// Если элемент не выбран, заполняем поля пустыми значениями
|
||||
_logger.LogTrace("Выбран новый элемент");
|
||||
SelectedId = 0;
|
||||
SelectedUuid = Guid.NewGuid().ToString();
|
||||
SelectedBrandName = "";
|
||||
SelectedMixtureName = "";
|
||||
SelectedMixtureNormalRate = -1;
|
||||
var dateTime = DateTime.UtcNow;
|
||||
Record.Id = 0;
|
||||
Record.Uuid = Guid.NewGuid().ToString();
|
||||
Record.CreateDateTime = dateTime;
|
||||
Record.EditDateTime = dateTime;
|
||||
Record.BrandName = "";
|
||||
Record.MixtureName = "";
|
||||
Record.MixtureNormalRate = -1;
|
||||
Record.Comment = "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обновить значение <see cref="CanScroll"/>
|
||||
/// Обновить значение <see cref="CanInteract"/>
|
||||
/// </summary>
|
||||
private void UpdateCanScroll()
|
||||
private void UpdateCanInteract()
|
||||
{
|
||||
CanScroll = !MenuOpened;
|
||||
CanInteract = !MenuOpened;
|
||||
CanInteract &= !InfoSectionIsScrolling;
|
||||
}
|
||||
|
||||
|
||||
@@ -185,6 +236,15 @@ public partial class BrandCalibrationViewModel : ViewModelBase
|
||||
/// Переключить видимость меню
|
||||
/// </summary>
|
||||
[RelayCommand] private void ToggleMenu() => MenuOpened = !MenuOpened;
|
||||
|
||||
/// <summary>
|
||||
/// Переключить информационную секцию на следующую
|
||||
/// </summary>
|
||||
[RelayCommand] private void InfoSectionPrevious() => InfoSectionCurrentIndex--;
|
||||
/// <summary>
|
||||
/// Переключить информационную секцию на предыдущую
|
||||
/// </summary>
|
||||
[RelayCommand] private void InfoSectionNext() => InfoSectionCurrentIndex++;
|
||||
|
||||
/// <summary>
|
||||
/// Создать новую запись
|
||||
@@ -212,7 +272,7 @@ public partial class BrandCalibrationViewModel : ViewModelBase
|
||||
// TODO: Добавить определение и отображение количества затрагиваемых, записей которые будут повреждены при удалении этой записи
|
||||
try
|
||||
{
|
||||
_logger.LogTrace("Удаление записи: {}={}, {}={}, {}={}", nameof(BrandRecord.Id), SelectedId, nameof(BrandRecord.BrandName), SelectedBrandName, nameof(BrandRecord.MixtureName), SelectedMixtureName);
|
||||
_logger.LogTrace("Удаление записи: {}={}, {}={}, {}={}", nameof(BrandRecord.Id), Record.Id, nameof(BrandRecord.BrandName), Record.BrandName, nameof(BrandRecord.MixtureName), Record.MixtureName);
|
||||
// Если запись не выбрана, то просто создаём новую запись
|
||||
if (MenuSelectedIndex < 0)
|
||||
{
|
||||
@@ -247,7 +307,7 @@ public partial class BrandCalibrationViewModel : ViewModelBase
|
||||
|
||||
// Сохраняем индекс удалённый записи и находим её в базе данных
|
||||
var deletedMenuIndex = MenuSelectedIndex;
|
||||
var deletedRecordId = SelectedId;
|
||||
var deletedRecordId = Record.Id;
|
||||
var deletedRecord = _context.BrandRecords.Find(deletedRecordId);
|
||||
// Создаём новую запись
|
||||
CreateRecord();
|
||||
@@ -299,17 +359,21 @@ public partial class BrandCalibrationViewModel : ViewModelBase
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogTrace("Сохранение записи: {}={}, {}={}, {}={}", nameof(BrandRecord.Id), SelectedId, nameof(BrandRecord.BrandName), SelectedBrandName, nameof(BrandRecord.MixtureName), SelectedMixtureName);
|
||||
_logger.LogTrace("Сохранение записи: {}={}, {}={}, {}={}", nameof(BrandRecord.Id), Record.Id, nameof(BrandRecord.BrandName), Record.BrandName, nameof(BrandRecord.MixtureName), Record.MixtureName);
|
||||
// Если запись не выбрана
|
||||
if (MenuSelectedIndex < 0)
|
||||
{
|
||||
_logger.LogTrace("Создаём новую запись");
|
||||
var dateTime = DateTime.UtcNow;
|
||||
// Создаём новую запись
|
||||
var newRecord = new BrandRecord(
|
||||
uuid: SelectedUuid,
|
||||
brandName: SelectedBrandName,
|
||||
mixtureName: SelectedMixtureName,
|
||||
mixtureNormalRate: SelectedMixtureNormalRate
|
||||
uuid: Record.Uuid,
|
||||
createDateTime: dateTime,
|
||||
editDateTime: dateTime,
|
||||
brandName: Record.BrandName,
|
||||
mixtureName: Record.MixtureName,
|
||||
mixtureNormalRate: Record.MixtureNormalRate,
|
||||
comment: Record.Comment
|
||||
);
|
||||
|
||||
// Добавляем запись в базу данных
|
||||
@@ -328,22 +392,22 @@ public partial class BrandCalibrationViewModel : ViewModelBase
|
||||
else
|
||||
{
|
||||
// Находим запись в базе данных
|
||||
var attachedRecord = _context.BrandRecords.Find(SelectedId);
|
||||
var attachedRecord = _context.BrandRecords.Find(Record.Id);
|
||||
// Если запись не найдена, то логгируем как ошибку
|
||||
if (attachedRecord is null)
|
||||
{
|
||||
_logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", SelectedId, nameof(_context.BrandRecords));
|
||||
_logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", Record.Id, nameof(_context.BrandRecords));
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogTrace("Записываем изменения в базу данных");
|
||||
// Если в данных есть изменения, то записываем их
|
||||
if (attachedRecord.BrandName != SelectedBrandName)
|
||||
attachedRecord.BrandName = SelectedBrandName;
|
||||
if (attachedRecord.MixtureName != SelectedMixtureName)
|
||||
attachedRecord.MixtureName = SelectedMixtureName;
|
||||
if (attachedRecord.MixtureNormalRate != SelectedMixtureNormalRate)
|
||||
attachedRecord.MixtureNormalRate = SelectedMixtureNormalRate;
|
||||
|
||||
// Записываем изменения
|
||||
attachedRecord.EditDateTime = DateTime.UtcNow;
|
||||
attachedRecord.BrandName = Record.BrandName;
|
||||
attachedRecord.MixtureName = Record.MixtureName;
|
||||
attachedRecord.MixtureNormalRate = Record.MixtureNormalRate;
|
||||
attachedRecord.Comment = Record.Comment;
|
||||
|
||||
// Сохраняем изменения в базе данных
|
||||
_context.SaveChanges();
|
||||
@@ -351,8 +415,8 @@ public partial class BrandCalibrationViewModel : ViewModelBase
|
||||
|
||||
// Записываем изменения в меню
|
||||
_logger.LogTrace("Записываем изменения в меню");
|
||||
MenuItems[MenuSelectedIndex].BrandName = SelectedBrandName;
|
||||
MenuItems[MenuSelectedIndex].MixtureName = SelectedMixtureName;
|
||||
MenuItems[MenuSelectedIndex].BrandName = Record.BrandName;
|
||||
MenuItems[MenuSelectedIndex].MixtureName = Record.MixtureName;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -53,6 +53,63 @@ public partial class SampleCalibrationViewModel : ViewModelBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Внутренняя модель записи
|
||||
/// </summary>
|
||||
public partial class RecordViewModel : ViewModelBase
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="SampleRecord.Id"/>
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial int Id { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// <see cref="SampleRecord.Uuid"/>
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial string Uuid { get; set; } = Guid.NewGuid().ToString();
|
||||
/// <summary>
|
||||
/// <see cref="SampleRecord.CreateDateTime"/>
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial DateTime CreateDateTime { get; set; } = DateTime.UtcNow;
|
||||
/// <summary>
|
||||
/// <see cref="SampleRecord.EditDateTime"/>
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial DateTime EditDateTime { get; set; } = DateTime.UtcNow;
|
||||
/// <summary>
|
||||
/// <see cref="SampleRecord.SampleName"/>
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial string SampleName { get; set; } = "";
|
||||
/// <summary>
|
||||
/// <see cref="SampleRecord.SampleComment"/>
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial string SampleComment { get; set; } = "";
|
||||
/// <summary>
|
||||
/// <see cref="SampleRecord.SamplingDateTime"/>
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial DateTime SamplingDateTime { get; set; } = DateTime.UtcNow;
|
||||
/// <summary>
|
||||
/// <see cref="SampleRecord.SamplingPlace"/>
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial string SamplingPlace { get; set; } = "";
|
||||
/// <summary>
|
||||
/// <see cref="SampleRecord.MixtureActualRate"/>
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial double MixtureActualRate { get; set; } = -1;
|
||||
/// <summary>
|
||||
/// <see cref="SampleRecord.MeasuredContent"/>
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial double MeasuredContent { get; set; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Представление <see cref="SampleRecord.ImageId"/>
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial AsyncImageRecordViewModel Image { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Данные <see cref="SampleRecord.FeatureIds"/>
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial List<float[]> Features { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Логгер
|
||||
/// </summary>
|
||||
@@ -83,19 +140,6 @@ public partial class SampleCalibrationViewModel : ViewModelBase
|
||||
/// </summary>
|
||||
private CancellationTokenSource? _captureImagesTaskCts = null;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="SampleRecord.BrandId"/> выбранного элемента
|
||||
/// </summary>
|
||||
private int _selectedBrandId = 0;
|
||||
/// <summary>
|
||||
/// Данные <see cref="SampleRecord.ImageId"/> выбранного элемента
|
||||
/// </summary>
|
||||
private string? _selectedImageData = null;
|
||||
/// <summary>
|
||||
/// Данные <see cref="SampleRecord.FeatureIds"/> выбранного элемента
|
||||
/// </summary>
|
||||
private List<float[]> _selectedFeaturesData = new();
|
||||
|
||||
/// <summary>
|
||||
/// Меню открыто
|
||||
/// </summary>
|
||||
@@ -109,43 +153,11 @@ public partial class SampleCalibrationViewModel : ViewModelBase
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial int MenuSelectedIndex { get; set; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Запись
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial RecordViewModel Record { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="SampleRecord.Id"/> выбранного элемента
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial int SelectedId { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// <see cref="SampleRecord.Uuid"/> выбранного элемента
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial string SelectedUuid { get; set; } = Guid.NewGuid().ToString();
|
||||
/// <summary>
|
||||
/// <see cref="SampleRecord.SampleName"/> выбранного элемента
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial string SelectedSampleName { get; set; } = "";
|
||||
/// <summary>
|
||||
/// <see cref="SampleRecord.SampleComment"/> выбранного элемента
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial string SelectedSampleComment { get; set; } = "";
|
||||
/// <summary>
|
||||
/// <see cref="SampleRecord.SamplingDateTime"/> выбранного элемента
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial DateTime SelectedSamplingDateTime { get; set; } = DateTime.UtcNow;
|
||||
/// <summary>
|
||||
/// <see cref="SampleRecord.SamplingPlace"/> выбранного элемента
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial string SelectedSamplingPlace { get; set; } = "";
|
||||
/// <summary>
|
||||
/// <see cref="SampleRecord.MixtureActualRate"/> выбранного элемента
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial double SelectedMixtureActualRate { get; set; } = -1;
|
||||
/// <summary>
|
||||
/// <see cref="SampleRecord.MeasuredContent"/> выбранного элемента
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial double SelectedMeasuredContent { get; set; } = -1;
|
||||
/// <summary>
|
||||
/// Представление <see cref="SampleRecord.ImageId"/> выбранного элемента
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial AsyncImageRecordViewModel SelectedImage { get; set; } = new();
|
||||
/// <summary>
|
||||
/// Список элементов выпадающего списка марок
|
||||
/// </summary>
|
||||
@@ -172,7 +184,6 @@ public partial class SampleCalibrationViewModel : ViewModelBase
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial bool MainSectionIsScrolling { get; set; } = false;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Текущий экран информационной секции
|
||||
/// </summary>
|
||||
@@ -200,9 +211,9 @@ public partial class SampleCalibrationViewModel : ViewModelBase
|
||||
[ObservableProperty] public partial string ImagesCapturingProgress { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Можно ли производить перемещение между экранами
|
||||
/// Можно ли взаимодействовать с объектом
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial bool CanScroll { get; set; }
|
||||
[ObservableProperty] public partial bool CanInteract { get; set; } = true;
|
||||
|
||||
public SampleCalibrationViewModel(
|
||||
ILogger<SampleCalibrationViewModel> logger,
|
||||
@@ -220,13 +231,6 @@ public partial class SampleCalibrationViewModel : ViewModelBase
|
||||
_navigation = navigation;
|
||||
_logger.LogInformation("Инициализация");
|
||||
|
||||
// Получаем записи меню из базы данных
|
||||
MenuItems = new ObservableCollection<MenuItemViewModel>(_context.SampleRecords.Select(r => new MenuItemViewModel(r.Id, r.SampleName)));
|
||||
if (MenuItems.Count() > 0) // Если записи есть,
|
||||
MenuSelectedIndex = 0; // Выбираем первую запись
|
||||
else // Если записей нет,
|
||||
MenuSelectedIndex = -1; // Устанавливаем, что запись не выбрана
|
||||
|
||||
// Получаем записи выпадающего списка марок из базы данных
|
||||
BrandComboBoxItems = new ObservableCollection<BrandComboBoxItemViewModel>(_context.BrandRecords.Select(r => new BrandComboBoxItemViewModel(r.Id, r.BrandName, r.MixtureName)));
|
||||
// Подписываемся на изменения базы данных для отслеживания изменений списка марок
|
||||
@@ -242,7 +246,7 @@ public partial class SampleCalibrationViewModel : ViewModelBase
|
||||
return;
|
||||
|
||||
// Если запись добавлена
|
||||
if (ea.NewState is EntityState.Added)
|
||||
if (ea.OldState is EntityState.Added && ea.NewState is EntityState.Unchanged)
|
||||
{
|
||||
// Добавляем запись
|
||||
BrandComboBoxItems.Add(new BrandComboBoxItemViewModel(brandRecord.Id, brandRecord.BrandName, brandRecord.MixtureName));
|
||||
@@ -284,13 +288,20 @@ public partial class SampleCalibrationViewModel : ViewModelBase
|
||||
});
|
||||
};
|
||||
|
||||
// Получаем записи меню из базы данных
|
||||
MenuItems = new ObservableCollection<MenuItemViewModel>(_context.SampleRecords.Select(r => new MenuItemViewModel(r.Id, r.SampleName)));
|
||||
if (MenuItems.Count() > 0) // Если записи есть,
|
||||
MenuSelectedIndex = 0; // Выбираем первую запись
|
||||
else // Если записей нет,
|
||||
MenuSelectedIndex = -1; // Устанавливаем, что запись не выбрана
|
||||
|
||||
// Подписываемся на изменения свойств для обновления CanScroll
|
||||
PropertyChanged += (_, ea) =>
|
||||
{
|
||||
if (ea.PropertyName == nameof(MenuOpened) ||
|
||||
ea.PropertyName == nameof(MainSectionIsScrolling) ||
|
||||
ea.PropertyName == nameof(InfoSectionIsScrolling))
|
||||
UpdateCanScroll();
|
||||
UpdateCanInteract();
|
||||
};
|
||||
|
||||
_logger.LogInformation("Инициализировано");
|
||||
@@ -314,32 +325,36 @@ public partial class SampleCalibrationViewModel : ViewModelBase
|
||||
if (selectedRecord is not null)
|
||||
{
|
||||
// Заполняем простые поля
|
||||
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;
|
||||
Record.Id = selectedRecord.Id;
|
||||
Record.Uuid = selectedRecord.Uuid;
|
||||
Record.CreateDateTime = selectedRecord.CreateDateTime;
|
||||
Record.EditDateTime = selectedRecord.EditDateTime;
|
||||
Record.SampleName = selectedRecord.SampleName;
|
||||
Record.SampleComment = selectedRecord.SampleComment;
|
||||
Record.SamplingDateTime = selectedRecord.SamplingDateTime;
|
||||
Record.SamplingPlace = selectedRecord.SamplingPlace;
|
||||
Record.MixtureActualRate = selectedRecord.MixtureActualRate;
|
||||
Record.MeasuredContent = selectedRecord.MeasuredContent;
|
||||
if (selectedRecord.Features.Count() > 0)
|
||||
Record.Features = selectedRecord.Features.Chunk(AnalyzerService.FEATURES_LENGTH).Select(v => v.ToArray()).ToList();
|
||||
else
|
||||
Record.Features = new List<float[]>();
|
||||
|
||||
// Ищем изображение в базе данных
|
||||
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;
|
||||
// В любом случае заполняем изображение
|
||||
Record.Image.Data = imageRecord?.Base64 ?? "";
|
||||
|
||||
// Ищем марку в базе данных
|
||||
var brandRecord = _context.BrandRecords.Find(_selectedBrandId);
|
||||
var brandRecord = _context.BrandRecords.Find(selectedRecord.BrandId);
|
||||
// Если марка не найдена в базе данных
|
||||
if (brandRecord is null)
|
||||
{
|
||||
// Логгируем как ошибку
|
||||
_logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", _selectedBrandId, nameof(_context.BrandRecords));
|
||||
_logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", selectedRecord.BrandId, nameof(_context.BrandRecords));
|
||||
// Устанавливаем, что элемент не выбран
|
||||
BrandComboBoxSelectedIndex = -1;
|
||||
}
|
||||
@@ -356,12 +371,6 @@ public partial class SampleCalibrationViewModel : ViewModelBase
|
||||
BrandComboBoxSelectedIndex = BrandComboBoxItems.IndexOf(brandComboBoxItem);
|
||||
}
|
||||
|
||||
// Ищем все признаки в базе данных
|
||||
_selectedFeaturesData = _context.FeatureRecords
|
||||
.Where(r => r.SampleRecordId == SelectedId)
|
||||
.Select(r => r.Values.ToArray())
|
||||
.ToList();
|
||||
|
||||
return;
|
||||
}
|
||||
// Если значение не найдено в базе данных
|
||||
@@ -380,36 +389,30 @@ public partial class SampleCalibrationViewModel : ViewModelBase
|
||||
|
||||
// Если элемент не выбран, заполняем поля пустыми значениями
|
||||
_logger.LogTrace("Выбран новый элемент");
|
||||
SelectedId = 0;
|
||||
SelectedUuid = Guid.NewGuid().ToString();
|
||||
SelectedSampleName = "";
|
||||
SelectedSampleComment = "";
|
||||
SelectedSamplingDateTime = DateTime.UtcNow;
|
||||
SelectedSamplingPlace = "";
|
||||
SelectedMixtureActualRate = -1;
|
||||
SelectedMeasuredContent = -1;
|
||||
SelectedImage.Data = null;
|
||||
var dateTime = DateTime.UtcNow;
|
||||
Record.Id = 0;
|
||||
Record.Uuid = Guid.NewGuid().ToString();
|
||||
Record.CreateDateTime = dateTime;
|
||||
Record.EditDateTime = dateTime;
|
||||
Record.SampleName = "";
|
||||
Record.SampleComment = "";
|
||||
Record.SamplingDateTime = dateTime;
|
||||
Record.SamplingPlace = "";
|
||||
Record.MixtureActualRate = -1;
|
||||
Record.MeasuredContent = -1;
|
||||
Record.Image.Data = "";
|
||||
Record.Features = new List<float[]>();
|
||||
BrandComboBoxSelectedIndex = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Вызывается при изменении <see cref="BrandComboBoxSelectedIndexC"/>
|
||||
/// Обновить значение <see cref="CanInteract"/>
|
||||
/// </summary>
|
||||
partial void OnBrandComboBoxSelectedIndexChanged(int value)
|
||||
private void UpdateCanInteract()
|
||||
{
|
||||
if (value >= 0 && value < BrandComboBoxItems.Count())
|
||||
_selectedBrandId = BrandComboBoxItems[value].Id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обновить значение <see cref="CanScroll"/>
|
||||
/// </summary>
|
||||
private void UpdateCanScroll()
|
||||
{
|
||||
CanScroll = !MenuOpened;
|
||||
CanScroll &= !MainSectionIsScrolling;
|
||||
CanScroll &= !InfoSectionIsScrolling;
|
||||
CanScroll &= !ImagesIsCapturing;
|
||||
CanInteract = !MenuOpened;
|
||||
CanInteract &= !MainSectionIsScrolling;
|
||||
CanInteract &= !InfoSectionIsScrolling;
|
||||
}
|
||||
|
||||
// Команды управления меню
|
||||
@@ -449,7 +452,7 @@ public partial class SampleCalibrationViewModel : ViewModelBase
|
||||
// Если уже создана новая запись, то MenuSelectedIndex может быть равен -1 и
|
||||
// при простом присвоении MenuSelectedIndex = -1 событие OnMenuSelectedIndexChanged не вызовется,
|
||||
// потому что событие срабатывает только при изменении значения, а значение было -1 и привилось ему -1,
|
||||
// поэтому если SelectedRecord != -1, то нужно явно вызвать метод OnMenuSelectedIndexChanged
|
||||
// поэтому если Record.Record != -1, то нужно явно вызвать метод OnMenuSelectedIndexChanged
|
||||
if (MenuSelectedIndex != -1)
|
||||
MenuSelectedIndex = -1;
|
||||
else
|
||||
@@ -465,7 +468,7 @@ public partial class SampleCalibrationViewModel : ViewModelBase
|
||||
// TODO: Добавить определение и отображение количества затрагиваемых, записей которые будут повреждены при удалении этой записи
|
||||
try
|
||||
{
|
||||
_logger.LogTrace("Удаление записи: {}={}, {}={}", nameof(SampleRecord.Id), SelectedId, nameof(SampleRecord.SampleName), SelectedSampleName);
|
||||
_logger.LogTrace("Удаление записи: {}={}, {}={}", nameof(SampleRecord.Id), Record.Id, nameof(SampleRecord.SampleName), Record.SampleName);
|
||||
// Если запись не выбрана, то просто создаём новую запись
|
||||
if (MenuSelectedIndex < 0)
|
||||
{
|
||||
@@ -500,7 +503,7 @@ public partial class SampleCalibrationViewModel : ViewModelBase
|
||||
|
||||
// Сохраняем индекс удалённый записи и находим её в базе данных
|
||||
var deletedMenuIndex = MenuSelectedIndex;
|
||||
var deletedRecordId = SelectedId;
|
||||
var deletedRecordId = Record.Id;
|
||||
var deletedRecord = _context.SampleRecords.Find(deletedRecordId);
|
||||
// Создаём новую запись
|
||||
CreateRecord();
|
||||
@@ -510,7 +513,7 @@ public partial class SampleCalibrationViewModel : ViewModelBase
|
||||
// Если удалённая запись не найдена в базе данных, то логгируем как ошибку
|
||||
if (deletedRecord is null)
|
||||
{
|
||||
_logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", deletedRecordId, nameof(_context.BrandRecords));
|
||||
_logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", deletedRecordId, nameof(_context.SampleRecords));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -552,33 +555,50 @@ public partial class SampleCalibrationViewModel : ViewModelBase
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogTrace("Сохранение записи: {}={}, {}={}", nameof(SampleRecord.Id), SelectedId, nameof(SampleRecord.SampleName), SelectedSampleName);
|
||||
_logger.LogTrace("Сохранение записи: {}={}, {}={}", nameof(SampleRecord.Id), Record.Id, nameof(SampleRecord.SampleName), Record.SampleName);
|
||||
// Если запись не выбрана
|
||||
if (MenuSelectedIndex < 0)
|
||||
{
|
||||
// Создаём запись изображения
|
||||
_logger.LogTrace("Создаём новую запись изображения");
|
||||
// Заполняем запись изображения
|
||||
var imageRecord = new ImageRecord(base64: _selectedImageData ?? "");
|
||||
// Добавляем запись изображения в базу данных
|
||||
var attachedImageRecord = _context.ImageRecords.Attach(imageRecord);
|
||||
// Сохраняем изменения в базе данных
|
||||
_context.SaveChanges();
|
||||
_logger.LogTrace("Запись изображения сохранена");
|
||||
var imageId = 0;
|
||||
if (!string.IsNullOrEmpty(Record.Image.Data))
|
||||
{
|
||||
_logger.LogTrace("Создаём новую запись изображения");
|
||||
// Заполняем запись изображения
|
||||
var imageRecord = new ImageRecord(base64: Record.Image.Data);
|
||||
// Добавляем запись изображения в базу данных
|
||||
var attachedImageRecord = _context.ImageRecords.Attach(imageRecord);
|
||||
// Сохраняем изменения в базе данных
|
||||
_context.SaveChanges();
|
||||
_logger.LogTrace("Запись изображения сохранена");
|
||||
imageId = attachedImageRecord.Entity.Id;
|
||||
}
|
||||
|
||||
// Определяем Id выбранной марки
|
||||
var selectedBrandId = 0;
|
||||
// Если значение выходит за верхнюю границу списка элементов меню, то логгируем как ошибку
|
||||
if (BrandComboBoxSelectedIndex >= BrandComboBoxItems.Count())
|
||||
_logger.LogError("Значение {}({}) больше количества элементов {}({})", nameof(BrandComboBoxSelectedIndex), BrandComboBoxSelectedIndex, nameof(BrandComboBoxItems), BrandComboBoxItems.Count());
|
||||
// Если значение больше 0, то достаём Id из списка элементов
|
||||
else if (BrandComboBoxSelectedIndex >= 0)
|
||||
selectedBrandId = BrandComboBoxItems[BrandComboBoxSelectedIndex].Id;
|
||||
|
||||
_logger.LogTrace("Создаём новую запись");
|
||||
var dateTime = DateTime.UtcNow;
|
||||
// Заполняем запись
|
||||
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: []
|
||||
uuid: Record.Uuid,
|
||||
createDateTime: dateTime,
|
||||
editDateTime: dateTime,
|
||||
sampleName: Record.SampleName,
|
||||
sampleComment: Record.SampleComment,
|
||||
samplingDateTime: Record.SamplingDateTime,
|
||||
brandId: selectedBrandId,
|
||||
samplingPlace: Record.SamplingPlace,
|
||||
mixtureActualRate: Record.MixtureActualRate,
|
||||
measuredContent: Record.MeasuredContent,
|
||||
imageId: imageId,
|
||||
features: Record.Features
|
||||
);
|
||||
// Добавляем запись в базу данных
|
||||
var attachedRecord = _context.SampleRecords.Add(newRecord);
|
||||
@@ -586,29 +606,6 @@ public partial class SampleCalibrationViewModel : ViewModelBase
|
||||
_context.SaveChanges();
|
||||
_logger.LogTrace("Запись сохранена");
|
||||
|
||||
_logger.LogTrace("Создаём новые записи признаков");
|
||||
// Заполняем записи признаков и добавляем их в базу данных
|
||||
var attachedFeatureRecords = new List<FeatureRecord>();
|
||||
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));
|
||||
// Выбираем последний элемент из меню
|
||||
@@ -618,11 +615,11 @@ public partial class SampleCalibrationViewModel : ViewModelBase
|
||||
else
|
||||
{
|
||||
// Находим запись в базе данных
|
||||
var attachedRecord = _context.SampleRecords.Find(SelectedId);
|
||||
var attachedRecord = _context.SampleRecords.Find(Record.Id);
|
||||
// Если запись не найдена, то логгируем как ошибку
|
||||
if (attachedRecord is null)
|
||||
{
|
||||
_logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", SelectedId, nameof(_context.SampleRecords));
|
||||
_logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", Record.Id, nameof(_context.SampleRecords));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -630,79 +627,65 @@ public partial class SampleCalibrationViewModel : ViewModelBase
|
||||
|
||||
// Ищем запись изображения в базе данных
|
||||
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++)
|
||||
// Если изображение есть
|
||||
if (!string.IsNullOrEmpty(Record.Image.Data))
|
||||
{
|
||||
var featureRecord = new FeatureRecord(
|
||||
values: _selectedFeaturesData[i],
|
||||
sampleRecordId: attachedRecord.Id
|
||||
);
|
||||
var attachedFeatureRecord = _context.FeatureRecords.Attach(featureRecord);
|
||||
attachedFeatureRecords.Append(attachedFeatureRecord.Entity);
|
||||
_logger.LogTrace("Создаём новую запись изображения");
|
||||
// Заполняем запись изображения
|
||||
var imageRecord = new ImageRecord(base64: Record.Image.Data);
|
||||
// Добавляем запись изображения в базу данных
|
||||
attachedImageRecord = _context.ImageRecords.Attach(imageRecord).Entity;
|
||||
// Сохраняем изменения в базе данных
|
||||
_context.SaveChanges();
|
||||
_logger.LogTrace("Запись изображения сохранена");
|
||||
attachedRecord.ImageId = attachedImageRecord.Id;
|
||||
}
|
||||
}
|
||||
// Если запись изображения найдена
|
||||
else
|
||||
{
|
||||
// Если изображение есть
|
||||
if (!string.IsNullOrEmpty(Record.Image.Data))
|
||||
{
|
||||
_logger.LogTrace("Записываем изменения в запись изображения");
|
||||
// Вносим изменения
|
||||
attachedImageRecord.Base64 = Record.Image.Data;
|
||||
// Сохраняем изменения в базе данных
|
||||
_context.SaveChanges();
|
||||
}
|
||||
// Если изображения нет
|
||||
else
|
||||
{
|
||||
// Удаляем изображение из базы данных
|
||||
_context.ImageRecords.Remove(attachedImageRecord);
|
||||
// Сохраняем изменения в базе данных
|
||||
_context.SaveChanges();
|
||||
attachedRecord.ImageId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
_context.SaveChanges();
|
||||
_logger.LogTrace("Записи признаков сохранена");
|
||||
// Определяем Id выбранной марки
|
||||
var selectedBrandId = 0;
|
||||
// Если значение выходит за верхнюю границу списка элементов меню, то логгируем как ошибку
|
||||
if (BrandComboBoxSelectedIndex >= BrandComboBoxItems.Count())
|
||||
_logger.LogError("Значение {}({}) больше количества элементов {}({})", nameof(BrandComboBoxSelectedIndex), BrandComboBoxSelectedIndex, nameof(BrandComboBoxItems), BrandComboBoxItems.Count());
|
||||
// Если значение больше 0, то достаём Id из списка элементов
|
||||
else if (BrandComboBoxSelectedIndex >= 0)
|
||||
selectedBrandId = BrandComboBoxItems[BrandComboBoxSelectedIndex].Id;
|
||||
|
||||
// Если в данных есть изменения, то записываем их
|
||||
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();
|
||||
// Записываем изменения
|
||||
attachedRecord.EditDateTime = DateTime.UtcNow;
|
||||
attachedRecord.SampleName = Record.SampleName;
|
||||
attachedRecord.SampleComment = Record.SampleComment;
|
||||
attachedRecord.SamplingDateTime = Record.SamplingDateTime;
|
||||
attachedRecord.BrandId = selectedBrandId;
|
||||
attachedRecord.SamplingPlace = Record.SamplingPlace;
|
||||
attachedRecord.MixtureActualRate = Record.MixtureActualRate;
|
||||
attachedRecord.MeasuredContent = Record.MeasuredContent;
|
||||
attachedRecord.Features = Record.Features.SelectMany(fs => fs).ToList();
|
||||
|
||||
// Сохраняем изменения в базе данных
|
||||
_context.SaveChanges();
|
||||
@@ -710,7 +693,7 @@ public partial class SampleCalibrationViewModel : ViewModelBase
|
||||
|
||||
// Записываем изменения в меню
|
||||
_logger.LogTrace("Записываем изменения в меню");
|
||||
MenuItems[MenuSelectedIndex].SampleName = SelectedSampleName;
|
||||
MenuItems[MenuSelectedIndex].SampleName = Record.SampleName;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -736,10 +719,7 @@ public partial class SampleCalibrationViewModel : ViewModelBase
|
||||
_captureImagesTaskCts = new CancellationTokenSource();
|
||||
var cancellationToken = _captureImagesTaskCts.Token;
|
||||
|
||||
// Обновляем данные в интерфейсе
|
||||
// Dispatcher.UIThread.Invoke нужен потому-что этот метод может (и скорее всего будет) вызван в другом потоке,
|
||||
// а изменения данных в интерфейсе могут быть вызваны только из потока в котором интерфейс был создан
|
||||
_ = Dispatcher.UIThread.InvokeAsync(() => ImagesCapturingProgress = $"Получение изображений: 0 / {ImageCapturerService.IlluminatorIntensities.Count()}");
|
||||
ImagesCapturingProgress = $"Получение изображений: 0 / {ImageCapturerService.IlluminatorIntensities.Count()}";
|
||||
|
||||
// Запускаем задачу получения изображений
|
||||
_сaptureImagesTask = Task.Run(async () =>
|
||||
@@ -748,7 +728,7 @@ public partial class SampleCalibrationViewModel : ViewModelBase
|
||||
var imagesData = new List<Task<ImageData>>();
|
||||
|
||||
// Обновляем данные в интерфейсе
|
||||
// Dispatcher.UIThread.Invoke нужен потому-что этот метод может (и скорее всего будет) вызван в другом потоке,
|
||||
// Dispatcher.UIThread.Invoke нужен потому-что неизвестно как будет выполнятся эта задача,
|
||||
// а изменения данных в интерфейсе могут быть вызваны только из потока в котором интерфейс был создан
|
||||
_ = Dispatcher.UIThread.InvokeAsync(() => ImagesIsCapturing = true);
|
||||
|
||||
@@ -761,7 +741,7 @@ public partial class SampleCalibrationViewModel : ViewModelBase
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// Обновляем данные в интерфейсе
|
||||
// Dispatcher.UIThread.Invoke нужен потому-что этот метод может (и скорее всего будет) вызван в другом потоке,
|
||||
// Dispatcher.UIThread.Invoke нужен потому-что неизвестно как будет выполнятся эта задача,
|
||||
// а изменения данных в интерфейсе могут быть вызваны только из потока в котором интерфейс был создан
|
||||
_ = Dispatcher.UIThread.InvokeAsync(() => ImagesCapturingProgress = $"Получение изображений: {i + 1} / {ImageCapturerService.IlluminatorIntensities.Count()}");
|
||||
|
||||
@@ -793,7 +773,7 @@ public partial class SampleCalibrationViewModel : ViewModelBase
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// Обновляем данные в интерфейсе
|
||||
// Dispatcher.UIThread.Invoke нужен потому-что этот метод может (и скорее всего будет) вызван в другом потоке,
|
||||
// Dispatcher.UIThread.Invoke нужен потому-что неизвестно как будет выполнятся эта задача,
|
||||
// а изменения данных в интерфейсе могут быть вызваны только из потока в котором интерфейс был создан
|
||||
_ = Dispatcher.UIThread.InvokeAsync(() => ImagesCapturingProgress = $"Обработка изображений");
|
||||
|
||||
@@ -846,7 +826,7 @@ public partial class SampleCalibrationViewModel : ViewModelBase
|
||||
var (avgImage, roi, features) = result.Value;
|
||||
|
||||
// Сохраняем вектор признаков
|
||||
_selectedFeaturesData = features;
|
||||
Record.Features = features;
|
||||
|
||||
// Обрабатываем среднее изображение
|
||||
avgImage = new OpenCvSharp.Mat(avgImage, roi);
|
||||
@@ -861,15 +841,14 @@ public partial class SampleCalibrationViewModel : ViewModelBase
|
||||
|
||||
// Кодируем среднее изображение
|
||||
OpenCvSharp.Cv2.ImEncode(".png", avgImage, out var avgData);
|
||||
_selectedImageData = Convert.ToBase64String(avgData);
|
||||
|
||||
// Очищаем память среднего изображения
|
||||
avgImage.Dispose();
|
||||
|
||||
// Обновляем данные в интерфейсе
|
||||
// Dispatcher.UIThread.Invoke нужен потому-что этот метод может (и скорее всего будет) вызван в другом потоке,
|
||||
// Dispatcher.UIThread.Invoke нужен потому-что неизвестно как будет выполнятся эта задача,
|
||||
// а изменения данных в интерфейсе могут быть вызваны только из потока в котором интерфейс был создан
|
||||
_ = Dispatcher.UIThread.InvokeAsync(() => SelectedImage.Data = _selectedImageData);
|
||||
_ = Dispatcher.UIThread.InvokeAsync(() => Record.Image.Data = Convert.ToBase64String(avgData));
|
||||
}, cancellationToken);
|
||||
|
||||
// Создаём задачу которая выполнится после завершения всех других
|
||||
@@ -922,7 +901,7 @@ public partial class SampleCalibrationViewModel : ViewModelBase
|
||||
}
|
||||
|
||||
// Обновляем данные в интерфейсе
|
||||
// Dispatcher.UIThread.Invoke нужен потому-что этот метод может (и скорее всего будет) вызван в другом потоке,
|
||||
// Dispatcher.UIThread.Invoke нужен потому-что неизвестно как будет выполнятся эта задача,
|
||||
// а изменения данных в интерфейсе могут быть вызваны только из потока в котором интерфейс был создан
|
||||
_ = Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
|
||||
@@ -35,6 +35,45 @@ public partial class SeparatorCalibrationViewModel : ViewModelBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Внутренняя модель записи
|
||||
/// </summary>
|
||||
public partial class RecordViewModel : ViewModelBase
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="SeparatorRecord.Id"/>
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial int Id { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// <see cref="SeparatorRecord.Uuid"/>
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial string Uuid { get; set; } = Guid.NewGuid().ToString();
|
||||
/// <summary>
|
||||
/// <see cref="SampleRecord.CreateDateTime"/>
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial DateTime CreateDateTime { get; set; } = DateTime.UtcNow;
|
||||
/// <summary>
|
||||
/// <see cref="SampleRecord.EditDateTime"/>
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial DateTime EditDateTime { get; set; } = DateTime.UtcNow;
|
||||
/// <summary>
|
||||
/// <see cref="SeparatorRecord.Type"/>
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial string Type { get; set; } = "";
|
||||
/// <summary>
|
||||
/// <see cref="SeparatorRecord.Comment"/>
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial string Comment { get; set; } = "";
|
||||
/// <summary>
|
||||
/// Представление <see cref="SeparatorRecord.ImageId"/>
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial AsyncImageRecordViewModel Image { get; set; } = new();
|
||||
/// <summary>
|
||||
/// Данные <see cref="SeparatorRecord.Features"/>
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial List<float[]> Features { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Логгер
|
||||
/// </summary>
|
||||
@@ -65,15 +104,6 @@ public partial class SeparatorCalibrationViewModel : ViewModelBase
|
||||
/// </summary>
|
||||
private CancellationTokenSource? _captureImagesTaskCts = null;
|
||||
|
||||
/// <summary>
|
||||
/// Данные <see cref="SeparatorRecord.ImageId"/> выбранного элемента
|
||||
/// </summary>
|
||||
private string? _selectedImageData = null;
|
||||
/// <summary>
|
||||
/// Данные <see cref="SeparatorRecord.FeatureIds"/> выбранного элемента
|
||||
/// </summary>
|
||||
private List<float[]> _selectedFeaturesData = new();
|
||||
|
||||
/// <summary>
|
||||
/// Меню открыто
|
||||
/// </summary>
|
||||
@@ -88,21 +118,9 @@ public partial class SeparatorCalibrationViewModel : ViewModelBase
|
||||
[ObservableProperty] public partial int MenuSelectedIndex { get; set; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="SeparatorRecord.Id"/> выбранного элемента
|
||||
/// Запись
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial int SelectedId { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// <see cref="SeparatorRecord.Uuid"/> выбранного элемента
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial string SelectedUuid { get; set; } = Guid.NewGuid().ToString();
|
||||
/// <summary>
|
||||
/// <see cref="SeparatorRecord.Type"/> выбранного элемента
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial string SelectedType { get; set; } = "";
|
||||
/// <summary>
|
||||
/// Представление <see cref="SeparatorRecord.ImageId"/> выбранного элемента
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial AsyncImageRecordViewModel SelectedImage { get; set; } = new();
|
||||
[ObservableProperty] public partial RecordViewModel Record { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Текущий экран главной секции
|
||||
@@ -112,6 +130,7 @@ public partial class SeparatorCalibrationViewModel : ViewModelBase
|
||||
/// Можно ли переходить на следующий экран главной секции
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial bool MainSectionCanScrollBackward { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Можно ли переходить на предыдущий экран главной секции
|
||||
/// </summary>
|
||||
@@ -120,6 +139,22 @@ public partial class SeparatorCalibrationViewModel : ViewModelBase
|
||||
/// Происходит ли переход между экранами главной секции
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial bool MainSectionIsScrolling { get; set; } = false;
|
||||
/// <summary>
|
||||
/// Текущий экран информационной секции
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial int InfoSectionCurrentIndex { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// Можно ли переходить на следующий экран информационной секции
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial bool InfoSectionCanScrollBackward { get; set; } = false;
|
||||
/// <summary>
|
||||
/// Можно ли переходить на предыдущий экран информационной секции
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial bool InfoSectionCanScrollForward { get; set; } = false;
|
||||
/// <summary>
|
||||
/// Происходит ли переход между экранами информационной секции
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial bool InfoSectionIsScrolling { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Проводится ли съёмка изображений
|
||||
@@ -131,9 +166,9 @@ public partial class SeparatorCalibrationViewModel : ViewModelBase
|
||||
[ObservableProperty] public partial string ImagesCapturingProgress { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Можно ли производить перемещение между экранами
|
||||
/// Можно ли взаимодействовать с объектом
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial bool CanScroll { get; set; }
|
||||
[ObservableProperty] public partial bool CanInteract { get; set; } = true;
|
||||
|
||||
public SeparatorCalibrationViewModel(
|
||||
ILogger<SeparatorCalibrationViewModel> logger,
|
||||
@@ -162,8 +197,9 @@ public partial class SeparatorCalibrationViewModel : ViewModelBase
|
||||
PropertyChanged += (_, ea) =>
|
||||
{
|
||||
if (ea.PropertyName == nameof(MenuOpened) ||
|
||||
ea.PropertyName == nameof(MainSectionIsScrolling))
|
||||
UpdateCanScroll();
|
||||
ea.PropertyName == nameof(MainSectionIsScrolling) ||
|
||||
ea.PropertyName == nameof(InfoSectionIsScrolling))
|
||||
UpdateCanInteract();
|
||||
};
|
||||
|
||||
_logger.LogInformation("Инициализировано");
|
||||
@@ -187,24 +223,26 @@ public partial class SeparatorCalibrationViewModel : ViewModelBase
|
||||
if (selectedRecord is not null)
|
||||
{
|
||||
// Заполняем простые поля
|
||||
SelectedId = selectedRecord.Id;
|
||||
SelectedUuid = selectedRecord.Uuid;
|
||||
SelectedType = selectedRecord.Type;
|
||||
Record.Id = selectedRecord.Id;
|
||||
Record.Uuid = selectedRecord.Uuid;
|
||||
Record.CreateDateTime = selectedRecord.CreateDateTime;
|
||||
Record.EditDateTime = selectedRecord.EditDateTime;
|
||||
Record.Type = selectedRecord.Type;
|
||||
Record.Comment = selectedRecord.Comment;
|
||||
if (selectedRecord.Features.Count() > 0)
|
||||
Record.Features = selectedRecord.Features.Chunk(AnalyzerService.FEATURES_LENGTH).Select(v => v.ToArray()).ToList();
|
||||
else
|
||||
Record.Features = new List<float[]>();
|
||||
|
||||
// Ищем изображение в базе данных
|
||||
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;
|
||||
// В любом случае заполняем изображение
|
||||
Record.Image.Data = imageRecord?.Base64 ?? "";
|
||||
|
||||
// Ищем все признаки в базе данных
|
||||
_selectedFeaturesData = _context.FeatureRecords
|
||||
.Where(r => r.SeparatorRecordId == SelectedId)
|
||||
.Select(r => r.Values.ToArray())
|
||||
.ToList();
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -224,20 +262,25 @@ public partial class SeparatorCalibrationViewModel : ViewModelBase
|
||||
|
||||
// Если элемент не выбран, заполняем поля пустыми значениями
|
||||
_logger.LogTrace("Выбран новый элемент");
|
||||
SelectedId = 0;
|
||||
SelectedUuid = Guid.NewGuid().ToString();
|
||||
SelectedType = "";
|
||||
SelectedImage.Data = null;
|
||||
var dateTime = DateTime.UtcNow;
|
||||
Record.Id = 0;
|
||||
Record.Uuid = Guid.NewGuid().ToString();
|
||||
Record.CreateDateTime = dateTime;
|
||||
Record.EditDateTime = dateTime;
|
||||
Record.Type = "";
|
||||
Record.Comment = "";
|
||||
Record.Image.Data = "";
|
||||
Record.Features = new List<float[]>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обновить значение <see cref="CanScroll"/>
|
||||
/// Обновить значение <see cref="CanInteract"/>
|
||||
/// </summary>
|
||||
private void UpdateCanScroll()
|
||||
private void UpdateCanInteract()
|
||||
{
|
||||
CanScroll = !MenuOpened;
|
||||
CanScroll &= !MainSectionIsScrolling;
|
||||
CanScroll &= !ImagesIsCapturing;
|
||||
CanInteract = !MenuOpened;
|
||||
CanInteract &= !MainSectionIsScrolling;
|
||||
CanInteract &= !InfoSectionIsScrolling;
|
||||
}
|
||||
|
||||
// Команды управления меню
|
||||
@@ -258,7 +301,15 @@ public partial class SeparatorCalibrationViewModel : ViewModelBase
|
||||
/// Переключить главную секцию информация <-> изображения
|
||||
/// </summary>
|
||||
[RelayCommand] private void SwitchMainSection() => MainSectionCurrentIndex = MainSectionCurrentIndex == 0 ? 1 : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Переключить информационную секцию на следующую
|
||||
/// </summary>
|
||||
[RelayCommand] private void InfoSectionPrevious() => InfoSectionCurrentIndex--;
|
||||
/// <summary>
|
||||
/// Переключить информационную секцию на предыдущую
|
||||
/// </summary>
|
||||
[RelayCommand] private void InfoSectionNext() => InfoSectionCurrentIndex++;
|
||||
|
||||
/// <summary>
|
||||
/// Создать новую запись
|
||||
/// </summary>
|
||||
@@ -285,7 +336,7 @@ public partial class SeparatorCalibrationViewModel : ViewModelBase
|
||||
// TODO: Добавить определение и отображение количества затрагиваемых, записей которые будут повреждены при удалении этой записи
|
||||
try
|
||||
{
|
||||
_logger.LogTrace("Удаление записи: {}={}, {}={}", nameof(SeparatorRecord.Id), SelectedId, nameof(SeparatorRecord.Type), SelectedType);
|
||||
_logger.LogTrace("Удаление записи: {}={}, {}={}", nameof(SeparatorRecord.Id), Record.Id, nameof(SeparatorRecord.Type), Record.Type);
|
||||
// Если запись не выбрана, то просто создаём новую запись
|
||||
if (MenuSelectedIndex < 0)
|
||||
{
|
||||
@@ -320,7 +371,7 @@ public partial class SeparatorCalibrationViewModel : ViewModelBase
|
||||
|
||||
// Сохраняем индекс удалённый записи и находим её в базе данных
|
||||
var deletedMenuIndex = MenuSelectedIndex;
|
||||
var deletedRecordId = SelectedId;
|
||||
var deletedRecordId = Record.Id;
|
||||
var deletedRecord = _context.SeparatorRecords.Find(deletedRecordId);
|
||||
// Создаём новую запись
|
||||
CreateRecord();
|
||||
@@ -330,7 +381,7 @@ public partial class SeparatorCalibrationViewModel : ViewModelBase
|
||||
// Если удалённая запись не найдена в базе данных, то логгируем как ошибку
|
||||
if (deletedRecord is null)
|
||||
{
|
||||
_logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", deletedRecordId, nameof(_context.BrandRecords));
|
||||
_logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", deletedRecordId, nameof(_context.SeparatorRecords));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -372,27 +423,36 @@ public partial class SeparatorCalibrationViewModel : ViewModelBase
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogTrace("Сохранение записи: {}={}, {}={}", nameof(SeparatorRecord.Id), SelectedId, nameof(SeparatorRecord.Type), SelectedType);
|
||||
_logger.LogTrace("Сохранение записи: {}={}, {}={}", nameof(SeparatorRecord.Id), Record.Id, nameof(SeparatorRecord.Type), Record.Type);
|
||||
// Если запись не выбрана
|
||||
if (MenuSelectedIndex < 0)
|
||||
{
|
||||
// Создаём запись изображения
|
||||
_logger.LogTrace("Создаём новую запись изображения");
|
||||
// Заполняем запись изображения
|
||||
var imageRecord = new ImageRecord(base64: _selectedImageData ?? "");
|
||||
// Добавляем запись изображения в базу данных
|
||||
var attachedImageRecord = _context.ImageRecords.Attach(imageRecord);
|
||||
// Сохраняем изменения в базе данных
|
||||
_context.SaveChanges();
|
||||
_logger.LogTrace("Запись изображения сохранена");
|
||||
var imageId = 0;
|
||||
if (!string.IsNullOrEmpty(Record.Image.Data))
|
||||
{
|
||||
_logger.LogTrace("Создаём новую запись изображения");
|
||||
// Заполняем запись изображения
|
||||
var imageRecord = new ImageRecord(base64: Record.Image.Data);
|
||||
// Добавляем запись изображения в базу данных
|
||||
var attachedImageRecord = _context.ImageRecords.Attach(imageRecord);
|
||||
// Сохраняем изменения в базе данных
|
||||
_context.SaveChanges();
|
||||
_logger.LogTrace("Запись изображения сохранена");
|
||||
imageId = attachedImageRecord.Entity.Id;
|
||||
}
|
||||
|
||||
_logger.LogTrace("Создаём новую запись");
|
||||
var dateTime = DateTime.UtcNow;
|
||||
// Заполняем запись
|
||||
var newRecord = new SeparatorRecord(
|
||||
uuid: SelectedUuid,
|
||||
type: SelectedType,
|
||||
imageId: attachedImageRecord.Entity.Id,
|
||||
featureIds: []
|
||||
uuid: Record.Uuid,
|
||||
createDateTime: dateTime,
|
||||
editDateTime: dateTime,
|
||||
type: Record.Type,
|
||||
comment: Record.Comment,
|
||||
imageId: imageId,
|
||||
features: Record.Features
|
||||
);
|
||||
// Добавляем запись в базу данных
|
||||
var attachedRecord = _context.SeparatorRecords.Add(newRecord);
|
||||
@@ -400,29 +460,6 @@ public partial class SeparatorCalibrationViewModel : ViewModelBase
|
||||
_context.SaveChanges();
|
||||
_logger.LogTrace("Запись сохранена");
|
||||
|
||||
_logger.LogTrace("Создаём новые записи признаков");
|
||||
// Заполняем записи признаков и добавляем их в базу данных
|
||||
var attachedFeatureRecords = new List<FeatureRecord>();
|
||||
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.Type));
|
||||
// Выбираем последний элемент из меню
|
||||
@@ -432,11 +469,11 @@ public partial class SeparatorCalibrationViewModel : ViewModelBase
|
||||
else
|
||||
{
|
||||
// Находим запись в базе данных
|
||||
var attachedRecord = _context.SeparatorRecords.Find(SelectedId);
|
||||
var attachedRecord = _context.SeparatorRecords.Find(Record.Id);
|
||||
// Если запись не найдена, то логгируем как ошибку
|
||||
if (attachedRecord is null)
|
||||
{
|
||||
_logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", SelectedId, nameof(_context.SeparatorRecords));
|
||||
_logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", Record.Id, nameof(_context.SeparatorRecords));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -449,62 +486,48 @@ public partial class SeparatorCalibrationViewModel : ViewModelBase
|
||||
{
|
||||
// Логгируем как ошибку
|
||||
_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.SeparatorRecordId == 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++)
|
||||
// Если изображение есть
|
||||
if (!string.IsNullOrEmpty(Record.Image.Data))
|
||||
{
|
||||
var featureRecord = new FeatureRecord(
|
||||
values: _selectedFeaturesData[i],
|
||||
sampleRecordId: attachedRecord.Id
|
||||
);
|
||||
var attachedFeatureRecord = _context.FeatureRecords.Attach(featureRecord);
|
||||
attachedFeatureRecords.Append(attachedFeatureRecord.Entity);
|
||||
_logger.LogTrace("Создаём новую запись изображения");
|
||||
// Заполняем запись изображения
|
||||
var imageRecord = new ImageRecord(base64: Record.Image.Data);
|
||||
// Добавляем запись изображения в базу данных
|
||||
attachedImageRecord = _context.ImageRecords.Attach(imageRecord).Entity;
|
||||
// Сохраняем изменения в базе данных
|
||||
_context.SaveChanges();
|
||||
_logger.LogTrace("Запись изображения сохранена");
|
||||
attachedRecord.ImageId = attachedImageRecord.Id;
|
||||
}
|
||||
}
|
||||
// Если запись найдена
|
||||
else
|
||||
{
|
||||
// Если изображение есть
|
||||
if (!string.IsNullOrEmpty(Record.Image.Data))
|
||||
{
|
||||
_logger.LogTrace("Записываем изменения в запись изображения");
|
||||
// Вносим изменения
|
||||
attachedImageRecord.Base64 = Record.Image.Data;
|
||||
// Сохраняем изменения в базе данных
|
||||
_context.SaveChanges();
|
||||
}
|
||||
// Если изображения нет
|
||||
else
|
||||
{
|
||||
// Удаляем изображение из базы данных
|
||||
_context.ImageRecords.Remove(attachedImageRecord);
|
||||
// Сохраняем изменения в базе данных
|
||||
_context.SaveChanges();
|
||||
attachedRecord.ImageId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
_context.SaveChanges();
|
||||
_logger.LogTrace("Записи признаков сохранена");
|
||||
|
||||
// Если в данных есть изменения, то записываем их
|
||||
if (attachedRecord.Type != SelectedType)
|
||||
attachedRecord.Type = SelectedType;
|
||||
if (attachedRecord.ImageId != attachedImageRecord.Id)
|
||||
attachedRecord.ImageId = attachedImageRecord.Id;
|
||||
// Для признаков особый случай, они в любом случае будут перезаписаны, потому что так быстрее
|
||||
attachedRecord.FeatureIds = attachedFeatureRecords.Select(r => r.Id).ToList();
|
||||
// Записываем изменения
|
||||
attachedRecord.EditDateTime = DateTime.UtcNow;
|
||||
attachedRecord.Type = Record.Type;
|
||||
attachedRecord.Comment = Record.Comment;
|
||||
attachedRecord.Features = Record.Features.SelectMany(fs => fs).ToList();
|
||||
|
||||
// Сохраняем изменения в базе данных
|
||||
_context.SaveChanges();
|
||||
@@ -512,7 +535,7 @@ public partial class SeparatorCalibrationViewModel : ViewModelBase
|
||||
|
||||
// Записываем изменения в меню
|
||||
_logger.LogTrace("Записываем изменения в меню");
|
||||
MenuItems[MenuSelectedIndex].Type = SelectedType;
|
||||
MenuItems[MenuSelectedIndex].Type = Record.Type;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -538,10 +561,7 @@ public partial class SeparatorCalibrationViewModel : ViewModelBase
|
||||
_captureImagesTaskCts = new CancellationTokenSource();
|
||||
var cancellationToken = _captureImagesTaskCts.Token;
|
||||
|
||||
// Обновляем данные в интерфейсе
|
||||
// Dispatcher.UIThread.Invoke нужен потому-что этот метод может (и скорее всего будет) вызван в другом потоке,
|
||||
// а изменения данных в интерфейсе могут быть вызваны только из потока в котором интерфейс был создан
|
||||
_ = Dispatcher.UIThread.InvokeAsync(() => ImagesCapturingProgress = $"Получение изображений: 0 / {ImageCapturerService.IlluminatorIntensities.Count()}");
|
||||
ImagesCapturingProgress = $"Получение изображений: 0 / {ImageCapturerService.IlluminatorIntensities.Count()}";
|
||||
|
||||
// Запускаем получение изображений асинхронно
|
||||
_сaptureImagesTask = Task.Run(async () =>
|
||||
@@ -550,7 +570,7 @@ public partial class SeparatorCalibrationViewModel : ViewModelBase
|
||||
var imagesData = new List<Task<ImageData>>();
|
||||
|
||||
// Обновляем данные в интерфейсе
|
||||
// Dispatcher.UIThread.Invoke нужен потому-что этот метод может (и скорее всего будет) вызван в другом потоке,
|
||||
// Dispatcher.UIThread.Invoke нужен потому-что неизвестно как будет выполнятся эта задача,
|
||||
// а изменения данных в интерфейсе могут быть вызваны только из потока в котором интерфейс был создан
|
||||
_ = Dispatcher.UIThread.InvokeAsync(() => ImagesIsCapturing = true);
|
||||
|
||||
@@ -563,7 +583,7 @@ public partial class SeparatorCalibrationViewModel : ViewModelBase
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// Обновляем данные в интерфейсе
|
||||
// Dispatcher.UIThread.Invoke нужен потому-что этот метод может (и скорее всего будет) вызван в другом потоке,
|
||||
// Dispatcher.UIThread.Invoke нужен потому-что неизвестно как будет выполнятся эта задача,
|
||||
// а изменения данных в интерфейсе могут быть вызваны только из потока в котором интерфейс был создан
|
||||
_ = Dispatcher.UIThread.InvokeAsync(() => ImagesCapturingProgress = $"Получение изображений: {i + 1} / {ImageCapturerService.IlluminatorIntensities.Count()}");
|
||||
|
||||
@@ -595,7 +615,7 @@ public partial class SeparatorCalibrationViewModel : ViewModelBase
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// Обновляем данные в интерфейсе
|
||||
// Dispatcher.UIThread.Invoke нужен потому-что этот метод может (и скорее всего будет) вызван в другом потоке,
|
||||
// Dispatcher.UIThread.Invoke нужен потому-что неизвестно как будет выполнятся эта задача,
|
||||
// а изменения данных в интерфейсе могут быть вызваны только из потока в котором интерфейс был создан
|
||||
_ = Dispatcher.UIThread.InvokeAsync(() => ImagesCapturingProgress = $"Обработка изображений");
|
||||
|
||||
@@ -648,7 +668,7 @@ public partial class SeparatorCalibrationViewModel : ViewModelBase
|
||||
var (avgImage, roi, features) = result.Value;
|
||||
|
||||
// Сохраняем вектор признаков
|
||||
_selectedFeaturesData = features;
|
||||
Record.Features = features;
|
||||
|
||||
// Обрабатываем среднее изображение
|
||||
avgImage = new OpenCvSharp.Mat(avgImage, roi);
|
||||
@@ -663,15 +683,14 @@ public partial class SeparatorCalibrationViewModel : ViewModelBase
|
||||
|
||||
// Кодируем среднее изображение
|
||||
OpenCvSharp.Cv2.ImEncode(".png", avgImage, out var avgData);
|
||||
_selectedImageData = Convert.ToBase64String(avgData);
|
||||
|
||||
// Очищаем память среднего изображения
|
||||
avgImage.Dispose();
|
||||
|
||||
|
||||
// Обновляем данные в интерфейсе
|
||||
// Dispatcher.UIThread.Invoke нужен потому-что этот метод может (и скорее всего будет) вызван в другом потоке,
|
||||
// Dispatcher.UIThread.Invoke нужен потому-что неизвестно как будет выполнятся эта задача,
|
||||
// а изменения данных в интерфейсе могут быть вызваны только из потока в котором интерфейс был создан
|
||||
_ = Dispatcher.UIThread.InvokeAsync(() => SelectedImage.Data = _selectedImageData);
|
||||
_ = Dispatcher.UIThread.InvokeAsync(() => Record.Image.Data = Convert.ToBase64String(avgData));
|
||||
}, cancellationToken);
|
||||
|
||||
// Создаём задачу которая выполнится после завершения всех других
|
||||
@@ -724,7 +743,7 @@ public partial class SeparatorCalibrationViewModel : ViewModelBase
|
||||
}
|
||||
|
||||
// Обновляем данные в интерфейсе
|
||||
// Dispatcher.UIThread.Invoke нужен потому-что этот метод может (и скорее всего будет) вызван в другом потоке,
|
||||
// Dispatcher.UIThread.Invoke нужен потому-что неизвестно как будет выполнятся эта задача,
|
||||
// а изменения данных в интерфейсе могут быть вызваны только из потока в котором интерфейс был создан
|
||||
_ = Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
@@ -737,6 +756,8 @@ public partial class SeparatorCalibrationViewModel : ViewModelBase
|
||||
});
|
||||
}
|
||||
|
||||
// Команда отмены задачи съёмки изображений
|
||||
/// <summary>
|
||||
/// Отменить задачу съёмки изображения
|
||||
/// </summary>
|
||||
[RelayCommand] private void CancelImageCapturing() => _captureImagesTaskCts?.Cancel();
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ using GSS2.ViewModels.AmineContent;
|
||||
|
||||
using ViewModelBase = GSS2.UI.Core.ViewModels.ViewModelBase;
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace GSS2.ViewModels;
|
||||
|
||||
public partial class AmineContentViewModel : ViewModelBase
|
||||
@@ -17,26 +19,54 @@ public partial class AmineContentViewModel : ViewModelBase
|
||||
[ObservableProperty] public partial SampleCalibrationViewModel SampleCalibration { get; set; }
|
||||
[ObservableProperty] public partial SeparatorCalibrationViewModel SeparatorCalibration { get; set; }
|
||||
// [ObservableProperty] public partial CameraViewModel Camera { get; set; }
|
||||
[ObservableProperty] public partial SystemViewModel System { get; set; }
|
||||
[ObservableProperty] public partial BrandCalibrationViewModel BrandCalibration { get; set; }
|
||||
// [ObservableProperty] public partial SystemViewModel System { get; set; }
|
||||
|
||||
[ObservableProperty] public partial int CurrentIndex { get; set; }
|
||||
[ObservableProperty] public partial bool IsScrolling { get; set; }
|
||||
[ObservableProperty] public partial int CurrentIndex { get; set; } = 0;
|
||||
[ObservableProperty] public partial bool IsScrolling { get; set; } = false;
|
||||
[ObservableProperty] public partial bool CanInteract { get; set; } = true;
|
||||
|
||||
public AmineContentViewModel(
|
||||
NavigationService navigationService,
|
||||
AnalysisViewModel analysis,
|
||||
SampleCalibrationViewModel sampleCalibration,
|
||||
SeparatorCalibrationViewModel separatorCalibration,
|
||||
// CameraViewModel camera,
|
||||
SystemViewModel system
|
||||
BrandCalibrationViewModel brandCalibration
|
||||
)
|
||||
{
|
||||
_navigationService = navigationService;
|
||||
Analysis = analysis;
|
||||
SampleCalibration = sampleCalibration;
|
||||
SeparatorCalibration = separatorCalibration;
|
||||
// Camera = camera;
|
||||
System = system;
|
||||
BrandCalibration = brandCalibration;
|
||||
|
||||
PropertyChanged += (_, ea) =>
|
||||
{
|
||||
if (ea.PropertyName == nameof(IsScrolling))
|
||||
UpdateCanInteract();
|
||||
};
|
||||
Analysis.PropertyChanged += SubViewModelPropertyChanged;
|
||||
SampleCalibration.PropertyChanged += SubViewModelPropertyChanged;
|
||||
SeparatorCalibration.PropertyChanged += SubViewModelPropertyChanged;
|
||||
BrandCalibration.PropertyChanged += SubViewModelPropertyChanged;
|
||||
}
|
||||
|
||||
private void SubViewModelPropertyChanged(object? sender, PropertyChangedEventArgs ea)
|
||||
{
|
||||
if (ea.PropertyName == nameof(Analysis.CanInteract) ||
|
||||
ea.PropertyName == nameof(Analysis.ImagesIsCapturing))
|
||||
UpdateCanInteract();
|
||||
}
|
||||
private void UpdateCanInteract()
|
||||
{
|
||||
CanInteract = !IsScrolling;
|
||||
CanInteract &= Analysis.CanInteract;
|
||||
CanInteract &= SampleCalibration.CanInteract;
|
||||
CanInteract &= SeparatorCalibration.CanInteract;
|
||||
CanInteract &= BrandCalibration.CanInteract;
|
||||
CanInteract &= !Analysis.ImagesIsCapturing;
|
||||
CanInteract &= !SampleCalibration.ImagesIsCapturing;
|
||||
CanInteract &= !SeparatorCalibration.ImagesIsCapturing;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
using GSS2.UI.Core.ViewModels;
|
||||
|
||||
namespace GSS2.ViewModels.Common;
|
||||
|
||||
public partial class CameraViewModel : ViewModelBase
|
||||
{
|
||||
[ObservableProperty] public partial GSS2.UI.Core.ViewModels.CameraViewModel Camera { get; set; }
|
||||
[ObservableProperty] public partial GSS2.UI.Core.ViewModels.IlluminatorControllerViewModel Illuminator { get; set; }
|
||||
|
||||
public CameraViewModel(
|
||||
GSS2.UI.Core.ViewModels.CameraViewModel camera,
|
||||
GSS2.UI.Core.ViewModels.IlluminatorControllerViewModel illuminator
|
||||
)
|
||||
{
|
||||
Camera = camera;
|
||||
Illuminator = illuminator;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,8 +16,12 @@ public partial class AnalysisView : UserControl
|
||||
|
||||
private void OnChartsUpdate(object? sender, EventArgs ea)
|
||||
{
|
||||
MeanValuesChart.CoreChart.Update();
|
||||
VariancesChart.CoreChart.Update();
|
||||
QualitiesChart.CoreChart.Update();
|
||||
ProcessedAreasHistogram.CoreChart.Update();
|
||||
UnprocessedAreasHistogram.CoreChart.Update();
|
||||
HotspotAreasHistogram.CoreChart.Update();
|
||||
OpticalIntegralsHistogram.CoreChart.Update();
|
||||
InnerStdDevsHistogram.CoreChart.Update();
|
||||
InnerHeterogeneitiesHistogram.CoreChart.Update();
|
||||
ParticleValuesHistogram.CoreChart.Update();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<Grid Background="{StaticResource MainBackground}">
|
||||
|
||||
<!-- Основная форма -->
|
||||
<Grid IsEnabled="{Binding !MenuOpened}">
|
||||
<Grid IsEnabled="{Binding CanInteract}">
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" /> <!-- Кнопка "Удалить" -->
|
||||
@@ -40,12 +40,14 @@
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" /> <!-- Кнопка меню -->
|
||||
<ColumnDefinition Width="*" /> <!-- Информация -->
|
||||
<ColumnDefinition Width="*" /> <!-- Информация -->
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.ColumnSpacing>5</Grid.ColumnSpacing>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" /> <!-- Информация -->
|
||||
<RowDefinition Height="*" /> <!-- Информация -->
|
||||
<RowDefinition Height="Auto" /> <!-- Кнопки -->
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.RowSpacing>5</Grid.RowSpacing>
|
||||
@@ -55,115 +57,201 @@
|
||||
Classes="vertical"
|
||||
Command="{Binding ToggleMenuCommand}"
|
||||
Grid.Column="0"
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="3"
|
||||
VerticalAlignment="Stretch"
|
||||
>
|
||||
<TextBlock Text="☰" />
|
||||
<TextBlock Text="☰"/>
|
||||
</Button>
|
||||
|
||||
<!-- Информация -->
|
||||
<Grid Grid.Column="1">
|
||||
<core:SectionPanel
|
||||
CanScrollBackward="{Binding InfoSectionCanScrollBackward}"
|
||||
CanScrollForward="{Binding InfoSectionCanScrollForward}"
|
||||
ClipToBounds="True"
|
||||
CurrentIndex="{Binding InfoSectionCurrentIndex}"
|
||||
Cyclic="True"
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="2"
|
||||
Grid.Row="0"
|
||||
IsScrolling="{Binding InfoSectionIsScrolling}"
|
||||
Orientation="Horizontal"
|
||||
>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<!-- Id / Uuid / BrandName / MixtureName / MixtureNormalRate -->
|
||||
<Grid>
|
||||
|
||||
<Grid.ColumnSpacing>5</Grid.ColumnSpacing>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" /> <!-- Id (подпись) | Uuid (подпись) -->
|
||||
<RowDefinition Height="Auto" /> <!-- Id (поле ) | Uuid (поле ) -->
|
||||
<RowDefinition Height="Auto" /> <!-- BrandName (подпись) | MixtureName (подпись) -->
|
||||
<RowDefinition Height="Auto" /> <!-- BrandName (поле ) | MixtureName (поле ) -->
|
||||
<RowDefinition Height="Auto" /> <!-- MixtureNormalRate (подпись) -->
|
||||
<RowDefinition Height="Auto" /> <!-- MixtureNormalRate (поле ) -->
|
||||
<RowDefinition Height="*" /> <!-- Пустое пространство -->
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnSpacing>5</Grid.ColumnSpacing>
|
||||
|
||||
<Grid.RowSpacing>5</Grid.RowSpacing>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" /> <!-- Id (подпись) | Uuid (подпись) -->
|
||||
<RowDefinition Height="Auto" /> <!-- Id (поле ) | Uuid (поле ) -->
|
||||
<RowDefinition Height="Auto" /> <!-- BrandName (подпись) -->
|
||||
<RowDefinition Height="Auto" /> <!-- BrandName (поле ) -->
|
||||
<RowDefinition Height="Auto" /> <!-- MixtureName (подпись) | MixtureNormalRate (подпись) -->
|
||||
<RowDefinition Height="Auto" /> <!-- MixtureName (поле ) | MixtureNormalRate (поле ) -->
|
||||
<RowDefinition Height="*" /> <!-- Пустое пространство -->
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Id (подпись) -->
|
||||
<TextBlock
|
||||
Classes="mini"
|
||||
Grid.Column="0"
|
||||
Grid.Row="0"
|
||||
Text="ИД:"
|
||||
/>
|
||||
<!-- Id (поле) -->
|
||||
<TextBox
|
||||
Classes="singleline"
|
||||
Grid.Column="0"
|
||||
Grid.Row="1"
|
||||
IsReadOnly="True"
|
||||
PlaceholderText="ИД"
|
||||
Text="{Binding SelectedId}"
|
||||
/>
|
||||
<!-- Uuid (подпись) -->
|
||||
<TextBlock
|
||||
Classes="mini"
|
||||
Grid.Column="1"
|
||||
Grid.Row="0"
|
||||
Text="Уникальный ИД:"
|
||||
/>
|
||||
<!-- Uuid (поле) -->
|
||||
<TextBox
|
||||
Classes="singleline"
|
||||
Grid.Column="1"
|
||||
Grid.Row="1"
|
||||
IsReadOnly="True"
|
||||
PlaceholderText="Уникальный ИД"
|
||||
Text="{Binding SelectedUuid}"
|
||||
/>
|
||||
<!-- BrandName (подпись) -->
|
||||
<TextBlock
|
||||
Classes="mini"
|
||||
Grid.Column="0"
|
||||
Grid.Row="2"
|
||||
Text="Марка продукта:"
|
||||
/>
|
||||
<!-- BrandName (поле) -->
|
||||
<controls:TouchTextBox
|
||||
Classes="singleline"
|
||||
Grid.Column="0"
|
||||
Grid.Row="3"
|
||||
PlaceholderText="Марка продукта"
|
||||
Text="{Binding SelectedBrandName}"
|
||||
/>
|
||||
<!-- MixtureName (подпись) -->
|
||||
<TextBlock
|
||||
Classes="mini"
|
||||
Grid.Column="1"
|
||||
Grid.Row="2"
|
||||
Text="Марка смеси:"
|
||||
/>
|
||||
<!-- MixtureName (поле) -->
|
||||
<controls:TouchTextBox
|
||||
Classes="singleline"
|
||||
Grid.Column="1"
|
||||
Grid.Row="3"
|
||||
PlaceholderText="Марка смеси"
|
||||
Text="{Binding SelectedMixtureName}"
|
||||
/>
|
||||
<!-- MixtureNormalRate (подпись) -->
|
||||
<TextBlock
|
||||
Classes="mini" Text="Норма расхода смеси (кг/т | гр/кг):"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Grid.Row="4"
|
||||
/>
|
||||
<!-- MixtureNormalRate (поле) -->
|
||||
<controls:TouchNumericUpDown
|
||||
Classes="singleline"
|
||||
FormatString="0.0000"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Grid.Row="5"
|
||||
Increment="0.0001"
|
||||
PlaceholderText="Норма расхода смеси (кг/т | гр/кг)"
|
||||
Value="{Binding SelectedMixtureNormalRate}"
|
||||
/>
|
||||
<Grid.RowSpacing>5</Grid.RowSpacing>
|
||||
|
||||
</Grid>
|
||||
<!-- Id (подпись) -->
|
||||
<TextBlock
|
||||
Classes="mini"
|
||||
Grid.Column="0"
|
||||
Grid.Row="0"
|
||||
Text="ИД:"
|
||||
/>
|
||||
<!-- Id (поле) -->
|
||||
<TextBox
|
||||
Classes="singleline"
|
||||
Grid.Column="0"
|
||||
Grid.Row="1"
|
||||
IsReadOnly="True"
|
||||
PlaceholderText="ИД"
|
||||
Text="{Binding Record.Id}"
|
||||
/>
|
||||
<!-- Uuid (подпись) -->
|
||||
<TextBlock
|
||||
Classes="mini"
|
||||
Grid.Column="1"
|
||||
Grid.Row="0"
|
||||
Text="Уникальный ИД:"
|
||||
/>
|
||||
<!-- Uuid (поле) -->
|
||||
<TextBox
|
||||
Classes="singleline"
|
||||
Grid.Column="1"
|
||||
Grid.Row="1"
|
||||
IsReadOnly="True"
|
||||
PlaceholderText="Уникальный ИД"
|
||||
Text="{Binding Record.Uuid}"
|
||||
/>
|
||||
<!-- BrandName (подпись) -->
|
||||
<TextBlock
|
||||
Classes="mini"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Grid.Row="2"
|
||||
Text="Марка продукта:"
|
||||
/>
|
||||
<!-- BrandName (поле) -->
|
||||
<controls:TouchTextBox
|
||||
Classes="singleline"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Grid.Row="3"
|
||||
PlaceholderText="Марка продукта"
|
||||
Text="{Binding Record.BrandName}"
|
||||
/>
|
||||
<!-- MixtureName (подпись) -->
|
||||
<TextBlock
|
||||
Classes="mini"
|
||||
Grid.Column="0"
|
||||
Grid.Row="4"
|
||||
Text="Марка смеси:"
|
||||
/>
|
||||
<!-- MixtureName (поле) -->
|
||||
<controls:TouchTextBox
|
||||
Classes="singleline"
|
||||
Grid.Column="0"
|
||||
Grid.Row="5"
|
||||
PlaceholderText="Марка смеси"
|
||||
Text="{Binding Record.MixtureName}"
|
||||
/>
|
||||
<!-- MixtureNormalRate (подпись) -->
|
||||
<TextBlock
|
||||
Classes="mini" Text="Норма расхода смеси (кг/т | гр/кг):"
|
||||
Grid.Column="1"
|
||||
Grid.Row="4"
|
||||
/>
|
||||
<!-- MixtureNormalRate (поле) -->
|
||||
<controls:TouchNumericUpDown
|
||||
Classes="singleline"
|
||||
FormatString="0.0000"
|
||||
Grid.Column="1"
|
||||
Grid.Row="5"
|
||||
Increment="0.0001"
|
||||
PlaceholderText="Норма расхода смеси (кг/т | гр/кг)"
|
||||
Value="{Binding Record.MixtureNormalRate}"
|
||||
/>
|
||||
|
||||
</Grid>
|
||||
|
||||
<!-- Comment -->
|
||||
<Grid>
|
||||
|
||||
<Grid.RowSpacing>5</Grid.RowSpacing>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" /> <!-- Comment (подпись) -->
|
||||
<RowDefinition Height="*" /> <!-- Comment (поле ) -->
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Comment (подпись) -->
|
||||
<TextBlock
|
||||
Classes="mini"
|
||||
Grid.Row="0"
|
||||
Text="Дополнительная информация:"
|
||||
/>
|
||||
<!-- Comment (поле) -->
|
||||
<controls:TouchTextBox
|
||||
Classes="multiline"
|
||||
Grid.Row="1"
|
||||
PlaceholderText="Дополнительная информация"
|
||||
Text="{Binding Record.Comment}"
|
||||
/>
|
||||
|
||||
</Grid>
|
||||
|
||||
</core:SectionPanel>
|
||||
|
||||
<!-- Кнопка влево -->
|
||||
<Button
|
||||
Command="{Binding InfoSectionPreviousCommand}"
|
||||
Grid.Column="1"
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
>
|
||||
|
||||
<Button.IsEnabled>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="InfoSectionCanScrollBackward" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="InfoSectionIsScrolling" />
|
||||
</MultiBinding>
|
||||
</Button.IsEnabled>
|
||||
|
||||
<TextBlock Text="◀"/>
|
||||
|
||||
</Button>
|
||||
|
||||
<!-- Кнопка вправо -->
|
||||
<Button
|
||||
Command="{Binding InfoSectionNextCommand}"
|
||||
Grid.Column="2"
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
>
|
||||
|
||||
<Button.IsEnabled>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="InfoSectionCanScrollForward" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="InfoSectionIsScrolling" />
|
||||
</MultiBinding>
|
||||
</Button.IsEnabled>
|
||||
|
||||
<TextBlock Text="▶"/>
|
||||
|
||||
</Button>
|
||||
|
||||
</Grid>
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<Grid Background="{StaticResource MainBackground}">
|
||||
|
||||
<!-- Основная форма -->
|
||||
<Grid IsEnabled="{Binding !MenuOpened}">
|
||||
<Grid IsEnabled="{Binding CanInteract}">
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" /> <!-- Кнопка "Удалить" -->
|
||||
@@ -51,8 +51,8 @@
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" /> <!-- Кнопка меню -->
|
||||
<ColumnDefinition Width="*" /> <!-- Информация -->
|
||||
<ColumnDefinition Width="*" /> <!-- Информация -->
|
||||
<ColumnDefinition Width="*" /> <!-- Информация -->
|
||||
<ColumnDefinition Width="*" /> <!-- Информация -->
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.ColumnSpacing>5</Grid.ColumnSpacing>
|
||||
@@ -101,12 +101,12 @@
|
||||
<Grid.ColumnSpacing>5</Grid.ColumnSpacing>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" /> <!-- Id (подпись) | Uuid (подпись) -->
|
||||
<RowDefinition Height="Auto" /> <!-- Id (поле ) | Uuid (поле ) -->
|
||||
<RowDefinition Height="Auto" /> <!-- Id (подпись) | Uuid (подпись) -->
|
||||
<RowDefinition Height="Auto" /> <!-- Id (поле ) | Uuid (поле ) -->
|
||||
<RowDefinition Height="Auto" /> <!-- SampleName (подпись) | BrandComboBox (подпись) -->
|
||||
<RowDefinition Height="Auto" /> <!-- SampleName (поле ) | BrandComboBox (поле ) -->
|
||||
<RowDefinition Height="Auto" /> <!-- SamplingDateTime (подпись) | SamplingPlace (подпись) -->
|
||||
<RowDefinition Height="Auto" /> <!-- SamplingDateTime (поле ) | SamplingPlace (поле ) -->
|
||||
<RowDefinition Height="Auto" /> <!-- SamplingDateTime (подпись) | SamplingPlace (подпись) -->
|
||||
<RowDefinition Height="Auto" /> <!-- SamplingDateTime (поле ) | SamplingPlace (поле ) -->
|
||||
<RowDefinition Height="*" /> <!-- Пустое пространство -->
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
@@ -126,7 +126,7 @@
|
||||
Grid.Row="1"
|
||||
IsReadOnly="True"
|
||||
PlaceholderText="ИД"
|
||||
Text="{Binding SelectedId}"
|
||||
Text="{Binding Record.Id}"
|
||||
/>
|
||||
<!-- Uuid (подпись) -->
|
||||
<TextBlock
|
||||
@@ -142,7 +142,7 @@
|
||||
Grid.Row="1"
|
||||
IsReadOnly="True"
|
||||
PlaceholderText="Уникальный ИД"
|
||||
Text="{Binding SelectedUuid}"
|
||||
Text="{Binding Record.Uuid}"
|
||||
/>
|
||||
<!-- SampleName (подпись) -->
|
||||
<TextBlock
|
||||
@@ -156,7 +156,7 @@
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Classes="singleline"
|
||||
Text="{Binding SelectedSampleName}"
|
||||
Text="{Binding Record.SampleName}"
|
||||
PlaceholderText="Название пробы"
|
||||
/>
|
||||
<!-- BrandComboBox (подпись) -->
|
||||
@@ -180,7 +180,7 @@
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:SampleCalibrationViewModel+BrandComboBoxItemViewModel">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
>
|
||||
<Run Text="{Binding Id, StringFormat='[{0}] '}" />
|
||||
@@ -204,7 +204,7 @@
|
||||
Grid.Column="0"
|
||||
Grid.Row="5"
|
||||
PlaceholderText="Дата и время отбора пробы"
|
||||
Text="{Binding SelectedSamplingDateTime, Converter={StaticResource DateTimeConverter}}"
|
||||
Text="{Binding Record.SamplingDateTime, Converter={StaticResource DateTimeConverter}}"
|
||||
/>
|
||||
<!-- SamplingPlace (подпись) -->
|
||||
<TextBlock
|
||||
@@ -219,7 +219,7 @@
|
||||
Grid.Column="1"
|
||||
Grid.Row="5"
|
||||
PlaceholderText="Место отбора пробы"
|
||||
Text="{Binding SelectedSamplingPlace}"
|
||||
Text="{Binding Record.SamplingPlace}"
|
||||
/>
|
||||
|
||||
</Grid>
|
||||
@@ -258,7 +258,7 @@
|
||||
Grid.Row="1"
|
||||
Increment="0.0001"
|
||||
PlaceholderText="Фактический расход смеси (кг/т | гр/кг)"
|
||||
Value="{Binding SelectedMixtureActualRate}"
|
||||
Value="{Binding Record.MixtureActualRate}"
|
||||
/>
|
||||
<!-- MeasuredContent (подпись) -->
|
||||
<TextBlock
|
||||
@@ -274,7 +274,7 @@
|
||||
Grid.Row="1"
|
||||
Increment="0.0001"
|
||||
PlaceholderText="Измеренное содержание смеси (кг/т | гр/кг)"
|
||||
Value="{Binding SelectedMeasuredContent}"
|
||||
Value="{Binding Record.MeasuredContent}"
|
||||
/>
|
||||
<!-- SampleComment (подпись) -->
|
||||
<TextBlock
|
||||
@@ -290,7 +290,7 @@
|
||||
Grid.ColumnSpan="2"
|
||||
Grid.Row="3"
|
||||
PlaceholderText="Дополнительная информация о пробе"
|
||||
Text="{Binding SelectedSampleComment}"
|
||||
Text="{Binding Record.SampleComment}"
|
||||
/>
|
||||
|
||||
</Grid>
|
||||
@@ -363,15 +363,14 @@
|
||||
<core:ZoomPanImage
|
||||
Background="Black"
|
||||
HorizontalAlignment="Stretch"
|
||||
ImageSource="{Binding SelectedImage.Image}"
|
||||
IsVisible="{Binding SelectedImage.IsLoading, Converter={x:Static BoolConverters.Not}}"
|
||||
ImageSource="{Binding Record.Image.Image}"
|
||||
VerticalAlignment="Stretch"
|
||||
>
|
||||
|
||||
<core:ZoomPanImage.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static ObjectConverters.IsNotNull}" Path="SelectedImage.Data" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="SelectedImage.IsLoading" />
|
||||
<Binding Converter="{x:Static StringConverters.IsNotNullOrEmpty}" Path="Record.Image.Data" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="Record.Image.IsLoading" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</core:ZoomPanImage.IsVisible>
|
||||
@@ -379,11 +378,11 @@
|
||||
</core:ZoomPanImage>
|
||||
|
||||
<!-- Оверлей "Изображение загружается" -->
|
||||
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Center" >
|
||||
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Center" >
|
||||
|
||||
<StackPanel.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="SelectedImage.IsLoading" />
|
||||
<Binding Path="Record.Image.IsLoading" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</StackPanel.IsVisible>
|
||||
@@ -402,8 +401,8 @@
|
||||
>
|
||||
<TextBlock.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static ObjectConverters.IsNull}" Path="SelectedImage.Data" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="SelectedImage.IsLoading" />
|
||||
<Binding Converter="{x:Static StringConverters.IsNullOrEmpty}" Path="Record.Image.Data" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="Record.Image.IsLoading" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</TextBlock.IsVisible>
|
||||
@@ -417,8 +416,8 @@
|
||||
|
||||
<StackPanel.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static ObjectConverters.IsNull}" Path="SelectedImage.Data" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="SelectedImage.IsLoading" />
|
||||
<Binding Converter="{x:Static StringConverters.IsNullOrEmpty}" Path="Record.Image.Data" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="Record.Image.IsLoading" />
|
||||
<Binding Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</StackPanel.IsVisible>
|
||||
@@ -433,18 +432,20 @@
|
||||
|
||||
<!-- Кнопка "Получить изображения" -->
|
||||
<Button
|
||||
Command="{Binding CaptureImageCommand}"
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{Binding CaptureImageCommand}"
|
||||
IsVisible="{Binding !ImagesIsCapturing}"
|
||||
>
|
||||
<TextBlock Text="Получить изображения"/>
|
||||
</Button>
|
||||
|
||||
<!-- Кнопка "Отменить съёмку" -->
|
||||
<Button
|
||||
Command="{Binding CancelImageCapturingCommand}"
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{Binding CancelImageCapturingCommand}"
|
||||
IsVisible="{Binding ImagesIsCapturing}"
|
||||
>
|
||||
<TextBlock Text="Отменить съёмку"/>
|
||||
</Button>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<Grid Background="{StaticResource MainBackground}">
|
||||
|
||||
<!-- Основная форма -->
|
||||
<Grid IsEnabled="{Binding !MenuOpened}">
|
||||
<Grid IsEnabled="{Binding CanInteract}">
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" /> <!-- Кнопка "Удалить" -->
|
||||
@@ -80,7 +80,8 @@
|
||||
<RowDefinition Height="Auto" /> <!-- Id (поле ) | Uuid (поле ) -->
|
||||
<RowDefinition Height="Auto" /> <!-- Type (подпись) -->
|
||||
<RowDefinition Height="Auto" /> <!-- Type (поле ) -->
|
||||
<RowDefinition Height="*" /> <!-- Пустое пространство -->
|
||||
<RowDefinition Height="Auto" /> <!-- Comment (подпись) -->
|
||||
<RowDefinition Height="*" /> <!-- Comment (поле ) -->
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.RowSpacing>5</Grid.RowSpacing>
|
||||
@@ -99,7 +100,7 @@
|
||||
Grid.Row="1"
|
||||
IsReadOnly="True"
|
||||
PlaceholderText="ИД"
|
||||
Text="{Binding SelectedId}"
|
||||
Text="{Binding Record.Id}"
|
||||
/>
|
||||
<!-- Uuid (подпись) -->
|
||||
<TextBlock
|
||||
@@ -115,7 +116,7 @@
|
||||
Grid.Row="1"
|
||||
IsReadOnly="True"
|
||||
PlaceholderText="Уникальный ИД"
|
||||
Text="{Binding SelectedUuid}"
|
||||
Text="{Binding Record.Uuid}"
|
||||
/>
|
||||
<!-- Type (подпись) -->
|
||||
<TextBlock
|
||||
@@ -131,25 +132,27 @@
|
||||
Grid.ColumnSpan="2"
|
||||
Grid.Row="3"
|
||||
PlaceholderText="Тип"
|
||||
Text="{Binding SelectedType}"
|
||||
Text="{Binding Record.Type}"
|
||||
/>
|
||||
|
||||
</Grid>
|
||||
<!-- Comment (подпись) -->
|
||||
<TextBlock
|
||||
Classes="mini"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Grid.Row="4"
|
||||
Text="Дополнительная информация:"
|
||||
/>
|
||||
<!-- Comment (поле) -->
|
||||
<controls:TouchTextBox
|
||||
Classes="multiline"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Grid.Row="5"
|
||||
PlaceholderText="Дополнительная информация"
|
||||
Text="{Binding Record.Comment}"
|
||||
/>
|
||||
|
||||
<!-- Информация -->
|
||||
<Grid Grid.Column="1">
|
||||
<!-- ИД (подпись) -->
|
||||
<!-- <TextBlock Grid.Row="0" Grid.Column="1" Classes="mini" Text="ИД:" /> -->
|
||||
<!-- ИД (поле) -->
|
||||
<!-- <TextBox Grid.Row="1" Grid.Column="1" Classes="singleline" Text="{Binding EditingRecord.Id}" PlaceholderText="ИД" IsReadOnly="True" /> -->
|
||||
<!-- Тип (подпись) -->
|
||||
<!-- <TextBlock Grid.Row="2" Grid.Column="1" Classes="mini" Text="Тип:" /> -->
|
||||
<!-- Тип (поле) -->
|
||||
<!-- <controls:TouchTextBox Grid.Row="3" Grid.Column="1" Classes="singleline" Text="{Binding EditingRecord.Type}" PlaceholderText="Тип" /> -->
|
||||
<!-- Партия (подпись) -->
|
||||
<!-- <TextBlock Grid.Row="4" Grid.Column="1" Classes="mini" Text="Партия:" /> -->
|
||||
<!-- Партия (поле) -->
|
||||
<!-- <controls:TouchTextBox Grid.Row="5" Grid.Column="1" Classes="singleline" Text="{Binding EditingRecord.Batch}" PlaceholderText="Партия" /> -->
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
@@ -166,25 +169,19 @@
|
||||
|
||||
<!-- Изображение -->
|
||||
<Grid>
|
||||
|
||||
<!-- Изображение -->
|
||||
<Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
<!-- Изображение -->
|
||||
<core:ZoomPanImage
|
||||
Background="Black"
|
||||
HorizontalAlignment="Stretch"
|
||||
ImageSource="{Binding SelectedImage.Image}"
|
||||
IsVisible="{Binding SelectedImage.IsLoading, Converter={x:Static BoolConverters.Not}}"
|
||||
ImageSource="{Binding Record.Image.Image}"
|
||||
VerticalAlignment="Stretch"
|
||||
>
|
||||
|
||||
<core:ZoomPanImage.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static ObjectConverters.IsNotNull}" Path="SelectedImage.Data" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="SelectedImage.IsLoading" />
|
||||
<Binding Converter="{x:Static StringConverters.IsNotNullOrEmpty}" Path="Record.Image.Data" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="Record.Image.IsLoading" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</core:ZoomPanImage.IsVisible>
|
||||
@@ -196,7 +193,7 @@
|
||||
|
||||
<StackPanel.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="SelectedImage.IsLoading" />
|
||||
<Binding Path="Record.Image.IsLoading" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</StackPanel.IsVisible>
|
||||
@@ -215,8 +212,8 @@
|
||||
>
|
||||
<TextBlock.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static ObjectConverters.IsNull}" Path="SelectedImage.Data" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="SelectedImage.IsLoading" />
|
||||
<Binding Converter="{x:Static StringConverters.IsNullOrEmpty}" Path="Record.Image.Data" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="Record.Image.IsLoading" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</TextBlock.IsVisible>
|
||||
@@ -230,8 +227,8 @@
|
||||
|
||||
<StackPanel.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static ObjectConverters.IsNull}" Path="SelectedImage.Data" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="SelectedImage.IsLoading" />
|
||||
<Binding Converter="{x:Static StringConverters.IsNullOrEmpty}" Path="Record.Image.Data" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="Record.Image.IsLoading" />
|
||||
<Binding Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</StackPanel.IsVisible>
|
||||
@@ -246,18 +243,20 @@
|
||||
|
||||
<!-- Кнопка "Получить изображения" -->
|
||||
<Button
|
||||
Command="{Binding CaptureImageCommand}"
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{Binding CaptureImageCommand}"
|
||||
IsVisible="{Binding !ImagesIsCapturing}"
|
||||
>
|
||||
<TextBlock Text="Получить изображения"/>
|
||||
</Button>
|
||||
|
||||
<!-- Кнопка "Отменить съёмку" -->
|
||||
<Button
|
||||
Command="{Binding CancelImageCapturingCommand}"
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{Binding CancelImageCapturingCommand}"
|
||||
IsVisible="{Binding ImagesIsCapturing}"
|
||||
>
|
||||
<TextBlock Text="Отменить съёмку"/>
|
||||
</Button>
|
||||
|
||||
@@ -36,31 +36,30 @@
|
||||
|
||||
<Border Background="{StaticResource MainBackground}" BorderThickness="0">
|
||||
|
||||
<Grid Margin="5" RowSpacing="5">
|
||||
<Grid Margin="5">
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/> <!-- Меню -->
|
||||
<RowDefinition Height="*"/> <!-- Основной контент -->
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Меню -->
|
||||
<ScrollViewer Grid.Row="0" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Disabled">
|
||||
<Grid.RowSpacing>5</Grid.RowSpacing>
|
||||
|
||||
<ScrollViewer.IsEnabled>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="IsScrolling" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="SeparatorCalibration.MenuOpened" />
|
||||
</MultiBinding>
|
||||
</ScrollViewer.IsEnabled>
|
||||
<!-- Меню -->
|
||||
<ScrollViewer
|
||||
Grid.Row="0"
|
||||
HorizontalScrollBarVisibility="Hidden"
|
||||
IsEnabled="{Binding CanInteract}"
|
||||
VerticalScrollBarVisibility="Disabled"
|
||||
>
|
||||
|
||||
<Grid ColumnSpacing="5">
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" MinWidth="200"/> <!-- Анализ -->
|
||||
<ColumnDefinition Width="*" MinWidth="200"/> <!-- Калибровка проб -->
|
||||
<ColumnDefinition Width="*" MinWidth="200"/> <!-- Калибровка сепаратора -->
|
||||
<!-- <ColumnDefinition Width="*" MinWidth="200"/> --> <!-- Камера -->
|
||||
<ColumnDefinition Width="*" MinWidth="200"/> <!-- Система -->
|
||||
<ColumnDefinition Width="*" MinWidth="200"/> <!-- Анализ -->
|
||||
<ColumnDefinition Width="*" MinWidth="200"/> <!-- Марки -->
|
||||
<ColumnDefinition Width="*" MinWidth="200"/> <!-- Сепараторы -->
|
||||
<ColumnDefinition Width="*" MinWidth="200"/> <!-- Пробы -->
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Анализ -->
|
||||
@@ -68,24 +67,19 @@
|
||||
<TextBlock Classes="menu-text" Text="Анализ"/>
|
||||
</Button>
|
||||
|
||||
<!-- Калибровка проб -->
|
||||
<!-- Марки -->
|
||||
<Button Grid.Column="1" Classes="menu" Command="{Binding ChangeSectionCommand}" CommandParameter="1">
|
||||
<TextBlock Classes="menu-text" Text="Калибровка проб"/>
|
||||
<TextBlock Classes="menu-text" Text="Марки"/>
|
||||
</Button>
|
||||
|
||||
<!-- Калибровка сепараторов -->
|
||||
|
||||
<!-- Сепараторы -->
|
||||
<Button Grid.Column="2" Classes="menu" Command="{Binding ChangeSectionCommand}" CommandParameter="2">
|
||||
<TextBlock Classes="menu-text" Text="Калибровка сепараторов"/>
|
||||
<TextBlock Classes="menu-text" Text="Сепараторы"/>
|
||||
</Button>
|
||||
|
||||
<!-- Камера -->
|
||||
<!-- <Button Grid.Column="3" Classes="menu" Command="{Binding ChangeSectionCommand}" CommandParameter="3">
|
||||
<TextBlock Classes="menu-text" Text="Камера"/>
|
||||
</Button> -->
|
||||
|
||||
<!-- Система -->
|
||||
<Button Grid.Column="3" Classes="menu" Command="{Binding ChangeSectionCommand}" CommandParameter="4">
|
||||
<TextBlock Classes="menu-text" Text="Система"/>
|
||||
|
||||
<!-- Пробы -->
|
||||
<Button Grid.Column="3" Classes="menu" Command="{Binding ChangeSectionCommand}" CommandParameter="3">
|
||||
<TextBlock Classes="menu-text" Text="Пробы"/>
|
||||
</Button>
|
||||
|
||||
</Grid>
|
||||
@@ -93,14 +87,16 @@
|
||||
|
||||
<!-- Основной контент -->
|
||||
<core:SectionPanel Grid.Row="1" ClipToBounds="True" CurrentIndex="{Binding CurrentIndex}" Orientation="Horizontal" IsScrolling="{Binding IsScrolling}">
|
||||
<ContentControl Content="{Binding Analysis}"/> <!-- Анализ -->
|
||||
<ContentControl Content="{Binding SampleCalibration}"/> <!-- Калибровка проб -->
|
||||
<ContentControl Content="{Binding SeparatorCalibration}"/> <!-- Калибровка сепаратора -->
|
||||
<!-- <ContentControl Content="{Binding Camera}"/> --> <!-- Камера -->
|
||||
<ContentControl Content="{Binding System}"/> <!-- Система -->
|
||||
<ContentControl Content="{Binding Analysis}"/> <!-- Анализ -->
|
||||
<ContentControl Content="{Binding BrandCalibration}"/> <!-- Марки -->
|
||||
<ContentControl Content="{Binding SeparatorCalibration}"/> <!-- Сепараторы -->
|
||||
<ContentControl Content="{Binding SampleCalibration}"/> <!-- Пробы -->
|
||||
<!-- <ContentControl Content="{Binding Camera}"/> --> <!-- Камера -->
|
||||
<!-- <ContentControl Content="{Binding System}"/> --> <!-- Система -->
|
||||
</core:SectionPanel>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Border>
|
||||
|
||||
</UserControl>
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
<UserControl
|
||||
x:Class="GSS2.Views.Common.CameraView"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:GSS2.ViewModels.Common"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
x:DataType="vm:CameraViewModel">
|
||||
|
||||
<UserControl.Resources>
|
||||
<SolidColorBrush x:Key="MainBackground" Color="#001e46" />
|
||||
<SolidColorBrush x:Key="ButtonBackground" Color="#0865b2" />
|
||||
<SolidColorBrush x:Key="ButtonForeground" Color="#ffffff" />
|
||||
<x:Double x:Key="ButtonFontSize">20</x:Double>
|
||||
</UserControl.Resources>
|
||||
|
||||
<UserControl.Styles>
|
||||
|
||||
<Style Selector="Button">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalAlignment" Value="Stretch"/>
|
||||
<Setter Property="Background" Value="{StaticResource ButtonBackground}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource ButtonForeground}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBlock">
|
||||
<Setter Property="TextWrapping" Value="Wrap"/>
|
||||
<Setter Property="TextAlignment" Value="Center"/>
|
||||
<Setter Property="FontSize" Value="{StaticResource ButtonFontSize}"/>
|
||||
</Style>
|
||||
|
||||
</UserControl.Styles>
|
||||
|
||||
<Border BorderThickness="0">
|
||||
<Grid>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/> <!-- Камера -->
|
||||
<ColumnDefinition Width="Auto"/> <!-- Осветитель -->
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ContentControl Grid.Column="0" Content="{Binding Camera}"/> <!-- Камера -->
|
||||
|
||||
<ContentControl Grid.Column="1" Content="{Binding Illuminator}"/> <!-- Осветитель -->
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
</UserControl>
|
||||
@@ -1,11 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace GSS2.Views.Common;
|
||||
|
||||
public partial class CameraView : UserControl
|
||||
{
|
||||
public CameraView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
Executable → Regular
Executable → Regular
Executable → Regular
Executable → Regular
@@ -20,8 +20,7 @@
|
||||
}
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"AmineContentCalibrationDatabasePath": "AmineContentCalibration.db",
|
||||
"AmineContentResultsDatabasePath": "AmineContentResults.db"
|
||||
"AmineContentDatabasePath": "AmineContent.db"
|
||||
},
|
||||
"Hardware": {
|
||||
"Illuminator": {
|
||||
|
||||
Reference in New Issue
Block a user