Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Porting MetadataControl #60

Merged
merged 6 commits into from
May 22, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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
3 changes: 3 additions & 0 deletions components/MetadataControl/OpenSolution.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@ECHO OFF

powershell ..\..\tooling\ProjectHeads\GenerateSingleSampleHeads.ps1 -componentPath %CD% %*
31 changes: 31 additions & 0 deletions components/MetadataControl/samples/Dependencies.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!--
WinUI 2 under UWP uses TargetFramework uap10.0.*
WinUI 3 under WinAppSdk uses TargetFramework net6.0-windows10.*
However, under Uno-powered platforms, both WinUI 2 and 3 can share the same TargetFramework.

MSBuild doesn't play nicely with this out of the box, so we've made it easy for you.

For .NET Standard packages, you can use the Nuget Package Manager in Visual Studio.
For UWP / WinAppSDK / Uno packages, place the package references here.
-->
<Project>
<!-- WinUI 2 / UWP -->
<ItemGroup Condition="'$(IsUwp)' == 'true'">
<!-- <PackageReference Include="Microsoft.Toolkit.Uwp.UI.Controls.Primitives" Version="7.1.2"/> -->
</ItemGroup>

<!-- WinUI 2 / Uno -->
<ItemGroup Condition="'$(IsUno)' == 'true' AND '$(WinUIMajorVersion)' == '2'">
<!-- <PackageReference Include="Uno.Microsoft.Toolkit.Uwp.UI.Controls.Primitives" Version="7.1.11"/> -->
</ItemGroup>

<!-- WinUI 3 / WinAppSdk -->
<ItemGroup Condition="'$(IsWinAppSdk)' == 'true'">
<!-- <PackageReference Include="CommunityToolkit.WinUI.UI.Controls.Primitives" Version="7.1.2"/> -->
</ItemGroup>

<!-- WinUI 3 / Uno -->
<ItemGroup Condition="'$(IsUno)' == 'true' AND '$(WinUIMajorVersion)' == '3'">
<!-- <PackageReference Include="Uno.CommunityToolkit.WinUI.UI.Controls.Primitives" Version="7.1.100-dev.15.g12261e2626"/> -->
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Project Sdk="MSBuild.Sdk.Extras/3.0.23">
<PropertyGroup>
<ToolkitComponentName>MetadataControl</ToolkitComponentName>
</PropertyGroup>

<!-- Sets this up as a toolkit component's sample project -->
<Import Project="$(ToolingDirectory)\ToolkitComponent.SampleProject.props" />
</Project>
60 changes: 60 additions & 0 deletions components/MetadataControl/samples/MetadataControl.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
title: MetadataControl
author: vgromfeld
description: The MetadataControl control displays a list of labels and hyper-links separated by a bullet.
keywords: MetadataControl, Control, metadata
dev_langs:
- csharp
category: Controls
subcategory: Layout
discussion-id: 0
issue-id: 0
---

# MetadataControl

The [MetadataControl](/dotnet/api/microsoft.toolkit.uwp.ui.controls.metadatacontrol) control displays a
list of labels and hyper-links separated by a bullet.
It also generates an accessible string representing its content.

The bullet separator can be customized using the `Separator` property.
`AccessibleSeparator` is used as a replacement for `Separator` to generate the accessible string.

