WPF реализация
This commit is contained in:
11
.vscode/launch.json
vendored
11
.vscode/launch.json
vendored
@@ -12,6 +12,17 @@
|
|||||||
"console": "integratedTerminal",
|
"console": "integratedTerminal",
|
||||||
"stopAtEntry": false
|
"stopAtEntry": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "WPF (Debug)",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "build-wpf",
|
||||||
|
"program": "${workspaceFolder}/src/Sms.TaskTwo.Wpf/bin/Debug/net8.0-windows/Sms.TaskTwo.Wpf.dll",
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}/src/Sms.TaskTwo.Wpf/bin/Debug/net8.0-windows",
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"stopAtEntry": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Attach",
|
"name": "Attach",
|
||||||
"type": "coreclr",
|
"type": "coreclr",
|
||||||
|
|||||||
27
.vscode/tasks.json
vendored
27
.vscode/tasks.json
vendored
@@ -41,6 +41,33 @@
|
|||||||
},
|
},
|
||||||
"problemMatcher": "$msCompile"
|
"problemMatcher": "$msCompile"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"label": "build-wpf",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
"${workspaceFolder}/src/Sms.TaskTwo.Wpf/Sms.TaskTwo.Wpf.csproj",
|
||||||
|
"/property:GenerateFullPaths=true",
|
||||||
|
"/consoleloggerparameters:NoSummary;ForceNoAlign"
|
||||||
|
],
|
||||||
|
"group": "build",
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "run-wpf",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"run",
|
||||||
|
"--project",
|
||||||
|
"${workspaceFolder}/src/Sms.TaskTwo.Wpf/Sms.TaskTwo.Wpf.csproj",
|
||||||
|
"--no-build"
|
||||||
|
],
|
||||||
|
"group": "none",
|
||||||
|
"problemMatcher": "$msCompile",
|
||||||
|
"dependsOn": "build-wpf"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "run-avalonia",
|
"label": "run-avalonia",
|
||||||
"command": "dotnet",
|
"command": "dotnet",
|
||||||
|
|||||||
13
README.md
13
README.md
@@ -1,6 +1,6 @@
|
|||||||
# SMS Task Two — редактор переменных среды
|
# SMS Task Two — редактор переменных среды
|
||||||
|
|
||||||
Десктопное приложение на **Avalonia** (.NET 8) для чтения и изменения пользовательских переменных среды. Ядро, ViewModels и модуль окружения вынесены в переносимые проекты для последующего порта на **WPF**.
|
Десктопное приложение на **Avalonia** и **WPF** (.NET 8) для чтения и изменения пользовательских переменных среды. Ядро, ViewModels и модуль окружения вынесены в общие проекты.
|
||||||
|
|
||||||
## Solution
|
## Solution
|
||||||
|
|
||||||
@@ -15,7 +15,8 @@
|
|||||||
| `Sms.Environment.Linux` | `~/.config/environment.d/99-sms-task-two.conf` |
|
| `Sms.Environment.Linux` | `~/.config/environment.d/99-sms-task-two.conf` |
|
||||||
| `Sms.TaskTwo.Core` | Конфигурация, сервис, логирование |
|
| `Sms.TaskTwo.Core` | Конфигурация, сервис, логирование |
|
||||||
| `Sms.TaskTwo.ViewModels` | MVVM (`CommunityToolkit.Mvvm`) |
|
| `Sms.TaskTwo.ViewModels` | MVVM (`CommunityToolkit.Mvvm`) |
|
||||||
| `Sms.TaskTwo.Avalonia` | UI-хост |
|
| `Sms.TaskTwo.Avalonia` | UI-хост (Avalonia) |
|
||||||
|
| `Sms.TaskTwo.Wpf` | UI-хост (WPF) |
|
||||||
|
|
||||||
## Сборка и запуск
|
## Сборка и запуск
|
||||||
|
|
||||||
@@ -83,6 +84,10 @@ dotnet run --project src/Sms.TaskTwo.Avalonia/Sms.TaskTwo.Avalonia.csproj
|
|||||||
2. Значения по умолчанию показываются в UI, в ОС записываются при первом изменении пользователем.
|
2. Значения по умолчанию показываются в UI, в ОС записываются при первом изменении пользователем.
|
||||||
3. Pixel-perfect вёрстка не требуется; элементы стилизованы по макету (заголовок, DataGrid, кнопки «−» / «×»).
|
3. Pixel-perfect вёрстка не требуется; элементы стилизованы по макету (заголовок, DataGrid, кнопки «−» / «×»).
|
||||||
|
|
||||||
## Порт на WPF
|
## WPF
|
||||||
|
|
||||||
Создать `Sms.TaskTwo.Wpf`, подключить `Sms.TaskTwo.Core`, `Sms.TaskTwo.ViewModels`, зарегистрировать `IEnvironmentVariableStore` так же, как в `App.axaml.cs` Avalonia-проекта.
|
```bash
|
||||||
|
dotnet run --project src/Sms.TaskTwo.Wpf/Sms.TaskTwo.Wpf.csproj
|
||||||
|
```
|
||||||
|
|
||||||
|
Конфигурация: [`src/Sms.TaskTwo.Wpf/appsettings.json`](src/Sms.TaskTwo.Wpf/appsettings.json) (аналогично Avalonia).
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<Project Path="src/Sms.Environment.Windows/Sms.Environment.Windows.csproj" />
|
<Project Path="src/Sms.Environment.Windows/Sms.Environment.Windows.csproj" />
|
||||||
<Project Path="src/Sms.Environment/Sms.Environment.csproj" />
|
<Project Path="src/Sms.Environment/Sms.Environment.csproj" />
|
||||||
<Project Path="src/Sms.TaskTwo.Avalonia/Sms.TaskTwo.Avalonia.csproj" />
|
<Project Path="src/Sms.TaskTwo.Avalonia/Sms.TaskTwo.Avalonia.csproj" />
|
||||||
|
<Project Path="src/Sms.TaskTwo.Wpf/Sms.TaskTwo.Wpf.csproj" />
|
||||||
<Project Path="src/Sms.TaskTwo.Core/Sms.TaskTwo.Core.csproj" />
|
<Project Path="src/Sms.TaskTwo.Core/Sms.TaskTwo.Core.csproj" />
|
||||||
<Project Path="src/Sms.TaskTwo.ViewModels/Sms.TaskTwo.ViewModels.csproj" />
|
<Project Path="src/Sms.TaskTwo.ViewModels/Sms.TaskTwo.ViewModels.csproj" />
|
||||||
</Folder>
|
</Folder>
|
||||||
|
|||||||
@@ -99,6 +99,11 @@
|
|||||||
Binding="{Binding RequiredValue, Mode=TwoWay}"
|
Binding="{Binding RequiredValue, Mode=TwoWay}"
|
||||||
Width="2*"
|
Width="2*"
|
||||||
MinWidth="140" />
|
MinWidth="140" />
|
||||||
|
<DataGridTextColumn Header="Актуальное значение"
|
||||||
|
Binding="{Binding ActualValueDisplay}"
|
||||||
|
IsReadOnly="True"
|
||||||
|
Width="2*"
|
||||||
|
MinWidth="140" />
|
||||||
<DataGridTextColumn Header="Комментарий"
|
<DataGridTextColumn Header="Комментарий"
|
||||||
Binding="{Binding Comment, Mode=TwoWay}"
|
Binding="{Binding Comment, Mode=TwoWay}"
|
||||||
Width="240"
|
Width="240"
|
||||||
|
|||||||
@@ -142,6 +142,9 @@ public sealed class EnvironmentVariablesService
|
|||||||
|
|
||||||
public string GetDisplayValue(string name) => ResolveRequiredValue(name);
|
public string GetDisplayValue(string name) => ResolveRequiredValue(name);
|
||||||
|
|
||||||
|
public string? GetProcessValue(string name) =>
|
||||||
|
_store.GetProcessEnvironment().TryGetValue(name, out var value) ? value : null;
|
||||||
|
|
||||||
public void SaveValue(string name, string value)
|
public void SaveValue(string name, string value)
|
||||||
{
|
{
|
||||||
var previous = _store.GetUserPersistedValue(name);
|
var previous = _store.GetUserPersistedValue(name);
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ public sealed partial class EnvironmentVariableRowViewModel : ObservableObject
|
|||||||
_service = service;
|
_service = service;
|
||||||
_onCustomRemoved = onCustomRemoved;
|
_onCustomRemoved = onCustomRemoved;
|
||||||
ApplySnapshot(row, suppressSave: true);
|
ApplySnapshot(row, suppressSave: true);
|
||||||
|
RefreshActualValue(_service.GetProcessValue(Field));
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Field { get; }
|
public string Field { get; }
|
||||||
@@ -34,6 +35,12 @@ public sealed partial class EnvironmentVariableRowViewModel : ObservableObject
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _useUserStore;
|
private bool _useUserStore;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _actualValue = string.Empty;
|
||||||
|
|
||||||
|
public string ActualValueDisplay =>
|
||||||
|
string.IsNullOrEmpty(ActualValue) ? "<нет>" : ActualValue;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string _requiredValue = string.Empty;
|
private string _requiredValue = string.Empty;
|
||||||
|
|
||||||
@@ -60,6 +67,7 @@ public sealed partial class EnvironmentVariableRowViewModel : ObservableObject
|
|||||||
if (value)
|
if (value)
|
||||||
{
|
{
|
||||||
_service.SaveValue(Field, RequiredValue);
|
_service.SaveValue(Field, RequiredValue);
|
||||||
|
RefreshActualValue(_service.GetProcessValue(Field));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,8 +82,11 @@ public sealed partial class EnvironmentVariableRowViewModel : ObservableObject
|
|||||||
BeginLoad();
|
BeginLoad();
|
||||||
RequiredValue = _service.GetDisplayValue(Field);
|
RequiredValue = _service.GetDisplayValue(Field);
|
||||||
EndLoad();
|
EndLoad();
|
||||||
|
RefreshActualValue(_service.GetProcessValue(Field));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
partial void OnActualValueChanged(string value) => OnPropertyChanged(nameof(ActualValueDisplay));
|
||||||
|
|
||||||
partial void OnRequiredValueChanged(string value)
|
partial void OnRequiredValueChanged(string value)
|
||||||
{
|
{
|
||||||
if (_isLoading || !UseUserStore)
|
if (_isLoading || !UseUserStore)
|
||||||
@@ -84,6 +95,7 @@ public sealed partial class EnvironmentVariableRowViewModel : ObservableObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
_service.SaveValue(Field, value);
|
_service.SaveValue(Field, value);
|
||||||
|
RefreshActualValue(_service.GetProcessValue(Field));
|
||||||
}
|
}
|
||||||
|
|
||||||
public event EventHandler? RowAppearanceChanged;
|
public event EventHandler? RowAppearanceChanged;
|
||||||
@@ -105,6 +117,11 @@ public sealed partial class EnvironmentVariableRowViewModel : ObservableObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void RefreshActualValue(string? processValue)
|
||||||
|
{
|
||||||
|
ActualValue = processValue ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
public void BeginLoad() => _isLoading = true;
|
public void BeginLoad() => _isLoading = true;
|
||||||
|
|
||||||
public void EndLoad() => _isLoading = false;
|
public void EndLoad() => _isLoading = false;
|
||||||
|
|||||||
@@ -127,6 +127,16 @@ public sealed partial class MainWindowViewModel : ObservableObject
|
|||||||
MoveItem(Rows, currentIndex, targetIndex);
|
MoveItem(Rows, currentIndex, targetIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RefreshProcessStates();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshProcessStates()
|
||||||
|
{
|
||||||
|
foreach (var row in Rows)
|
||||||
|
{
|
||||||
|
row.RefreshActualValue(_service.GetProcessValue(row.Field));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void MoveItem(
|
private static void MoveItem(
|
||||||
|
|||||||
24
src/Sms.TaskTwo.Wpf/App.xaml
Normal file
24
src/Sms.TaskTwo.Wpf/App.xaml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<Application x:Class="Sms.TaskTwo.Wpf.App"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
Startup="OnStartup"
|
||||||
|
Exit="OnExit">
|
||||||
|
<Application.Resources>
|
||||||
|
<Style x:Key="RoundedGridStyle" TargetType="DataGrid">
|
||||||
|
<Setter Property="RowHeight" Value="32" />
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="GridLinesVisibility" Value="All" />
|
||||||
|
<Setter Property="HeadersVisibility" Value="Column" />
|
||||||
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
|
<Setter Property="CanUserReorderColumns" Value="False" />
|
||||||
|
<Setter Property="CanUserResizeColumns" Value="True" />
|
||||||
|
<Setter Property="CanUserSortColumns" Value="False" />
|
||||||
|
<Setter Property="AutoGenerateColumns" Value="False" />
|
||||||
|
</Style>
|
||||||
|
<Style TargetType="DataGridColumnHeader">
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="BorderBrush" Value="Transparent" />
|
||||||
|
<Setter Property="Padding" Value="8,4" />
|
||||||
|
</Style>
|
||||||
|
</Application.Resources>
|
||||||
|
</Application>
|
||||||
54
src/Sms.TaskTwo.Wpf/App.xaml.cs
Normal file
54
src/Sms.TaskTwo.Wpf/App.xaml.cs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Sms.Environment;
|
||||||
|
using Sms.Environment.Linux;
|
||||||
|
using Sms.Environment.Windows;
|
||||||
|
using Sms.TaskTwo.Core.DependencyInjection;
|
||||||
|
using Sms.TaskTwo.Core.Logging;
|
||||||
|
using Sms.TaskTwo.ViewModels;
|
||||||
|
using Sms.TaskTwo.Wpf.Views;
|
||||||
|
|
||||||
|
namespace Sms.TaskTwo.Wpf;
|
||||||
|
|
||||||
|
public partial class App : Application
|
||||||
|
{
|
||||||
|
public static IServiceProvider Services { get; private set; } = null!;
|
||||||
|
|
||||||
|
private void OnStartup(object sender, StartupEventArgs e)
|
||||||
|
{
|
||||||
|
Services = ConfigureServices();
|
||||||
|
var mainViewModel = Services.GetRequiredService<MainWindowViewModel>();
|
||||||
|
MainWindow = new MainWindow(mainViewModel);
|
||||||
|
MainWindow.Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnExit(object sender, ExitEventArgs e)
|
||||||
|
{
|
||||||
|
if (Services.GetService<ConsoleLog>() is IDisposable log)
|
||||||
|
{
|
||||||
|
log.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IServiceProvider ConfigureServices()
|
||||||
|
{
|
||||||
|
var configuration = new ConfigurationBuilder()
|
||||||
|
.SetBasePath(AppContext.BaseDirectory)
|
||||||
|
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
services.AddSingleton<IConfiguration>(configuration);
|
||||||
|
services.AddSingleton<IEnvironmentVariableStore>(CreateEnvironmentStore);
|
||||||
|
services.AddTaskTwoCore(configuration);
|
||||||
|
services.AddSingleton<MainWindowViewModel>();
|
||||||
|
|
||||||
|
return services.BuildServiceProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnvironmentVariableStore CreateEnvironmentStore(IServiceProvider _) =>
|
||||||
|
OperatingSystem.IsWindows()
|
||||||
|
? new WindowsEnvironmentVariableStore()
|
||||||
|
: new LinuxEnvironmentVariableStore();
|
||||||
|
}
|
||||||
6
src/Sms.TaskTwo.Wpf/AppResources.cs
Normal file
6
src/Sms.TaskTwo.Wpf/AppResources.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Sms.TaskTwo.Wpf;
|
||||||
|
|
||||||
|
public static class AppResources
|
||||||
|
{
|
||||||
|
public const string WindowTitle = "Тестовое WPF-приложение для SmartMealService";
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Data;
|
||||||
|
|
||||||
|
namespace Sms.TaskTwo.Wpf.Converters;
|
||||||
|
|
||||||
|
public sealed class StringNotEmptyToVisibilityConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) =>
|
||||||
|
value is string text && !string.IsNullOrEmpty(text)
|
||||||
|
? Visibility.Visible
|
||||||
|
: Visibility.Collapsed;
|
||||||
|
|
||||||
|
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) =>
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
28
src/Sms.TaskTwo.Wpf/Sms.TaskTwo.Wpf.csproj
Normal file
28
src/Sms.TaskTwo.Wpf/Sms.TaskTwo.Wpf.csproj
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.1" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Sms.Environment.Linux\Sms.Environment.Linux.csproj" />
|
||||||
|
<ProjectReference Include="..\Sms.Environment.Windows\Sms.Environment.Windows.csproj" />
|
||||||
|
<ProjectReference Include="..\Sms.TaskTwo.Core\Sms.TaskTwo.Core.csproj" />
|
||||||
|
<ProjectReference Include="..\Sms.TaskTwo.ViewModels\Sms.TaskTwo.ViewModels.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="appsettings.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
156
src/Sms.TaskTwo.Wpf/Views/MainWindow.xaml
Normal file
156
src/Sms.TaskTwo.Wpf/Views/MainWindow.xaml
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
<Window x:Class="Sms.TaskTwo.Wpf.Views.MainWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:vm="clr-namespace:Sms.TaskTwo.ViewModels;assembly=Sms.TaskTwo.ViewModels"
|
||||||
|
xmlns:app="clr-namespace:Sms.TaskTwo.Wpf"
|
||||||
|
xmlns:conv="clr-namespace:Sms.TaskTwo.Wpf.Converters"
|
||||||
|
Title="{x:Static app:AppResources.WindowTitle}"
|
||||||
|
Width="960"
|
||||||
|
Height="600"
|
||||||
|
MinWidth="760"
|
||||||
|
MinHeight="480"
|
||||||
|
Background="#F5F5F5">
|
||||||
|
<Window.Resources>
|
||||||
|
<conv:StringNotEmptyToVisibilityConverter x:Key="StringNotEmptyToVisibility" />
|
||||||
|
</Window.Resources>
|
||||||
|
<Border CornerRadius="12"
|
||||||
|
Background="White"
|
||||||
|
BorderBrush="#C8C8C8"
|
||||||
|
BorderThickness="1"
|
||||||
|
Margin="12"
|
||||||
|
ClipToBounds="True">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid Grid.Row="0"
|
||||||
|
Margin="12,12,12,0">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<CheckBox Content="Отображать все переменные"
|
||||||
|
IsChecked="{Binding ShowAllVariables}"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
<Button Grid.Column="1"
|
||||||
|
Content="Обновить"
|
||||||
|
Command="{Binding RefreshCommand}"
|
||||||
|
MinWidth="100"
|
||||||
|
Margin="8,0,0,0" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid Grid.Row="1"
|
||||||
|
Margin="12,8,12,0">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="2*" />
|
||||||
|
<ColumnDefinition Width="3*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Grid.Column="0"
|
||||||
|
Text="Новая:"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
<Grid Grid.Column="1" Margin="8,0,0,0">
|
||||||
|
<TextBox Text="{Binding NewVariableName, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
<TextBlock Text="Имя переменной"
|
||||||
|
IsHitTestVisible="False"
|
||||||
|
Margin="6,0,0,0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="#9E9E9E">
|
||||||
|
<TextBlock.Style>
|
||||||
|
<Style TargetType="TextBlock">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding NewVariableName}" Value="">
|
||||||
|
<Setter Property="Visibility" Value="Visible" />
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Style>
|
||||||
|
</TextBlock>
|
||||||
|
</Grid>
|
||||||
|
<Grid Grid.Column="2" Margin="8,0,0,0">
|
||||||
|
<TextBox Text="{Binding NewVariableValue, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
<TextBlock Text="Значение"
|
||||||
|
IsHitTestVisible="False"
|
||||||
|
Margin="6,0,0,0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="#9E9E9E">
|
||||||
|
<TextBlock.Style>
|
||||||
|
<Style TargetType="TextBlock">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding NewVariableValue}" Value="">
|
||||||
|
<Setter Property="Visibility" Value="Visible" />
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Style>
|
||||||
|
</TextBlock>
|
||||||
|
</Grid>
|
||||||
|
<Button Grid.Column="3"
|
||||||
|
Content="Добавить"
|
||||||
|
Command="{Binding AddVariableCommand}"
|
||||||
|
MinWidth="100"
|
||||||
|
Margin="8,0,0,0" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="2"
|
||||||
|
Margin="12,4,12,0">
|
||||||
|
<TextBlock Foreground="#C62828"
|
||||||
|
Text="{Binding AddVariableError}"
|
||||||
|
Visibility="{Binding AddVariableError, Converter={StaticResource StringNotEmptyToVisibility}}" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Border Grid.Row="3"
|
||||||
|
Margin="12,4,12,12"
|
||||||
|
CornerRadius="8"
|
||||||
|
ClipToBounds="True"
|
||||||
|
Background="White"
|
||||||
|
BorderBrush="#B0B0B0"
|
||||||
|
BorderThickness="1">
|
||||||
|
<DataGrid x:Name="VariablesGrid"
|
||||||
|
Style="{StaticResource RoundedGridStyle}"
|
||||||
|
ItemsSource="{Binding Rows}"
|
||||||
|
LoadingRow="OnLoadingRow"
|
||||||
|
UnloadingRow="OnUnloadingRow">
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTemplateColumn Header=""
|
||||||
|
Width="44">
|
||||||
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type vm:EnvironmentVariableRowViewModel}">
|
||||||
|
<CheckBox IsChecked="{Binding UseUserStore, Mode=TwoWay}"
|
||||||
|
IsEnabled="{Binding CanChangeUserStore}"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</DataTemplate>
|
||||||
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
|
</DataGridTemplateColumn>
|
||||||
|
<DataGridTextColumn Header="Поле"
|
||||||
|
Binding="{Binding Field}"
|
||||||
|
IsReadOnly="True"
|
||||||
|
Width="210"
|
||||||
|
MinWidth="150" />
|
||||||
|
<DataGridTextColumn Header="Требуемое значение"
|
||||||
|
Binding="{Binding RequiredValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
Width="2*"
|
||||||
|
MinWidth="140" />
|
||||||
|
<DataGridTextColumn Header="Актуальное значение"
|
||||||
|
Binding="{Binding ActualValueDisplay}"
|
||||||
|
IsReadOnly="True"
|
||||||
|
Width="2*"
|
||||||
|
MinWidth="140" />
|
||||||
|
<DataGridTextColumn Header="Комментарий"
|
||||||
|
Binding="{Binding Comment, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
Width="240"
|
||||||
|
MinWidth="180" />
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Window>
|
||||||
128
src/Sms.TaskTwo.Wpf/Views/MainWindow.xaml.cs
Normal file
128
src/Sms.TaskTwo.Wpf/Views/MainWindow.xaml.cs
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using Sms.TaskTwo.ViewModels;
|
||||||
|
|
||||||
|
namespace Sms.TaskTwo.Wpf.Views;
|
||||||
|
|
||||||
|
public partial class MainWindow : Window
|
||||||
|
{
|
||||||
|
private static readonly Brush AppSettingsBrush = new SolidColorBrush(Color.FromRgb(0xE3, 0xF2, 0xFD));
|
||||||
|
private static readonly Brush CustomBrush = new SolidColorBrush(Color.FromRgb(0xFF, 0xF8, 0xE1));
|
||||||
|
private static readonly Brush AppSettingsUserStoreBrush = new SolidColorBrush(Color.FromRgb(0xC8, 0xE6, 0xC9));
|
||||||
|
private static readonly Brush UserStoreBorderBrush = new SolidColorBrush(Color.FromRgb(0x2E, 0x7D, 0x32));
|
||||||
|
private static readonly Brush DefaultRowBrush = Brushes.White;
|
||||||
|
|
||||||
|
private readonly Dictionary<EnvironmentVariableRowViewModel, DataGridRow> _gridRows = new();
|
||||||
|
|
||||||
|
public MainWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MainWindow(MainWindowViewModel viewModel) : this()
|
||||||
|
{
|
||||||
|
DataContext = viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLoadingRow(object sender, DataGridRowEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Row.DataContext is not EnvironmentVariableRowViewModel row)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_gridRows.TryGetValue(row, out var previousRow) && previousRow != e.Row)
|
||||||
|
{
|
||||||
|
UnsubscribeRow(previousRow, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
_gridRows[row] = e.Row;
|
||||||
|
ApplyRowStyle(e.Row, row);
|
||||||
|
|
||||||
|
row.RowAppearanceChanged -= OnRowAppearanceChanged;
|
||||||
|
row.RowAppearanceChanged += OnRowAppearanceChanged;
|
||||||
|
|
||||||
|
e.Row.DataContextChanged -= OnRowDataContextChanged;
|
||||||
|
e.Row.DataContextChanged += OnRowDataContextChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUnloadingRow(object sender, DataGridRowEventArgs e)
|
||||||
|
{
|
||||||
|
var row = _gridRows.FirstOrDefault(pair => pair.Value == e.Row).Key;
|
||||||
|
if (row is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnsubscribeRow(e.Row, row);
|
||||||
|
_gridRows.Remove(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnsubscribeRow(DataGridRow gridRow, EnvironmentVariableRowViewModel row)
|
||||||
|
{
|
||||||
|
row.RowAppearanceChanged -= OnRowAppearanceChanged;
|
||||||
|
gridRow.DataContextChanged -= OnRowDataContextChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRowDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is not DataGridRow gridRow)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var row = _gridRows.FirstOrDefault(pair => pair.Value == gridRow).Key;
|
||||||
|
if (row is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnsubscribeRow(gridRow, row);
|
||||||
|
_gridRows.Remove(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRowAppearanceChanged(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is not EnvironmentVariableRowViewModel row)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_gridRows.TryGetValue(row, out var gridRow))
|
||||||
|
{
|
||||||
|
ApplyRowStyle(gridRow, row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ApplyRowStyle(DataGridRow gridRow, EnvironmentVariableRowViewModel row)
|
||||||
|
{
|
||||||
|
if (row.IsFromAppSettings && row.UseUserStore)
|
||||||
|
{
|
||||||
|
gridRow.Background = AppSettingsUserStoreBrush;
|
||||||
|
}
|
||||||
|
else if (row.IsFromAppSettings)
|
||||||
|
{
|
||||||
|
gridRow.Background = AppSettingsBrush;
|
||||||
|
}
|
||||||
|
else if (row.IsCustom)
|
||||||
|
{
|
||||||
|
gridRow.Background = CustomBrush;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gridRow.Background = DefaultRowBrush;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row.UseUserStore)
|
||||||
|
{
|
||||||
|
gridRow.BorderBrush = UserStoreBorderBrush;
|
||||||
|
gridRow.BorderThickness = new Thickness(0, 0, 0, 2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gridRow.BorderBrush = Brushes.Transparent;
|
||||||
|
gridRow.BorderThickness = new Thickness(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/Sms.TaskTwo.Wpf/app.manifest
Normal file
10
src/Sms.TaskTwo.Wpf/app.manifest
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<assemblyIdentity version="1.0.0.0" name="Sms.TaskTwo.Wpf.Desktop"/>
|
||||||
|
|
||||||
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||||
|
<application>
|
||||||
|
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||||
|
</application>
|
||||||
|
</compatibility>
|
||||||
|
</assembly>
|
||||||
16
src/Sms.TaskTwo.Wpf/appsettings.json
Normal file
16
src/Sms.TaskTwo.Wpf/appsettings.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"EnvironmentVariables": {
|
||||||
|
"Names": [
|
||||||
|
"SMS_MEAL_SERVER_URL",
|
||||||
|
"SMS_MEAL_API_KEY"
|
||||||
|
],
|
||||||
|
"CommentsVariableName": "SMS_TASK_TWO_COMMENTS",
|
||||||
|
"CustomVariablesVariableName": "SMS_TASK_TWO_CUSTOM_VARS",
|
||||||
|
"Defaults": {
|
||||||
|
"SMS_MEAL_SERVER_URL": "https://localhost"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Logging": {
|
||||||
|
"LogDirectory": "logs"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user