Skip to content

Commit cffaa7f

Browse files
committed
metadata control + sample app update
1 parent f2301cf commit cffaa7f

File tree

11 files changed

+389
-0
lines changed

11 files changed

+389
-0
lines changed

Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@
269269
<Content Include="Icons\Services.png" />
270270
<Content Include="SamplePages\Animations\Effects\FadeBehavior.png" />
271271
<Content Include="SamplePages\ColorPicker\ColorPicker.png" />
272+
<Content Include="SamplePages\MetadataControl\MetadataControl.png" />
272273
<Content Include="SamplePages\TilesBrush\TilesBrush.png" />
273274
<Content Include="SamplePages\Eyedropper\Eyedropper.png" />
274275
<Content Include="SamplePages\OnDevice\OnDevice.png" />
@@ -497,6 +498,9 @@
497498
<Compile Include="SamplePages\FocusBehavior\FocusBehaviorPage.xaml.cs">
498499
<DependentUpon>FocusBehaviorPage.xaml</DependentUpon>
499500
</Compile>
501+
<Compile Include="SamplePages\MetadataControl\MetadataControlPage.xaml.cs">
502+
<DependentUpon>MetadataControlPage.xaml</DependentUpon>
503+
</Compile>
500504
<Compile Include="SamplePages\TilesBrush\TilesBrushPage.xaml.cs">
501505
<DependentUpon>TilesBrushPage.xaml</DependentUpon>
502506
</Compile>
@@ -975,6 +979,14 @@
975979
<Generator>MSBuild:Compile</Generator>
976980
<SubType>Designer</SubType>
977981
</Page>
982+
<Content Include="SamplePages\MetadataControl\MetadataControlCode.bind">
983+
<SubType>Designer</SubType>
984+
<Generator>MSBuild:Compile</Generator>
985+
</Content>
986+
<Page Include="SamplePages\MetadataControl\MetadataControlPage.xaml">
987+
<SubType>Designer</SubType>
988+
<Generator>MSBuild:Compile</Generator>
989+
</Page>
978990
<Page Include="SamplePages\TilesBrush\TilesBrushPage.xaml">
979991
<Generator>MSBuild:Compile</Generator>
980992
<SubType>Designer</SubType>
883 Bytes
Loading
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<Page
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
5+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
6+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
7+
mc:Ignorable="d">
8+
9+
<StackPanel Padding="12">
10+
<controls:MetadataControl
11+
x:Name="metadataControl"
12+
Separator="@[Separator:String: • ]"
13+
AccessibleSeparator="@[AccessibleSeparator:String:, ]"/>
14+
</StackPanel>
15+
</Page>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<Page x:Class="Microsoft.Toolkit.Uwp.SampleApp.SamplePages.MetadataControlPage"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
5+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
6+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
7+
mc:Ignorable="d">
8+
9+
<StackPanel Padding="12">
10+
<controls:MetadataControl x:Name="metadataControl" />
11+
</StackPanel>
12+
</Page>
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Collections.ObjectModel;
7+
using Microsoft.Toolkit.Uwp.SampleApp.Common;
8+
using Microsoft.Toolkit.Uwp.UI.Controls;
9+
using Microsoft.Toolkit.Uwp.UI.Extensions;
10+
using Windows.UI.Xaml;
11+
using Windows.UI.Xaml.Controls;
12+
13+
namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
14+
{
15+
/// <summary>
16+
/// A page that shows how to use the MetadataControl
17+
/// </summary>
18+
public sealed partial class MetadataControlPage : Page, IXamlRenderListener
19+
{
20+
private static readonly string[] Labels = "Lorem ipsum dolor sit amet consectetur adipiscing elit".Split(" ");
21+
22+
private readonly Random _random;
23+
private readonly ObservableCollection<MetadataUnit> _units;
24+
private readonly DelegateCommand<object> _command;
25+
private MetadataControl _metadataControl;
26+
27+
public MetadataControlPage()
28+
{
29+
_random = new Random();
30+
_units = new ObservableCollection<MetadataUnit>();
31+
_command = new DelegateCommand<object>(OnExecuteCommand);
32+
InitializeComponent();
33+
Setup();
34+
}
35+
36+
public void OnXamlRendered(FrameworkElement control)
37+
{
38+
_metadataControl = control.FindChildByName("metadataControl") as MetadataControl;
39+
if (_metadataControl != null)
40+
{
41+
_metadataControl.MetadataUnits = _units;
42+
}
43+
}
44+
45+
private void Setup()
46+
{
47+
SampleController.Current.RegisterNewCommand("Add label", (sender, args) =>
48+
{
49+
_units.Add(new MetadataUnit { Label = GetRandomLabel() });
50+
});
51+
52+
SampleController.Current.RegisterNewCommand("Add command", (sender, args) =>
53+
{
54+
var label = GetRandomLabel();
55+
_units.Add(new MetadataUnit
56+
{
57+
Label = label,
58+
Command = _command,
59+
CommandParameter = label,
60+
});
61+
});
62+
63+
SampleController.Current.RegisterNewCommand("Clear", (sender, args) =>
64+
{
65+
_units.Clear();
66+
});
67+
}
68+
69+
private string GetRandomLabel() => Labels[_random.Next(Labels.Length)];
70+
71+
private async void OnExecuteCommand(object obj)
72+
{
73+
var dialog = new ContentDialog
74+
{
75+
Title = "Command invoked",
76+
Content = $"Command parameter: {obj}",
77+
CloseButtonText = "OK"
78+
};
79+
80+
await dialog.ShowAsync();
81+
}
82+
}
83+
}

Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,16 @@
136136
"Icon": "/SamplePages/RadialProgressBar/RadialProgressBar.png",
137137
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/RadialProgressBar.md"
138138
},
139+
{
140+
"Name": "MetadataControl",
141+
"Type": "MetadataControlPage",
142+
"Subcategory": "Status and Info",
143+
"About": "The control displays a list of metadata separated by bullets. The entries can either be strings or commands.",
144+
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/MetadataControl",
145+
"XamlCodeFile": "MetadataControlCode.bind",
146+
"Icon": "/SamplePages/MetadataControl/MetadataControl.png",
147+
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/MetadataControl.md"
148+
},
139149
{
140150
"Name": "RotatorTile",
141151
"Type": "RotatorTilePage",
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Collections.Generic;
6+
using System.Collections.Specialized;
7+
using System.Text;
8+
using Windows.UI.Xaml;
9+
using Windows.UI.Xaml.Automation;
10+
using Windows.UI.Xaml.Automation.Peers;
11+
using Windows.UI.Xaml.Controls;
12+
using Windows.UI.Xaml.Documents;
13+
14+
namespace Microsoft.Toolkit.Uwp.UI.Controls
15+
{
16+
/// <summary>
17+
/// Display <see cref="MetadataUnit"/>s separated by bullets.
18+
/// </summary>
19+
[TemplatePart(Name = TextContainerPart, Type = typeof(TextBlock))]
20+
public sealed class MetadataControl : Control
21+
{
22+
/// <summary>
23+
/// The DP to store the <see cref="Separator"/> property value.
24+
/// </summary>
25+
public static readonly DependencyProperty SeparatorProperty = DependencyProperty.Register(
26+
nameof(Separator),
27+
typeof(string),
28+
typeof(MetadataControl),
29+
new PropertyMetadata(" • ", OnPropertyChanged));
30+
31+
/// <summary>
32+
/// The DP to store the <see cref="AccessibleSeparator"/> property value.
33+
/// </summary>
34+
public static readonly DependencyProperty AccessibleSeparatorProperty = DependencyProperty.Register(
35+
nameof(AccessibleSeparator),
36+
typeof(string),
37+
typeof(MetadataControl),
38+
new PropertyMetadata(", ", OnPropertyChanged));
39+
40+
/// <summary>
41+
/// The DP to store the <see cref="MetadataUnits"/> property value.
42+
/// </summary>
43+
public static readonly DependencyProperty MetadataUnitsProperty = DependencyProperty.Register(
44+
nameof(MetadataUnits),
45+
typeof(IEnumerable<MetadataUnit>),
46+
typeof(MetadataControl),
47+
new PropertyMetadata(null, OnMetadataUnitsChanged));
48+
49+
private const string TextContainerPart = "TextContainer";
50+
51+
private TextBlock _textContainer;
52+
53+
/// <summary>
54+
/// Initializes a new instance of the <see cref="MetadataControl"/> class.
55+
/// </summary>
56+
public MetadataControl()
57+
{
58+
DefaultStyleKey = typeof(MetadataControl);
59+
ActualThemeChanged += OnActualThemeChanged;
60+
}
61+
62+
/// <summary>
63+
/// Gets or sets the separator to display between the <see cref="MetadataUnit"/>.
64+
/// </summary>
65+
public string Separator
66+
{
67+
get => (string)GetValue(SeparatorProperty);
68+
set => SetValue(SeparatorProperty, value);
69+
}
70+
71+
/// <summary>
72+
/// Gets or sets the separator that will be used to generate the accessible string representing the control content.
73+
/// </summary>
74+
public string AccessibleSeparator
75+
{
76+
get => (string)GetValue(AccessibleSeparatorProperty);
77+
set => SetValue(AccessibleSeparatorProperty, value);
78+
}
79+
80+
/// <summary>
81+
/// Gets or sets he <see cref="MetadataUnit"/> to display in the control.
82+
/// If it implements <see cref="INotifyCollectionChanged"/>, the control will automatically update itself.
83+
/// </summary>
84+
public IEnumerable<MetadataUnit> MetadataUnits
85+
{
86+
get => (IEnumerable<MetadataUnit>)GetValue(MetadataUnitsProperty);
87+
set => SetValue(MetadataUnitsProperty, value);
88+
}
89+
90+
/// <inheritdoc/>
91+
protected override void OnApplyTemplate()
92+
{
93+
_textContainer = GetTemplateChild(TextContainerPart) as TextBlock;
94+
Update();
95+
}
96+
97+
private static void OnMetadataUnitsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
98+
{
99+
var control = (MetadataControl)d;
100+
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args) => control.Update();
101+
102+
if (e.OldValue is INotifyCollectionChanged oldNcc)
103+
{
104+
oldNcc.CollectionChanged -= OnCollectionChanged;
105+
}
106+
107+
if (e.NewValue is INotifyCollectionChanged newNcc)
108+
{
109+
newNcc.CollectionChanged += OnCollectionChanged;
110+
}
111+
112+
control.Update();
113+
}
114+
115+
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
116+
=> ((MetadataControl)d).Update();
117+
118+
private void OnActualThemeChanged(FrameworkElement sender, object args) => Update();
119+
120+
private void Update()
121+
{
122+
if (_textContainer is null)
123+
{
124+
// The template is not ready yet.
125+
return;
126+
}
127+
128+
_textContainer.Inlines.Clear();
129+
130+
if (MetadataUnits is null)
131+
{
132+
AutomationProperties.SetName(_textContainer, string.Empty);
133+
NotifyLiveRegionChanged();
134+
return;
135+
}
136+
137+
Inline unitToAppend;
138+
var accessibleString = new StringBuilder();
139+
foreach (var unit in MetadataUnits)
140+
{
141+
if (_textContainer.Inlines.Count > 0)
142+
{
143+
_textContainer.Inlines.Add(new Run { Text = Separator });
144+
accessibleString.Append(AccessibleSeparator ?? Separator);
145+
}
146+
147+
unitToAppend = new Run
148+
{
149+
Text = unit.Label,
150+
};
151+
152+
if (unit.Command != null)
153+
{
154+
var hyperLink = new Hyperlink
155+
{
156+
UnderlineStyle = UnderlineStyle.None,
157+
Foreground = _textContainer.Foreground,
158+
};
159+
hyperLink.Inlines.Add(unitToAppend);
160+
161+
void OnHyperlinkClicked(Hyperlink sender, HyperlinkClickEventArgs args)
162+
{
163+
if (unit.Command.CanExecute(unit.CommandParameter))
164+
{
165+
unit.Command.Execute(unit.CommandParameter);
166+
}
167+
}
168+
169+
hyperLink.Click += OnHyperlinkClicked;
170+
171+
unitToAppend = hyperLink;
172+
}
173+
174+
var unitAccessibleLabel = unit.AccessibleLabel ?? unit.Label;
175+
AutomationProperties.SetName(unitToAppend, unitAccessibleLabel);
176+
accessibleString.Append(unitAccessibleLabel);
177+
178+
_textContainer.Inlines.Add(unitToAppend);
179+
}
180+
181+
AutomationProperties.SetName(_textContainer, accessibleString.ToString());
182+
NotifyLiveRegionChanged();
183+
}
184+
185+
private void NotifyLiveRegionChanged()
186+
{
187+
if (AutomationPeer.ListenerExists(AutomationEvents.LiveRegionChanged))
188+
{
189+
var peer = FrameworkElementAutomationPeer.FromElement(this);
190+
peer?.RaiseAutomationEvent(AutomationEvents.LiveRegionChanged);
191+
}
192+
}
193+
}
194+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<ResourceDictionary
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls">
5+
6+
<Style TargetType="controls:MetadataControl">
7+
<Setter Property="Template">
8+
<Setter.Value>
9+
<ControlTemplate TargetType="controls:MetadataControl">
10+
<TextBlock x:Name="TextContainer" TextWrapping="Wrap" />
11+
</ControlTemplate>
12+
</Setter.Value>
13+
</Setter>
14+
</Style>
15+
</ResourceDictionary>

0 commit comments

Comments
 (0)