The control needs a list of [MediadataItem](https://github.com/windows-toolkit/WindowsCommunityToolkit/blob/main/Microsoft.Toolkit.Uwp.UI.Controls.Core/MetadataControl/MetadataItem.cs).
niels9001 marked this conversation as resolved.
Show resolved Hide resolved
Each item will be displayed either as a text or as an hyper-link (if the `Command`property is set).

The default control template is using on a `TextBlock`. The style of this `TextBlock` can be customized using the `TextBlockStyle` property.

> [!SAMPLE MetadataControlSample]
niels9001 marked this conversation as resolved.
Show resolved Hide resolved

## Example

Add the control in the page:

```xaml
<controls:MetadataControl
x:Name="metadataControl"
Separator=" "
AccessibleSeparator=", "/>
```

Add items to control:

```cs
metadataControl.Items = new[]
{
new MetadataItem { Label = "Hello" },
new MetadataItem { Label = "World", Command = myCommand },
};
```

## MediadataItem

A `MediadataItem` contains the information about one entry which will be displayed in the `MetadataControl`

| Property | Type | Description |
| -- | -- | -- |
| Label | String | Gets or sets the label of the item |
| AccessibleLabel | String | Gets or sets the automation name that will be set on the item. If not set, `Label` will be used. |
| Command | ICommand | Gets or sets the command associated to the item. If null, the item will be displayed as a text field. If set, the item will be displayed as an hyperlink. |
| CommandParameter | Object | Gets or sets the parameter that will be provided to the `Command`|
234 changes: 234 additions & 0 deletions components/MetadataControl/samples/MetadataControlSample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using CommunityToolkit.WinUI.Controls;
using System.Windows.Input;

namespace MetadataControlExperiment.Samples;

/// <summary>
/// An example sample page of a custom control inheriting from Panel.
/// </summary>
[ToolkitSampleTextOption("Separator", " • ", Title = "Separator")]
[ToolkitSampleTextOption("AccessibleSeparator", ", ", Title = "AccessibleSeparator")]

[ToolkitSample(id: nameof(MetadataControlSample), "MetadataControl", description: $"A sample for showing how to create and use a {nameof(MetadataControl)} control.")]
public sealed partial class MetadataControlSample : Page
{
private static readonly string[] Labels = "Lorem ipsum dolor sit amet consectetur adipiscing elit".Split(' ');

private readonly Random _random;
private readonly ObservableCollection<MetadataItem> _units;
private readonly DelegateCommand<object> _command;

public MetadataControlSample()
{
this.InitializeComponent();
_random = new Random();
_units = new ObservableCollection<MetadataItem>();
_command = new DelegateCommand<object>(OnExecuteCommand);
metadataControl.Items = _units;
}

private string GetRandomLabel() => Labels[_random!.Next(Labels.Length)];

private void OnExecuteCommand(object obj)
{
OutputTxt.Text = $"Command invoked - parameter: {obj}";
}

private void AddLabel_Click(object sender, RoutedEventArgs e)
{
if (_units != null)
{
_units.Add(new MetadataItem { Label = GetRandomLabel() });
}
}

private void AddCommand_Click(object sender, RoutedEventArgs e)
{
if (_units != null)
{
var label = GetRandomLabel();
_units.Add(new MetadataItem
{
Label = label,
Command = _command!,
CommandParameter = label,
});
}
}

private void Clear_Click(object sender, RoutedEventArgs e)
{
if (_units != null)
{
OutputTxt.Text = "";
_units.Clear();
}
}
}

public class DelegateCommand : ICommand
niels9001 marked this conversation as resolved.
Show resolved Hide resolved
{
private readonly Action commandExecuteAction;

private readonly Func<bool> commandCanExecute;

/// <summary>
/// Initializes a new instance of the <see cref="DelegateCommand"/> class.
/// </summary>
/// <param name="execute">
/// The action to execute when called.
/// </param>
/// <param name="canExecute">
/// The function to call to determine if the command can execute the action.
/// </param>
/// <exception cref="ArgumentNullException">
/// Thrown if the execute action is null.
/// </exception>
public DelegateCommand(Action execute, Func<bool>? canExecute = null)
{
if (execute == null)
{
throw new ArgumentNullException(nameof(execute));
}

commandExecuteAction = execute;
commandCanExecute = canExecute ?? (() => true);
}

/// <summary>
/// Occurs when changes occur that affect whether or not the command should execute.
/// </summary>
public event EventHandler? CanExecuteChanged;

/// <summary>
/// Defines the method that determines whether the command can execute in its current state.
/// </summary>
/// <param name="parameter">
/// The parameter used by the command.
/// </param>
/// <returns>
/// Returns a value indicating whether this command can be executed.
/// </returns>
public bool CanExecute(object? parameter = null)
{
try
{
return commandCanExecute();
}
catch
{
return false;
}
}

/// <summary>
/// Defines the method to be called when the command is invoked.
/// </summary>
/// <param name="parameter">
/// The parameter used by the command.
/// </param>
public void Execute(object? parameter)
{
if (!CanExecute(parameter))
{
return;
}

try
{
commandExecuteAction();
}
catch
{
Debugger.Break();
}
}

public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}


public class DelegateCommand<T> : ICommand
{
private readonly Action<T> commandExecuteAction;

private readonly Func<T, bool> commandCanExecute;

public DelegateCommand(Action<T> executeAction, Func<T, bool>? canExecute = null)
{
if (executeAction == null)
{
throw new ArgumentNullException(nameof(executeAction));
}

commandExecuteAction = executeAction;
commandCanExecute = canExecute ?? (e => true);
}

/// <summary>
/// Occurs when changes occur that affect whether or not the command should execute.
/// </summary>
public event EventHandler? CanExecuteChanged;

/// <summary>
/// Defines the method that determines whether the command can execute in its current state.
/// </summary>
/// <param name="parameter">
/// The parameter used by the command.
/// </param>
/// <returns>
/// Returns a value indicating whether this command can be executed.
/// </returns>
public bool CanExecute(object? parameter)
{
try
{
return commandCanExecute(ConvertParameterValue(parameter!));
}
catch
{
return false;
}
}

/// <summary>
/// Defines the method to be called when the command is invoked.
/// </summary>
/// <param name="parameter">
/// The parameter used by the command.
/// </param>
public void Execute(object? parameter)
{
if (!CanExecute(parameter))
{
return;
}

try
{
commandExecuteAction(ConvertParameterValue(parameter!));
}
catch
{
Debugger.Break();
}
}

public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}

private static T ConvertParameterValue(object parameter)
{
parameter = parameter is T ? parameter : Convert.ChangeType(parameter, typeof(T));
return (T)parameter;
}
}
37 changes: 37 additions & 0 deletions components/MetadataControl/samples/MetadataControlSample.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!-- Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE file in the project root for more information. -->
<Page x:Class="MetadataControlExperiment.Samples.MetadataControlSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:MetadataControlExperiment.Samples"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">

