Кнопка обновления и столбец актуального значения

This commit is contained in:
2026-06-04 19:46:35 +03:00
parent e07fc408eb
commit caac16f88b
7 changed files with 155 additions and 55 deletions

View File

@@ -36,6 +36,16 @@ public sealed class LinuxEnvironmentVariableStore : IEnvironmentVariableStore
return System.Environment.GetEnvironmentVariable(name);
}
public string? GetUserPersistedValue(string name)
{
if (LoadMergedFromDirectory().TryGetValue(name, out var value))
{
return value;
}
return null;
}
public void Set(string name, string value)
{
try

View File

@@ -1,4 +1,5 @@
using System.Runtime.InteropServices;
using Microsoft.Win32;
using Sms.Environment;
namespace Sms.Environment.Windows;
@@ -7,14 +8,23 @@ public sealed class WindowsEnvironmentVariableStore : IEnvironmentVariableStore
{
private const int HWND_BROADCAST = 0xffff;
private const int WM_SETTINGCHANGE = 0x001A;
private const string EnvironmentKeyPath = "Environment";
public string? Get(string name) =>
System.Environment.GetEnvironmentVariable(name, EnvironmentVariableTarget.User)
GetUserPersistedValue(name)
?? System.Environment.GetEnvironmentVariable(name, EnvironmentVariableTarget.Process);
public string? GetUserPersistedValue(string name)
{
using var key = Registry.CurrentUser.OpenSubKey(EnvironmentKeyPath);
return key?.GetValue(name) as string;
}
public void Set(string name, string value)
{
System.Environment.SetEnvironmentVariable(name, value, EnvironmentVariableTarget.User);
using var key = Registry.CurrentUser.OpenSubKey(EnvironmentKeyPath, writable: true)
?? Registry.CurrentUser.CreateSubKey(EnvironmentKeyPath, writable: true);
key.SetValue(name, value, RegistryValueKind.ExpandString);
System.Environment.SetEnvironmentVariable(name, value, EnvironmentVariableTarget.Process);
BroadcastEnvironmentChange();
}
@@ -39,15 +49,36 @@ public sealed class WindowsEnvironmentVariableStore : IEnvironmentVariableStore
public IReadOnlyDictionary<string, string> GetProcessEnvironment() =>
ToDictionary(System.Environment.GetEnvironmentVariables(EnvironmentVariableTarget.Process));
public IReadOnlyDictionary<string, string> GetUserPersistedEnvironment() =>
ToDictionary(System.Environment.GetEnvironmentVariables(EnvironmentVariableTarget.User));
public IReadOnlyDictionary<string, string> GetUserPersistedEnvironment()
{
var result = new Dictionary<string, string>(StringComparer.Ordinal);
using var key = Registry.CurrentUser.OpenSubKey(EnvironmentKeyPath);
if (key is null)
{
return result;
}
public bool IsPersistedInUserStore(string name) =>
System.Environment.GetEnvironmentVariable(name, EnvironmentVariableTarget.User) is not null;
foreach (var name in key.GetValueNames())
{
if (key.GetValue(name) is string value)
{
result[name] = value;
}
}
return result;
}
public bool IsPersistedInUserStore(string name)
{
using var key = Registry.CurrentUser.OpenSubKey(EnvironmentKeyPath);
return key?.GetValue(name) is not null;
}
public void RemoveFromUserStore(string name)
{
System.Environment.SetEnvironmentVariable(name, null, EnvironmentVariableTarget.User);
using var key = Registry.CurrentUser.OpenSubKey(EnvironmentKeyPath, writable: true);
key?.DeleteValue(name, throwOnMissingValue: false);
System.Environment.SetEnvironmentVariable(name, null, EnvironmentVariableTarget.Process);
BroadcastEnvironmentChange();
}

View File

@@ -4,6 +4,8 @@ public interface IEnvironmentVariableStore
{
string? Get(string name);
string? GetUserPersistedValue(string name);
void Set(string name, string value);
bool Exists(string name);

View File

@@ -41,10 +41,18 @@
Click="OnCloseClick" />
</Grid>
<CheckBox Grid.Row="1"
Margin="12,8,12,0"
Content="Отображать все переменные"
IsChecked="{Binding ShowAllVariables}" />
<Grid Grid.Row="1"
Margin="12,8,12,0"
ColumnDefinitions="*,Auto"
ColumnSpacing="8">
<CheckBox Content="Отображать все переменные"
IsChecked="{Binding ShowAllVariables}"
VerticalAlignment="Center" />
<Button Grid.Column="1"
Content="Обновить"
Command="{Binding RefreshCommand}"
MinWidth="100" />
</Grid>
<Grid Grid.Row="2"
Margin="12,8,12,0"
@@ -92,13 +100,21 @@
<DataGridTextColumn Header="Поле"
Binding="{Binding Field}"
IsReadOnly="True"
Width="2*" />
<DataGridTextColumn Header="Значение"
Binding="{Binding Value, Mode=TwoWay}"
Width="3*" />
Width="210"
MinWidth="150" />
<DataGridTextColumn Header="Требуемое значение"
Binding="{Binding RequiredValue, Mode=TwoWay}"
Width="2*"
MinWidth="140" />
<DataGridTextColumn Header="Актуальное значение"
Binding="{Binding ActualValueDisplay}"
IsReadOnly="True"
Width="2*"
MinWidth="140" />
<DataGridTextColumn Header="Комментарий"
Binding="{Binding Comment, Mode=TwoWay}"
Width="3*" />
Width="240"
MinWidth="180" />
<DataGridTemplateColumn Header=""
Width="80">
<DataGridTemplateColumn.CellTemplate>

View File

@@ -49,7 +49,7 @@ public sealed class EnvironmentVariablesService
continue;
}
var value = ResolveValue(name);
var value = ResolveRequiredValue(name);
_store.Set(name, value);
_log.WriteLine(
$"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [INFO] Created configured variable: {name}={value}");
@@ -64,7 +64,7 @@ public sealed class EnvironmentVariablesService
foreach (var name in _options.Names)
{
rows.Add(CreateRow(name, ResolveValue(name)));
rows.Add(CreateRow(name, ResolveRequiredValue(name)));
knownNames.Add(name);
}
@@ -72,7 +72,7 @@ public sealed class EnvironmentVariablesService
{
if (knownNames.Add(name))
{
rows.Add(CreateRow(name, ResolveValue(name)));
rows.Add(CreateRow(name, ResolveRequiredValue(name)));
}
}
@@ -89,7 +89,7 @@ public sealed class EnvironmentVariablesService
continue;
}
rows.Add(CreateRow(pair.Key, pair.Value));
rows.Add(CreateRow(pair.Key, ResolveRequiredValue(pair.Key)));
}
return rows;
@@ -137,14 +137,17 @@ public sealed class EnvironmentVariablesService
public EnvironmentVariableRow GetRowSnapshot(string name)
{
ReloadMetadata();
return CreateRow(name, ResolveValue(name));
return CreateRow(name, ResolveRequiredValue(name));
}
public string GetDisplayValue(string name) => ResolveValue(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)
{
var previous = _store.Get(name);
var previous = _store.GetUserPersistedValue(name);
_store.Set(name, value);
_log.WriteLine(
$"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [INFO] Changed value: {name}={value} (previous: {previous ?? "<none>"})");
@@ -183,7 +186,7 @@ public sealed class EnvironmentVariablesService
return;
}
var previous = _store.Get(name);
var previous = _store.GetUserPersistedValue(name);
_store.RemoveFromUserStore(name);
_log.WriteLine(
$"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [INFO] Removed from user store: {name} (previous: {previous ?? "<none>"})");
@@ -215,17 +218,21 @@ public sealed class EnvironmentVariablesService
string.Equals(name, _options.CommentsVariableName, StringComparison.Ordinal)
|| string.Equals(name, _options.CustomVariablesVariableName, StringComparison.Ordinal);
private string ResolveValue(string name)
private string ResolveRequiredValue(string name)
{
var existing = _store.Get(name);
if (existing is not null)
if (_store.GetUserPersistedValue(name) is { } persisted)
{
return existing;
return persisted;
}
if (_options.Defaults.TryGetValue(name, out var defaultValue))
if (_configuredNames.Contains(name) || _customNames.Contains(name))
{
return defaultValue;
if (_options.Defaults.TryGetValue(name, out var defaultValue))
{
return defaultValue;
}
return string.Empty;
}
return string.Empty;

View File

@@ -17,6 +17,7 @@ public sealed partial class EnvironmentVariableRowViewModel : ObservableObject
IsCustom = row.IsCustom;
_service = service;
ApplySnapshot(row, suppressSave: true);
RefreshActualValue(_service.GetProcessValue(Field));
}
public string Field { get; }
@@ -28,10 +29,16 @@ public sealed partial class EnvironmentVariableRowViewModel : ObservableObject
[ObservableProperty]
private bool _isPersistedInUserStore;
[ObservableProperty]
private string _actualValue = string.Empty;
public string ActualValueDisplay =>
string.IsNullOrEmpty(ActualValue) ? "<нет>" : ActualValue;
public string UserStoreBadge => IsPersistedInUserStore ? "USER" : string.Empty;
[ObservableProperty]
private string _value = string.Empty;
private string _requiredValue = string.Empty;
[ObservableProperty]
private string _comment = string.Empty;
@@ -43,30 +50,9 @@ public sealed partial class EnvironmentVariableRowViewModel : ObservableObject
RowAppearanceChanged?.Invoke(this, EventArgs.Empty);
}
public event EventHandler? RowAppearanceChanged;
partial void OnActualValueChanged(string value) => OnPropertyChanged(nameof(ActualValueDisplay));
public void ApplySnapshot(EnvironmentVariableRow row, bool suppressSave = false)
{
if (suppressSave)
{
BeginLoad();
}
Value = row.Value;
Comment = row.Comment;
IsPersistedInUserStore = row.IsPersistedInUserStore;
if (suppressSave)
{
EndLoad();
}
}
public void BeginLoad() => _isLoading = true;
public void EndLoad() => _isLoading = false;
partial void OnValueChanged(string value)
partial void OnRequiredValueChanged(string value)
{
if (_isLoading)
{
@@ -78,8 +64,38 @@ public sealed partial class EnvironmentVariableRowViewModel : ObservableObject
{
IsPersistedInUserStore = true;
}
RefreshActualValue(_service.GetProcessValue(Field));
}
public event EventHandler? RowAppearanceChanged;
public void ApplySnapshot(EnvironmentVariableRow row, bool suppressSave = false)
{
if (suppressSave)
{
BeginLoad();
}
RequiredValue = row.Value;
Comment = row.Comment;
IsPersistedInUserStore = row.IsPersistedInUserStore;
if (suppressSave)
{
EndLoad();
}
}
public void RefreshActualValue(string? processValue)
{
ActualValue = processValue ?? string.Empty;
}
public void BeginLoad() => _isLoading = true;
public void EndLoad() => _isLoading = false;
partial void OnCommentChanged(string value)
{
if (_isLoading)
@@ -103,8 +119,9 @@ public sealed partial class EnvironmentVariableRowViewModel : ObservableObject
_service.DeleteFromUserStore(Field);
BeginLoad();
IsPersistedInUserStore = false;
Value = _service.GetDisplayValue(Field);
RequiredValue = _service.GetDisplayValue(Field);
EndLoad();
RefreshActualValue(_service.GetProcessValue(Field));
}
public event EventHandler? Removed;

View File

@@ -35,6 +35,13 @@ public sealed partial class MainWindowViewModel : ObservableObject
partial void OnNewVariableNameChanged(string value) => AddVariableCommand.NotifyCanExecuteChanged();
[RelayCommand]
private void Refresh()
{
SyncRowsFromService();
RefreshProcessStates();
}
[RelayCommand(CanExecute = nameof(CanAddVariable))]
private void AddVariable()
{
@@ -121,6 +128,16 @@ public sealed partial class MainWindowViewModel : ObservableObject
MoveItem(Rows, currentIndex, targetIndex);
}
}
RefreshProcessStates();
}
private void RefreshProcessStates()
{
foreach (var row in Rows)
{
row.RefreshActualValue(_service.GetProcessValue(row.Field));
}
}
private static void MoveItem(