Skip to content

Commit

Permalink
Merge pull request #22 from giard-alexandre/feat/show-hide-all-columns
Browse files Browse the repository at this point in the history
Feat/show hide all columns
  • Loading branch information
giard-alexandre authored Oct 4, 2024
2 parents e004384 + d82eeb2 commit 7238102
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 40 deletions.
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,31 @@ Trying to bring some of the features of Enterprise 3rd-party WPF controls to Ava
- [x] Sort using DynamicData
- [x] Reorder Columns
- [x] Show/Hide Columns
- [x] FlatTreeDataGid support
- [ ] Expose Serializable DataGrid "state" object.
- [ ] Per-Column Filtering using DynamicData
- [ ] Clear filters
- [ ] HierarchicalTreeDataGridSource support




## Attributions

### FontAwesome

This project uses icons from Font Awesome Free.

Font Awesome Free License: [CC BY 4.0 License](https://creativecommons.org/licenses/by/4.0/)

You can find more information about Font Awesome's licensing on their [official website](https://fontawesome.com/license/free)
and their [project website](https://github.com/FortAwesome/Font-Awesome/blob/6.x/LICENSE.txt).

### Material Design Icons

This project uses icons from Material Design Icons.

Material Design Icons License: [Apache License 2.0](https://github.com/google/material-design-icons/blob/master/LICENSE)

You can find more information about Material Design Icons on their [official website](https://developers.google.com/fonts/docs/material_icons)
and their [project website](https://github.com/google/material-design-icons).
4 changes: 2 additions & 2 deletions src/DynamicTreeDataGrid/Controls/ColumnEditorWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:DynamicTreeDataGrid.Controls"
mc:Ignorable="d" SizeToContent="WidthAndHeight" WindowStartupLocation="CenterOwner"
mc:Ignorable="d" SizeToContent="Width" WindowStartupLocation="CenterOwner"
x:Class="DynamicTreeDataGrid.Controls.ColumnEditorWindow"
Title="Column Editor" MinWidth="300">
Title="Column Editor" MinWidth="300" Height="500">
<controls:ColumnListView />
</Window>
44 changes: 29 additions & 15 deletions src/DynamicTreeDataGrid/Controls/ColumnListView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,33 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="DynamicTreeDataGrid.Controls.ColumnListView"
x:DataType="columns:IDynamicColumns">
<ScrollViewer Name="MainContainer" HorizontalAlignment="Stretch">
<!-- Background="Transparent" is required here in order for the DragDrop stuff to work properly. -->
<ItemsRepeater ItemsSource="{Binding .}" DragDrop.AllowDrop="True"
Background="Transparent" Margin="10">
<ItemsRepeater.Layout>
<NonVirtualizingStackLayout />
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<DataTemplate x:DataType="columns:IDynamicColumn">
<controls:ColumnItemView ToolTip.Tip="{Binding Name}" Padding="5"
PointerPressed="OnPointerPressed" />
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</ScrollViewer>
<DockPanel>
<UniformGrid DockPanel.Dock="Top" Columns="2" Rows="1">
<UniformGrid.Styles>
<Style Selector="Button">
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="Margin" Value="10" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
</UniformGrid.Styles>
<Button Click="ShowAllClicked">Show All</Button>
<Button Click="HideAllClicked">Hide All</Button>
</UniformGrid>
<ScrollViewer Name="MainContainer" HorizontalAlignment="Stretch">
<!-- Background="Transparent" is required here in order for the DragDrop stuff to work properly. -->
<ItemsRepeater ItemsSource="{Binding .}" DragDrop.AllowDrop="True"
Background="Transparent" Margin="10">
<ItemsRepeater.Layout>
<NonVirtualizingStackLayout />
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<DataTemplate x:DataType="columns:IDynamicColumn">
<controls:ColumnItemView ToolTip.Tip="{Binding Name}" Padding="5"
PointerPressed="OnPointerPressed" />
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</ScrollViewer>
</DockPanel>
</UserControl>
24 changes: 21 additions & 3 deletions src/DynamicTreeDataGrid/Controls/ColumnListView.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Avalonia.Controls;
using Avalonia.Controls.Models.TreeDataGrid;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.VisualTree;

Expand All @@ -28,6 +29,7 @@ private async void OnPointerPressed(object? sender, PointerPressedEventArgs e) {
if (border.DataContext is not IColumn column) return;

_draggedItem = border;

// Disable Hit test otherwise this control will be the drop target since the mouse is over it.
_draggedItem.IsHitTestVisible = false;

Expand All @@ -44,10 +46,9 @@ private void DragOver(object? sender, DragEventArgs e) {

// Render the item under the mouse and follow the mouse.
var currentPosition = e.GetPosition(MainContainer);
var offsetX = currentPosition.X - _draggedItem.Bounds.Position.X - _draggedItem.Bounds.Width/2;
var offsetY = currentPosition.Y - _draggedItem.Bounds.Position.Y - _draggedItem.Bounds.Height/2;
var offsetX = currentPosition.X - _draggedItem.Bounds.Position.X - _draggedItem.Bounds.Width / 2;
var offsetY = currentPosition.Y - _draggedItem.Bounds.Position.Y - _draggedItem.Bounds.Height / 2;
_draggedItem.RenderTransform = new TranslateTransform(offsetX, offsetY);

}

private void Drop(object? sender, DragEventArgs e) {
Expand Down Expand Up @@ -94,4 +95,21 @@ private static bool TryGetColumn(Control? control, [MaybeNullWhen(false)] out ID
}
}
}

private void ShowAllClicked(object? sender, RoutedEventArgs e) {
if (DataContext is not IDynamicColumns columns) return;

foreach (var column in (IReadOnlyList<IDynamicColumn>)columns) {
column.Visible = true;
}
}

private void HideAllClicked(object? sender, RoutedEventArgs e) {
if (DataContext is not IDynamicColumns columns) return;

((IReadOnlyList<IDynamicColumn>)columns)[0].Visible = true;
for (int i = 1; i < ((IReadOnlyList<IDynamicColumn>)columns).Count; i++) {
((IReadOnlyList<IDynamicColumn>)columns)[i].Visible = false;
}
}
}
172 changes: 152 additions & 20 deletions src/DynamicTreeDataGrid/DynamicFlatTreeDataGridSource.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;

using Avalonia.Controls;
using Avalonia.Controls.Models;
using Avalonia.Controls.Models.TreeDataGrid;
using Avalonia.Controls.Selection;
using Avalonia.Input;

using DynamicData;
using DynamicData.Aggregation;
Expand All @@ -11,53 +17,65 @@

namespace DynamicTreeDataGrid;

public class DynamicFlatTreeDataGridSource<TModel, TModelKey> : FlatTreeDataGridSource<TModel>,
IDynamicTreeDataGridSource<TModel>
public class DynamicFlatTreeDataGridSource<TModel, TModelKey> : NotifyingBase, IDynamicTreeDataGridSource<TModel>,
IDisposable
where TModel : class where TModelKey : notnull {
// By default, the filtering function just includes all rows.
private readonly ISubject<Func<TModel, bool>> _filterSource = new BehaviorSubject<Func<TModel, bool>>(_ => true);
private readonly IObservable<IChangeSet<TModel, TModelKey>> _changeSet;
private readonly IObservable<IComparer<TModel>> _sort;
private readonly ISubject<IComparer<TModel>> _sortSource = new Subject<IComparer<TModel>>();
private readonly Subject<IComparer<TModel>?> _sortSource = new ();
private readonly IObservable<Func<TModel, bool>> _itemsFilter;
private readonly CompositeDisposable _d = new();
private readonly ReadOnlyObservableCollection<TModel> _items;

public DynamicFlatTreeDataGridSource(IObservable<IChangeSet<TModel, TModelKey>> changes) : base([]) {
public DynamicFlatTreeDataGridSource(IObservable<IChangeSet<TModel, TModelKey>> changes) {
_itemsFilter = _filterSource;

// Use RefCount to avoid duplicate work
_changeSet = changes.RefCount();
_sort = _sortSource;
TotalCount = _changeSet.Count();

// Setup Sort notifications
_sort = _sortSource.Select(comparer => comparer ?? new NoSortComparer<TModel>());
var sortDisposable = _sort.Subscribe(comparer => {
// Reverse the NoSortComparer for this field from FlatTreeDataGridSource
_comparer = comparer is NoSortComparer<TModel> ? null : comparer;
Sorted?.Invoke();
});

var filteredChanges = _changeSet.Filter(_itemsFilter);
FilteredCount = filteredChanges.Count();

var myOperation = filteredChanges.Sort(_sort)
.Bind(out var list)
var disposable = filteredChanges.Sort(_sort) // Use SortAndBind?
.Bind(out _items)
.DisposeMany()
.Subscribe(set => Console.WriteLine("Changeset changed."));
.Subscribe();

Items = list;
_itemsView = TreeDataGridItemsSourceView<TModel>.GetOrCreate(_items);

// TODO: Setup Sorted event for treeDataGridSourceImplementation?
}
// Setup Disposables
_d.Add(disposable);
_d.Add(sortDisposable);

// TODO: Fix CreateRows()? Does this now work since we set the comparer?
}


public IObservable<int> FilteredCount { get; }
public IObservable<int> TotalCount { get; }

public new DynamicColumnList<TModel> Columns { get; } = [];
public DynamicColumnList<TModel> Columns { get; } = [];
IDynamicColumns IDynamicTreeDataGridSource.Columns => Columns;
IColumns ITreeDataGridSource.Columns => Columns.DisplayedColumns;

// TODO: Change to check the sort observable.
// public bool IsSorted => _comparer is not null;

public new event Action? Sorted;

bool ITreeDataGridSource.SortBy(IColumn? column, ListSortDirection direction) => SortBy(column, direction);

/// <summary>
///
/// </summary>
/// <param name="column"></param>
/// <param name="direction"></param>
/// <returns></returns>
/// <remarks>Slight changes to <see cref="ITreeDataGridSource.SortBy"/> but DynamicData-aware</remarks>
public bool SortBy(IColumn? column, ListSortDirection direction) {
if (column is IColumn<TModel> typedColumn) {
if (!Columns.Contains(typedColumn))
Expand All @@ -70,7 +88,6 @@ public bool SortBy(IColumn? column, ListSortDirection direction) {

// Trigger a new sort notification.
_sortSource.OnNext(comparerInstance);
Sorted?.Invoke();
foreach (var c in Columns)
c.SortDirection = c == column ? direction : null;
}
Expand All @@ -80,4 +97,119 @@ public bool SortBy(IColumn? column, ListSortDirection direction) {

return false;
}


#region From FlatTreeDataGrid

// private IEnumerable<TModel> _items;
private TreeDataGridItemsSourceView<TModel> _itemsView;
private AnonymousSortableRows<TModel>? _rows;
private IComparer<TModel>? _comparer;
private ITreeDataGridSelection? _selection;
private bool _isSelectionSet;


public IRows Rows => _rows ??= CreateRows();

public IEnumerable<TModel> Items => _items;

public ITreeDataGridSelection? Selection {
get {
if (_selection == null && !_isSelectionSet)
_selection = new TreeDataGridRowSelectionModel<TModel>(this);
return _selection;
}
set {
if (_selection != value) {
if (value?.Source != _items)
throw new InvalidOperationException("Selection source must be set to Items.");
_selection = value;
_isSelectionSet = true;
RaisePropertyChanged();
}
}
}

IEnumerable<object> ITreeDataGridSource.Items => Items;

public ITreeDataGridCellSelectionModel<TModel>? CellSelection =>
Selection as ITreeDataGridCellSelectionModel<TModel>;

public ITreeDataGridRowSelectionModel<TModel>? RowSelection => Selection as ITreeDataGridRowSelectionModel<TModel>;
public bool IsHierarchical => false;
public bool IsSorted => _comparer is not null;

public event Action? Sorted;

void ITreeDataGridSource.DragDropRows(ITreeDataGridSource source,
IEnumerable<IndexPath> indexes,
IndexPath targetIndex,
TreeDataGridRowDropPosition position,
DragDropEffects effects) {
if (effects != DragDropEffects.Move)
throw new NotSupportedException("Only move is currently supported for drag/drop.");
if (IsSorted)
throw new NotSupportedException("Drag/drop is not supported on sorted data.");
if (position == TreeDataGridRowDropPosition.Inside)
throw new ArgumentException("Invalid drop position.", nameof(position));
if (indexes.Any(x => x.Count != 1))
throw new ArgumentException("Invalid source index.", nameof(indexes));
if (targetIndex.Count != 1)
throw new ArgumentException("Invalid target index.", nameof(targetIndex));
if (_items is not IList<TModel> items)
throw new InvalidOperationException("Items does not implement IList<T>.");

if (position == TreeDataGridRowDropPosition.None)
return;

var ti = targetIndex[0];

if (position == TreeDataGridRowDropPosition.After)
++ti;

var sourceItems = new List<TModel>();

foreach (var src in indexes.OrderByDescending(x => x)) {
var i = src[0];
sourceItems.Add(items[i]);
items.RemoveAt(i);

if (i < ti)
--ti;
}

for (var si = sourceItems.Count - 1; si >= 0; --si) {
items.Insert(ti++, sourceItems[si]);
}
}

IEnumerable<object> ITreeDataGridSource.GetModelChildren(object model) { return Enumerable.Empty<object>(); }

private AnonymousSortableRows<TModel> CreateRows() {
return new AnonymousSortableRows<TModel>(_itemsView, _comparer);
}

#endregion


#region IDisposable

private bool _disposed;

private void Dispose(bool disposing) {
if (_disposed || !disposing) {
return;
}

_d.Dispose();
_rows?.Dispose();
_disposed = true;
}

public new void Dispose() {

Check warning on line 209 in src/DynamicTreeDataGrid/DynamicFlatTreeDataGridSource.cs

View workflow job for this annotation

GitHub Actions / 🏗 Build / 🏗 Build

The member 'DynamicFlatTreeDataGridSource<TModel, TModelKey>.Dispose()' does not hide an accessible member. The new keyword is not required.

Check warning on line 209 in src/DynamicTreeDataGrid/DynamicFlatTreeDataGridSource.cs

View workflow job for this annotation

GitHub Actions / 🏗 Build / 🏗 Build

The member 'DynamicFlatTreeDataGridSource<TModel, TModelKey>.Dispose()' does not hide an accessible member. The new keyword is not required.
Dispose(true);
GC.SuppressFinalize(this);
}

#endregion
}
9 changes: 9 additions & 0 deletions src/DynamicTreeDataGrid/NoSortComparer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace DynamicTreeDataGrid;

/// <summary>
/// A comparer that doesn't affect the comparison at all (will not change the order of items)
/// </summary>
/// <typeparam name="T"></typeparam>
public class NoSortComparer<T> : IComparer<T> {
public int Compare(T? x, T? y) => 0;
}

0 comments on commit 7238102

Please sign in to comment.