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

[SettingsControls] Using ItemsRepeater #367

Merged
merged 6 commits into from
Feb 21, 2023
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,5 @@ You can easily override certain properties to create custom experiences. For ins
`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.

> [!SAMPLE SettingsExpanderItemsSourceSample]

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.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!-- 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. -->
<!-- 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="SettingsControlsExperiment.Samples.SettingsPageExample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Expand All @@ -23,95 +23,97 @@
</Style>
</Page.Resources>
<ScrollViewer>
<StackPanel MaxWidth="1000"
HorizontalAlignment="Stretch"
Spacing="{StaticResource SettingsCardSpacing}">
<win:StackPanel.ChildrenTransitions>
<win:EntranceThemeTransition FromVerticalOffset="50" />
<win:RepositionThemeTransition IsStaggeringEnabled="False" />
</win:StackPanel.ChildrenTransitions>
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
Text="Section 1" />
<labs:SettingsCard Description="This is a default card, with the Header, HeaderIcon, Description and Content set"
Header="This is the Header">
<labs:SettingsCard.HeaderIcon>
<FontIcon Glyph="&#xE125;" />
</labs:SettingsCard.HeaderIcon>
<ToggleSwitch IsOn="True" />
</labs:SettingsCard>
<Grid>
<StackPanel MaxWidth="1000"
HorizontalAlignment="Stretch"
Spacing="{StaticResource SettingsCardSpacing}">
<win:StackPanel.ChildrenTransitions>
<win:EntranceThemeTransition FromVerticalOffset="50" />
<win:RepositionThemeTransition IsStaggeringEnabled="False" />
</win:StackPanel.ChildrenTransitions>
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
Text="Section 1" />
<labs:SettingsCard Description="This is a default card, with the Header, HeaderIcon, Description and Content set"
Header="This is the Header">
<labs:SettingsCard.HeaderIcon>
<FontIcon Glyph="&#xE125;" />
</labs:SettingsCard.HeaderIcon>
<ToggleSwitch IsOn="True" />
</labs:SettingsCard>

<labs:SettingsExpander Description="The SettingsExpander has the same properties as a SettingsCard"
Header="SettingsExpander">
<labs:SettingsExpander.HeaderIcon>
<FontIcon Glyph="&#xE91B;" />
</labs:SettingsExpander.HeaderIcon>
<Button Content="Content"
Style="{StaticResource AccentButtonStyle}" />
<labs:SettingsExpander Description="The SettingsExpander has the same properties as a SettingsCard"
Header="SettingsExpander">
<labs:SettingsExpander.HeaderIcon>
<FontIcon Glyph="&#xE91B;" />
</labs:SettingsExpander.HeaderIcon>
<Button Content="Content"
Style="{StaticResource AccentButtonStyle}" />

<labs:SettingsExpander.Items>
<labs:SettingsCard Header="A basic SettingsCard within an SettingsExpander">
<Button Content="Button" />
</labs:SettingsCard>
<labs:SettingsCard Description="SettingsCard within an Expander can be made clickable too!"
Header="This item can be clicked"
IsClickEnabled="True" />
<labs:SettingsExpander.Items>
<labs:SettingsCard Header="A basic SettingsCard within an SettingsExpander">
<Button Content="Button" />
</labs:SettingsCard>
<labs:SettingsCard Description="SettingsCard within an Expander can be made clickable too!"
Header="This item can be clicked"
IsClickEnabled="True" />

<labs:SettingsCard ContentAlignment="Left">
<CheckBox Content="Here the ContentAlignment is set to Left. This is great for e.g. CheckBoxes or RadioButtons" />
</labs:SettingsCard>
</labs:SettingsExpander.Items>
</labs:SettingsExpander>
<labs:SettingsCard ContentAlignment="Left">
<CheckBox Content="Here the ContentAlignment is set to Left. This is great for e.g. CheckBoxes or RadioButtons" />
</labs:SettingsCard>
</labs:SettingsExpander.Items>
</labs:SettingsExpander>

<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
Text="Section 2" />
<labs:SettingsCard Description="Another card to show grouping of cards"
Header="Another SettingsCard">
<labs:SettingsCard.HeaderIcon>
<FontIcon Glyph="&#xE799;" />
</labs:SettingsCard.HeaderIcon>
<ComboBox SelectedIndex="0">
<ComboBoxItem>Option 1</ComboBoxItem>
<ComboBoxItem>Option 2</ComboBoxItem>
<ComboBoxItem>Option 3</ComboBoxItem>
</ComboBox>
</labs:SettingsCard>
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
Text="Section 2" />
<labs:SettingsCard Description="Another card to show grouping of cards"
Header="Another SettingsCard">
<labs:SettingsCard.HeaderIcon>
<FontIcon Glyph="&#xE799;" />
</labs:SettingsCard.HeaderIcon>
<ComboBox SelectedIndex="0">
<ComboBoxItem>Option 1</ComboBoxItem>
<ComboBoxItem>Option 2</ComboBoxItem>
<ComboBoxItem>Option 3</ComboBoxItem>
</ComboBox>
</labs:SettingsCard>

<labs:SettingsCard Description="Another card to show grouping of cards"
Header="Yet another SettingsCard">
<labs:SettingsCard.HeaderIcon>
<FontIcon Glyph="&#xE29B;" />
</labs:SettingsCard.HeaderIcon>
<Button Content="Content" />
</labs:SettingsCard>
<labs:SettingsCard Description="Another card to show grouping of cards"
Header="Yet another SettingsCard">
<labs:SettingsCard.HeaderIcon>
<FontIcon Glyph="&#xE29B;" />
</labs:SettingsCard.HeaderIcon>
<Button Content="Content" />
</labs:SettingsCard>

<!-- Example 'About' section -->
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
Text="About" />
<!-- Example 'About' section -->
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
Text="About" />

<labs:SettingsExpander Description="© 2023. All rights reserved."
Header="Community Toolkit Gallery">
<labs:SettingsExpander.HeaderIcon>
<BitmapIcon ShowAsMonochrome="False"
UriSource="ms-appx:///Assets/AppTitleBar.scale-200.png" />
</labs:SettingsExpander.HeaderIcon>
<TextBlock win:IsTextSelectionEnabled="True"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="Version 1.0.0.0" />
<labs:SettingsExpander.Items>
<labs:SettingsCard HorizontalContentAlignment="Left"
ContentAlignment="Left">
<StackPanel Margin="-12,0,0,0"
Orientation="Vertical">
<HyperlinkButton Content="Link 1" />
<HyperlinkButton Content="Link 2" />
<HyperlinkButton Content="Link 3" />
</StackPanel>
</labs:SettingsCard>
</labs:SettingsExpander.Items>
</labs:SettingsExpander>
<HyperlinkButton Margin="0,8,0,0"
Content="Send feedback" />
</StackPanel>
<labs:SettingsExpander Description="© 2023. All rights reserved."
Header="Community Toolkit Gallery">
<labs:SettingsExpander.HeaderIcon>
<BitmapIcon ShowAsMonochrome="False"
UriSource="ms-appx:///Assets/AppTitleBar.scale-200.png" />
</labs:SettingsExpander.HeaderIcon>
<TextBlock win:IsTextSelectionEnabled="True"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="Version 1.0.0.0" />
<labs:SettingsExpander.Items>
<labs:SettingsCard HorizontalContentAlignment="Left"
ContentAlignment="Left">
<StackPanel Margin="-12,0,0,0"
Orientation="Vertical">
<HyperlinkButton Content="Link 1" />
<HyperlinkButton Content="Link 2" />
<HyperlinkButton Content="Link 3" />
</StackPanel>
</labs:SettingsCard>
</labs:SettingsExpander.Items>
</labs:SettingsExpander>
<HyperlinkButton Margin="0,8,0,0"
Content="Send feedback" />
</StackPanel>
</Grid>
</ScrollViewer>
</Page>
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<Description>
This package contains the SettingsCard and SettingsExpander controls.
</Description>
<Version>0.0.14</Version>
<Version>0.0.15</Version>
<LangVersion>10.0</LangVersion>
</PropertyGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// 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.
namespace CommunityToolkit.Labs.WinUI;

//// Implement properties for ItemsControl like behavior.
public partial class SettingsExpander
{
public IList<object> Items
{
get { return (IList<object>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}

public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register(nameof(Items), typeof(IList<object>), typeof(SettingsExpander), new PropertyMetadata(null, OnItemsConnectedPropertyChanged));

public object ItemsSource
{
get { return (object)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}

public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register(nameof(ItemsSource), typeof(object), typeof(SettingsExpander), new PropertyMetadata(null, OnItemsConnectedPropertyChanged));

public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}

public static readonly DependencyProperty ItemTemplateProperty =
DependencyProperty.Register(nameof(ItemTemplate), typeof(DataTemplate), typeof(SettingsExpander), new PropertyMetadata(null));

public StyleSelector ItemContainerStyleSelector
{
get { return (StyleSelector)GetValue(ItemContainerStyleSelectorProperty); }
set { SetValue(ItemContainerStyleSelectorProperty, value); }
}

public static readonly DependencyProperty ItemContainerStyleSelectorProperty =
DependencyProperty.Register(nameof(ItemContainerStyleSelector), typeof(StyleSelector), typeof(SettingsExpander), new PropertyMetadata(null));

private static void OnItemsConnectedPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
if (dependencyObject is SettingsExpander expander && expander._itemsRepeater is not null)
{
var datasource = expander.ItemsSource;

if (datasource is null)
{
datasource = expander.Items;
}

expander._itemsRepeater.ItemsSource = datasource;
}
}

private void ItemsRepeater_ElementPrepared(MUXC.ItemsRepeater sender, MUXC.ItemsRepeaterElementPreparedEventArgs args)
{
if (ItemContainerStyleSelector != null &&
args.Element is FrameworkElement element &&
element.ReadLocalValue(FrameworkElement.StyleProperty) == DependencyProperty.UnsetValue)
{
// TODO: Get item from args.Index?
element.Style = ItemContainerStyleSelector.SelectStyle(null, element);
}
}
}
65 changes: 24 additions & 41 deletions labs/SettingsControls/src/SettingsExpander/SettingsExpander.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,43 @@

namespace CommunityToolkit.Labs.WinUI;

//// TODO: Ideally would use ItemsRepeater here, but it has a layout issue
//// trying to request all the available horizontal space: https://github.com/microsoft/microsoft-ui-xaml/issues/3842
public partial class SettingsExpander : ItemsControl
//// Note: ItemsRepeater will request all the available horizontal space: https://github.com/microsoft/microsoft-ui-xaml/issues/3842
[TemplatePart(Name = PART_ItemsRepeater, Type = typeof(MUXC.ItemsRepeater))]
public partial class SettingsExpander : Control
{
private const string PART_ItemsRepeater = "PART_ItemsRepeater";

private MUXC.ItemsRepeater? _itemsRepeater;

/// <summary>
/// The SettingsExpander is a collapsable control to host multiple SettingsCards.
/// </summary>
public SettingsExpander()
{
this.DefaultStyleKey = typeof(SettingsExpander);
Items = new List<object>();
}

/// <inheritdoc />
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
RegisterAutomation();

if (_itemsRepeater != null)
{
_itemsRepeater.ElementPrepared -= this.ItemsRepeater_ElementPrepared;
}

_itemsRepeater = GetTemplateChild(PART_ItemsRepeater) as MUXC.ItemsRepeater;

if (_itemsRepeater != null)
{
_itemsRepeater.ElementPrepared += this.ItemsRepeater_ElementPrepared;

// Update it's source based on our current items properties.
OnItemsConnectedPropertyChanged(this, null!); // Can't get it to accept type here? (DependencyPropertyChangedEventArgs)EventArgs.Empty
}
}

private void RegisterAutomation()
Expand All @@ -44,44 +64,7 @@ protected override AutomationPeer OnCreateAutomationPeer()
{
return new SettingsExpanderAutomationPeer(this);
}

//// Our <see cref="ItemsControl"/> is to set its effective container to a <see cref="SettingsCard"/>.
//// We need this to be able to custom style the container within the <see cref="SettingsExpander"/>.
//// We can't use <see cref="ItemsControl"/> directly as-is because the <see cref="ItemsPresenter"/> automatically
//// injects data content into the container, creating nested SettingsCards, which we don't want.
//// It means we can't template the whole <see cref="SettingsCard"/> 'container' similar to the new WinUI patterns
//// for things like <see cref="MUXC.NavigationView"/> and <see cref="MUXC.TabView"/>.
//// We can't use <see cref="MUXC.ItemsRepeater"/> due to an issue where it tries to use all horizontal width
//// within an <see cref="MUXC.Expander"/>. See https://github.com/microsoft/microsoft-ui-xaml/issues/3842.

protected override bool IsItemItsOwnContainerOverride(object item)
{
// Mainly for the Items scenario, if we're already a SettingsCard, we don't have to do anything.
// And for ItemsSource, if we're a StyledContentPresenter, we're already done our work below.
return item is SettingsCard or StyledContentPresenter;
}

/// <inheritdoc />
protected override DependencyObject GetContainerForItemOverride()
{
//// Note: We can't just use SettingsCard here, as otherwise we get nested SettingsCards with how ItemsControl works,
//// as it's not built to support the template syntax containing the container.

// We need to return a ContentPresenter which knows how to Style our inner SettingsCards.
StyledContentPresenter presenter = new();

// We want to bind the style selector that we're using here in our control to these new presenters.
Binding binding = new()
{
Source = this,
Path = new PropertyPath("ItemContainerStyleSelector"),
Mode = BindingMode.OneWay,
};
presenter.SetBinding(StyledContentPresenter.ContentStyleSelectorProperty, binding);

return presenter;
}


private void OnIsExpandedChanged(bool oldValue, bool newValue)
{
var peer = FrameworkElementAutomationPeer.FromElement(this) as SettingsExpanderAutomationPeer;
Expand Down
Loading