<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>

<controls:MetadataControl x:Name="metadataControl"
AccessibleSeparator="{x:Bind AccessibleSeparator, Mode=OneWay}"
Separator="{x:Bind Separator, Mode=OneWay}" />

<TextBlock x:Name="OutputTxt"
Grid.Row="1"
Margin="0,0,0,24"
FontWeight="SemiBold" />
<StackPanel Grid.Row="2"
Orientation="Horizontal"
Spacing="8">
<Button Click="AddLabel_Click"
Content="Add label" />
<Button Click="AddCommand_Click"
Content="Add command" />
<Button Click="Clear_Click"
Content="Clear" />
</StackPanel>
</Grid>
</Page>
13 changes: 13 additions & 0 deletions components/MetadataControl/src/AdditionalAssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Runtime.CompilerServices;

// These `InternalsVisibleTo` calls are intended to make it easier for
// for any internal code to be testable in all the different test projects
// used with the Labs infrastructure.
[assembly: InternalsVisibleTo("MetadataControl.Tests.Uwp")]
[assembly: InternalsVisibleTo("MetadataControl.Tests.WinAppSdk")]
[assembly: InternalsVisibleTo("CommunityToolkit.Tests.Uwp")]
[assembly: InternalsVisibleTo("CommunityToolkit.Tests.WinAppSdk")]
Loading