Skip to content

Commit

Permalink
Merge pull request #180 from AvaloniaUI/feature/template-cell-edit
Browse files Browse the repository at this point in the history
Implement edit mode for template cells.
  • Loading branch information
maxkatz6 authored Jun 15, 2023
2 parents 2fdcfc8 + 73696cf commit 10794f5
Show file tree
Hide file tree
Showing 22 changed files with 347 additions and 106 deletions.
20 changes: 12 additions & 8 deletions docs/column-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,18 @@ The first parameter in the constructor is a nested column, you would usually wan
The sample above is taken from [this article](https://github.com/AvaloniaUI/Avalonia.Controls.TreeDataGrid/blob/master/docs/get-started-hierarchical.md). If you feel like you need more examples feel free to check it, there is a sample that shows how to use `HierarchicalExpanderColumn` and how to run a whole `TreeDataGrid` using it.

## TemplateColumn
TemplateColumn is the most customizable option to create a column. You can put basically everything that you can put into `IDataTemplate` into this column cells.

There are two ways to instantiate a `TemplateColumn`:
TemplateColumn is the most customizable way to create a column. Because cell contents are described by a data template, the options for how each cell is displayed is almost unlimited.

The `TemplateColumn` constructor takes the following parameters:

- `header`: The Column header
- `cellTemplate`: A data template which describes how cells in the column will be displayed
- `cellEditingTemplate`: A data template which describes how cells in the column will be displayed when in edit mode. If no `cellEditingTemplate` is provided, then edit mode will be disabled for the column.
- `width`: The width of the column
- `options`: Less frequently used options for the column

There are two overloads of the `TemplateColumn` constructor, which corresponds to the two ways in which a template can be specified:

1. Using a `FuncDataTemplate` to create a template in code:

Expand Down Expand Up @@ -93,9 +102,4 @@ new TemplateColumn<Person>(
new TemplateColumn<Person>("Selected", "CheckBoxCell");
```

`TemplateColumn` has only one generic parameter, it is your model type, same as in `TextColumn`, Person in this case. Code above will create a column with header *"Selected"* and `CheckBox` in each cell.

`TemplateColumn` has two required parameters. The first one is the column header as everywhere. The second is either:

1. An `IDataTemplate`; a template that contains stuff that you want to be displayed in the cells of this column.
2. The key of a template defined in a XAML resource.
`TemplateColumn` has only one generic parameter, it is your model type, the same as in `TextColumn`; `Person` in this case. The code above will create a column with header *"Selected"* and a `CheckBox` in each cell.
33 changes: 30 additions & 3 deletions samples/TreeDataGridDemo/MainWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@
<TreeDataGrid Name="countries"
Source="{Binding Countries.Source}"
AutoDragDropRows="True">
<TreeDataGrid.Resources>
<!-- Template for Region column cells -->
<DataTemplate x:Key="RegionCell" DataType="m:Country">
<TextBlock Text="{Binding Region}"/>
</DataTemplate>
<DataTemplate x:Key="RegionEditCell" DataType="m:Country">
<ComboBox ItemsSource="{x:Static m:Countries.Regions}"
SelectedItem="{Binding Region}"/>
</DataTemplate>

</TreeDataGrid.Resources>
<TreeDataGrid.Styles>
<Style Selector="TreeDataGrid TreeDataGridRow:nth-last-child(2n)">
<Setter Property="Background" Value="#20808080"/>
Expand Down Expand Up @@ -62,8 +73,8 @@
<TextBlock Classes="realized-count" DockPanel.Dock="Bottom"/>
<TreeDataGrid Name="fileViewer" Source="{Binding Files.Source}">
<TreeDataGrid.Resources>
<!-- Template for Name column cells -->

<!-- Template for Name column cells -->
<DataTemplate x:Key="FileNameCell" DataType="m:FileTreeNodeModel">
<StackPanel Orientation="Horizontal">
<Image Margin="0 0 4 0"
Expand All @@ -78,7 +89,23 @@
<TextBlock Text="{Binding Name}" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>


<!-- Edit template for Name column cells -->
<DataTemplate x:Key="FileNameEditCell" DataType="m:FileTreeNodeModel">
<StackPanel Orientation="Horizontal">
<Image Margin="0 0 4 0"
VerticalAlignment="Center">
<Image.Source>
<MultiBinding Converter="{x:Static vm:FilesPageViewModel.FileIconConverter}">
<Binding Path="IsDirectory"/>
<Binding Path="IsExpanded"/>
</MultiBinding>
</Image.Source>
</Image>
<TextBox Text="{Binding Name}" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>

</TreeDataGrid.Resources>
<TreeDataGrid.Styles>
<Style Selector="TreeDataGrid TreeDataGridRow:nth-child(2n)">
Expand Down
2 changes: 2 additions & 0 deletions samples/TreeDataGridDemo/Models/Countries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace TreeDataGridDemo.Models
internal static class Countries
{
private static IReadOnlyList<Country>? _all;
private static IReadOnlyList<string>? _regions;

private static IEnumerable<Country> GetCountries()
{
Expand Down Expand Up @@ -238,5 +239,6 @@ private static IEnumerable<Country> GetCountries()
}

public static IReadOnlyList<Country> All => _all ??= GetCountries().ToList();
public static IReadOnlyList<string> Regions => _regions ??= All.Select(x => x.Region).ToList();
}
}
8 changes: 7 additions & 1 deletion samples/TreeDataGridDemo/Models/FileTreeNodeModel.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using Avalonia.Threading;
using ReactiveUI;

namespace TreeDataGridDemo.Models
{
public class FileTreeNodeModel : ReactiveObject
public class FileTreeNodeModel : ReactiveObject, IEditableObject
{
private string _path;
private string _name;
private string? _undoName;
private long? _size;
private DateTimeOffset? _modified;
private FileSystemWatcher? _watcher;
Expand Down Expand Up @@ -153,6 +155,10 @@ private ObservableCollection<FileTreeNodeModel> LoadChildren()
};
}

void IEditableObject.BeginEdit() => _undoName = _name;
void IEditableObject.CancelEdit() => _name = _undoName!;
void IEditableObject.EndEdit() => _undoName = null;

private void OnChanged(object sender, FileSystemEventArgs e)
{
if (e.ChangeType == WatcherChangeTypes.Changed && File.Exists(e.FullPath))
Expand Down
4 changes: 2 additions & 2 deletions samples/TreeDataGridDemo/ViewModels/CountriesPageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ public CountriesPageViewModel()
{
new TextColumn<Country, string>("Country", x => x.Name, (r, v) => r.Name = v, new GridLength(6, GridUnitType.Star), new()
{
IsTextSearchEnabled = true
IsTextSearchEnabled = true,
}),
new TextColumn<Country, string>("Region", x => x.Region, new GridLength(4, GridUnitType.Star)),
new TemplateColumn<Country>("Region", "RegionCell", "RegionEditCell"),
new TextColumn<Country, int>("Population", x => x.Population, new GridLength(3, GridUnitType.Star)),
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()
Expand Down
1 change: 1 addition & 0 deletions samples/TreeDataGridDemo/ViewModels/FilesPageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public FilesPageViewModel()
new TemplateColumn<FileTreeNodeModel>(
"Name",
"FileNameCell",
"FileNameEditCell",
new GridLength(1, GridUnitType.Star),
new()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Reactive.Subjects;
using System.Reflection;
using Avalonia.Data;

namespace Avalonia.Controls.Models.TreeDataGrid
Expand Down Expand Up @@ -33,6 +34,7 @@ public CheckBoxCell(
}

public bool CanEdit => false;
public BeginEditGestures EditGestures => BeginEditGestures.None;
public bool SingleTapEdit => false;
public bool IsReadOnly { get; }
public bool IsThreeState { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,41 @@

namespace Avalonia.Controls.Models.TreeDataGrid
{
[Flags]
public enum BeginEditGestures
{
/// <summary>
/// A cell will only enter edit mode programmatically.
/// </summary>
None = 0x00,

/// <summary>
/// A cell will enter edit mode when the user presses F2.
/// </summary>
F2 = 0x01,

/// <summary>
/// A cell will enter edit mode when the user taps it.
/// </summary>
Tap = 0x02,

/// <summary>
/// A cell will enter edit mode when the user double-taps it.
/// </summary>
DoubleTap = 0x4,

/// <summary>
/// A cell will enter edit mode in conjuction with a gesture only when the cell or row
/// is currently selected.
/// </summary>
WhenSelected = 0x1000,

/// <summary>
/// A cell will enter edit mode when the user presses F2 or double-taps it.
/// </summary>
Default = F2 | DoubleTap,
}

/// <summary>
/// Holds less commonly-used options for an <see cref="IColumn{TModel}"/>.
/// </summary>
Expand Down Expand Up @@ -45,5 +80,10 @@ public class ColumnOptions<TModel>
/// Gets or sets a custom comparison for descending ordered columns.
/// </summary>
public Comparison<TModel?>? CompareDescending { get; set; }

/// <summary>
/// Gets or sets the gesture(s) that will cause a cell to enter edit mode.
/// </summary>
public BeginEditGestures BeginEditGestures { get; set; } = BeginEditGestures.Default;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ public ExpanderCell(

public bool CanEdit => _inner.CanEdit;
public ICell Content => _inner;
public BeginEditGestures EditGestures => _inner.EditGestures;
public IExpanderRow<TModel> Row { get; }
public bool SingleTapEdit => _inner.SingleTapEdit;
public bool ShowExpander => Row.ShowExpander;
public object? Value => _inner.Value;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,9 @@ public interface ICell
bool CanEdit { get; }

/// <summary>
/// Gets a value indicating whether a single tap will begin edit mode on a cell.
/// Gets the gesture(s) that will cause the cell to enter edit mode.
/// </summary>
/// <remarks>
/// If false, a double-tap is required to enter edit mode.
/// </remarks>
bool SingleTapEdit { get; }
BeginEditGestures EditGestures { get; }

/// <summary>
/// Gets the value of the cell.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Avalonia.Media;

namespace Avalonia.Controls.Models.TreeDataGrid
{
public interface ICellOptions
{
/// <summary>
/// Gets the gesture(s) that will cause the cell to enter edit mode.
/// </summary>
BeginEditGestures BeginEditGestures { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Avalonia.Media;

namespace Avalonia.Controls.Models.TreeDataGrid
{
public interface ITemplateCellOptions : ICellOptions
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,8 @@

namespace Avalonia.Controls.Models.TreeDataGrid
{
public interface ITextCellOptions
public interface ITextCellOptions : ICellOptions
{
/// <summary>
/// Gets a value indicating whether a single tap will begin edit mode on a cell.
/// </summary>
/// <remarks>
/// If false, a double-tap is required to enter edit mode.
/// </remarks>
bool SingleTapEdit { get; }

/// <summary>
/// Gets the text trimming mode for the cell.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@

using System;
using System.ComponentModel;
using Avalonia.Controls.Templates;

namespace Avalonia.Controls.Models.TreeDataGrid
{
public class TemplateCell : ICell
public class TemplateCell : ICell, IEditableObject
{
public TemplateCell(object? value, Func<Control, IDataTemplate> getCellTemplate)
private ITemplateCellOptions? _options;

public TemplateCell(
object? value,
Func<Control, IDataTemplate> getCellTemplate,
Func<Control, IDataTemplate>? getCellEditingTemplate,
ITemplateCellOptions? options)
{
GetCellTemplate = getCellTemplate;
GetCellEditingTemplate = getCellEditingTemplate;
Value = value;
_options = options;
}

public bool CanEdit => false;
public bool SingleTapEdit => false;
public bool CanEdit => GetCellEditingTemplate is not null;
public BeginEditGestures EditGestures => _options?.BeginEditGestures ?? BeginEditGestures.Default;
public Func<Control, IDataTemplate> GetCellTemplate { get; }
public Func<Control, IDataTemplate>? GetCellEditingTemplate { get; }
public object? Value { get; }

void IEditableObject.BeginEdit() => (Value as IEditableObject)?.BeginEdit();
void IEditableObject.CancelEdit() => (Value as IEditableObject)?.CancelEdit();
void IEditableObject.EndEdit() => (Value as IEditableObject)?.EndEdit();
}
}
Loading

0 comments on commit 10794f5

Please sign in to comment.