Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/text column formatting #283

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
8 changes: 6 additions & 2 deletions samples/TreeDataGridDemo/ViewModels/CountriesPageViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Models.TreeDataGrid;
Expand Down Expand Up @@ -26,12 +27,15 @@ public CountriesPageViewModel()
IsTextSearchEnabled = true,
}),
new TemplateColumn<Country>("Region", "RegionCell", "RegionEditCell"),
new TextColumn<Country, int>("Population", x => x.Population, new GridLength(3, GridUnitType.Star)),
new TextColumn<Country, int>("Population", x => x.Population, new GridLength(3, GridUnitType.Star),
new TextColumnOptions<Country> { StringFormat = "{0:N}" }),
new TextColumn<Country, int>("Area", x => x.Area, new GridLength(3, GridUnitType.Star)),
new TextColumn<Country, int>("GDP", x => x.GDP, new GridLength(3, GridUnitType.Star), new()
{
TextAlignment = Avalonia.Media.TextAlignment.Right,
MaxWidth = new GridLength(150)
MaxWidth = new GridLength(150),
StringFormat = "{0:C}", // Format as Currency
FormatCultureInfo = CultureInfo.GetCultureInfo("en-US"), // Format this as USD
}),
}
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Avalonia.Media;
using System.Globalization;

using Avalonia.Media;

