WIP and refactor start on Installer Config Dialog
This commit is contained in:
@@ -7,3 +7,4 @@
|
||||
**/obj/
|
||||
|
||||
vm/
|
||||
.vs/
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
@@ -2,6 +2,10 @@
|
||||
"Version": 1,
|
||||
"WorkspaceRootPath": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\",
|
||||
"Documents": [
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}|Covenant.Setup.Authoring.csproj|c:\\users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\mainwindow.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}|Covenant.Setup.Authoring.csproj|solutionrelative:mainwindow.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}|Covenant.Setup.Authoring.csproj|c:\\users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\app.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}",
|
||||
"RelativeMoniker": "D:0:0:{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}|Covenant.Setup.Authoring.csproj|solutionrelative:app.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}"
|
||||
@@ -18,11 +22,24 @@
|
||||
"DocumentGroups": [
|
||||
{
|
||||
"DockedWidth": 200,
|
||||
"SelectedChildIndex": 1,
|
||||
"SelectedChildIndex": 0,
|
||||
"Children": [
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 1,
|
||||
"DocumentIndex": 0,
|
||||
"Title": "MainWindow.cs",
|
||||
"DocumentMoniker": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\MainWindow.cs",
|
||||
"RelativeDocumentMoniker": "MainWindow.cs",
|
||||
"ToolTip": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\MainWindow.cs",
|
||||
"RelativeToolTip": "MainWindow.cs",
|
||||
"ViewState": "AgIAABsBAAAAAAAAAIA5wBsBAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2026-05-05T23:22:18.637Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 2,
|
||||
"Title": "Program.cs",
|
||||
"DocumentMoniker": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\Program.cs",
|
||||
"RelativeDocumentMoniker": "Program.cs",
|
||||
@@ -30,12 +47,11 @@
|
||||
"RelativeToolTip": "Program.cs",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAsAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2026-05-05T00:32:51.733Z",
|
||||
"EditorCaption": ""
|
||||
"WhenOpened": "2026-05-05T00:32:51.733Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 0,
|
||||
"DocumentIndex": 1,
|
||||
"Title": "App.xaml",
|
||||
"DocumentMoniker": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\App.xaml",
|
||||
"RelativeDocumentMoniker": "App.xaml",
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
"Version": 1,
|
||||
"WorkspaceRootPath": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\",
|
||||
"Documents": [
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}|Covenant.Setup.Authoring.csproj|c:\\users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\installerconfigdialog.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}",
|
||||
"RelativeMoniker": "D:0:0:{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}|Covenant.Setup.Authoring.csproj|solutionrelative:installerconfigdialog.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}|Covenant.Setup.Authoring.csproj|c:\\users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\mainwindow.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}|Covenant.Setup.Authoring.csproj|solutionrelative:mainwindow.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}|Covenant.Setup.Authoring.csproj|c:\\users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\app.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}",
|
||||
"RelativeMoniker": "D:0:0:{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}|Covenant.Setup.Authoring.csproj|solutionrelative:app.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}"
|
||||
@@ -18,11 +26,37 @@
|
||||
"DocumentGroups": [
|
||||
{
|
||||
"DockedWidth": 200,
|
||||
"SelectedChildIndex": 1,
|
||||
"SelectedChildIndex": 0,
|
||||
"Children": [
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 0,
|
||||
"Title": "InstallerConfigDialog.xaml",
|
||||
"DocumentMoniker": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\InstallerConfigDialog.xaml",
|
||||
"RelativeDocumentMoniker": "InstallerConfigDialog.xaml",
|
||||
"ToolTip": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\InstallerConfigDialog.xaml*",
|
||||
"RelativeToolTip": "InstallerConfigDialog.xaml*",
|
||||
"ViewState": "AgIAABwAAAAAAAAAAAAAACMAAAAZAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003549|",
|
||||
"WhenOpened": "2026-05-06T00:18:02.89Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 1,
|
||||
"Title": "MainWindow.cs",
|
||||
"DocumentMoniker": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\MainWindow.cs",
|
||||
"RelativeDocumentMoniker": "MainWindow.cs",
|
||||
"ToolTip": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\MainWindow.cs",
|
||||
"RelativeToolTip": "MainWindow.cs",
|
||||
"ViewState": "AgIAABsBAAAAAAAAAIA5wBsBAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2026-05-05T23:22:18.637Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 3,
|
||||
"Title": "Program.cs",
|
||||
"DocumentMoniker": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\Program.cs",
|
||||
"RelativeDocumentMoniker": "Program.cs",
|
||||
@@ -30,12 +64,11 @@
|
||||
"RelativeToolTip": "Program.cs",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAsAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2026-05-05T00:32:51.733Z",
|
||||
"EditorCaption": ""
|
||||
"WhenOpened": "2026-05-05T00:32:51.733Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 0,
|
||||
"DocumentIndex": 2,
|
||||
"Title": "App.xaml",
|
||||
"DocumentMoniker": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\App.xaml",
|
||||
"RelativeDocumentMoniker": "App.xaml",
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<ContentDialog
|
||||
x:Class="Covenant.Setup.Authoring.InstallerConfigDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Covenant.Setup.Authoring"
|
||||
Title="Installer Config"
|
||||
PrimaryButtonText="Save"
|
||||
IsPrimaryButtonEnabled="{Binding CanSaveConfig, Mode=OneWay}"
|
||||
PrimaryButtonClick="SaveButton_Click"
|
||||
CloseButtonText="Close">
|
||||
|
||||
<Grid
|
||||
MinWidth="640"
|
||||
ColumnSpacing="8"
|
||||
RowSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Text="{Binding StatusText}"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<TextBox
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
MaxWidth="200"
|
||||
Header="Covenant Setup Executable"
|
||||
PlaceholderText="C:\path\to\covenant-setup.exe"
|
||||
Text="{Binding CovenantSetupPath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
|
||||
<Button
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
MinWidth="96"
|
||||
VerticalAlignment="Bottom"
|
||||
Content="Browse"
|
||||
Click="BrowseCovenantSetupButton_Click" />
|
||||
|
||||
<TextBox
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
MaxWidth="200"
|
||||
Header="Output Directory"
|
||||
PlaceholderText="C:\path\to\dist"
|
||||
Text="{Binding OutputDirectory, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
|
||||
<Button
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
MinWidth="96"
|
||||
VerticalAlignment="Bottom"
|
||||
Content="Choose"
|
||||
Click="ChooseOutputDirectoryButton_Click" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.ColumnSpan="2"
|
||||
Text="{Binding MessageText}"
|
||||
TextWrapping="Wrap" />
|
||||
</Grid>
|
||||
</ContentDialog>
|
||||
@@ -0,0 +1,64 @@
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Windows.Storage.Pickers;
|
||||
using WinRT.Interop;
|
||||
|
||||
namespace Covenant.Setup.Authoring;
|
||||
|
||||
internal sealed partial class InstallerConfigDialog : ContentDialog
|
||||
{
|
||||
private readonly InstallerConfigViewModel _viewModel;
|
||||
private readonly IntPtr _ownerWindowHandle;
|
||||
|
||||
public InstallerConfigDialog(InstallerConfigViewModel viewModel, IntPtr ownerWindowHandle)
|
||||
{
|
||||
_viewModel = viewModel;
|
||||
_ownerWindowHandle = ownerWindowHandle;
|
||||
InitializeComponent();
|
||||
DataContext = viewModel;
|
||||
}
|
||||
|
||||
private async void BrowseCovenantSetupButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs args)
|
||||
{
|
||||
var picker = new FileOpenPicker
|
||||
{
|
||||
SuggestedStartLocation = PickerLocationId.ComputerFolder
|
||||
};
|
||||
InitializeWithWindow.Initialize(picker, _ownerWindowHandle);
|
||||
picker.FileTypeFilter.Add(".exe");
|
||||
|
||||
var file = await picker.PickSingleFileAsync();
|
||||
if (file is not null)
|
||||
{
|
||||
_ = await _viewModel.AcceptToolPathAsync(file.Path);
|
||||
}
|
||||
}
|
||||
|
||||
private async void ChooseOutputDirectoryButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs args)
|
||||
{
|
||||
var picker = new FolderPicker
|
||||
{
|
||||
SuggestedStartLocation = PickerLocationId.DocumentsLibrary
|
||||
};
|
||||
InitializeWithWindow.Initialize(picker, _ownerWindowHandle);
|
||||
picker.FileTypeFilter.Add("*");
|
||||
|
||||
var folder = await picker.PickSingleFolderAsync();
|
||||
if (folder is not null)
|
||||
{
|
||||
_viewModel.OutputDirectory = folder.Path;
|
||||
}
|
||||
}
|
||||
|
||||
private async void SaveButton_Click(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
{
|
||||
var deferral = args.GetDeferral();
|
||||
try
|
||||
{
|
||||
args.Cancel = !await _viewModel.SaveConfigAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
deferral.Complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,434 @@
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Covenant.Setup.Authoring;
|
||||
|
||||
internal enum InstallerConfigStatusKind
|
||||
{
|
||||
Neutral,
|
||||
Success,
|
||||
Error
|
||||
}
|
||||
|
||||
internal sealed class InstallerConfigViewModel : INotifyPropertyChanged
|
||||
{
|
||||
private const string MissingToolStatus = "covenant-setup.exe was not found. Packaging is disabled.";
|
||||
private const string UnvalidatedToolStatus = "covenant-setup.exe was not validated. Packaging is disabled.";
|
||||
|
||||
private readonly Func<CovenantSetupTool?> _locateCovenantSetupTool;
|
||||
private readonly Func<string, CancellationToken, Task<ToolValidationResult>> _validateCovenantSetupTool;
|
||||
private CovenantSetupTool? _covenantSetupTool;
|
||||
private string _covenantSetupPath = string.Empty;
|
||||
private string _outputDirectory = Path.Combine(Environment.CurrentDirectory, "dist");
|
||||
private string _statusText = MissingToolStatus;
|
||||
private string _messageText = string.Empty;
|
||||
private InstallerConfigStatusKind _statusKind = InstallerConfigStatusKind.Error;
|
||||
private InstallerConfigStatusKind _messageKind = InstallerConfigStatusKind.Neutral;
|
||||
private bool _isCheckingTool;
|
||||
private bool _isBuilding;
|
||||
private bool _hasManifestValidationErrors;
|
||||
private bool _syncingAcceptedToolPath;
|
||||
|
||||
public InstallerConfigViewModel()
|
||||
: this(CovenantSetupToolLocator.Find, CovenantSetupToolValidator.ValidateAsync)
|
||||
{
|
||||
}
|
||||
|
||||
internal InstallerConfigViewModel(
|
||||
Func<CovenantSetupTool?> locateCovenantSetupTool,
|
||||
Func<string, CancellationToken, Task<ToolValidationResult>> validateCovenantSetupTool)
|
||||
{
|
||||
_locateCovenantSetupTool = locateCovenantSetupTool;
|
||||
_validateCovenantSetupTool = validateCovenantSetupTool;
|
||||
RefreshToolCheckCommand = new RelayCommand(RefreshToolCheck, () => !IsBusy);
|
||||
ValidateToolPathCommand = new AsyncRelayCommand(ValidateToolPathAsync, () => CanValidateToolPath);
|
||||
SaveConfigCommand = new AsyncRelayCommand(SaveConfigAsync, () => CanSaveConfig);
|
||||
RefreshToolCheck();
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
public ICommand RefreshToolCheckCommand { get; }
|
||||
|
||||
public ICommand ValidateToolPathCommand { get; }
|
||||
|
||||
public ICommand SaveConfigCommand { get; }
|
||||
|
||||
public CovenantSetupTool? CovenantSetupTool
|
||||
{
|
||||
get => _covenantSetupTool;
|
||||
private set
|
||||
{
|
||||
if (SetProperty(ref _covenantSetupTool, value))
|
||||
{
|
||||
OnPropertyChanged(nameof(HasCovenantSetupTool));
|
||||
OnPropertyChanged(nameof(CanSaveConfig));
|
||||
RaiseCommandStatesChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasCovenantSetupTool => CovenantSetupTool is not null;
|
||||
|
||||
public string CovenantSetupPath
|
||||
{
|
||||
get => _covenantSetupPath;
|
||||
set
|
||||
{
|
||||
if (!SetProperty(ref _covenantSetupPath, value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OnPropertyChanged(nameof(CanValidateToolPath));
|
||||
RaiseCommandStatesChanged();
|
||||
|
||||
if (!_syncingAcceptedToolPath && !IsAcceptedToolPath(value))
|
||||
{
|
||||
ClearValidatedTool(UnvalidatedToolStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string OutputDirectory
|
||||
{
|
||||
get => _outputDirectory;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _outputDirectory, value))
|
||||
{
|
||||
OnPropertyChanged(nameof(CanSaveConfig));
|
||||
RaiseCommandStatesChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string StatusText
|
||||
{
|
||||
get => _statusText;
|
||||
private set => SetProperty(ref _statusText, value);
|
||||
}
|
||||
|
||||
public InstallerConfigStatusKind StatusKind
|
||||
{
|
||||
get => _statusKind;
|
||||
private set => SetProperty(ref _statusKind, value);
|
||||
}
|
||||
|
||||
public string MessageText
|
||||
{
|
||||
get => _messageText;
|
||||
private set => SetProperty(ref _messageText, value);
|
||||
}
|
||||
|
||||
public InstallerConfigStatusKind MessageKind
|
||||
{
|
||||
get => _messageKind;
|
||||
private set => SetProperty(ref _messageKind, value);
|
||||
}
|
||||
|
||||
public bool IsCheckingTool
|
||||
{
|
||||
get => _isCheckingTool;
|
||||
private set
|
||||
{
|
||||
if (SetProperty(ref _isCheckingTool, value))
|
||||
{
|
||||
NotifyBusyStateChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsBuilding
|
||||
{
|
||||
get => _isBuilding;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _isBuilding, value))
|
||||
{
|
||||
NotifyBusyStateChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsBusy => IsCheckingTool || IsBuilding;
|
||||
|
||||
public bool HasManifestValidationErrors
|
||||
{
|
||||
get => _hasManifestValidationErrors;
|
||||
private set
|
||||
{
|
||||
if (SetProperty(ref _hasManifestValidationErrors, value))
|
||||
{
|
||||
OnPropertyChanged(nameof(CanSaveConfig));
|
||||
RaiseCommandStatesChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanValidateToolPath =>
|
||||
!IsBusy &&
|
||||
!string.IsNullOrWhiteSpace(CovenantSetupPath);
|
||||
|
||||
public bool CanSaveConfig =>
|
||||
!IsBusy &&
|
||||
!HasManifestValidationErrors &&
|
||||
!string.IsNullOrWhiteSpace(CovenantSetupPath) &&
|
||||
!string.IsNullOrWhiteSpace(OutputDirectory);
|
||||
|
||||
public void RefreshToolCheck()
|
||||
{
|
||||
if (IsBusy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var tool = _locateCovenantSetupTool();
|
||||
if (tool is null)
|
||||
{
|
||||
ClearValidatedTool(MissingToolStatus);
|
||||
SetMessage(string.Empty, InstallerConfigStatusKind.Neutral);
|
||||
return;
|
||||
}
|
||||
|
||||
AcceptValidatedTool(tool);
|
||||
SetMessage(string.Empty, InstallerConfigStatusKind.Neutral);
|
||||
}
|
||||
|
||||
public async Task<bool> ValidateToolPathAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await AcceptToolPathAsync(CovenantSetupPath, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<bool> AcceptToolPathAsync(
|
||||
string path,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (IsBusy)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
SyncToolPath(string.Empty);
|
||||
ClearValidatedTool(MissingToolStatus);
|
||||
SetMessage(string.Empty, InstallerConfigStatusKind.Neutral);
|
||||
return false;
|
||||
}
|
||||
|
||||
IsCheckingTool = true;
|
||||
SetMessage("Checking covenant-setup...", InstallerConfigStatusKind.Neutral);
|
||||
try
|
||||
{
|
||||
SyncToolPath(path.Trim());
|
||||
var result = await _validateCovenantSetupTool(CovenantSetupPath, cancellationToken);
|
||||
if (result.IsValid && result.Tool is not null)
|
||||
{
|
||||
AcceptValidatedTool(result.Tool);
|
||||
SetMessage(string.Empty, InstallerConfigStatusKind.Neutral);
|
||||
return true;
|
||||
}
|
||||
|
||||
ClearValidatedTool(result.Message);
|
||||
SetMessage(
|
||||
result.Message + " Pick a valid covenant-setup executable.",
|
||||
InstallerConfigStatusKind.Error);
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsCheckingTool = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetManifestValidationState(bool hasErrors)
|
||||
{
|
||||
HasManifestValidationErrors = hasErrors;
|
||||
}
|
||||
|
||||
public async Task<bool> SaveConfigAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (IsBuilding)
|
||||
{
|
||||
SetMessage("Installer configuration cannot be saved while a build is running.", InstallerConfigStatusKind.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (HasManifestValidationErrors)
|
||||
{
|
||||
SetMessage("Resolve manifest validation errors before saving installer configuration.", InstallerConfigStatusKind.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsAcceptedToolPath(CovenantSetupPath))
|
||||
{
|
||||
var accepted = await ValidateToolPathAsync(cancellationToken);
|
||||
if (!accepted)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(OutputDirectory))
|
||||
{
|
||||
SetMessage("Choose an output directory before saving installer configuration.", InstallerConfigStatusKind.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
SetMessage("Installer configuration saved.", InstallerConfigStatusKind.Success);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ClearMessage()
|
||||
{
|
||||
SetMessage(string.Empty, InstallerConfigStatusKind.Neutral);
|
||||
}
|
||||
|
||||
private void AcceptValidatedTool(CovenantSetupTool tool)
|
||||
{
|
||||
CovenantSetupTool = tool;
|
||||
SyncToolPath(tool.Path);
|
||||
StatusText = "Packaging enabled: " + tool.Path;
|
||||
StatusKind = InstallerConfigStatusKind.Success;
|
||||
}
|
||||
|
||||
private void ClearValidatedTool(string statusText)
|
||||
{
|
||||
CovenantSetupTool = null;
|
||||
StatusText = statusText;
|
||||
StatusKind = InstallerConfigStatusKind.Error;
|
||||
}
|
||||
|
||||
private void SyncToolPath(string path)
|
||||
{
|
||||
_syncingAcceptedToolPath = true;
|
||||
try
|
||||
{
|
||||
CovenantSetupPath = path;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_syncingAcceptedToolPath = false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsAcceptedToolPath(string path) =>
|
||||
CovenantSetupTool is not null &&
|
||||
string.Equals(path?.Trim(), CovenantSetupTool.Path, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
private void SetMessage(string message, InstallerConfigStatusKind kind)
|
||||
{
|
||||
MessageText = message;
|
||||
MessageKind = kind;
|
||||
}
|
||||
|
||||
private void NotifyBusyStateChanged()
|
||||
{
|
||||
OnPropertyChanged(nameof(IsBusy));
|
||||
OnPropertyChanged(nameof(CanValidateToolPath));
|
||||
OnPropertyChanged(nameof(CanSaveConfig));
|
||||
RaiseCommandStatesChanged();
|
||||
}
|
||||
|
||||
private bool SetProperty<T>(
|
||||
ref T field,
|
||||
T value,
|
||||
[CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(field, value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
field = value;
|
||||
OnPropertyChanged(propertyName);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
if (propertyName is not null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
|
||||
private void RaiseCommandStatesChanged()
|
||||
{
|
||||
((RelayCommand)RefreshToolCheckCommand).RaiseCanExecuteChanged();
|
||||
((AsyncRelayCommand)ValidateToolPathCommand).RaiseCanExecuteChanged();
|
||||
((AsyncRelayCommand)SaveConfigCommand).RaiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
private sealed class RelayCommand : ICommand
|
||||
{
|
||||
private readonly Action _execute;
|
||||
private readonly Func<bool>? _canExecute;
|
||||
|
||||
public RelayCommand(Action execute, Func<bool>? canExecute = null)
|
||||
{
|
||||
_execute = execute;
|
||||
_canExecute = canExecute;
|
||||
}
|
||||
|
||||
public event EventHandler? CanExecuteChanged;
|
||||
|
||||
public bool CanExecute(object? parameter) => _canExecute?.Invoke() ?? true;
|
||||
|
||||
public void Execute(object? parameter) => _execute();
|
||||
|
||||
public void RaiseCanExecuteChanged()
|
||||
{
|
||||
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class AsyncRelayCommand : ICommand
|
||||
{
|
||||
private readonly Func<CancellationToken, Task> _execute;
|
||||
private readonly Func<bool>? _canExecute;
|
||||
private CancellationTokenSource? _currentExecution;
|
||||
|
||||
public AsyncRelayCommand(
|
||||
Func<CancellationToken, Task> execute,
|
||||
Func<bool>? canExecute = null)
|
||||
{
|
||||
_execute = execute;
|
||||
_canExecute = canExecute;
|
||||
}
|
||||
|
||||
public event EventHandler? CanExecuteChanged;
|
||||
|
||||
public bool CanExecute(object? parameter) =>
|
||||
_currentExecution is null &&
|
||||
(_canExecute?.Invoke() ?? true);
|
||||
|
||||
public async void Execute(object? parameter)
|
||||
{
|
||||
if (!CanExecute(parameter))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_currentExecution = new CancellationTokenSource();
|
||||
RaiseCanExecuteChanged();
|
||||
try
|
||||
{
|
||||
await _execute(_currentExecution.Token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_currentExecution?.Dispose();
|
||||
_currentExecution = null;
|
||||
RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void RaiseCanExecuteChanged()
|
||||
{
|
||||
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
-311
@@ -20,19 +20,13 @@ namespace Covenant.Setup.Authoring;
|
||||
internal sealed class MainWindow : Window
|
||||
{
|
||||
private readonly MainViewModel _viewModel = new();
|
||||
private readonly InstallerConfigViewModel _installerConfigViewModel = new();
|
||||
private readonly List<Border> _sectionBorders = [];
|
||||
private readonly List<Border> _rowBorders = [];
|
||||
private readonly List<TextBlock> _secondaryTextBlocks = [];
|
||||
private TextBlock _statusText = null!;
|
||||
private TextBlock _toolStatusText = null!;
|
||||
private TextBlock _installerDialogMessageText = null!;
|
||||
private TextBox _previewBox = null!;
|
||||
private Button _copyPreviewButton = null!;
|
||||
private TextBox _covenantSetupPathBox = null!;
|
||||
private Button _browseCovenantSetupButton = null!;
|
||||
private Button _refreshToolButton = null!;
|
||||
private TextBox _outputDirectoryBox = null!;
|
||||
private Button _chooseOutputButton = null!;
|
||||
private Button _generateInstallerButton = null!;
|
||||
private Grid _rootGrid = null!;
|
||||
private ToggleSwitch _themeToggle = null!;
|
||||
@@ -41,7 +35,6 @@ internal sealed class MainWindow : Window
|
||||
private int _copyPreviewFeedbackVersion;
|
||||
private bool _syncingThemeToggle;
|
||||
private bool _usingSystemTheme;
|
||||
private bool _syncingCovenantSetupPath;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
@@ -56,10 +49,7 @@ internal sealed class MainWindow : Window
|
||||
RefreshStatusBrush();
|
||||
}
|
||||
if (args.PropertyName is nameof(MainViewModel.HasCovenantSetupTool)
|
||||
or nameof(MainViewModel.CanPackage)
|
||||
or nameof(MainViewModel.CovenantSetupStatus)
|
||||
or nameof(MainViewModel.CovenantSetupPath)
|
||||
or nameof(MainViewModel.OutputDirectory))
|
||||
or nameof(MainViewModel.CanPackage))
|
||||
{
|
||||
RefreshPackageControls();
|
||||
}
|
||||
@@ -291,97 +281,6 @@ internal sealed class MainWindow : Window
|
||||
Labeled("Primary Payload", payloadBox));
|
||||
}
|
||||
|
||||
private FrameworkElement BuildInstallerConfigContent()
|
||||
{
|
||||
_toolStatusText = new TextBlock
|
||||
{
|
||||
TextWrapping = TextWrapping.Wrap
|
||||
};
|
||||
_toolStatusText.SetBinding(
|
||||
TextBlock.TextProperty,
|
||||
new Binding
|
||||
{
|
||||
Path = new PropertyPath(nameof(MainViewModel.CovenantSetupStatus)),
|
||||
Mode = BindingMode.OneWay
|
||||
});
|
||||
|
||||
_covenantSetupPathBox = new TextBox
|
||||
{
|
||||
PlaceholderText = @"C:\path\to\covenant-setup.exe",
|
||||
Text = _viewModel.CovenantSetupPath
|
||||
};
|
||||
_covenantSetupPathBox.TextChanged += (_, _) =>
|
||||
{
|
||||
if (_syncingCovenantSetupPath)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.Equals(_covenantSetupPathBox.Text, _viewModel.CovenantSetupPath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_viewModel.RejectCovenantSetupTool("covenant-setup.exe was not validated. Packaging is disabled.");
|
||||
}
|
||||
};
|
||||
_covenantSetupPathBox.LostFocus += async (_, _) => await ValidateTypedCovenantSetupPathAsync(showInvalidMessage: true);
|
||||
|
||||
_browseCovenantSetupButton = new Button
|
||||
{
|
||||
Content = "Browse",
|
||||
Width = 96
|
||||
};
|
||||
_browseCovenantSetupButton.Click += async (_, _) => await BrowseCovenantSetupPathAsync();
|
||||
|
||||
_outputDirectoryBox = BoundTextBox(nameof(MainViewModel.OutputDirectory), @"C:\path\to\dist");
|
||||
|
||||
_chooseOutputButton = new Button
|
||||
{
|
||||
Content = "Choose",
|
||||
Width = 96
|
||||
};
|
||||
_chooseOutputButton.Click += async (_, _) => await ChooseOutputDirectoryAsync();
|
||||
|
||||
_refreshToolButton = new Button
|
||||
{
|
||||
Content = "Refresh Tool Check",
|
||||
Width = 156
|
||||
};
|
||||
_refreshToolButton.Click += (_, _) =>
|
||||
{
|
||||
_viewModel.RefreshCovenantSetupTool();
|
||||
SyncCovenantSetupPathBox();
|
||||
SetInstallerDialogMessage(string.Empty);
|
||||
};
|
||||
|
||||
var actionRow = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
HorizontalAlignment = HorizontalAlignment.Right,
|
||||
Spacing = 8
|
||||
};
|
||||
actionRow.Children.Add(_refreshToolButton);
|
||||
|
||||
_installerDialogMessageText = new TextBlock
|
||||
{
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
MaxLines = 6
|
||||
};
|
||||
_secondaryTextBlocks.Add(_installerDialogMessageText);
|
||||
|
||||
var stack = new StackPanel
|
||||
{
|
||||
Spacing = 10,
|
||||
MinWidth = 640,
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch
|
||||
};
|
||||
stack.Children.Add(_toolStatusText);
|
||||
stack.Children.Add(DialogFieldGrid(
|
||||
("Covenant Setup Executable", _covenantSetupPathBox, _browseCovenantSetupButton),
|
||||
("Output Directory", _outputDirectoryBox, _chooseOutputButton)));
|
||||
stack.Children.Add(actionRow);
|
||||
stack.Children.Add(_installerDialogMessageText);
|
||||
return stack;
|
||||
}
|
||||
|
||||
private FrameworkElement BuildDirectoriesSection()
|
||||
{
|
||||
var pathBox = new TextBox { PlaceholderText = @"{LocalAppData}\Vendor\App" };
|
||||
@@ -505,42 +404,24 @@ internal sealed class MainWindow : Window
|
||||
|
||||
private async Task ShowInstallerConfigAsync()
|
||||
{
|
||||
var dialog = new ContentDialog
|
||||
{
|
||||
Title = "Installer Config",
|
||||
CloseButtonText = "Close",
|
||||
Width = 1080,
|
||||
MinWidth = 1080,
|
||||
Content = BuildInstallerConfigContent()
|
||||
};
|
||||
_installerConfigViewModel.SetManifestValidationState(_viewModel.HasValidationErrors);
|
||||
_installerConfigViewModel.IsBuilding = _isPackaging;
|
||||
|
||||
var dialog = new InstallerConfigDialog(
|
||||
_installerConfigViewModel,
|
||||
WindowNative.GetWindowHandle(this));
|
||||
if (Content is FrameworkElement root)
|
||||
{
|
||||
dialog.XamlRoot = root.XamlRoot;
|
||||
}
|
||||
|
||||
dialog.Closing += (_, args) =>
|
||||
var result = await dialog.ShowAsync();
|
||||
if (result == ContentDialogResult.Primary)
|
||||
{
|
||||
if (_isPackaging)
|
||||
{
|
||||
args.Cancel = true;
|
||||
}
|
||||
};
|
||||
|
||||
RefreshPackageControls();
|
||||
_ = await dialog.ShowAsync();
|
||||
|
||||
if (_installerDialogMessageText is not null)
|
||||
{
|
||||
_secondaryTextBlocks.Remove(_installerDialogMessageText);
|
||||
_viewModel.SetCovenantSetupTool(_installerConfigViewModel.CovenantSetupTool);
|
||||
_viewModel.OutputDirectory = _installerConfigViewModel.OutputDirectory;
|
||||
RefreshPackageControls();
|
||||
}
|
||||
_toolStatusText = null!;
|
||||
_installerDialogMessageText = null!;
|
||||
_covenantSetupPathBox = null!;
|
||||
_browseCovenantSetupButton = null!;
|
||||
_refreshToolButton = null!;
|
||||
_outputDirectoryBox = null!;
|
||||
_chooseOutputButton = null!;
|
||||
}
|
||||
|
||||
private async Task SaveManifestAsync()
|
||||
@@ -614,95 +495,6 @@ internal sealed class MainWindow : Window
|
||||
return file.Path;
|
||||
}
|
||||
|
||||
private async Task BrowseCovenantSetupPathAsync()
|
||||
{
|
||||
var picker = new FileOpenPicker();
|
||||
InitializeWithWindow.Initialize(picker, WindowNative.GetWindowHandle(this));
|
||||
picker.SuggestedStartLocation = PickerLocationId.ComputerFolder;
|
||||
picker.FileTypeFilter.Add(".exe");
|
||||
|
||||
var file = await picker.PickSingleFileAsync();
|
||||
if (file is not null)
|
||||
{
|
||||
await AcceptCovenantSetupPathAsync(file.Path, showInvalidMessage: true);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ValidateTypedCovenantSetupPathAsync(bool showInvalidMessage)
|
||||
{
|
||||
if (_covenantSetupPathBox is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var path = _covenantSetupPathBox.Text.Trim();
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
_viewModel.RejectCovenantSetupTool("covenant-setup.exe was not found. Packaging is disabled.");
|
||||
SetInstallerDialogMessage(string.Empty);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Equals(path, _viewModel.CovenantSetupPath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await AcceptCovenantSetupPathAsync(path, showInvalidMessage);
|
||||
}
|
||||
|
||||
private async Task<bool> AcceptCovenantSetupPathAsync(string path, bool showInvalidMessage)
|
||||
{
|
||||
SetInstallerDialogMessage("Checking covenant-setup...");
|
||||
var result = await CovenantSetupToolValidator.ValidateAsync(path, CancellationToken.None);
|
||||
if (result.IsValid && result.Tool is not null)
|
||||
{
|
||||
_viewModel.SetCovenantSetupTool(result.Tool);
|
||||
SyncCovenantSetupPathBox();
|
||||
SetInstallerDialogMessage(string.Empty);
|
||||
return true;
|
||||
}
|
||||
|
||||
_viewModel.RejectCovenantSetupTool(result.Message);
|
||||
if (showInvalidMessage)
|
||||
{
|
||||
SetInstallerDialogMessage(result.Message + " Run the help check by selecting the real covenant-setup.exe.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void SyncCovenantSetupPathBox()
|
||||
{
|
||||
if (_covenantSetupPathBox is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_syncingCovenantSetupPath = true;
|
||||
try
|
||||
{
|
||||
_covenantSetupPathBox.Text = _viewModel.CovenantSetupPath;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_syncingCovenantSetupPath = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ChooseOutputDirectoryAsync()
|
||||
{
|
||||
var picker = new FolderPicker();
|
||||
InitializeWithWindow.Initialize(picker, WindowNative.GetWindowHandle(this));
|
||||
picker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
|
||||
picker.FileTypeFilter.Add("*");
|
||||
|
||||
var folder = await picker.PickSingleFolderAsync();
|
||||
if (folder is not null)
|
||||
{
|
||||
_viewModel.OutputDirectory = folder.Path;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GenerateInstallerAsync(Func<string, string, Task>? showMessageAsync = null)
|
||||
{
|
||||
showMessageAsync ??= ShowNoticeAsync;
|
||||
@@ -734,6 +526,7 @@ internal sealed class MainWindow : Window
|
||||
_lastSavedManifestPath = manifestPath;
|
||||
|
||||
_isPackaging = true;
|
||||
_installerConfigViewModel.IsBuilding = true;
|
||||
RefreshPackageControls();
|
||||
try
|
||||
{
|
||||
@@ -758,6 +551,7 @@ internal sealed class MainWindow : Window
|
||||
finally
|
||||
{
|
||||
_isPackaging = false;
|
||||
_installerConfigViewModel.IsBuilding = false;
|
||||
RefreshPackageControls();
|
||||
}
|
||||
}
|
||||
@@ -806,14 +600,6 @@ internal sealed class MainWindow : Window
|
||||
AutomationProperties.SetName(_copyPreviewButton, "Copy TOML preview");
|
||||
}
|
||||
|
||||
private void SetInstallerDialogMessage(string message)
|
||||
{
|
||||
if (_installerDialogMessageText is not null)
|
||||
{
|
||||
_installerDialogMessageText.Text = message;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowNoticeAsync(string title, string message)
|
||||
{
|
||||
var dialog = new ContentDialog
|
||||
@@ -924,39 +710,11 @@ internal sealed class MainWindow : Window
|
||||
|
||||
private void RefreshPackageControls()
|
||||
{
|
||||
var controlsAvailable = !_isPackaging;
|
||||
if (_covenantSetupPathBox is not null)
|
||||
{
|
||||
_covenantSetupPathBox.IsEnabled = controlsAvailable;
|
||||
}
|
||||
if (_browseCovenantSetupButton is not null)
|
||||
{
|
||||
_browseCovenantSetupButton.IsEnabled = controlsAvailable;
|
||||
}
|
||||
if (_refreshToolButton is not null)
|
||||
{
|
||||
_refreshToolButton.IsEnabled = controlsAvailable;
|
||||
}
|
||||
if (_outputDirectoryBox is not null)
|
||||
{
|
||||
_outputDirectoryBox.IsEnabled = controlsAvailable;
|
||||
}
|
||||
if (_chooseOutputButton is not null)
|
||||
{
|
||||
_chooseOutputButton.IsEnabled = controlsAvailable;
|
||||
}
|
||||
if (_generateInstallerButton is not null)
|
||||
{
|
||||
_generateInstallerButton.IsEnabled = _viewModel.HasCovenantSetupTool && !_isPackaging;
|
||||
_generateInstallerButton.Content = _isPackaging ? "Building..." : "Save and Build";
|
||||
}
|
||||
var brushes = CreateThemeBrushes();
|
||||
if (_toolStatusText is not null)
|
||||
{
|
||||
_toolStatusText.Foreground = _viewModel.HasCovenantSetupTool
|
||||
? brushes.SuccessText
|
||||
: brushes.ErrorText;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyThemeBrushes()
|
||||
@@ -994,20 +752,6 @@ internal sealed class MainWindow : Window
|
||||
}
|
||||
|
||||
RefreshStatusBrush();
|
||||
RefreshPackageStatusBrush();
|
||||
}
|
||||
|
||||
private void RefreshPackageStatusBrush()
|
||||
{
|
||||
if (_toolStatusText is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var brushes = CreateThemeBrushes();
|
||||
_toolStatusText.Foreground = _viewModel.HasCovenantSetupTool
|
||||
? brushes.SuccessText
|
||||
: brushes.ErrorText;
|
||||
}
|
||||
|
||||
private ElementTheme EffectiveTheme()
|
||||
@@ -1123,47 +867,6 @@ internal sealed class MainWindow : Window
|
||||
return stack;
|
||||
}
|
||||
|
||||
private static Grid DialogFieldGrid(params (string Label, TextBox TextBox, Button Button)[] fields)
|
||||
{
|
||||
var grid = new Grid
|
||||
{
|
||||
ColumnSpacing = 6,
|
||||
RowSpacing = 2,
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch
|
||||
};
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
|
||||
|
||||
for (var index = 0; index < fields.Length; index++)
|
||||
{
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
||||
|
||||
var label = new TextBlock
|
||||
{
|
||||
Text = fields[index].Label,
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
FontSize = 12
|
||||
};
|
||||
Grid.SetRow(label, index * 2);
|
||||
Grid.SetColumn(label, 0);
|
||||
grid.Children.Add(label);
|
||||
|
||||
fields[index].TextBox.HorizontalAlignment = HorizontalAlignment.Stretch;
|
||||
Grid.SetRow(fields[index].TextBox, (index * 2) + 1);
|
||||
Grid.SetColumn(fields[index].TextBox, 0);
|
||||
grid.Children.Add(fields[index].TextBox);
|
||||
|
||||
fields[index].Button.HorizontalAlignment = HorizontalAlignment.Left;
|
||||
fields[index].Button.VerticalAlignment = VerticalAlignment.Bottom;
|
||||
Grid.SetRow(fields[index].Button, (index * 2) + 1);
|
||||
Grid.SetColumn(fields[index].Button, 1);
|
||||
grid.Children.Add(fields[index].Button);
|
||||
}
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
private static Grid TwoColumn(FrameworkElement left, FrameworkElement right)
|
||||
{
|
||||
var grid = new Grid { ColumnSpacing = 6 };
|
||||
|
||||
Reference in New Issue
Block a user