Skip to content

Commit 1a40ef4

Browse files
[SettingsControls] Using ItemsRepeater (#367)
* Swap back SettingsExpander to use ItemsRepeater over ItemsControl * Remove redundant code + comments * Use UniformGridLayout vs. StackLayout * Bump version no. * Adding note and fix sample * Update labs/SettingsControls/samples/SettingsControls.Samples/SettingsExpander.md Co-authored-by: Michael Hawker MSFT (XAML Llama) <[email protected]> --------- Co-authored-by: Michael Hawker MSFT (XAML Llama) <[email protected]>
1 parent 507fba9 commit 1a40ef4

File tree

6 files changed

+205
-130
lines changed

6 files changed

+205
-130
lines changed

labs/SettingsControls/samples/SettingsControls.Samples/SettingsExpander.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,5 @@ You can easily override certain properties to create custom experiences. For ins
2424
`SettingsExpander` is also an `ItemsControl`, so its items can be driven by a collection and the `ItemsSource` property. You can use the `ItemTemplate` to define how your data object is represented as a `SettingsCard`, as shown below. The `ItemsHeader` and `ItemsFooter` property can be used to host custom content at the start or end of the items list.
2525

2626
> [!SAMPLE SettingsExpanderItemsSourceSample]
27+
28+
NOTE: Due to [a bug](https://github.com/microsoft/microsoft-ui-xaml/issues/3842) related to the `ItemsRepeater` used in `SettingsExpander`, there might be visual glitches whenever the `SettingsExpander` expands and a `MaxWidth` is set on a parent `StackPanel`. As a workaround, the `StackPanel` (that has the `MaxWidth` set) can be wrapped in a `Grid` to overcome this issue. See the `SettingsPageExample` for snippet.
Lines changed: 86 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!-- 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. -->
1+
<!-- 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. -->
22
<Page x:Class="SettingsControlsExperiment.Samples.SettingsPageExample"
33
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
44
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -23,95 +23,97 @@
2323
</Style>
2424
</Page.Resources>
2525
<ScrollViewer>
26-
<StackPanel MaxWidth="1000"
27-
HorizontalAlignment="Stretch"
28-
Spacing="{StaticResource SettingsCardSpacing}">
29-
<win:StackPanel.ChildrenTransitions>
30-
<win:EntranceThemeTransition FromVerticalOffset="50" />
31-
<win:RepositionThemeTransition IsStaggeringEnabled="False" />
32-
</win:StackPanel.ChildrenTransitions>
33-
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
34-
Text="Section 1" />
35-
<labs:SettingsCard Description="This is a default card, with the Header, HeaderIcon, Description and Content set"
36-
Header="This is the Header">
37-
<labs:SettingsCard.HeaderIcon>
38-
<FontIcon Glyph="&#xE125;" />
39-
</labs:SettingsCard.HeaderIcon>
40-
<ToggleSwitch IsOn="True" />
41-
</labs:SettingsCard>
26+
<Grid>
27+
<StackPanel MaxWidth="1000"
28+
HorizontalAlignment="Stretch"
29+
Spacing="{StaticResource SettingsCardSpacing}">
30+
<win:StackPanel.ChildrenTransitions>
31+
<win:EntranceThemeTransition FromVerticalOffset="50" />
32+
<win:RepositionThemeTransition IsStaggeringEnabled="False" />
33+
</win:StackPanel.ChildrenTransitions>
34+
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
35+
Text="Section 1" />
36+
<labs:SettingsCard Description="This is a default card, with the Header, HeaderIcon, Description and Content set"
37+
Header="This is the Header">
38+
<labs:SettingsCard.HeaderIcon>
39+
<FontIcon Glyph="&#xE125;" />
40+
</labs:SettingsCard.HeaderIcon>
41+
<ToggleSwitch IsOn="True" />
42+
</labs:SettingsCard>
4243

43-
<labs:SettingsExpander Description="The SettingsExpander has the same properties as a SettingsCard"
44-
Header="SettingsExpander">
45-
<labs:SettingsExpander.HeaderIcon>
46-
<FontIcon Glyph="&#xE91B;" />
47-
</labs:SettingsExpander.HeaderIcon>
48-
<Button Content="Content"
49-
Style="{StaticResource AccentButtonStyle}" />
44+
<labs:SettingsExpander Description="The SettingsExpander has the same properties as a SettingsCard"
45+
Header="SettingsExpander">
46+
<labs:SettingsExpander.HeaderIcon>
47+
<FontIcon Glyph="&#xE91B;" />
48+
</labs:SettingsExpander.HeaderIcon>
49+
<Button Content="Content"
50+
Style="{StaticResource AccentButtonStyle}" />
5051

51-
<labs:SettingsExpander.Items>
52-
<labs:SettingsCard Header="A basic SettingsCard within an SettingsExpander">
53-
<Button Content="Button" />
54-
</labs:SettingsCard>
55-
<labs:SettingsCard Description="SettingsCard within an Expander can be made clickable too!"
56-
Header="This item can be clicked"
57-
IsClickEnabled="True" />
52+
<labs:SettingsExpander.Items>
53+
<labs:SettingsCard Header="A basic SettingsCard within an SettingsExpander">
54+
<Button Content="Button" />
55+
</labs:SettingsCard>
56+
<labs:SettingsCard Description="SettingsCard within an Expander can be made clickable too!"
57+
Header="This item can be clicked"
58+
IsClickEnabled="True" />
5859

59-
<labs:SettingsCard ContentAlignment="Left">
60-
<CheckBox Content="Here the ContentAlignment is set to Left. This is great for e.g. CheckBoxes or RadioButtons" />
61-
</labs:SettingsCard>
62-
</labs:SettingsExpander.Items>
63-
</labs:SettingsExpander>
60+
<labs:SettingsCard ContentAlignment="Left">
61+
<CheckBox Content="Here the ContentAlignment is set to Left. This is great for e.g. CheckBoxes or RadioButtons" />
62+
</labs:SettingsCard>
63+
</labs:SettingsExpander.Items>
64+
</labs:SettingsExpander>
6465

65-
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
66-
Text="Section 2" />
67-
<labs:SettingsCard Description="Another card to show grouping of cards"
68-
Header="Another SettingsCard">
69-
<labs:SettingsCard.HeaderIcon>
70-
<FontIcon Glyph="&#xE799;" />
71-
</labs:SettingsCard.HeaderIcon>
72-
<ComboBox SelectedIndex="0">
73-
<ComboBoxItem>Option 1</ComboBoxItem>
74-
<ComboBoxItem>Option 2</ComboBoxItem>
75-
<ComboBoxItem>Option 3</ComboBoxItem>
76-
</ComboBox>
77-
</labs:SettingsCard>
66+
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
67+
Text="Section 2" />
68+
<labs:SettingsCard Description="Another card to show grouping of cards"
69+
Header="Another SettingsCard">
70+
<labs:SettingsCard.HeaderIcon>
71+
<FontIcon Glyph="&#xE799;" />
72+
</labs:SettingsCard.HeaderIcon>
73+
<ComboBox SelectedIndex="0">
74+
<ComboBoxItem>Option 1</ComboBoxItem>
75+
<ComboBoxItem>Option 2</ComboBoxItem>
76+
<ComboBoxItem>Option 3</ComboBoxItem>
77+
</ComboBox>
78+
</labs:SettingsCard>
7879

79-
<labs:SettingsCard Description="Another card to show grouping of cards"
80-
Header="Yet another SettingsCard">
81-
<labs:SettingsCard.HeaderIcon>
82-
<FontIcon Glyph="&#xE29B;" />
83-
</labs:SettingsCard.HeaderIcon>
84-
<Button Content="Content" />
85-
</labs:SettingsCard>
80+
<labs:SettingsCard Description="Another card to show grouping of cards"
81+
Header="Yet another SettingsCard">
82+
<labs:SettingsCard.HeaderIcon>
83+
<FontIcon Glyph="&#xE29B;" />
84+
</labs:SettingsCard.HeaderIcon>
85+
<Button Content="Content" />
86+
</labs:SettingsCard>
8687

87-
<!-- Example 'About' section -->
88-
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
89-
Text="About" />
88+
<!-- Example 'About' section -->
89+
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
90+
Text="About" />
9091

91-
<labs:SettingsExpander Description="© 2023. All rights reserved."
92-
Header="Community Toolkit Gallery">
93-
<labs:SettingsExpander.HeaderIcon>
94-
<BitmapIcon ShowAsMonochrome="False"
95-
UriSource="ms-appx:///Assets/AppTitleBar.scale-200.png" />
96-
</labs:SettingsExpander.HeaderIcon>
97-
<TextBlock win:IsTextSelectionEnabled="True"
98-
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
99-
Style="{StaticResource CaptionTextBlockStyle}"
100-
Text="Version 1.0.0.0" />
101-
<labs:SettingsExpander.Items>
102-
<labs:SettingsCard HorizontalContentAlignment="Left"
103-
ContentAlignment="Left">
104-
<StackPanel Margin="-12,0,0,0"
105-
Orientation="Vertical">
106-
<HyperlinkButton Content="Link 1" />
107-
<HyperlinkButton Content="Link 2" />
108-
<HyperlinkButton Content="Link 3" />
109-
</StackPanel>
110-
</labs:SettingsCard>
111-
</labs:SettingsExpander.Items>
112-
</labs:SettingsExpander>
113-
<HyperlinkButton Margin="0,8,0,0"
114-
Content="Send feedback" />
115-
</StackPanel>
92+
<labs:SettingsExpander Description="© 2023. All rights reserved."
93+
Header="Community Toolkit Gallery">
94+
<labs:SettingsExpander.HeaderIcon>
95+
<BitmapIcon ShowAsMonochrome="False"
96+
UriSource="ms-appx:///Assets/AppTitleBar.scale-200.png" />
97+
</labs:SettingsExpander.HeaderIcon>
98+
<TextBlock win:IsTextSelectionEnabled="True"
99+
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
100+
Style="{StaticResource CaptionTextBlockStyle}"
101+
Text="Version 1.0.0.0" />
102+
<labs:SettingsExpander.Items>
103+
<labs:SettingsCard HorizontalContentAlignment="Left"
104+
ContentAlignment="Left">
105+
<StackPanel Margin="-12,0,0,0"
106+
Orientation="Vertical">
107+
<HyperlinkButton Content="Link 1" />
108+
<HyperlinkButton Content="Link 2" />
109+
<HyperlinkButton Content="Link 3" />
110+
</StackPanel>
111+
</labs:SettingsCard>
112+
</labs:SettingsExpander.Items>
113+
</labs:SettingsExpander>
114+
<HyperlinkButton Margin="0,8,0,0"
115+
Content="Send feedback" />
116+
</StackPanel>
117+
</Grid>
116118
</ScrollViewer>
117119
</Page>

labs/SettingsControls/src/CommunityToolkit.Labs.WinUI.SettingsControls.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
<Description>
1919
This package contains the SettingsCard and SettingsExpander controls.
2020
</Description>
21-
<Version>0.0.14</Version>
21+
<Version>0.0.15</Version>
2222
<LangVersion>10.0</LangVersion>
2323
</PropertyGroup>
2424

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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+
namespace CommunityToolkit.Labs.WinUI;
5+
6+
//// Implement properties for ItemsControl like behavior.
7+
public partial class SettingsExpander
8+
{
9+
public IList<object> Items
10+
{
11+
get { return (IList<object>)GetValue(ItemsProperty); }
12+
set { SetValue(ItemsProperty, value); }
13+
}
14+
15+
public static readonly DependencyProperty ItemsProperty =
16+
DependencyProperty.Register(nameof(Items), typeof(IList<object>), typeof(SettingsExpander), new PropertyMetadata(null, OnItemsConnectedPropertyChanged));
17+
18+
public object ItemsSource
19+
{
20+
get { return (object)GetValue(ItemsSourceProperty); }
21+
set { SetValue(ItemsSourceProperty, value); }
22+
}
23+
24+
public static readonly DependencyProperty ItemsSourceProperty =
25+
DependencyProperty.Register(nameof(ItemsSource), typeof(object), typeof(SettingsExpander), new PropertyMetadata(null, OnItemsConnectedPropertyChanged));
26+
27+
public DataTemplate ItemTemplate
28+
{
29+
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
30+
set { SetValue(ItemTemplateProperty, value); }
31+
}
32+
33+
public static readonly DependencyProperty ItemTemplateProperty =
34+
DependencyProperty.Register(nameof(ItemTemplate), typeof(DataTemplate), typeof(SettingsExpander), new PropertyMetadata(null));
35+
36+
public StyleSelector ItemContainerStyleSelector
37+
{
38+
get { return (StyleSelector)GetValue(ItemContainerStyleSelectorProperty); }
39+
set { SetValue(ItemContainerStyleSelectorProperty, value); }
40+
}
41+
42+
public static readonly DependencyProperty ItemContainerStyleSelectorProperty =
43+
DependencyProperty.Register(nameof(ItemContainerStyleSelector), typeof(StyleSelector), typeof(SettingsExpander), new PropertyMetadata(null));
44+
45+
private static void OnItemsConnectedPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
46+
{
47+
if (dependencyObject is SettingsExpander expander && expander._itemsRepeater is not null)
48+
{
49+
var datasource = expander.ItemsSource;
50+
51+
if (datasource is null)
52+
{
53+
datasource = expander.Items;
54+
}
55+
56+
expander._itemsRepeater.ItemsSource = datasource;
57+
}
58+
}
59+
60+
private void ItemsRepeater_ElementPrepared(MUXC.ItemsRepeater sender, MUXC.ItemsRepeaterElementPreparedEventArgs args)
61+
{
62+
if (ItemContainerStyleSelector != null &&
63+
args.Element is FrameworkElement element &&
64+
element.ReadLocalValue(FrameworkElement.StyleProperty) == DependencyProperty.UnsetValue)
65+
{
66+
// TODO: Get item from args.Index?
67+
element.Style = ItemContainerStyleSelector.SelectStyle(null, element);
68+
}
69+
}
70+
}

labs/SettingsControls/src/SettingsExpander/SettingsExpander.cs

Lines changed: 24 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,43 @@
66

77
namespace CommunityToolkit.Labs.WinUI;
88

9-
//// TODO: Ideally would use ItemsRepeater here, but it has a layout issue
10-
//// trying to request all the available horizontal space: https://github.com/microsoft/microsoft-ui-xaml/issues/3842
11-
public partial class SettingsExpander : ItemsControl
9+
//// Note: ItemsRepeater will request all the available horizontal space: https://github.com/microsoft/microsoft-ui-xaml/issues/3842
10+
[TemplatePart(Name = PART_ItemsRepeater, Type = typeof(MUXC.ItemsRepeater))]
11+
public partial class SettingsExpander : Control
1212
{
13+
private const string PART_ItemsRepeater = "PART_ItemsRepeater";
14+
15+
private MUXC.ItemsRepeater? _itemsRepeater;
16+
1317
/// <summary>
1418
/// The SettingsExpander is a collapsable control to host multiple SettingsCards.
1519
/// </summary>
1620
public SettingsExpander()
1721
{
1822
this.DefaultStyleKey = typeof(SettingsExpander);
23+
Items = new List<object>();
1924
}
2025

2126
/// <inheritdoc />
2227
protected override void OnApplyTemplate()
2328
{
2429
base.OnApplyTemplate();
2530
RegisterAutomation();
31+
32+
if (_itemsRepeater != null)
33+
{
34+
_itemsRepeater.ElementPrepared -= this.ItemsRepeater_ElementPrepared;
35+
}
36+
37+
_itemsRepeater = GetTemplateChild(PART_ItemsRepeater) as MUXC.ItemsRepeater;
38+
39+
if (_itemsRepeater != null)
40+
{
41+
_itemsRepeater.ElementPrepared += this.ItemsRepeater_ElementPrepared;
42+
43+
// Update it's source based on our current items properties.
44+
OnItemsConnectedPropertyChanged(this, null!); // Can't get it to accept type here? (DependencyPropertyChangedEventArgs)EventArgs.Empty
45+
}
2646
}
2747

2848
private void RegisterAutomation()
@@ -44,44 +64,7 @@ protected override AutomationPeer OnCreateAutomationPeer()
4464
{
4565
return new SettingsExpanderAutomationPeer(this);
4666
}
47-
48-
//// Our <see cref="ItemsControl"/> is to set its effective container to a <see cref="SettingsCard"/>.
49-
//// We need this to be able to custom style the container within the <see cref="SettingsExpander"/>.
50-
//// We can't use <see cref="ItemsControl"/> directly as-is because the <see cref="ItemsPresenter"/> automatically
51-
//// injects data content into the container, creating nested SettingsCards, which we don't want.
52-
//// It means we can't template the whole <see cref="SettingsCard"/> 'container' similar to the new WinUI patterns
53-
//// for things like <see cref="MUXC.NavigationView"/> and <see cref="MUXC.TabView"/>.
54-
//// We can't use <see cref="MUXC.ItemsRepeater"/> due to an issue where it tries to use all horizontal width
55-
//// within an <see cref="MUXC.Expander"/>. See https://github.com/microsoft/microsoft-ui-xaml/issues/3842.
56-
57-
protected override bool IsItemItsOwnContainerOverride(object item)
58-
{
59-
// Mainly for the Items scenario, if we're already a SettingsCard, we don't have to do anything.
60-
// And for ItemsSource, if we're a StyledContentPresenter, we're already done our work below.
61-
return item is SettingsCard or StyledContentPresenter;
62-
}
63-
64-
/// <inheritdoc />
65-
protected override DependencyObject GetContainerForItemOverride()
66-
{
67-
//// Note: We can't just use SettingsCard here, as otherwise we get nested SettingsCards with how ItemsControl works,
68-
//// as it's not built to support the template syntax containing the container.
69-
70-
// We need to return a ContentPresenter which knows how to Style our inner SettingsCards.
71-
StyledContentPresenter presenter = new();
72-
73-
// We want to bind the style selector that we're using here in our control to these new presenters.
74-
Binding binding = new()
75-
{
76-
Source = this,
77-
Path = new PropertyPath("ItemContainerStyleSelector"),
78-
Mode = BindingMode.OneWay,
79-
};
80-
presenter.SetBinding(StyledContentPresenter.ContentStyleSelectorProperty, binding);
81-
82-
return presenter;
83-
}
84-
67+
8568
private void OnIsExpandedChanged(bool oldValue, bool newValue)
8669
{
8770
var peer = FrameworkElementAutomationPeer.FromElement(this) as SettingsExpanderAutomationPeer;

0 commit comments

Comments
 (0)