Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions doc/helpers/AutoGrid-extensions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# AutoGrid Extensions

This document describes the extensions available for the `AutoGrid` control in the Uno.Toolkit library.

## Overview

The `AutoGrid` control provides a flexible way to automatically generate grid rows and columns based on its content. The extensions described here enhance its functionality and simplify common layout scenarios.

## Extensions

### 1. AutoGrid.SetRowCount

Sets the number of rows in the `AutoGrid`.

**Usage:**
```xml
<toolkit:AutoGrid toolkit:AutoGridExtensions.RowCount="3">
<!-- Children -->
</toolkit:AutoGrid>
```

**Type:** `int`

### 2. AutoGrid.SetColumnCount

Sets the number of columns in the `AutoGrid`.

**Usage:**
```xml
<toolkit:AutoGrid toolkit:AutoGridExtensions.ColumnCount="2">
<!-- Children -->
</toolkit:AutoGrid>
```

**Type:** `int`

### 3. AutoGrid.SetRowSpacing

Specifies the spacing between rows.

**Usage:**
```xml
<toolkit:AutoGrid toolkit:AutoGridExtensions.RowSpacing="8">
<!-- Children -->
</toolkit:AutoGrid>
```

**Type:** `double`

### 4. AutoGrid.SetColumnSpacing

Specifies the spacing between columns.

**Usage:**
```xml
<toolkit:AutoGrid toolkit:AutoGridExtensions.ColumnSpacing="8">
<!-- Children -->
</toolkit:AutoGrid>
```

**Type:** `double`

## Example

```xml
<toolkit:AutoGrid
toolkit:AutoGridExtensions.RowCount="2"
toolkit:AutoGridExtensions.ColumnCount="3"
toolkit:AutoGridExtensions.RowSpacing="10"
toolkit:AutoGridExtensions.ColumnSpacing="10">
<Button Content="A"/>
<Button Content="B"/>
<Button Content="C"/>
<Button Content="D"/>
<Button Content="E"/>
<Button Content="F"/>
</toolkit:AutoGrid>
```

## Remarks

- If row or column count is not specified, the control will auto-calculate based on the number of children.

## See Also

- [AutoGrid Control](AutoGrid.md)
- [Uno.Toolkit Documentation](../README.md)
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<Page x:Class="Uno.Toolkit.Samples.Content.Controls.AutoGridPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Uno.Toolkit.Samples.Content.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sample="using:Uno.Toolkit.Samples"
xmlns:utu="using:Uno.Toolkit.UI"
xmlns:void="There is no mistake so great that it cannot be undone."
mc:Ignorable="d void"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<sample:SamplePageLayout x:Name="SamplePageLayout" IsDesignAgnostic="True">
<sample:SamplePageLayout.DesignAgnosticTemplate>
<DataTemplate>
<ScrollViewer HorizontalScrollMode="Disabled">
<StackPanel>
<TextBlock Text="The AutoGrid behavior automatically assigns Grid.Row and Grid.Column properties to the grid's child elements. It also updates these property dynamically as views are added to or removed from the grid." Style="{StaticResource BodyTextBlockStyle}" />

<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Add:" />
<Button Content="Child" Click="AddChild" />
<Button Content="Child to the front"
Click="AddChild"
Tag="0" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Remove:" />
<Button Content="First Child"
Click="RemoveChild"
Tag="0" />
<Button Content="Last Child" Click="RemoveChild" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="SetOrientation:" />
<Button Content="Horizontal" Click="SetOrientation" />
<Button Content="Vertical" Click="SetOrientation" />
</StackPanel>
<Button Content="Clear Children" Click="ClearChildren" />

<Button Content="DebugVT" Click="DebugVT" />
</StackPanel>

<Border Margin="20"
BorderBrush="Red"
BorderThickness="2">
<Grid x:Name="SutGrid"
MaxWidth="400"
MaxHeight="400"
RowDefinitions="*,*,*"
ColumnDefinitions="*,*,*"
utu:AutoGrid.Enable="True" />
</Border>
</StackPanel>
</ScrollViewer>
</DataTemplate>
</sample:SamplePageLayout.DesignAgnosticTemplate>
</sample:SamplePageLayout>

</Grid>
</Page>
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using Windows.Foundation;
using Windows.Foundation.Collections;

// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238

namespace Uno.Toolkit.Samples.Content.Controls;
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class AutoGridPage : Page
{
public AutoGridPage()
{
this.InitializeComponent();
}
}
145 changes: 145 additions & 0 deletions src/Uno.Toolkit.UI/Behaviors/AutoGrid.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
using System;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Uno.Disposables;

