Browse Source

Add DialogHelper and text field dialog

pull/55/head
Ionite 1 year ago
parent
commit
68c082eab9
No known key found for this signature in database
  1. 79
      StabilityMatrix.Avalonia/Controls/BetterContentDialog.cs
  2. 167
      StabilityMatrix.Avalonia/DialogHelper.cs
  3. 59
      StabilityMatrix.Avalonia/ViewModels/CheckpointFile.cs

79
StabilityMatrix.Avalonia/Controls/BetterContentDialog.cs

@ -37,12 +37,51 @@ public class BetterContentDialog : ContentDialog
HideCoreMethod.Invoke(this, null);
}
// Also get button properties to hide on command execution change
[NotNull]
protected static readonly FieldInfo? PrimaryButtonField = typeof(ContentDialog).GetField(
"_primaryButton", BindingFlags.Instance | BindingFlags.NonPublic);
protected Button? PrimaryButton
{
get => (Button?) PrimaryButtonField.GetValue(this)!;
set => PrimaryButtonField.SetValue(this, value);
}
[NotNull]
protected static readonly FieldInfo? SecondaryButtonField = typeof(ContentDialog).GetField(
"_secondaryButton", BindingFlags.Instance | BindingFlags.NonPublic);
protected Button? SecondaryButton
{
get => (Button?) SecondaryButtonField.GetValue(this)!;
set => SecondaryButtonField.SetValue(this, value);
}
[NotNull]
protected static readonly FieldInfo? CloseButtonField = typeof(ContentDialog).GetField(
"_closeButton", BindingFlags.Instance | BindingFlags.NonPublic);
protected Button? CloseButton
{
get => (Button?) CloseButtonField.GetValue(this)!;
set => CloseButtonField.SetValue(this, value);
}
static BetterContentDialog()
{
if (ResultField is null) throw new NullReferenceException(
"ResultField was not resolved");
if (HideCoreMethod is null) throw new NullReferenceException(
"HideCoreMethod was not resolved");
if (ResultField is null)
{
throw new NullReferenceException("ResultField was not resolved");
}
if (HideCoreMethod is null)
{
throw new NullReferenceException("HideCoreMethod was not resolved");
}
if (PrimaryButtonField is null || SecondaryButtonField is null || CloseButtonField is null)
{
throw new NullReferenceException("Button fields were not resolved");
}
}
#endregion
@ -84,11 +123,35 @@ public class BetterContentDialog : ContentDialog
private void TryBindButtons()
{
if ((Content as Control)?.DataContext is not ContentDialogViewModelBase viewModel) return;
if ((Content as Control)?.DataContext is ContentDialogViewModelBase viewModel)
{
viewModel.PrimaryButtonClick += OnDialogButtonClick;
viewModel.SecondaryButtonClick += OnDialogButtonClick;
viewModel.CloseButtonClick += OnDialogButtonClick;
}
viewModel.PrimaryButtonClick += OnDialogButtonClick;
viewModel.SecondaryButtonClick += OnDialogButtonClick;
viewModel.CloseButtonClick += OnDialogButtonClick;
// If commands provided, bind OnCanExecuteChanged to hide buttons
if (PrimaryButtonCommand is not null && PrimaryButton is not null)
{
PrimaryButtonCommand.CanExecuteChanged += (_, _) =>
PrimaryButton.IsEnabled = PrimaryButtonCommand.CanExecute(null);
// Also set initial state
PrimaryButton.IsEnabled = PrimaryButtonCommand.CanExecute(null);
}
if (SecondaryButtonCommand is not null && SecondaryButton is not null)
{
SecondaryButtonCommand.CanExecuteChanged += (_, _) =>
SecondaryButton.IsEnabled = SecondaryButtonCommand.CanExecute(null);
// Also set initial state
SecondaryButton.IsEnabled = SecondaryButtonCommand.CanExecute(null);
}
if (CloseButtonCommand is not null && CloseButton is not null)
{
CloseButtonCommand.CanExecuteChanged += (_, _) =>
CloseButton.IsEnabled = CloseButtonCommand.CanExecute(null);
// Also set initial state
CloseButton.IsEnabled = CloseButtonCommand.CanExecute(null);
}
}
protected void OnDialogButtonClick(object? sender, ContentDialogResult e)

167
StabilityMatrix.Avalonia/DialogHelper.cs