namespace Avalonia.Controls.Models.TreeDataGrid
{
Expand All @@ -12,6 +14,16 @@ public interface ITextCell : ICell
/// </summary>
string? Text { get; set; }

/// <summary>
/// Format to use for the string
/// </summary>
string StringFormat { get; }

/// <summary>
/// Culture info used in conjunction with <see cref="StringFormat"/>
/// </summary>
CultureInfo FormatCultureInfo { get; }

/// <summary>
/// Gets the cell's text trimming mode.
/// </summary>
Expand All @@ -21,7 +33,8 @@ public interface ITextCell : ICell
/// Gets the cell's text wrapping mode.
/// </summary>
TextWrapping TextWrapping { get; }


/// <summary>
/// Gets the cell's text alignment mode.
/// </summary>
TextAlignment TextAlignment { get; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
using Avalonia.Media;
using System.Globalization;

using Avalonia.Media;

namespace Avalonia.Controls.Models.TreeDataGrid
{
public interface ITextCellOptions : ICellOptions
{
/// <summary>
/// Format to use for the string
/// </summary>
string StringFormat { get; }

/// <summary>
/// Culture info used in conjunction with <see cref="StringFormat"/>
/// </summary>
CultureInfo FormatCultureInfo { get; }

/// <summary>
/// Gets the text trimming mode for the cell.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reactive.Subjects;
using Avalonia.Data;
using Avalonia.Media;
Expand Down Expand Up @@ -42,6 +43,8 @@ public TextCell(
public bool CanEdit => !IsReadOnly;
public BeginEditGestures EditGestures => _options?.BeginEditGestures ?? BeginEditGestures.Default;
public bool IsReadOnly { get; }
public string StringFormat => _options?.StringFormat ?? "{0}";
public CultureInfo FormatCultureInfo => _options?.FormatCultureInfo ?? CultureInfo.InvariantCulture;
public TextTrimming TextTrimming => _options?.TextTrimming ?? TextTrimming.None;
public TextWrapping TextWrapping => _options?.TextWrapping ?? TextWrapping.NoWrap;
public TextAlignment TextAlignment => _options?.TextAlignment ?? TextAlignment.Left;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Avalonia.Media;
using System.Globalization;

using Avalonia.Media;

namespace Avalonia.Controls.Models.TreeDataGrid
{
Expand All @@ -13,6 +15,16 @@ public class TextColumnOptions<TModel> : ColumnOptions<TModel>, ITextCellOptions
/// </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 FormatCultureInfo { get; set; } = CultureInfo.InvariantCulture;

/// <summary>
/// Gets or sets the text trimming mode for the cells in the column.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.ComponentModel;
using System.Globalization;

using Avalonia.Controls.Models.TreeDataGrid;
using Avalonia.Controls.Selection;
using Avalonia.Input;
Expand All @@ -9,6 +11,16 @@ namespace Avalonia.Controls.Primitives
{
public class TreeDataGridTextCell : TreeDataGridCell
{
public static readonly DirectProperty<TreeDataGridTextCell, string> StringFormatProperty =
AvaloniaProperty.RegisterDirect<TreeDataGridTextCell, string>(
nameof(StringFormat),
o => o.StringFormat);

public static readonly DirectProperty<TreeDataGridTextCell, CultureInfo> FormatCultureInfoProperty =
AvaloniaProperty.RegisterDirect<TreeDataGridTextCell, CultureInfo>(
nameof(FormatCultureInfo),
o => o.FormatCultureInfo);

public static readonly DirectProperty<TreeDataGridTextCell, TextTrimming> TextTrimmingProperty =
AvaloniaProperty.RegisterDirect<TreeDataGridTextCell, TextTrimming>(
nameof(TextTrimming),
Expand All @@ -32,11 +44,25 @@ public class TreeDataGridTextCell : TreeDataGridCell
(o,v)=> o.TextAlignment = v);

private string? _value;
private string _stringFormat = "{0}";
private CultureInfo _formatCultureInfo = CultureInfo.InvariantCulture;
private TextBox? _edit;
private TextTrimming _textTrimming = TextTrimming.CharacterEllipsis;
private TextWrapping _textWrapping = TextWrapping.NoWrap;
private TextAlignment _textAlignment = TextAlignment.Left;

public string StringFormat
{
get => _stringFormat;
set => SetAndRaise(StringFormatProperty, ref _stringFormat, value);
}

public CultureInfo FormatCultureInfo
{
get => _formatCultureInfo;
set => SetAndRaise(FormatCultureInfoProperty, ref _formatCultureInfo, value);
}

public TextTrimming TextTrimming
{
get => _textTrimming;
Expand Down Expand Up @@ -71,7 +97,10 @@ public override void Realize(
int columnIndex,
int rowIndex)
{
Value = model.Value?.ToString();
var format = (model as ITextCell)?.StringFormat ?? "{0}";
var culture = (model as ITextCell)?.FormatCultureInfo ?? CultureInfo.InvariantCulture;

Value = string.Format(culture, format, model.Value);
TextTrimming = (model as ITextCell)?.TextTrimming ?? TextTrimming.CharacterEllipsis;
TextWrapping = (model as ITextCell)?.TextWrapping ?? TextWrapping.NoWrap;
TextAlignment = (model as ITextCell)?.TextAlignment ?? TextAlignment.Left;
Expand Down Expand Up @@ -102,8 +131,11 @@ protected override void OnModelPropertyChanged(object? sender, PropertyChangedEv
{
base.OnModelPropertyChanged(sender, e);

if (e.PropertyName == nameof(ITextCell.Value))
Value = Model?.Value?.ToString();
if (e.PropertyName != nameof(ITextCell.Value)) return;
var format = (Model as ITextCell)?.StringFormat ?? "{0}";
var culture = (Model as ITextCell)?.FormatCultureInfo ?? CultureInfo.InvariantCulture;

Value = string.Format(culture, format, Model?.Value);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System.Globalization;
using System.Reactive.Subjects;

using Avalonia.Controls.Models.TreeDataGrid;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Headless.XUnit;

using Xunit;

namespace Avalonia.Controls.TreeDataGridTests.Primitives
{
public class TreeDataGridTextCellTests
{
[AvaloniaTheory(Timeout = 10000)]
[InlineData(10.1, null, "{0:n3}", "10.100")]
[InlineData(20.12345, null, "{0:n2}", "20.12")]
[InlineData(5050.50, null, "{0:n2}", "5,050.50")]
[InlineData(5050.50, "en-US", "{0:C}", "$5,050.50")]
public void Formats_Double_Properly(double input, string? cultureString, string formatString, string expected)
{
CultureInfo? culture = null;
if (cultureString is not null)
culture = CultureInfo.GetCultureInfo(cultureString);

var cell = SetupCellForType(input, culture, formatString);
Assert.Equal(expected, cell.Value);
}

[AvaloniaTheory(Timeout = 10000)]
[InlineData(1_000_000, null, "{0:n0}", "1,000,000")]
[InlineData(2032, null, "{0:n2}", "2,032.00")]
[InlineData(5050, null, "{0}", "5050")]
[InlineData(5050, "en-US", "{0:C}", "$5,050.00")]
public void Formats_Int_Properly(int input, string? cultureString, string formatString, string expected)
{
CultureInfo? culture = null;
if (cultureString is not null)
culture = CultureInfo.GetCultureInfo(cultureString);

var cell = SetupCellForType(input, culture, formatString);
Assert.Equal(expected, cell.Value);
}

[AvaloniaTheory(Timeout = 10000)]
[InlineData(10.1, "10.1")]
[InlineData(20.12345, "20.12345")]
[InlineData(5050.50, "5050.5")]
public void Formats_Doublet_With_Default_Formatter_Properly(double input, string expected)
{
var cell = SetupCellForType(input, null, null);
Assert.Equal(expected, cell.Value);
}

[AvaloniaTheory(Timeout = 10000)]
[InlineData(1_000_000, "1000000")]
[InlineData(2032, "2032")]
[InlineData(5050, "5050")]
public void Formats_Int_With_Default_Formatter_Properly(int input, string expected)
{
var cell = SetupCellForType(input, null, null);
Assert.Equal(expected, cell.Value);
}

private static TreeDataGridTextCell SetupCellForType<T>(T input, CultureInfo? cultureInfo, string? formatString)
{
var subject = new Subject<BindingValue<T>>();
var cell = new TreeDataGridTextCell();
var options = new TextColumnOptions<T>();
if (formatString is not null)
options.StringFormat = formatString;

if (cultureInfo is not null)
options.FormatCultureInfo = cultureInfo;
var model = new TextCell<T>(subject, true, options);
subject.OnNext(new BindingValue<T>(input));
cell.Realize(new TestElementFactory(), null, model, 0, 0);
return cell;
}
}
}