#if IS_WINUI
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
#else
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
#endif

namespace Uno.Toolkit.UI;

public static partial class AutoGrid;

partial class AutoGrid
{
#region DependencyProperty: Enable

public static DependencyProperty EnableProperty { [DynamicDependency(nameof(GetEnable))] get; } = DependencyProperty.RegisterAttached(
"Enable",
typeof(bool),
typeof(AutoGrid),
new PropertyMetadata(default(bool), OnEnableChanged));

/// <summary>
/// Gets or sets whether the AutoGrid behavior is enabled on the specified Grid.
/// </summary>
[DynamicDependency(nameof(SetEnable))]
public static bool GetEnable(Grid obj) => (bool)obj.GetValue(EnableProperty);
[DynamicDependency(nameof(GetEnable))]
public static void SetEnable(Grid obj, bool value) => obj.SetValue(EnableProperty, value);

#endregion
#region DependencyProperty: Orientation = Horizontal

public static DependencyProperty OrientationProperty { [DynamicDependency(nameof(GetOrientation))] get; } = DependencyProperty.RegisterAttached(
"Orientation",
typeof(Orientation),
typeof(AutoGrid),
new PropertyMetadata(Orientation.Horizontal, OnOrientationChanged));

/// <summary>
/// Gets or sets the orientation in which the AutoGrid fills its children first.
/// </summary>
[DynamicDependency(nameof(SetOrientation))]
public static Orientation GetOrientation(Grid obj) => (Orientation)obj.GetValue(OrientationProperty);
[DynamicDependency(nameof(GetOrientation))]
public static void SetOrientation(Grid obj, Orientation value) => obj.SetValue(OrientationProperty, value);

#endregion
#region DependencyProperty: [private] Subscription

private static DependencyProperty SubscriptionProperty { [DynamicDependency(nameof(GetSubscription))] get; } = DependencyProperty.RegisterAttached(
"Subscription",
typeof(IDisposable),
typeof(AutoGrid),
new PropertyMetadata(default(IDisposable)));

[DynamicDependency(nameof(SetSubscription))]
private static IDisposable? GetSubscription(Grid obj) => (IDisposable?)obj.GetValue(SubscriptionProperty);
[DynamicDependency(nameof(GetSubscription))]
private static void SetSubscription(Grid obj, IDisposable? value) => obj.SetValue(SubscriptionProperty, value);

#endregion

private static void OnEnableChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
#if !HAS_UNO
throw new PlatformNotSupportedException("AutoGrid is not supported on WinUI, since we cannot subscribe to vector changes on Panel.Children.");
#else
if (sender is Grid g)
{
if (GetSubscription(g) is { } subscription)
{
subscription.Dispose();
SetSubscription(g, null);
}
if (e.NewValue is true)
{
void OnGridChildrenChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
RefreshLayout(g, e);
}
g.Children.CollectionChanged += OnGridChildrenChanged;
SetSubscription(g, Disposable.Create(() => g.Children.CollectionChanged -= OnGridChildrenChanged));
}
if (e.OldValue is false)
{
RefreshLayout(g);
}
}
#endif
}
private static void OnOrientationChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (sender is Grid g)
{
RefreshLayout(g);
}
}
private static void RefreshLayout(Grid g, NotifyCollectionChangedEventArgs? e = default)
{
if (GetEnable(g) is not true) return;

void SetCoordinates(UIElement? uie, int row, int column)
{
if (uie is not FrameworkElement fe) return;

Grid.SetRow(fe, row);
Grid.SetColumn(fe, column);
}

if (e?.OldItems is { Count: > 0 } removed)
{
foreach (var item in removed)
{
SetCoordinates(item as UIElement, 0, 0);
}
}

var endIndex = g.Children.Count - 1;
var startIndex = e?.Action switch
{
NotifyCollectionChangedAction.Add => e.NewStartingIndex == -1 ? endIndex : e.NewStartingIndex,
NotifyCollectionChangedAction.Remove => e.OldStartingIndex == -1 ? endIndex : e.OldStartingIndex,
_ => 0,
};
var rowCount = Math.Max(g.RowDefinitions.Count, 1);
var columnCount = Math.Max(g.ColumnDefinitions.Count, 1);
var fillByColumnsFirst = GetOrientation(g) is Orientation.Horizontal;

for(var i = startIndex; i <= endIndex; i++)
{
var (row, column) = fillByColumnsFirst
? (i / columnCount, i % columnCount)
: (i % rowCount, i / rowCount);

SetCoordinates(g.Children[i], row, column);
}
}
}
Loading