Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/Keflon/FunctionZero.Maui.Controls
Virtualising TreeView and ListView
https://github.com/Keflon/FunctionZero.Maui.Controls
csharp
Last synced: 30 days ago
JSON representation
Virtualising TreeView and ListView
- Host: GitHub
- URL: https://github.com/Keflon/FunctionZero.Maui.Controls
- Owner: Keflon
- License: mit
- Created: 2022-06-27T13:23:25.000Z (over 2 years ago)
- Default Branch: master
- Last Pushed: 2024-03-31T15:52:43.000Z (9 months ago)
- Last Synced: 2024-11-04T18:12:35.057Z (about 1 month ago)
- Topics: csharp
- Language: C#
- Homepage:
- Size: 1.04 MB
- Stars: 96
- Watchers: 4
- Forks: 5
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: License.md
Awesome Lists containing this project
- awesome-dotnet-maui - FunctionZero.Maui.Controls - square)](https://github.com/Keflon/FunctionZero.Maui.Controls/stargazers)|[![GitHub last-commit](https://img.shields.io/github/last-commit/Keflon/FunctionZero.Maui.Controls?style=flat-square)](https://github.com/Keflon/FunctionZero.Maui.Controls/commits) (UI)
README
# Latest
### .NET 8 support. Nuget [here](https://www.nuget.org/packages/FunctionZero.Maui.Controls)
Use package [8.0.0](https://www.nuget.org/packages/FunctionZero.Maui.Controls/8.0.0-pre1) and above if you are building against .NET 8.
Use package [2.3.4-pre2](https://www.nuget.org/packages/FunctionZero.Maui.Controls/2.3.4-pre2) if you are building against .NET 7.
Use package [2.0.0](https://www.nuget.org/packages/FunctionZero.Maui.Controls/2.0.0) if you are building against .NET 6.
# Workarounds
If you're having trouble with the MAUI `TabbedPage` or `FlyoutPage` see [below](#workarounds)# Controls
[NuGet package](https://www.nuget.org/packages/FunctionZero.Maui.Controls)1. [ListViewZero](#listviewzero)
1. [TreeViewZero](#treeviewzero)
1. [MaskViewZero](#maskviewzero)## ListViewZero
### Features
- A fully virtualising list-view that doesn't [leak memory](https://github.com/dotnet/maui/issues/8151) or [enforce arbitrary item spacing](https://github.com/dotnet/maui/issues/4520).
- Very high performance
- All rendering uses cross-platform codeIf you can use a `CollectionView` or a `ListView` you will have no trouble with a `ListViewZero`
TODO: Sample image
### ListViewZero exposes the following properties
Property | Type | Bindable | Purpose
:----- | :---- | :----: | :-----
ItemContainerStyle | Style | Yes | An optional `Style` that can be applied to the `ListItemZero` instances that represent each node. This can be used to modify how selected-items are rendered.
ItemHeight | float | Yes | The height of each row in the list-view
ItemsSource | object | Yes | Set this to the IEnumerable (usually found in your view-model) that contains your items
ItemTemplate | DataTemplate | Yes | Used to draw the data for each node. Set this to a `DataTemplate` or a `DataTemplateSelector`. See below.
ScrollOffset | float | YES! | This is the absolute offset and can bound to.
SelectedItem | object | Yes | Set to the currently selected item, i.e. an instance of your *ViewModel* data, or null
SelectedItems | IList | Yes | All currently selected items. Default is an `ObservableCollection`. You can bind to it or set your own collection, and if it supports `INotifyCollectionChanged` the `ListViewZero` will track it.
SelectionMode | SelectionMode | Yes | Allows a `SelectionMode` of None, Single or Multiple.
RemainingItems | int | Yes | This tracks the number of items in the `ItemsSource` that are below the bottom of the `ListViewZero`.
RemainingItemsChangedCommand | ICommand | Yes | This is raised whenever `RemainingItems` changes. The _command parameter_ is set to `RemainingItems`.### Create a ListViewZero
Given a collection of items
```csharp
public IEnumerable ListData { get; }
```
Add the namespace:
```xml
xmlns:cz="clr-namespace:FunctionZero.Maui.Controls;assembly=FunctionZero.Maui.Controls"
```
Then declare a `ListViewZero` like this:
```xml
```
### Tracking changes in the data
If the ItemsSource supports `INotifyCollectionChanged`, the list-view will track all changes automatically. E.g.
```csharp
public ObservableCollection ListData { get; }
```
If the properties on your items support `INotifyPropertyChanged` then they too will be tracked.For example, `ListViewZero` will track changes to `Name` property on the following node:
```csharp
public class Person : BaseClassWithInpc
{
private string _name;
public string Name
{
get => _name;
set => SetProperty(ref _name, value);
}
}
```### SelectionMode
Similar to the `CollectionView`, allowed values are *None, Single or Multiple*. You can change this property at runtime, e.g. via `Binding`### SelectedItem / SelectedItems
`SelectedItem` tracks the currently selected item, and can be databound to your ViewModel`SelectedItems` defaults to an `ObservableCollection` and tracks all items whose `IsSelected` property is true. The default `BindingMode` is `TwoWay`
In your view-model you can bind to the default collection (BindingMode OneWayToSource) or replace it with your own collection (BindingMode OneWay or TwoWay)
The `ListViewZero` will maintain the contents of the collection for you, and you can modify the collection from your view-model to programatically select items## Styling SelectedItems
You can replace this styling by setting the `ItemContainerStyle` property on your `ListViewZero`
~~Selected items are rendered using a VisualStateManager and 3 of the 4 *CommonStates*~~
Selected items are rendered using a VisualStateManager and the following statesCommon State | Description | IsSelected | IsPrimary | SelectionMode
:----- | :---- | :---- | :---- | :----
Normal | The ListViewItem is not selected | False | False | Any
ItemFocused | The ListViewItem is the primary-selection | True | True | Single or Multiple
Selected | The ListViewItem is selected but not the primary | True | False | Multiple
Disabled | Not used | n/a | n/a | n/aThis is the default `Style` used to modify the `BackgroundColor` of selected items, and can serve as a baseline for your own
```xml<Setter Property="VisualStateManager.VisualStateGroups" >
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates"><!-- BackgroundColor must have a value set or the other states cannot 'put back' the original color -->
<!-- I *think* this is due to a bug in MAUI because unappyling a Setter ought to revert to the original value or default -->
<VisualState x:Name="Normal" >
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="Transparent" />
</VisualState.Setters>
</VisualState><VisualState x:Name="ItemFocused">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="Cyan" />
</VisualState.Setters>
</VisualState><VisualState x:Name="Selected">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="AliceBlue" />
</VisualState.Setters>
</VisualState></VisualStateGroup>
</VisualStateGroupList>
</Setter>```
And set it like this:
```xml
`. You can bind to it or set your own collection, and if it supports `INotifyCollectionChanged` the `TreeViewZero` will track it.
SelectionMode | SelectionMode | Yes | Alloows a `SelectionMode` of None, Single or Multiple.
TreeChildren | IEnumerable | No | This is exposed for future capabilities and exposes all items *potentially* visible in the viewport.
TreeItemControlTemplate | ControlTemplate | Yes | Alows you to replace the default `ControlTemplate` used to render each node
TreeItemTemplate | TemplateProvider | Yes | Used to draw the data for each node. Set this to a `TreeItemDataTemplate` or a `TreeItemDataTemplateSelector`. See below.### TreeItemDataTemplate
`TreeItemDataTemplate` tells a tree-node how to draw its content, how to get its children and whether it should bind `IsExpanded` to the underlying data.
It declares the following properties:Property | Type | Purpose
:----- | :---- | :-----
ChildrenPropertyName | string | The name of the property used to find the node children
IsExpandedPropertyName | string | The name of the property used to store whether the node is expanded
ItemTemplate | DataTemplate | The DataTemplate used to draw this node
TargetType | Type | When used in a `TreeItemDataTemplateSelector`, identifies the least-derived nodes the ItemTemplate can be applied to.### Create a TreeViewZero
Given a hierarchy of `MyNode`
```csharp
public class MyNode
{
public string Name { get; set;}
public IEnumerable MyNodeChildren { get; set; }
}
```Add the namespace:
```xml
xmlns:cz="clr-namespace:FunctionZero.Maui.Controls;assembly=FunctionZero.Maui.Controls"
```Then declare a `TreeViewZero` like this:
```xml
```
## Tracking changes in the data
If the children of a node support `INotifyCollectionChanged`, the TreeView will track all changes automatically.
If the properties on your node support `INotifyPropertyChanged` then they too will be tracked.For example, TreeViewZero will track changes to `Name`, `IsExpanded` and any
modifications to the `Children` collection on the following node:
```csharp
public class MyObservableNode : BaseClassWithInpc
{
private string _name;
public string Name
{
get => _name;
set => SetProperty(ref _name, value);
}private bool _isMyNodeExpanded;
public bool IsMyNodeExpanded
{
get => _isMyNodeExpanded;
set => SetProperty(ref _isMyNodeExpanded, value);
}public ObservableCollection Children {get; set;}
}
```
This is how to bind the `IsMyNodeExpanded` from our data, to `IsExpanded` on the TreeNode ...```xml
...
```
## SelectionMode
The `TreeViewZero` allows selection modes *None, Single or Multiple*.
Please see the [ListViewZero](#selecteditem-selecteditems) docs for how to use the SelectionMode property.## Styling SelectedItems
Use this to style each tree-node, e.g. to change how selected items are rendered.
See [Styling SelectedItems](#styling-selecteditems) on the `ListViewZero` for details, or use the following as a guide:```xml
<Setter Property="VisualStateManager.VisualStateGroups" >
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates"><!-- BackgroundColor must have a value set or the other states cannot 'put back' the original color -->
<!-- I *think* this is due to a bug in MAUI because unappyling a Setter ought to revert to the original value or default -->
<VisualState x:Name="Normal" >
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="Transparent" />
</VisualState.Setters>
</VisualState><VisualState x:Name="ItemFocused">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="Cyan" />
</VisualState.Setters>
</VisualState><VisualState x:Name="Selected">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="AliceBlue" />
</VisualState.Setters>
</VisualState></VisualStateGroup>
</VisualStateGroupList>
</Setter>```
And set it like this:
```xml
```
### Customising TreeItemDataTemplateSelector
If you want **full-control** over the `TreeItemTemplate` per node, you can easily implement your own
`TreeItemDataTemplateSelector` and override `OnSelectTemplateProvider`. Here's an example that chooses a template
based on whether the node has children or not:```csharp
public class MyTreeItemDataTemplateSelector : TemplateProvider
{
/// These should be set in the xaml markup. (or code-behind, if that's how you roll)
public TreeItemDataTemplate TrunkTemplate{ get; set; }
public TreeItemDataTemplate LeafTemplate{ get; set; }public override TreeItemDataTemplate OnSelectTemplateProvider(object item)
{
if(item is MyTreeNode mtn)
if((mtn.Children != null) && (mtn.Children.Count != 0))
return TrunkTemplate;
return LeafTemplate;
}
}
```
Take a look at [TreeItemDataTemplateSelector.cs](https://github.com/Keflon/FunctionZero.Maui.Controls/blob/master/FunctionZero.Maui.Controls/TreeItemDataTemplateSelector.cs)
for an example of how to provide a *collection* of `TreeItemDataTemplate` instances to your TemplateProvider.## Drawing your own tree-nodes
Do this if you want to change the way the whole tree-node is drawn, e.g. to replace the *chevron*.
It is a two-step process.
1. Create a `ControlTemplate` for the node
1. Apply it to the `TreeViewZero`The *templated parent* for the `ControlTemplate` is a `ListItemZero`. It exposes these properties:
Property | Type | Purpose
:----- | :----: | :-----
IsPrimary | bool | If selection is allowed, this tracks the current `SelectedItem`
IsSelected | bool | If the current item is selected, this is true. Note we can have multiple items *selected*, but only one *SelectedItem*
ItemIndex | int | For internal use when managing the cache
ItemTemplate | DataTemplate | The `DataTemplate` used to generate the `ListViewItem` ContentThe `BindingContext` of the *templated parent* is a [TreeNodeContainer](https://github.com/Keflon/FunctionZero.TreeListItemsSourceZero) and includes the following properties:
Property | Type | Purpose
:----- | :----: | :-----
Indent | int | How deep the node should be indented. It is equal to `NestLevel`, or `NestLevel-1` if the Tree Root is not shown.
NestLevel | int | The depth of the node in the data.
IsExpanded | bool | This property reflects whether the TreeNode is expanded.
ShowChevron | bool | Whether the chevron is drawn. True if the node has children.
Data | object | This is the tree-node data for this TreeNodeZero instance, i.e. your data!### Step 1 - Create a `ControlTemplate` ...
You can base the `ControlTemplate` on the default, show here, or bake your own entirely.
```xml
```
### Step 2 - give it to the TreeView ...
```xml
```
## MaskViewZero
There's a cool new control for masking out areas of the screen.
It's really boring writing documentation so here's a quick sample whilst I finish the control off.### Put your UI inside a `MaskZero` control, e.g. using a `ControlTemplate` ...
```xaml
```
Notice we are binding to the control's `~Request` properties.
This means any changes will be animated, using the `Easing` functions you provide.Now give some of your controls a `MaskZero.MaskName`
```xaml
```
Finally, add to your `ViewModel` the properties the `ControlTemplate` binds to, and set them, simple as that!
```csharp
private async Task DoTheThingAsync()
{
while (true)
{
await Task.Delay(2000);TargetName = "banana";
MaskColor = Colors.Red;
MaskEdgeColor = Colors.Black;
await Task.Delay(2000);TargetName = "radish";
MaskColor = Colors.Purple;
MaskEdgeColor = Colors.Black;
await Task.Delay(2000);TargetName = "melon";
MaskColor = Colors.Blue;
MaskEdgeColor = Colors.Red;
await Task.Delay(2000);TargetName = "grapefruit";
MaskColor = Colors.Yellow;
MaskEdgeColor = Colors.Black;
}
}
```Run the demo to see different controls highlighted, with animated color, shape and opacity changes. Code is here:
- [CircleMaskPage.xaml](https://github.com/Keflon/FunctionZero.Maui.Controls/blob/master/SampleApp/Mvvm/Pages/Mask/CircleMaskPage.xaml)
- [CircleMaskPageVm.cs](https://github.com/Keflon/FunctionZero.Maui.Controls/blob/master/SampleApp/Mvvm/PageViewModels/Mask/CircleMaskPageVm.cs)# Workarounds:
## `AdaptedTabbedPage` [MAUI bug 14572](https://github.com/dotnet/maui/issues/14572)
- Use it when you want to use `ItemsSource` and `ItemTemplate`. Stick with `TabbedPage` if you're manipulating the `Children` collection directly.
- This implementation replaces `ItemsSource` by hiding the base implementation.
This means if you set it up in code-behind, you must ensure you have a reference of type `AdaptedTabbedPage` when you set `ItemsSource`.
If your reference is of type `TabbedPage` or `MultiPage` you'll be setting the _base_ `ItemsSource` and the crash will remain.
### Update:
- ~~`SelectedItem` now has limited support. Setting it in code works fine and swaps to the correct Tab, however swapping by interacting with the UI does not
update `SelectedItem`, because doing so would cause the WinUI crash we're trying to dodge.~~
- `SelectedItem` is fine. If you think it's causing problems, set `UseExperimentalSelectedItem` to false.## `AdaptedFlyoutPage` [MAUI bug 13496](https://github.com/dotnet/maui/issues/13496)
- Basically if the Flyout loses focus and the FlyoutLayoutBehavior is `Popover`,
it assumes the flyout has been dismissed and sets the `IsPresented` property to false.