forked from amkovkov/GranuSightSoftware2
feat: MainView
This commit is contained in:
@@ -0,0 +1,12 @@
|
|||||||
|
<Application
|
||||||
|
x:Class="GSS2.Application"
|
||||||
|
xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="using:GSS2"
|
||||||
|
RequestedThemeVariant="Dark">
|
||||||
|
|
||||||
|
<Application.Styles>
|
||||||
|
<FluentTheme />
|
||||||
|
</Application.Styles>
|
||||||
|
|
||||||
|
</Application>
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
|
||||||
|
using GSS2.ViewModels;
|
||||||
|
using GSS2.Views;
|
||||||
|
using GSS2.UI.Core;
|
||||||
|
using GSS2.UI.Core.Services;
|
||||||
|
using GSS2.UI.Core.ViewModels;
|
||||||
|
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace GSS2;
|
||||||
|
|
||||||
|
public partial class Application : Avalonia.Application
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _services;
|
||||||
|
private readonly bool _fullscreen;
|
||||||
|
private readonly ILogger<Application> _logger;
|
||||||
|
private readonly DependencyInjectionViewLocator _viewLocator;
|
||||||
|
|
||||||
|
private bool _allShutdownSequencesRunning;
|
||||||
|
|
||||||
|
public Application(IServiceProvider services, bool fullscreen = false)
|
||||||
|
{
|
||||||
|
_services = services;
|
||||||
|
_fullscreen = fullscreen;
|
||||||
|
|
||||||
|
// Создаём AvaloniaLogSink, который перенаправляет логи из Avalonia в хост
|
||||||
|
var loggerFactory = _services.GetRequiredService<ILoggerFactory>();
|
||||||
|
Avalonia.Logging.Logger.Sink = new AvaloniaLogSink(loggerFactory);
|
||||||
|
|
||||||
|
// Создаём DependencyInjectionViewLocator
|
||||||
|
_logger = _services.GetRequiredService<ILogger<Application>>();
|
||||||
|
_viewLocator = _services.GetRequiredService<DependencyInjectionViewLocator>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
|
||||||
|
// Добавляем наш DependencyInjectionViewLocator в DataTemplates приложения
|
||||||
|
DataTemplates.Add(_viewLocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnFrameworkInitializationCompleted()
|
||||||
|
{
|
||||||
|
// Получаем lifetime хоста
|
||||||
|
var hostLifetime = _services.GetRequiredService<IHostApplicationLifetime>();
|
||||||
|
|
||||||
|
// Регистрируем обработчик начатия Ctrl+C
|
||||||
|
Console.CancelKeyPress += (_, _) =>
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Закрытие приложения вызвано из консоли");
|
||||||
|
hostLifetime.StopApplication();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Создаём обработчик события закрытия хоста
|
||||||
|
hostLifetime.ApplicationStopped.Register(() =>
|
||||||
|
{
|
||||||
|
// Если всё что нужно для закрытия приложения уже вызвано, то ничего не делаем
|
||||||
|
if (_allShutdownSequencesRunning)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Иначе это событие вызвано не из интерфейса и графический интерфейс нужно закрыть
|
||||||
|
Dispatcher.InvokeShutdown();
|
||||||
|
_allShutdownSequencesRunning = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Создаём обработчик события закрытия графического интерфейса
|
||||||
|
Dispatcher.ShutdownStarted += (_, _) =>
|
||||||
|
{
|
||||||
|
// Если всё что нужно для закрытия приложения уже вызвано, то ничего не делаем
|
||||||
|
if (_allShutdownSequencesRunning)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Иначе это событие вызвано из интерфейса и хост нужно закрыть
|
||||||
|
_logger.LogInformation("Закрытие приложения вызвано из интерфейса");
|
||||||
|
hostLifetime.StopApplication();
|
||||||
|
_allShutdownSequencesRunning = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ApplicationLifetime == null, то невозможно определить в какой среде мы запускаемся,
|
||||||
|
// но такого быть не должно
|
||||||
|
if (ApplicationLifetime is null)
|
||||||
|
{
|
||||||
|
_logger.LogCritical("Приложение не может быть полностью инициализировано, ApplicationLifetime == null");
|
||||||
|
|
||||||
|
// Я не уверен, будет ли правильно в данном классе вместо выбрасывания исключений,
|
||||||
|
// использовать Environment.Exit,
|
||||||
|
// но если использовать исключения, то их может поймать какой-нибудь обработчик,
|
||||||
|
// а Environment.Exit гарантированно завершит текущий процесс
|
||||||
|
hostLifetime.StopApplication();
|
||||||
|
Environment.Exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создаём сервис навигации между элементами управления
|
||||||
|
var navigationService = _services.GetRequiredService<NavigationService>();
|
||||||
|
|
||||||
|
// Создаём начальный элемент управления и переходим к нему
|
||||||
|
var mainView = new MainView()
|
||||||
|
{
|
||||||
|
DataContext = _services.GetRequiredService<MainViewModel>()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Создаём главный элемент управления
|
||||||
|
// на основании типа ApplicationLifetime, который зависит от того как мы запустили приложение
|
||||||
|
// StartWithClassicDesktopLifetime IClassicDesktopStyleApplicationLifetime
|
||||||
|
// StartLinuxDrm ISingleViewApplicationLifetime
|
||||||
|
switch (ApplicationLifetime)
|
||||||
|
{
|
||||||
|
case IClassicDesktopStyleApplicationLifetime desktop:
|
||||||
|
_logger.LogInformation("Приложение запускается в классическом режиме");
|
||||||
|
_logger.LogInformation("Приложение запускается в {} режиме", _fullscreen ? "полноэкранном" : "оконном");
|
||||||
|
|
||||||
|
// Создаём главное окно
|
||||||
|
var mainWindow = new MainWindow()
|
||||||
|
{
|
||||||
|
DataContext = new MainWindowViewModel()
|
||||||
|
{
|
||||||
|
WindowDecorations = _fullscreen ? Avalonia.Controls.WindowDecorations.None : Avalonia.Controls.WindowDecorations.Full,
|
||||||
|
Topmost = _fullscreen,
|
||||||
|
WindowState = _fullscreen ? Avalonia.Controls.WindowState.FullScreen : Avalonia.Controls.WindowState.Normal
|
||||||
|
},
|
||||||
|
};
|
||||||
|
desktop.MainWindow = mainWindow;
|
||||||
|
|
||||||
|
// Переключаемся на начальный элемент управления
|
||||||
|
navigationService.NavigateTo(mainView);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ISingleViewApplicationLifetime singleView:
|
||||||
|
_logger.LogInformation("Приложение запускается в режиме отображение единой формы");
|
||||||
|
|
||||||
|
// Просто устанавливаем главным элементом управления начальный элемент
|
||||||
|
singleView.MainView = mainView;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
_logger.LogCritical("Приложение не может быть полностью инициализировано, ApplicationLifetime имеет неизвестный тип, наследованный от: {}", string.Join(", ", ApplicationLifetime.GetType().GetInterfaces().Select(i => i.FullName)));
|
||||||
|
hostLifetime.StopApplication();
|
||||||
|
Environment.Exit(1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
base.OnFrameworkInitializationCompleted();
|
||||||
|
}
|
||||||
|
}
|
||||||
+128
-2
@@ -1,5 +1,17 @@
|
|||||||
using System.CommandLine;
|
using System.CommandLine;
|
||||||
|
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.OpenGL.Egl;
|
||||||
|
|
||||||
|
using GSS2.Core.Logging;
|
||||||
|
using GSS2.Core;
|
||||||
|
using GSS2.Core.Analysis.AmineContent.Database.Calibration;
|
||||||
|
using GSS2.Core.Analysis.AmineContent.Database.Results;
|
||||||
|
|
||||||
namespace GSS2.Commands;
|
namespace GSS2.Commands;
|
||||||
|
|
||||||
public static class UI
|
public static class UI
|
||||||
@@ -31,7 +43,6 @@ public static class UI
|
|||||||
RenderingMode = new("--mode", ["--rendering-mode", "-m"])
|
RenderingMode = new("--mode", ["--rendering-mode", "-m"])
|
||||||
{
|
{
|
||||||
Description = "Режим рендеринга",
|
Description = "Режим рендеринга",
|
||||||
|
|
||||||
DefaultValueFactory = (_) => RenderingModes.X11
|
DefaultValueFactory = (_) => RenderingModes.X11
|
||||||
};
|
};
|
||||||
HideConsole = new("--hide-console", ["--no-console", "-c"])
|
HideConsole = new("--hide-console", ["--no-console", "-c"])
|
||||||
@@ -53,7 +64,6 @@ public static class UI
|
|||||||
Command.Add(RenderingMode);
|
Command.Add(RenderingMode);
|
||||||
Command.Add(HideConsole);
|
Command.Add(HideConsole);
|
||||||
Command.SetAction(CommandAction);
|
Command.SetAction(CommandAction);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -69,6 +79,122 @@ public static class UI
|
|||||||
|
|
||||||
private static void CommandAction(ParseResult result)
|
private static void CommandAction(ParseResult result)
|
||||||
{
|
{
|
||||||
|
/// Получаем значения аргументов командной строки
|
||||||
|
var fullscreen = result.GetValue(Fullscreen);
|
||||||
|
var renderingMode = result.GetValue(RenderingMode);
|
||||||
|
var hideConsole = result.GetValue(HideConsole);
|
||||||
|
|
||||||
|
// Если выбран режим рендеринга DRM, то приложение будет рисоваться вместе с консолью,
|
||||||
|
// поэтому если не перехватить консоль, то вывод в неё будет рисоваться вместе с интерфейсом
|
||||||
|
if (hideConsole)
|
||||||
|
SilenceConsole();
|
||||||
|
|
||||||
|
// Собираем хост
|
||||||
|
var host = BuildHost();
|
||||||
|
|
||||||
|
// Запускаем хост асинхронно без блокировки потока
|
||||||
|
_ = host.RunAsync();
|
||||||
|
|
||||||
|
// Собираем графическое приложение
|
||||||
|
var ui = BuildApplication(host, fullscreen);
|
||||||
|
switch (renderingMode)
|
||||||
|
{
|
||||||
|
case RenderingModes.X11:
|
||||||
|
// Если мы запускаемся под X11,
|
||||||
|
// то очевидно у нас есть полноценный рабочий стол,
|
||||||
|
// поэтому запускаем графический интерфейс в классическом режиме с главным окном
|
||||||
|
ui.StartWithClassicDesktopLifetime(Program.Args);
|
||||||
|
break;
|
||||||
|
case RenderingModes.DRM:
|
||||||
|
// Если мы запускаемся в DRM (Direct Rendering Mode),
|
||||||
|
// то у нас нет рабочего стола и окон,
|
||||||
|
// потому что отрисовка (вроде как) идёт напрямую в кадровый буфер,
|
||||||
|
// поэтому запускаем графический интерфейс в DRM.
|
||||||
|
// Если работает X11, или любая другая графическая система,
|
||||||
|
// то запустить приложение в DRM не получится,
|
||||||
|
// потому что DRI (Direct Rendering Interface) занят этой графической системой
|
||||||
|
// TODO: Подумать над тем, чтобы брать DRI и scale для DRM рендеринга из переменных среды
|
||||||
|
// TODO: Подумать над тем, чтобы при запуске в DRM убивать приложения, которые занимают DRI
|
||||||
|
ui.StartLinuxDrm(Program.Args, "/dev/dri/card1", 1.0);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void SilenceConsole()
|
||||||
|
{
|
||||||
|
var start = new ThreadStart(() =>
|
||||||
|
{
|
||||||
|
Console.CursorVisible = false;
|
||||||
|
while (true)
|
||||||
|
Console.ReadKey(true);
|
||||||
|
});
|
||||||
|
var thread = new Thread(start)
|
||||||
|
{
|
||||||
|
IsBackground = true
|
||||||
|
};
|
||||||
|
thread.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IHost BuildHost()
|
||||||
|
{
|
||||||
|
// Создаём хост
|
||||||
|
var builder = Host.CreateApplicationBuilder();
|
||||||
|
|
||||||
|
// Добавляем конфигурацию из appsettings.json
|
||||||
|
builder.Configuration.AddAppSettingsJson();
|
||||||
|
|
||||||
|
// Конфигурируем логгер
|
||||||
|
builder.Logging.ConfigureLogging();
|
||||||
|
builder.Services.AddSingleton<LibCameraLogSink>();
|
||||||
|
|
||||||
|
// Добавляем сервисы баз данных
|
||||||
|
builder.Services.AddAmineContentCalibrationContext(builder.Configuration);
|
||||||
|
builder.Services.AddAmineContentResultsContext(builder.Configuration);
|
||||||
|
|
||||||
|
// Добавляем сервисы аппаратной части и прочее
|
||||||
|
builder.Services.AddLightsService("Hardware:Lights");
|
||||||
|
builder.Services.AddTemperatureHumidityService("Hardware:TemperatureHumidity");
|
||||||
|
builder.Services.AddIlluminatorService("Hardware:Illuminator");
|
||||||
|
builder.Services.AddCameraService("Hardware:Camera", true);
|
||||||
|
builder.Services.AddComputeResourcesService();
|
||||||
|
builder.Services.AddImageStorageService(new DirectoryInfo("images"));
|
||||||
|
|
||||||
|
// Добавляем элементы интерфейса как сервисы
|
||||||
|
builder.Services.AddViewsAndModels();
|
||||||
|
|
||||||
|
// Собираем хост
|
||||||
|
var host = builder.Build();
|
||||||
|
|
||||||
|
// Поскольку LibCameraLogSink, который перенаправляет логи из libcamera в хост,
|
||||||
|
// не будет создан ни одним из других сервисов, его необходимо создать превентивно
|
||||||
|
host.Services.GetRequiredService<LibCameraLogSink>();
|
||||||
|
|
||||||
|
// Применяем миграции баз данных если их схема данных изменялась
|
||||||
|
using (var scope = host.Services.CreateScope())
|
||||||
|
{
|
||||||
|
var context = scope.ServiceProvider.GetRequiredService<AmineContentCalibrationContext>();
|
||||||
|
context.Database.Migrate();
|
||||||
|
}
|
||||||
|
using (var scope = host.Services.CreateScope())
|
||||||
|
{
|
||||||
|
var context = scope.ServiceProvider.GetRequiredService<AmineContentResultsContext>();
|
||||||
|
context.Database.Migrate();
|
||||||
|
}
|
||||||
|
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AppBuilder BuildApplication(IHost host, bool fullscreen) =>
|
||||||
|
AppBuilder.Configure(() => new Application(host.Services, fullscreen))
|
||||||
|
.UsePlatformDetect()
|
||||||
|
.UseSkia()
|
||||||
|
.LogToTrace()
|
||||||
|
.With(new X11PlatformOptions
|
||||||
|
{
|
||||||
|
RenderingMode = [X11RenderingMode.Egl]
|
||||||
|
})
|
||||||
|
.With(new EglDisplayOptions
|
||||||
|
{
|
||||||
|
SupportsContextSharing = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
using GSS2.UI.Core.ViewModels;
|
||||||
|
using GSS2.UI.Core.Services;
|
||||||
|
using GSS2.Views;
|
||||||
|
using GSS2.ViewModels;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace GSS2;
|
||||||
|
|
||||||
|
public static class DependencyInjectionHelper
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddViewsAndModels(this IServiceCollection services) =>
|
||||||
|
services
|
||||||
|
.AddSingleton<DependencyInjectionViewLocator>((services) =>
|
||||||
|
new DependencyInjectionViewLocator(
|
||||||
|
services.GetRequiredService<ILogger<DependencyInjectionViewLocator>>(),
|
||||||
|
services
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.AddSingleton<NavigationService>((_) => new NavigationService(Application.Current?.ApplicationLifetime))
|
||||||
|
.AddSingleton<MainWindowViewModel>()
|
||||||
|
.AddSingleton<MainView>()
|
||||||
|
.AddSingleton<MainViewModel>()
|
||||||
|
.AddSingleton<AmineContentView>()
|
||||||
|
.AddSingleton<AmineContentViewModel>();
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Templates;
|
||||||
|
|
||||||
|
using GSS2.UI.Core.ViewModels;
|
||||||
|
|
||||||
|
namespace GSS2;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DependencyInjectionViewLocator"/> - интерфейс между Avalonia и механизмом DI (Dependency Injection)
|
||||||
|
/// из Microsoft.Extensions.Hosting.<br/>
|
||||||
|
/// Создаёт View для переданной ViewModel.<br/>
|
||||||
|
/// Обязательным условием его работы является, то что:
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item>Класс <i>View</i> находится в пространстве имён <i>SomeNameSpace.Views</i> и имеет имя <i>SomeClassView</i></item>
|
||||||
|
/// <item>Класс <i>ViewModel</i> находится в пространстве имён <i>SomeNameSpace.ViewModels</i> и имеет имя <i>SomeClassViewModel</i></item>
|
||||||
|
/// <item>Класс <i>ViewModel</i> наследован от <see cref="ViewModelBase"/></item>
|
||||||
|
/// <item>Класс <i>View</i> наследован от <see cref="Control"/></item>
|
||||||
|
/// </list>
|
||||||
|
/// </summary>
|
||||||
|
public class DependencyInjectionViewLocator : IDataTemplate
|
||||||
|
{
|
||||||
|
private readonly ILogger<DependencyInjectionViewLocator> _logger;
|
||||||
|
private readonly IServiceProvider _services;
|
||||||
|
|
||||||
|
public DependencyInjectionViewLocator(ILogger<DependencyInjectionViewLocator> logger, IServiceProvider services)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_services = services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Match(object? data) => data is ViewModelBase;
|
||||||
|
|
||||||
|
public Control? Build(object? data)
|
||||||
|
{
|
||||||
|
// Если data - null или каким-то образом не ViewModelBase, то возвращаем null
|
||||||
|
if (data is not ViewModelBase viewModel)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Получаем имя класса ViewModel с полным описанием сборки
|
||||||
|
var viewModelName = viewModel.GetType().AssemblyQualifiedName;
|
||||||
|
if (viewModelName is null)
|
||||||
|
{
|
||||||
|
_logger.LogError("Не удалось получить имя класса ViewModel: {}", viewModel);
|
||||||
|
return new Control();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Заменяем ".ViewModel" на ".View" в имени класса ViewModel и получаем имя класса View и его тип
|
||||||
|
var viewName = viewModelName.Replace("ViewModel", "View");
|
||||||
|
var viewType = Type.GetType(viewName);
|
||||||
|
if (viewType is null)
|
||||||
|
{
|
||||||
|
_logger.LogError("Не удалось найти класс View \"{}\"", viewName);
|
||||||
|
return new Control();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ищем тип View в сервисах
|
||||||
|
var view = _services.GetService(viewType);
|
||||||
|
if (view is null)
|
||||||
|
{
|
||||||
|
_logger.LogError("Класс View \"{}\" не зарегистрирован", viewName);
|
||||||
|
return new Control();
|
||||||
|
}
|
||||||
|
if (view is not Control control)
|
||||||
|
{
|
||||||
|
_logger.LogError("Класс View \"{}\" не наследован от Control", viewName);
|
||||||
|
return new Control();
|
||||||
|
}
|
||||||
|
|
||||||
|
control.DataContext = viewModel;
|
||||||
|
return control;
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
-1
@@ -2,8 +2,17 @@
|
|||||||
|
|
||||||
public partial class Program
|
public partial class Program
|
||||||
{
|
{
|
||||||
// Список аргументов командной строки
|
// Аргументы командной строки
|
||||||
private static List<string> _args = new List<string>();
|
private static List<string> _args = new List<string>();
|
||||||
|
public static string[] Args
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var copy = new string[_args.Count()];
|
||||||
|
_args.CopyTo(copy);
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Точка входа
|
/// Точка входа
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
|
||||||
|
using GSS2.UI.Core.Services;
|
||||||
|
using GSS2.UI.Core.ViewModels;
|
||||||
|
|
||||||
|
namespace GSS2.ViewModels;
|
||||||
|
|
||||||
|
public partial class MainViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private Control? _amineContentAnalysisView = null;
|
||||||
|
|
||||||
|
private readonly NavigationService _navigationService;
|
||||||
|
private readonly DependencyInjectionViewLocator _viewLocator;
|
||||||
|
|
||||||
|
[ObservableProperty] public partial AmineContentViewModel AmineContentAnalysisViewModel { get; set; }
|
||||||
|
|
||||||
|
public MainViewModel(
|
||||||
|
NavigationService navigationService,
|
||||||
|
DependencyInjectionViewLocator viewLocator,
|
||||||
|
AmineContentViewModel amineContentAnalysisViewModel
|
||||||
|
)
|
||||||
|
{
|
||||||
|
_navigationService = navigationService;
|
||||||
|
_viewLocator = viewLocator;
|
||||||
|
|
||||||
|
AmineContentAnalysisViewModel = amineContentAnalysisViewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public void NavigateToAmineContentAnalysis()
|
||||||
|
{
|
||||||
|
if (_amineContentAnalysisView is null)
|
||||||
|
_amineContentAnalysisView = _viewLocator.Build(AmineContentAnalysisViewModel);
|
||||||
|
if (_amineContentAnalysisView is not null)
|
||||||
|
_navigationService.NavigateTo(_amineContentAnalysisView);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand] public void NavigateToColorimetricAnalysis() { }
|
||||||
|
[RelayCommand] public void NavigateToGranulometricAnalysis() { }
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<UserControl
|
||||||
|
x:Class="GSS2.Views.MainView"
|
||||||
|
xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:vm="using:GSS2.ViewModels"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
x:DataType="vm:MainViewModel">
|
||||||
|
|
||||||
|
<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 Background="{StaticResource MainBackground}" BorderThickness="0">
|
||||||
|
<Grid ColumnSpacing="5" Margin="5">
|
||||||
|
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Button Grid.Column="0" Command="{Binding NavigateToAmineContentAnalysisCommand}">
|
||||||
|
<TextBlock Text="Анализ на кондиционирующие добавки"/>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button Grid.Column="1" Command="{Binding NavigateToColorimetricAnalysisCommand}" IsEnabled="False">
|
||||||
|
<TextBlock Text="Анализ цветности"/>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button Grid.Column="2" Command="{Binding NavigateToGranulometricAnalysisCommand}" IsEnabled="False">
|
||||||
|
<TextBlock Text="Анализ гранулометрического состава"/>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
</UserControl>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace GSS2.Views;
|
||||||
|
|
||||||
|
public partial class MainView : UserControl
|
||||||
|
{
|
||||||
|
public MainView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user