Skip to content

Commit

Permalink
Merge branch 'master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
grokys authored Dec 13, 2024
2 parents e27abdd + 1cd818d commit bdf88bc
Show file tree
Hide file tree
Showing 38 changed files with 1,080 additions and 158 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
with:
dotnet-version: |
6.0.x
7.0.x
8.0.x
- name: Install dependencies
run: dotnet restore
- name: Build
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsAsErrors>nullable</WarningsAsErrors>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>9</LangVersion>
<LangVersion>12</LangVersion>
<!-- https://github.com/dotnet/msbuild/issues/2661 -->
<AddSyntheticProjectReferencesForSolutionDependencies>false</AddSyntheticProjectReferencesForSolutionDependencies>
<MSBuildEnableWorkloadResolver>false</MSBuildEnableWorkloadResolver>
Expand Down
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "7.0.101",
"version": "8.0.101",
"rollForward": "latestFeature"
}
}
1 change: 1 addition & 0 deletions samples/TreeDataGridDemo/App.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public class App : Application
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
Bogus.Randomizer.Seed = new System.Random(0);
}

public override void OnFrameworkInitializationCompleted()
Expand Down
5 changes: 5 additions & 0 deletions samples/TreeDataGridDemo/MainWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@
DockPanel.Dock="Right">
Cell Selection
</CheckBox>
<CheckBox IsChecked="{Binding Files.FlatList}"
Margin="4 0 0 0"
DockPanel.Dock="Right">
Flat
</CheckBox>
<TextBox Text="{Binding Files.SelectedPath, Mode=OneWay}"
Margin="4 0 0 0"
VerticalContentAlignment="Center"
Expand Down
192 changes: 137 additions & 55 deletions samples/TreeDataGridDemo/ViewModels/FilesPageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Linq;
using System.Reactive.Linq;
using System.Runtime.InteropServices;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Models.TreeDataGrid;
using Avalonia.Controls.Selection;
Expand All @@ -20,6 +19,9 @@ namespace TreeDataGridDemo.ViewModels
public class FilesPageViewModel : ReactiveObject
{
private static IconConverter? s_iconConverter;
private readonly HierarchicalTreeDataGridSource<FileTreeNodeModel>? _treeSource;
private FlatTreeDataGridSource<FileTreeNodeModel>? _flatSource;
private ITreeDataGridSource<FileTreeNodeModel> _source;
private bool _cellSelection;
private FileTreeNodeModel? _root;
private string _selectedDrive;
Expand All @@ -38,61 +40,17 @@ public FilesPageViewModel()
_selectedDrive = Drives.FirstOrDefault() ?? "/";
}

Source = new HierarchicalTreeDataGridSource<FileTreeNodeModel>(Array.Empty<FileTreeNodeModel>())
{
Columns =
{
new CheckBoxColumn<FileTreeNodeModel>(
null,
x => x.IsChecked,
(o, v) => o.IsChecked = v,
options: new()
{
CanUserResizeColumn = false,
}),
new HierarchicalExpanderColumn<FileTreeNodeModel>(
new TemplateColumn<FileTreeNodeModel>(
"Name",
"FileNameCell",
"FileNameEditCell",
new GridLength(1, GridUnitType.Star),
new()
{
CompareAscending = FileTreeNodeModel.SortAscending(x => x.Name),
CompareDescending = FileTreeNodeModel.SortDescending(x => x.Name),
IsTextSearchEnabled = true,
TextSearchValueSelector = x => x.Name
}),
x => x.Children,
x => x.HasChildren,
x => x.IsExpanded),
new TextColumn<FileTreeNodeModel, long?>(
"Size",
x => x.Size,
options: new()
{
CompareAscending = FileTreeNodeModel.SortAscending(x => x.Size),
CompareDescending = FileTreeNodeModel.SortDescending(x => x.Size),
}),
new TextColumn<FileTreeNodeModel, DateTimeOffset?>(
"Modified",
x => x.Modified,
options: new()
{
CompareAscending = FileTreeNodeModel.SortAscending(x => x.Modified),
CompareDescending = FileTreeNodeModel.SortDescending(x => x.Modified),
}),
}
};

Source.RowSelection!.SingleSelect = false;
Source.RowSelection.SelectionChanged += SelectionChanged;
_source = _treeSource = CreateTreeSource();

this.WhenAnyValue(x => x.SelectedDrive)
.Subscribe(x =>
{
_root = new FileTreeNodeModel(_selectedDrive, isDirectory: true, isRoot: true);
Source.Items = new[] { _root };

if (_treeSource is not null)
_treeSource.Items = new[] { _root };
else if (_flatSource is not null)
_flatSource.Items = _root.Children;
});
}

Expand All @@ -115,6 +73,16 @@ public bool CellSelection

public IList<string> Drives { get; }

public bool FlatList
{
get => Source != _treeSource;
set
{
if (value != FlatList)
Source = value ? _flatSource ??= CreateFlatSource() : _treeSource!;
}
}

public string SelectedDrive
{
get => _selectedDrive;
Expand All @@ -127,7 +95,11 @@ public string? SelectedPath
set => SetSelectedPath(value);
}

public HierarchicalTreeDataGridSource<FileTreeNodeModel> Source { get; }
public ITreeDataGridSource<FileTreeNodeModel> Source
{
get => _source;
private set => this.RaiseAndSetIfChanged(ref _source, value);
}

public static IMultiValueConverter FileIconConverter
{
Expand All @@ -151,11 +123,115 @@ public static IMultiValueConverter FileIconConverter
}
}