@ -0,0 +1,167 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
using StabilityMatrix.Avalonia.Controls;
namespace StabilityMatrix.Avalonia;
public static class DialogHelper
{
/// <summary>
/// Create a generic textbox entry content dialog.
/// </summary>
public static BetterContentDialog CreateTextEntryDialog(
string title,
string description,
IReadOnlyList<TextBoxField> textFields)
{
Dispatcher.UIThread.CheckAccess();
var stackPanel = new StackPanel();
var grid = new Grid
{
RowDefinitions =
{
new RowDefinition(GridLength.Auto),
new RowDefinition(GridLength.Star)
},
Children =
{
new TextBlock
{
Text = description
},
stackPanel
}
};
// Disable primary button if any textboxes are invalid
var primaryCommand = new RelayCommand(delegate { },
() =>
{
var invalidCount = textFields.Count(field => !field.IsValid);
Debug.WriteLine($"Checking can execute: {invalidCount} invalid fields");
return invalidCount == 0;
});
// Create textboxes
foreach (var field in textFields)
{
var label = new TextBlock
{
Text = field.Label
};
stackPanel.Children.Add(label);
var textBox = new TextBox
{
[!TextBox.TextProperty] = new Binding("TextProperty"),
Watermark = field.Watermark,
DataContext = field,
};
stackPanel.Children.Add(textBox);
// When IsValid property changes, update invalid count and primary button
field.PropertyChanged += (sender, args) =>
{
if (args.PropertyName == nameof(TextBoxField.IsValid))
{
primaryCommand.NotifyCanExecuteChanged();
}
};
// Set initial value
textBox.Text = field.Text;
// See if initial value is valid
try
{
field.Validator?.Invoke(field.Text);
}
catch (Exception)
{
field.IsValid = false;
}
}
return new BetterContentDialog
{
Title = title,
Content = grid,
PrimaryButtonText = "OK",
CloseButtonText = "Cancel",
IsPrimaryButtonEnabled = true,
PrimaryButtonCommand = primaryCommand,
DefaultButton = ContentDialogButton.Primary
};
}
}
// Text fields
public sealed class TextBoxField : INotifyPropertyChanged
{
// Label above the textbox
public string Label { get; init; } = string.Empty;
// Actual text value
public string Text { get; set; } = string.Empty;
// Watermark text
public string Watermark { get; init; } = string.Empty;
/// <summary>
/// Validation action on text changes. Throw exception if invalid.
/// </summary>
public Action<string>? Validator { get; init; }
public string TextProperty
{
get => Text;
[DebuggerStepThrough]
set
{
try
{
Validator?.Invoke(value);
}
catch (Exception e)
{
IsValid = false;
throw new DataValidationException(e.Message);
}
Text = value;
IsValid = true;
OnPropertyChanged();
}
}
// Default to true if no validator is provided
private bool isValid;
public bool IsValid
{
get => Validator == null || isValid;
set
{
isValid = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler? PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

59
StabilityMatrix.Avalonia/ViewModels/CheckpointFile.cs

@ -6,10 +6,12 @@ using System.IO;
using System.Linq;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using Avalonia.Data;
using Avalonia.Media.Imaging;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
using NLog;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper;
@ -104,27 +106,42 @@ public partial class CheckpointFile : ViewModelBase
[RelayCommand]
private async Task RenameAsync()
{
// TODO: Fix with new dialogs
// var responses = await dialogFactory.ShowTextEntryDialog("Rename Model", new []
// {
// ("File Name", FileName)
// });
// var name = responses?.FirstOrDefault();
// if (name == null) return;
//
// // Rename file in OS
// try
// {
// var newFilePath = Path.Combine(Path.GetDirectoryName((string?) FilePath) ?? "", name);
// File.Move(FilePath, newFilePath);
// FilePath = newFilePath;
// }
// catch (Exception e)
// {
// Console.WriteLine(e);
// throw;
// }
// Parent folder path
var parentPath = Path.GetDirectoryName(FilePath) ?? "";
var textFields = new TextBoxField[]
{
new()
{
Label = "File name",
Validator = text =>
{
if (string.IsNullOrWhiteSpace(text)) throw new
DataValidationException("File name is required");
if (File.Exists(Path.Combine(parentPath, text))) throw new
DataValidationException("File name already exists");
}
}
};
var dialog = DialogHelper.CreateTextEntryDialog("Rename Model", "", textFields);
if (await dialog.ShowAsync() == ContentDialogResult.Primary)
{
var name = textFields[0].Text;
// Rename file in OS
try
{
var newFilePath = Path.Combine(parentPath, name);
File.Move(FilePath, newFilePath);
FilePath = newFilePath;
}
catch (Exception e)
{
Logger.Warn(e, $"Failed to rename checkpoint file {FilePath}");
}
}
}
[RelayCommand]

Loading…
Cancel
Save