I'm struggling with how to refer/bind to the current item in a CollectionView
's DataTemplate
.
I've looked at questions like this, and thought I could apply the solution(s) to MAUI due to it and WPF both using XAML, but Intellisense complains that {Binding /}
is not a valid binding path. Doing more digging revealed that I should use {Binding .}
. So I tried that, however, trying to run my application results in it not even loading at all.
My xaml currently looks like this:
TreeNode.xaml
<maui:ReactiveContentView
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:maui="clr-namespace:ReactiveUI.Maui;assembly=ReactiveUI.Maui"
xmlns:vm="clr-namespace:turquoise_frontend.Controls"
xmlns:math="clr-namespace:HexInnovation;assembly=MathConverter.Maui"
x:TypeArguments="vm:TreeNodeModel"
x:DataType="vm:TreeNodeModel"
x:Class="turquoise_frontend.Controls.TreeNode" x:Name="this">
<VerticalStackLayout x:Name="sl2">
<!-- stuff omitted due to being irrelevant here... -->
<CollectionView ItemsSource="Children" x:Name="_ChildrenStackLayout" CanMixGroups="True" CanReorderItems="True" ReorderCompleted="_ChildrenStackLayout_ReorderCompleted">
<CollectionView.ItemTemplate>
<DataTemplate>
<vm:TreeNode/>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</VerticalStackLayout>
</maui:ReactiveContentView>
I'm guessing the slowdown and the error is caused by the runtime interpreting {Binding .}
to mean the object that the current xaml file represents as opposed to whatever the ItemTemplate
is for, thus causing an infinite recursion.
So what am I supposed to write if I want to create a recursive layout that can take advantage of a CollectionView
and also doesn't loop infinitely? Is what I'm trying to do even possible right now?
TreeNode.xaml.cs
public partial class TreeNode : ReactiveContentView<TreeNodeModel>
{
public double? IndentWidth => Depth * SpacerWidth;
public double? PaddingWidth => IndentWidth + 30;
public int SpacerWidth { get; } = 40;
private int? Depth => ViewModel?.Value.Depth ?? 0;
public string ExpandText { get => (ViewModel?.Children?.Count ?? 0) > 0 ? "▷" : ""; }
public TreeNode(TreeNodeModel tnm)
{
this.WhenAnyValue(x => x.ViewModel.isExpanded).ObserveOn(RxApp.MainThreadScheduler).Subscribe(b =>
{
_ChildrenStackLayout.IsVisible = b;
});
this.WhenAnyValue(b => b.ViewModel.selected).ObserveOn(RxApp.MainThreadScheduler).Subscribe(a =>
{
SelectionBoxView.IsVisible = a;
});
this.WhenAnyValue(b => b.ViewModel).ObserveOn(RxApp.MainThreadScheduler).WhereNotNull().Subscribe(vm =>
{
vm.Children.CollectionChanged += (o, e) =>
{
_SpacerBoxView.WidthRequest = IndentWidth ?? 0;
};
});
BindingContext = ViewModel = tnm;
InitializeComponent();
}
}
TreeNodeModel.cs
public class TreeNodeModel : ReactiveObject
{
private ITreeNode _value;
private ObservableCollection<TreeNodeModel> _children;
private TreeNodeModel? _parent;
private bool _toggled;
private bool _selected;
private bool _expanded;
private string contract;
[Reactive]
public ITreeNode Value { get => _value; set => this.RaiseAndSetIfChanged(ref _value, value); }
[Reactive]
public TreeNodeModel? Parent
{
get => _parent; set
{
_parent = value;
Value.Parent = value?.Value;
}
}
[Reactive]
public ObservableCollection<TreeNodeModel> Children
{
get
{
var nc = new ObservableCollection<TreeNodeModel>();
foreach (var i in Value.Children ?? new ObservableCollection<ITreeNode>())
{
var b = new TreeNodeModel(i, this.contract)
{
Tree = this.Tree
};
b.Parent = this;
nc.Add(b);
}
return nc;
}
}
public TreeNodeModel Root
{
get
{
TreeNodeModel top = this;
do
{
if (top.Parent != null)
top = top.Parent;
else break;
} while (top != null);
return top ?? this;
}
}
[Reactive]
public bool isExpanded { get => _expanded; set => this.RaiseAndSetIfChanged(ref _expanded, value); }
[Reactive] public bool Toggled { get => _toggled; set => this.RaiseAndSetIfChanged(ref _toggled, value); }
public TreeModel Tree { get; set; }
[Reactive]
public bool selected { get => _selected; set => this.RaiseAndSetIfChanged(ref _selected, value); }
public TreeNodeModel(ITreeNode val, string contract)
{
this.contract = contract;
Toggled = val.Toggled;
selected = false;
isExpanded = false;
_value = val;
if (val.Parent != null)
_parent = new TreeNodeModel(val.Parent, contract) {
Tree = this.Tree
};
this.WhenAnyValue(b => b.Toggled).Subscribe(a =>
{
_value.Toggled = a;
});
this.WhenAnyValue(b => b.isExpanded);
this.WhenAnyValue(b => b.Toggled);
this.WhenAnyValue(b => b.selected);
}
}
ITreeNode
interface
public interface ITreeNode
{
public object Value { get; set; }
public ObservableCollection<ITreeNode> Children { get; }
public ITreeNode? Parent { get; set; }
public int Depth { get; }
public int ID();
public bool IsBranch { get; }
public string AsString { get; }
public bool Toggled { get; set; }
public void Add(ITreeNode tn, int index);
public void Add(ITreeNode tn);
public void Remove(ITreeNode tn);
public void Remove(int index);
public bool Equals(ITreeNode other);
public event NotifyCollectionChangedEventHandler NotifyCollectionChanged;
}