private FlatTreeDataGridSource<FileTreeNodeModel> CreateFlatSource()
{
var result = new FlatTreeDataGridSource<FileTreeNodeModel>(_root!.Children)
{
Columns =
{
new CheckBoxColumn<FileTreeNodeModel>(
null,
x => x.IsChecked,
(o, v) => o.IsChecked = v,
options: new()
{
CanUserResizeColumn = false,
}),
new TemplateColumn<FileTreeNodeModel>(
"Name",
"FileNameCell",
"FileNameEditCell",
new GridLength(1, GridUnitType.Star),
new()
{
CompareAscending = FileTreeNodeModel.SortAscending(x => x.Name),
CompareDescending = FileTreeNodeModel.SortDescending(x => x.Name),
IsTextSearchEnabled = true,
TextSearchValueSelector = x => x.Name
}),
new TextColumn<FileTreeNodeModel, long?>(
"Size",
x => x.Size,
options: new()
{
CompareAscending = FileTreeNodeModel.SortAscending(x => x.Size),
CompareDescending = FileTreeNodeModel.SortDescending(x => x.Size),
}),
new TextColumn<FileTreeNodeModel, DateTimeOffset?>(
"Modified",
x => x.Modified,
options: new()
{
CompareAscending = FileTreeNodeModel.SortAscending(x => x.Modified),
CompareDescending = FileTreeNodeModel.SortDescending(x => x.Modified),
}),
}
};

result.RowSelection!.SingleSelect = false;
result.RowSelection.SelectionChanged += SelectionChanged;
return result;
}

private HierarchicalTreeDataGridSource<FileTreeNodeModel> CreateTreeSource()
{
var result = new HierarchicalTreeDataGridSource<FileTreeNodeModel>(Array.Empty<FileTreeNodeModel>())
{
Columns =
{
new CheckBoxColumn<FileTreeNodeModel>(
null,
x => x.IsChecked,
(o, v) => o.IsChecked = v,
options: new()
{
CanUserResizeColumn = false,
}),
new HierarchicalExpanderColumn<FileTreeNodeModel>(
new TemplateColumn<FileTreeNodeModel>(
"Name",
"FileNameCell",
"FileNameEditCell",
new GridLength(1, GridUnitType.Star),
new()
{
CompareAscending = FileTreeNodeModel.SortAscending(x => x.Name),
CompareDescending = FileTreeNodeModel.SortDescending(x => x.Name),
IsTextSearchEnabled = true,
TextSearchValueSelector = x => x.Name
}),
x => x.Children,
x => x.HasChildren,
x => x.IsExpanded),
new TextColumn<FileTreeNodeModel, long?>(
"Size",
x => x.Size,
options: new()
{
CompareAscending = FileTreeNodeModel.SortAscending(x => x.Size),
CompareDescending = FileTreeNodeModel.SortDescending(x => x.Size),
}),
new TextColumn<FileTreeNodeModel, DateTimeOffset?>(
"Modified",
x => x.Modified,
options: new()
{
CompareAscending = FileTreeNodeModel.SortAscending(x => x.Modified),
CompareDescending = FileTreeNodeModel.SortDescending(x => x.Modified),
}),
}
};

result.RowSelection!.SingleSelect = false;
result.RowSelection.SelectionChanged += SelectionChanged;
return result;
}

private void SetSelectedPath(string? value)
{
if (string.IsNullOrEmpty(value))
{
Source.RowSelection!.Clear();
GetRowSelection(Source).Clear();
return;
}

Expand Down Expand Up @@ -204,12 +280,18 @@ private void SetSelectedPath(string? value)
}
}

Source.RowSelection!.SelectedIndex = index;
GetRowSelection(Source).SelectedIndex = index;
}

private ITreeDataGridRowSelectionModel<FileTreeNodeModel> GetRowSelection(ITreeDataGridSource source)
{
return source.Selection as ITreeDataGridRowSelectionModel<FileTreeNodeModel> ??
throw new InvalidOperationException("Expected a row selection model.");
}

private void SelectionChanged(object? sender, TreeSelectionModelSelectionChangedEventArgs<FileTreeNodeModel> e)
{
var selectedPath = Source.RowSelection?.SelectedItem?.Path;
var selectedPath = GetRowSelection(Source).SelectedItem?.Path;
this.RaiseAndSetIfChanged(ref _selectedPath, selectedPath, nameof(SelectedPath));

foreach (var i in e.DeselectedItems)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<IsPackable>True</IsPackable>
<LangVersion>10</LangVersion>
<RootNamespace>Avalonia.Controls</RootNamespace>
</PropertyGroup>
<PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,16 @@ public static TypedBinding<TIn, TOut> TwoWay<TOut>(Expression<Func<TIn, TOut>> e
$"Cannot create a two-way binding for '{expression}' because the expression does not target a property.",
nameof(expression));

if (property.GetGetMethod() is null)
MethodInfo? getMethodInfo = property.GetGetMethod(true);
if (getMethodInfo is null || getMethodInfo.IsPrivate)
throw new ArgumentException(
$"Cannot create a two-way binding for '{expression}' because the property has no getter.",
$"Cannot create a two-way binding for '{expression}' because the property has no getter or the getter is private.",
nameof(expression));

if (property.GetSetMethod() is null)
MethodInfo? setMethodInfo = property.GetSetMethod(true);
if (setMethodInfo is null || setMethodInfo.IsPrivate)
throw new ArgumentException(
$"Cannot create a two-way binding for '{expression}' because the property has no setter.",
$"Cannot create a two-way binding for '{expression}' because the property has no setter or the setter is private.",
nameof(expression));

// TODO: This is using reflection and mostly untested. Unit test it properly and
Expand Down
Loading

0 comments on commit bdf88bc

Please sign in to comment.