Add StringFormat option to text cells.

This commit is contained in:
Steven Kirk 2024-11-01 16:28:21 +01:00
Родитель da3f3ab247
Коммит 39f10e539a
7 изменённых файлов: 121 добавлений и 25 удалений

Просмотреть файл

@ -1,4 +1,6 @@
using Avalonia.Media;
using System.Globalization;
using Avalonia.Media;
namespace Avalonia.Controls.Models.TreeDataGrid
{
@ -21,7 +23,8 @@ namespace Avalonia.Controls.Models.TreeDataGrid
/// Gets the cell's text wrapping mode.
/// </summary>
TextWrapping TextWrapping { get; }
/// <summary>
/// Gets the cell's text alignment mode.
/// </summary>
TextAlignment TextAlignment { get; }

Просмотреть файл

@ -1,9 +1,21 @@
using Avalonia.Media;
using System.Globalization;
using Avalonia.Media;
namespace Avalonia.Controls.Models.TreeDataGrid
{
public interface ITextCellOptions : ICellOptions
{
/// <summary>
/// Gets the format string to be used to format the cell value.
/// </summary>
string StringFormat { get; }
/// <summary>
/// Gets the culture to be used in conjunction with <see cref="StringFormat"/>.
/// </summary>
CultureInfo Culture { get; }
/// <summary>
/// Gets the text trimming mode for the cell.
/// </summary>

Просмотреть файл

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reactive.Subjects;
using Avalonia.Data;
using Avalonia.Media;
@ -11,11 +10,11 @@ namespace Avalonia.Controls.Models.TreeDataGrid
public class TextCell<T> : NotifyingBase, ITextCell, IDisposable, IEditableObject
{
private readonly ISubject<BindingValue<T>>? _binding;
private readonly ITextCellOptions? _options;
private readonly IDisposable? _subscription;
[AllowNull] private T? _value;
[AllowNull] private T? _cancelValue;
private string? _editText;
private T? _value;
private bool _isEditing;
private ITextCellOptions? _options;
public TextCell(T? value)
{
@ -48,15 +47,31 @@ namespace Avalonia.Controls.Models.TreeDataGrid
public string? Text
{
get => _value?.ToString();
set{
if (string.IsNullOrEmpty(value))
get
{
if (_isEditing)
return _editText;
else if (_options?.StringFormat is { } format)
return string.Format(_options.Culture ?? CultureInfo.CurrentCulture, format, _value);
else
return _value?.ToString();
}
set
{
if (_isEditing)
{
Value = default(T?);
_editText = value;
}
else
{
Value = (T?)Convert.ChangeType(value, typeof(T));
try
{
Value = (T?)Convert.ChangeType(value, typeof(T));
}
catch
{
// TODO: Data validation errors.
}
}
}
}
@ -78,7 +93,7 @@ namespace Avalonia.Controls.Models.TreeDataGrid
if (!_isEditing && !IsReadOnly)
{
_isEditing = true;
_cancelValue = Value;
_editText = Text;
}
}
@ -86,19 +101,19 @@ namespace Avalonia.Controls.Models.TreeDataGrid
{
if (_isEditing)
{
Value = _cancelValue;
_isEditing = false;
_cancelValue = default;
_editText = null;
}
}
public void EndEdit()
{
if (_isEditing && !EqualityComparer<T>.Default.Equals(_value, _cancelValue))
if (_isEditing)
{
var text = _editText;
_isEditing = false;
_cancelValue = default;
_binding!.OnNext(_value!);
_editText = null;
Text = text;
}
}

Просмотреть файл

@ -1,4 +1,6 @@
using Avalonia.Media;
using System.Globalization;
using Avalonia.Media;
namespace Avalonia.Controls.Models.TreeDataGrid
{
@ -13,6 +15,16 @@ namespace Avalonia.Controls.Models.TreeDataGrid
/// </summary>
public bool IsTextSearchEnabled { get; set; }
/// <summary>
/// Gets or sets the format string for the cells in the column.
/// </summary>
public string StringFormat { get; set; } = "{0}";
/// <summary>
/// Culture info used in conjunction with <see cref="StringFormat"/>
/// </summary>
public CultureInfo Culture { get; set; } = CultureInfo.CurrentCulture;
/// <summary>
/// Gets or sets the text trimming mode for the cells in the column.
/// </summary>

Просмотреть файл

@ -93,7 +93,10 @@ namespace Avalonia.Controls.Primitives
protected void EndEdit()
{
if (EndEditCore() && Model is IEditableObject editable)
{
editable.EndEdit();
UpdateValue();
}
}
protected void SubscribeToModelChanges()
@ -108,6 +111,10 @@ namespace Avalonia.Controls.Primitives
inpc.PropertyChanged -= OnModelPropertyChanged;
}
protected virtual void UpdateValue()
{
}
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
_treeDataGrid = this.FindLogicalAncestorOfType<TreeDataGrid>();

Просмотреть файл

@ -1,8 +1,8 @@
using System.ComponentModel;
using System.Globalization;
using System.Reflection;
using Avalonia.Controls.Models.TreeDataGrid;
using Avalonia.Controls.Selection;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
namespace Avalonia.Controls.Primitives
@ -64,6 +64,7 @@ namespace Avalonia.Controls.Primitives
get => _textAlignment;
set => SetAndRaise(TextAlignmentProperty, ref _textAlignment, value);
}
public override void Realize(
TreeDataGridElementFactory factory,
ITreeDataGridSelectionInteraction? selection,
@ -71,7 +72,7 @@ namespace Avalonia.Controls.Primitives
int columnIndex,
int rowIndex)
{
Value = model.Value?.ToString();
Value = (model as ITextCell)?.Text;
TextTrimming = (model as ITextCell)?.TextTrimming ?? TextTrimming.CharacterEllipsis;
TextWrapping = (model as ITextCell)?.TextWrapping ?? TextWrapping.NoWrap;
TextAlignment = (model as ITextCell)?.TextAlignment ?? TextAlignment.Left;
@ -85,6 +86,11 @@ namespace Avalonia.Controls.Primitives
base.Unrealize();
}
protected override void UpdateValue()
{
Value = (Model as ITextCell)?.Text;
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);

Просмотреть файл

@ -63,7 +63,7 @@ namespace Avalonia.Controls.TreeDataGridTests.Models
target.Text = "new";
Assert.Equal("new", target.Text);
Assert.Equal("new", target.Value);
Assert.Equal("initial", target.Value);
Assert.Equal(new[] { "initial"}, result);
target.EndEdit();
@ -86,7 +86,7 @@ namespace Avalonia.Controls.TreeDataGridTests.Models
target.Text = "new";
Assert.Equal("new", target.Text);
Assert.Equal("new", target.Value);
Assert.Equal("initial", target.Value);
Assert.Equal(new[] { "initial" }, result);
target.CancelEdit();
@ -95,5 +95,46 @@ namespace Avalonia.Controls.TreeDataGridTests.Models
Assert.Equal("initial", target.Value);
Assert.Equal(new[] { "initial" }, result);
}
public class StringFormat
{
[AvaloniaFact(Timeout = 10000)]
public void Initial_Int_Value_Is_Formatted()
{
var binding = new BehaviorSubject<BindingValue<int>>(42);
var target = new TextCell<int>(binding, true, GetOptions());
Assert.Equal("42.00", target.Text);
Assert.Equal(42, target.Value);
}
[AvaloniaFact(Timeout = 10000)]
public void Int_Value_Is_Formatted_After_Editing()
{
var binding = new BehaviorSubject<BindingValue<int>>(42);
var target = new TextCell<int>(binding, false, GetOptions());
var result = new List<int>();
binding.Subscribe(x => result.Add(x.Value));
target.BeginEdit();
target.Text = "43";
Assert.Equal("43", target.Text);
Assert.Equal(42, target.Value);
Assert.Equal(new[] { 42 }, result);
target.EndEdit();
Assert.Equal("43.00", target.Text);
Assert.Equal(43, target.Value);
Assert.Equal(new[] { 42, 43 }, result);
}
private ITextCellOptions? GetOptions(string format = "{0:n2}")
{
return new TextColumnOptions<int> { StringFormat = format };
}
}
}
}