From cc30e378baade792f2c0f3c454128115444c1093 Mon Sep 17 00:00:00 2001
From: michael-hawker <24302614+michael-hawker@users.noreply.github.com>
Date: Tue, 11 Jul 2023 14:21:30 -0700
Subject: [PATCH 1/9] Add initial port of Header Behaviors for ListViewBase
components
Add doc and Samples showing for ListView, GridView, and HeaderedTreeView
Tested on WASDK (uses composition, so won't work on Uno)
Having issues with UWP head?
---
.../samples/Behaviors.Samples.csproj | 5 +
components/Behaviors/samples/Behaviors.md | 2 +-
.../Headers/FadeHeaderBehaviorSample.xaml | 56 ++++
.../Headers/FadeHeaderBehaviorSample.xaml.cs | 19 ++
.../samples/Headers/HeaderBehaviors.md | 33 +++
.../QuickReturnHeaderBehaviorSample.xaml | 88 ++++++
.../QuickReturnHeaderBehaviorSample.xaml.cs | 19 ++
.../Headers/StickyHeaderBehaviorSample.xaml | 36 +++
.../StickyHeaderBehaviorSample.xaml.cs | 119 ++++++++
.../Behaviors/src/ApiInformationHelper.cs | 4 +
.../src/Headers/FadeHeaderBehavior.cs | 155 ++++++++++
.../src/Headers/QuickReturnHeaderBehavior.cs | 277 ++++++++++++++++++
.../src/Headers/StickyHeaderBehavior.cs | 261 +++++++++++++++++
13 files changed, 1073 insertions(+), 1 deletion(-)
create mode 100644 components/Behaviors/samples/Headers/FadeHeaderBehaviorSample.xaml
create mode 100644 components/Behaviors/samples/Headers/FadeHeaderBehaviorSample.xaml.cs
create mode 100644 components/Behaviors/samples/Headers/HeaderBehaviors.md
create mode 100644 components/Behaviors/samples/Headers/QuickReturnHeaderBehaviorSample.xaml
create mode 100644 components/Behaviors/samples/Headers/QuickReturnHeaderBehaviorSample.xaml.cs
create mode 100644 components/Behaviors/samples/Headers/StickyHeaderBehaviorSample.xaml
create mode 100644 components/Behaviors/samples/Headers/StickyHeaderBehaviorSample.xaml.cs
create mode 100644 components/Behaviors/src/Headers/FadeHeaderBehavior.cs
create mode 100644 components/Behaviors/src/Headers/QuickReturnHeaderBehavior.cs
create mode 100644 components/Behaviors/src/Headers/StickyHeaderBehavior.cs
diff --git a/components/Behaviors/samples/Behaviors.Samples.csproj b/components/Behaviors/samples/Behaviors.Samples.csproj
index 8b835c2b..2f1a83e6 100644
--- a/components/Behaviors/samples/Behaviors.Samples.csproj
+++ b/components/Behaviors/samples/Behaviors.Samples.csproj
@@ -5,6 +5,11 @@
+
+
+
+
+
diff --git a/components/Behaviors/samples/Behaviors.md b/components/Behaviors/samples/Behaviors.md
index 2f9ad2ff..7188a217 100644
--- a/components/Behaviors/samples/Behaviors.md
+++ b/components/Behaviors/samples/Behaviors.md
@@ -6,7 +6,7 @@ keywords: Behaviors
dev_langs:
- csharp
category: Xaml
-subcategory: None
+subcategory: Behaviors
discussion-id: 0
issue-id: 0
icon: Assets/Behaviors.png
diff --git a/components/Behaviors/samples/Headers/FadeHeaderBehaviorSample.xaml b/components/Behaviors/samples/Headers/FadeHeaderBehaviorSample.xaml
new file mode 100644
index 00000000..157d378a
--- /dev/null
+++ b/components/Behaviors/samples/Headers/FadeHeaderBehaviorSample.xaml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ One
+ Two
+ Three
+ Four
+ Five
+ Six
+ Seven
+ Eight
+ Nine
+ Ten
+ Eleven
+ Twelve
+ Thirteen
+ Fourteen
+ Fifteen
+ Sixteen
+ Seventeen
+ Eighteen
+ Nineteen
+ Twenty
+
+
+
diff --git a/components/Behaviors/samples/Headers/FadeHeaderBehaviorSample.xaml.cs b/components/Behaviors/samples/Headers/FadeHeaderBehaviorSample.xaml.cs
new file mode 100644
index 00000000..177a8a9c
--- /dev/null
+++ b/components/Behaviors/samples/Headers/FadeHeaderBehaviorSample.xaml.cs
@@ -0,0 +1,19 @@
+// 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.Behaviors;
+
+namespace BehaviorsExperiment.Samples;
+
+///
+/// An empty page that can be used on its own or navigated to within a Frame.
+///
+[ToolkitSample(id: nameof(FadeHeaderBehaviorSample), nameof(FadeHeaderBehavior), description: $"A sample for showing how to use the {nameof(FadeHeaderBehavior)}.")]
+public sealed partial class FadeHeaderBehaviorSample : Page
+{
+ public FadeHeaderBehaviorSample()
+ {
+ this.InitializeComponent();
+ }
+}
diff --git a/components/Behaviors/samples/Headers/HeaderBehaviors.md b/components/Behaviors/samples/Headers/HeaderBehaviors.md
new file mode 100644
index 00000000..a698cc67
--- /dev/null
+++ b/components/Behaviors/samples/Headers/HeaderBehaviors.md
@@ -0,0 +1,33 @@
+---
+title: Header Behaviors
+author: michael-hawker
+description: Behaviors modifying the headers of ListView based components when scrolling.
+keywords: Behaviors
+dev_langs:
+ - csharp
+category: Xaml
+subcategory: Behaviors
+discussion-id: 0
+issue-id: 0
+icon: Assets/Behaviors.png
+---
+
+The `FadeHeaderBehavior`, `QuickReturnHeaderBehavior`, and `StickyHeaderBehavior` apply behaviors to `ListView`, `GridView`, and `HeaderedTreeView` Headers.
+
+## FadeHeaderBehavior
+
+The FadeHeaderBehavior causes the Header of the scrolling collection to fade in and out as the user scrolls at the top of the collection.
+
+> [!Sample FadeHeaderBehaviorSample]
+
+## QuickReturnBehavior
+
+The QuickReturnHeaderBehavior causes the Header of the scrolling collection to return back into view as soon as the user scrolls up even if they are not near the top of the collection.
+
+> [!Sample QuickReturnHeaderBehaviorSample]
+
+## StickyHeaderBehavior
+
+The StickyHeaderBehavior causes the Header of the scrolling collection to stay in view as the user scrolls up and down in the collection.
+
+> [!Sample StickyHeaderBehaviorSample]
diff --git a/components/Behaviors/samples/Headers/QuickReturnHeaderBehaviorSample.xaml b/components/Behaviors/samples/Headers/QuickReturnHeaderBehaviorSample.xaml
new file mode 100644
index 00000000..ba494d7c
--- /dev/null
+++ b/components/Behaviors/samples/Headers/QuickReturnHeaderBehaviorSample.xaml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ One
+ Two
+ Three
+ Four
+ Five
+ Six
+ Seven
+ Eight
+ Nine
+ Ten
+ Eleven
+ Twelve
+ Thirteen
+ Fourteen
+ Fifteen
+ Sixteen
+ Seventeen
+ Eighteen
+ Nineteen
+ Twenty
+ Twenty-One
+ Twenty-Two
+ Twenty-Three
+ Twenty-Four
+ Twenty-Five
+ Twenty-Six
+ Twenty-Seven
+ Twenty-Eight
+ Twenty-Nine
+ Thirty
+ Thirty-One
+ Thirty-Two
+ Thirty-Three
+ Thirty-Four
+ Thirty-Five
+ Thirty-Six
+ Thirty-Seven
+ Thirty-Eight
+ Thirty-Nine
+ Forty
+ Forty-One
+ Forty-Two
+ Forty-Three
+ Forty-Four
+ Forty-Five
+ Forty-Six
+ Forty-Seven
+ Forty-Eight
+ Forty-Nine
+ Fifty
+
+
+
diff --git a/components/Behaviors/samples/Headers/QuickReturnHeaderBehaviorSample.xaml.cs b/components/Behaviors/samples/Headers/QuickReturnHeaderBehaviorSample.xaml.cs
new file mode 100644
index 00000000..51b80f27
--- /dev/null
+++ b/components/Behaviors/samples/Headers/QuickReturnHeaderBehaviorSample.xaml.cs
@@ -0,0 +1,19 @@
+// 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.Behaviors;
+
+namespace BehaviorsExperiment.Samples;
+
+///
+/// An empty page that can be used on its own or navigated to within a Frame.
+///
+[ToolkitSample(id: nameof(QuickReturnHeaderBehaviorSample), nameof(QuickReturnHeaderBehavior), description: $"A sample for showing how to use the {nameof(QuickReturnHeaderBehavior)}.")]
+public sealed partial class QuickReturnHeaderBehaviorSample : Page
+{
+ public QuickReturnHeaderBehaviorSample()
+ {
+ this.InitializeComponent();
+ }
+}
diff --git a/components/Behaviors/samples/Headers/StickyHeaderBehaviorSample.xaml b/components/Behaviors/samples/Headers/StickyHeaderBehaviorSample.xaml
new file mode 100644
index 00000000..da6c35b0
--- /dev/null
+++ b/components/Behaviors/samples/Headers/StickyHeaderBehaviorSample.xaml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/Behaviors/samples/Headers/StickyHeaderBehaviorSample.xaml.cs b/components/Behaviors/samples/Headers/StickyHeaderBehaviorSample.xaml.cs
new file mode 100644
index 00000000..7e041180
--- /dev/null
+++ b/components/Behaviors/samples/Headers/StickyHeaderBehaviorSample.xaml.cs
@@ -0,0 +1,119 @@
+// 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.Behaviors;
+
+namespace BehaviorsExperiment.Samples;
+
+///
+/// An empty page that can be used on its own or navigated to within a Frame.
+///
+[ToolkitSample(id: nameof(StickyHeaderBehaviorSample), nameof(StickyHeaderBehavior), description: $"A sample for showing how to use the {nameof(StickyHeaderBehavior)}.")]
+public sealed partial class StickyHeaderBehaviorSample : Page
+{
+ public ObservableCollection Items { get; }
+
+ public StickyHeaderBehaviorSample()
+ {
+ this.InitializeComponent();
+ Items = GetData();
+ }
+
+
+ private ObservableCollection GetData()
+ {
+ var list = new ObservableCollection();
+ ExplorerItem folder1 = new ExplorerItem()
+ {
+ Name = "Work Documents",
+ Children =
+ {
+ new ExplorerItem()
+ {
+ Name = "Functional Specifications",
+ Children =
+ {
+ new ExplorerItem()
+ {
+ Name = "TreeView spec",
+ }
+ }
+ },
+ new ExplorerItem()
+ {
+ Name = "Feature Schedule",
+ },
+ new ExplorerItem()
+ {
+ Name = "Overall Project Plan",
+ },
+ new ExplorerItem()
+ {
+ Name = "Feature Resources Allocation",
+ }
+ }
+ };
+ ExplorerItem folder2 = new ExplorerItem()
+ {
+ Name = "Personal Folder",
+ Children =
+ {
+ new ExplorerItem()
+ {
+ Name = "Home Remodel Folder",
+ Children =
+ {
+ new ExplorerItem()
+ {
+ Name = "Contractor Contact Info",
+ },
+ new ExplorerItem()
+ {
+ Name = "Paint Color Scheme",
+ },
+ new ExplorerItem()
+ {
+ Name = "Flooring Woodgrain type",
+ },
+ new ExplorerItem()
+ {
+ Name = "Kitchen Cabinet Style",
+ }
+ }
+ }
+ }
+ };
+
+ list.Add(folder1);
+ list.Add(folder2);
+
+ for (int i = 0; i < 40; i++)
+ {
+ list.Add(new() { Name = $"Folder {i + 1}" });
+ }
+
+ return list;
+ }
+}
+
+public class ExplorerItem
+{
+ public string? Name { get; set; }
+ private ObservableCollection? _children;
+ public ObservableCollection Children
+ {
+ get
+ {
+ if (_children == null)
+ {
+ _children = new ObservableCollection();
+ }
+ return _children;
+ }
+ set
+ {
+ _children = value;
+ }
+ }
+}
diff --git a/components/Behaviors/src/ApiInformationHelper.cs b/components/Behaviors/src/ApiInformationHelper.cs
index 15fed176..4282fcab 100644
--- a/components/Behaviors/src/ApiInformationHelper.cs
+++ b/components/Behaviors/src/ApiInformationHelper.cs
@@ -8,6 +8,10 @@ namespace CommunityToolkit.WinUI.Behaviors;
internal class ApiInformationHelper
{
+#if WINUI2
// 1903 - 18362
public static bool IsXamlRootAvailable { get; } = ApiInformation.IsPropertyPresent("Windows.UI.Xaml.UIElement", "XamlRoot");
+#elif WINUI3
+ public static bool IsXamlRootAvailable => true;
+#endif
}
diff --git a/components/Behaviors/src/Headers/FadeHeaderBehavior.cs b/components/Behaviors/src/Headers/FadeHeaderBehavior.cs
new file mode 100644
index 00000000..a093d0d4
--- /dev/null
+++ b/components/Behaviors/src/Headers/FadeHeaderBehavior.cs
@@ -0,0 +1,155 @@
+// 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.Animations.Expressions;
+
+#if WINUI3
+using Microsoft.UI.Composition;
+using Microsoft.UI.Xaml.Hosting;
+using ListViewBase = Microsoft.UI.Xaml.Controls.ListViewBase;
+#else
+using Windows.UI.Composition;
+using Windows.UI.Xaml.Hosting;
+using ListViewBase = Windows.UI.Xaml.Controls.ListViewBase;
+#endif
+
+namespace CommunityToolkit.WinUI.Behaviors;
+
+///
+/// Performs an fade animation on a ListView or GridView Header using composition.
+///
+///
+/// Microsoft.Xaml.Interactivity.Behavior{Windows.UI.Xaml.UIElement}
+///
+public class FadeHeaderBehavior : BehaviorBase
+{
+ ///
+ /// Attaches the behavior to the associated object.
+ ///
+ ///
+ /// true if attaching succeeded; otherwise false.
+ ///
+ protected override bool Initialize()
+ {
+ var result = AssignFadeAnimation();
+ return result;
+ }
+
+ ///
+ /// Detaches the behavior from the associated object.
+ ///
+ ///
+ /// true if detaching succeeded; otherwise false.
+ ///
+ protected override bool Uninitialize()
+ {
+ RemoveFadeAnimation();
+ return true;
+ }
+
+ ///
+ /// If any of the properties are changed then the animation is automatically started depending on the AutomaticallyStart property.
+ ///
+ /// The dependency object.
+ /// The instance containing the event data.
+ private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ var b = d as FadeHeaderBehavior;
+ b?.AssignFadeAnimation();
+ }
+
+ ///
+ /// The UIElement that will be faded.
+ ///
+ public static readonly DependencyProperty HeaderElementProperty = DependencyProperty.Register(
+ nameof(HeaderElement), typeof(UIElement), typeof(FadeHeaderBehavior), new PropertyMetadata(null, PropertyChangedCallback));
+
+ ///
+ /// Gets or sets the target element for the Fading behavior.
+ ///
+ ///
+ /// Set this using the header of a ListView or GridView. You can use the entire root of the header or an element within the header.
+ ///
+ /// Using this example Header:
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// The behavior would be implemented like this
+ ///
+ ///
+ public UIElement HeaderElement
+ {
+ get { return (UIElement)GetValue(HeaderElementProperty); }
+ set { SetValue(HeaderElementProperty, value); }
+ }
+
+ ///
+ /// Uses Composition API to get the UIElement and sets an ExpressionAnimation
+ /// The ExpressionAnimation uses the height of the UIElement to calculate an opacity value
+ /// for the Header as it is scrolling off-screen. The opacity reaches 0 when the Header
+ /// is entirely scrolled off.
+ ///
+ /// true if the assignment was successful; otherwise, false.
+ private bool AssignFadeAnimation()
+ {
+ if (AssociatedObject == null)
+ {
+ return false;
+ }
+
+ var scroller = AssociatedObject as ScrollViewer ?? AssociatedObject.FindDescendant();
+ if (scroller == null)
+ {
+ return false;
+ }
+
+ var listView = AssociatedObject as ListViewBase ?? AssociatedObject.FindDescendant();
+
+ if (listView != null && listView.ItemsPanelRoot != null)
+ {
+ Canvas.SetZIndex(listView.ItemsPanelRoot, -1);
+ }
+
+ // Implicit operation: Find the Header object of the control if it uses ListViewBase
+ if (HeaderElement == null && listView != null)
+ {
+ HeaderElement = (listView.Header as UIElement)!;
+ }
+
+ // If no header is set or detected, return.
+ if (HeaderElement == null || HeaderElement.RenderSize.Height == 0d)
+ {
+ return false;
+ }
+
+ // Get the ScrollViewer's ManipulationPropertySet
+ var scrollViewerManipulationPropSet = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(scroller);
+ var scrollPropSet = scrollViewerManipulationPropSet.GetSpecializedReference();
+
+ // Use the ScrollViewer's Y offset and the header's height to calculate the opacity percentage. Clamp it between 0% and 100%
+ var headerHeight = (float)HeaderElement.RenderSize.Height;
+ var opacityExpression = ExpressionFunctions.Clamp(1 - (-scrollPropSet.Translation.Y / headerHeight), 0, 1);
+
+ // Begin animating
+ var targetElement = ElementCompositionPreview.GetElementVisual(HeaderElement);
+ targetElement.StartAnimation("Opacity", opacityExpression);
+
+ return true;
+ }
+
+ ///
+ /// Remove the opacity animation from the UIElement.
+ ///
+ private void RemoveFadeAnimation()
+ {
+ if (HeaderElement != null)
+ {
+ var targetElement = ElementCompositionPreview.GetElementVisual(HeaderElement);
+ targetElement.StopAnimation("Opacity");
+ targetElement.Opacity = 1.0f;
+ }
+ }
+}
diff --git a/components/Behaviors/src/Headers/QuickReturnHeaderBehavior.cs b/components/Behaviors/src/Headers/QuickReturnHeaderBehavior.cs
new file mode 100644
index 00000000..79e4cc94
--- /dev/null
+++ b/components/Behaviors/src/Headers/QuickReturnHeaderBehavior.cs
@@ -0,0 +1,277 @@
+// 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.Animations.Expressions;
+
+#if WINUI3
+using Microsoft.UI.Composition;
+using Microsoft.UI.Xaml.Hosting;
+using ListViewBase = Microsoft.UI.Xaml.Controls.ListViewBase;
+#else
+using Windows.UI.Composition;
+using Windows.UI.Xaml.Hosting;
+using ListViewBase = Windows.UI.Xaml.Controls.ListViewBase;
+#endif
+
+namespace CommunityToolkit.WinUI.Behaviors;
+
+///
+/// Performs an animation on a ListView or GridView Header to make it quick return using composition.
+///
+///
+/// Microsoft.Xaml.Interactivity.Behavior{Windows.UI.Xaml.UIElement}
+///
+public class QuickReturnHeaderBehavior : BehaviorBase
+{
+ ///
+ /// Attaches the behavior to the associated object.
+ ///
+ ///
+ /// true if attaching succeeded; otherwise false.
+ ///
+ protected override bool Initialize()
+ {
+ var result = AssignAnimation();
+ return result;
+ }
+
+ ///
+ /// Detaches the behavior from the associated object.
+ ///
+ ///
+ /// true if detaching succeeded; otherwise false.
+ ///
+ protected override bool Uninitialize()
+ {
+ RemoveAnimation();
+ return true;
+ }
+
+ ///
+ /// The UIElement that will be faded.
+ ///
+ public static readonly DependencyProperty HeaderElementProperty = DependencyProperty.Register(
+ nameof(HeaderElement), typeof(UIElement), typeof(QuickReturnHeaderBehavior), new PropertyMetadata(null, PropertyChangedCallback));
+
+ private ScrollViewer? _scrollViewer;
+ private double _headerPosition;
+ private CompositionPropertySet? _scrollProperties;
+ private CompositionPropertySet? _animationProperties;
+ private Visual? _headerVisual;
+
+ ///
+ /// Gets or sets the target element for the ScrollHeader behavior.
+ ///
+ ///
+ /// Set this using the header of a ListView or GridView.
+ ///
+ public UIElement HeaderElement
+ {
+ get { return (UIElement)GetValue(HeaderElementProperty); }
+ set { SetValue(HeaderElementProperty, value); }
+ }
+
+ ///
+ /// Show the header
+ ///
+ public void Show()
+ {
+ if (_headerVisual != null && _scrollViewer != null)
+ {
+ _animationProperties?.InsertScalar("OffsetY", 0.0f);
+ }
+ }
+
+ ///
+ /// If any of the properties are changed then the animation is automatically started depending on the QuickReturn and IsSticky properties.
+ ///
+ /// The dependency object.
+ /// The instance containing the event data.
+ private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ var b = d as QuickReturnHeaderBehavior;
+ b?.AssignAnimation();
+ }
+
+ ///
+ /// Uses Composition API to get the UIElement and sets an ExpressionAnimation
+ /// The ExpressionAnimation uses the height of the UIElement to calculate an opacity value
+ /// for the Header as it is scrolling off-screen. The opacity reaches 0 when the Header
+ /// is entirely scrolled off.
+ ///
+ /// true if the assignment was successful; otherwise, false.
+ private bool AssignAnimation()
+ {
+ StopAnimation();
+
+ if (AssociatedObject == null)
+ {
+ return false;
+ }
+
+ if (_scrollViewer == null)
+ {
+ _scrollViewer = AssociatedObject as ScrollViewer ?? AssociatedObject.FindDescendant();
+ }
+
+ if (_scrollViewer == null)
+ {
+ return false;
+ }
+
+ var listView = AssociatedObject as ListViewBase ?? AssociatedObject.FindDescendant();
+
+ if (listView != null && listView.ItemsPanelRoot != null)
+ {
+ Canvas.SetZIndex(listView.ItemsPanelRoot, -1);
+ }
+
+ if (_scrollProperties == null)
+ {
+ _scrollProperties = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(_scrollViewer);
+ }
+
+ if (_scrollProperties == null)
+ {
+ return false;
+ }
+
+ // Implicit operation: Find the Header object of the control if it uses ListViewBase
+ if (HeaderElement == null && listView != null)
+ {
+ HeaderElement = (listView.Header as UIElement)!;
+ }
+
+ var headerElement = HeaderElement as FrameworkElement;
+ if (headerElement == null || headerElement.RenderSize.Height == 0)
+ {
+ return false;
+ }
+
+ if (_headerVisual == null)
+ {
+ _headerVisual = ElementCompositionPreview.GetElementVisual(headerElement);
+ }
+
+ if (_headerVisual == null)
+ {
+ return false;
+ }
+
+ _scrollViewer.ViewChanged -= ScrollViewer_ViewChanged;
+ _scrollViewer.ViewChanged += ScrollViewer_ViewChanged;
+
+ _scrollViewer.GotFocus -= ScrollViewer_GotFocus;
+ _scrollViewer.GotFocus += ScrollViewer_GotFocus;
+
+ headerElement.SizeChanged -= ScrollHeader_SizeChanged;
+ headerElement.SizeChanged += ScrollHeader_SizeChanged;
+
+ var compositor = _scrollProperties.Compositor;
+
+ if (_animationProperties == null)
+ {
+ _animationProperties = compositor.CreatePropertySet();
+ _animationProperties.InsertScalar("OffsetY", 0.0f);
+ }
+
+ var propSetOffset = _animationProperties.GetReference().GetScalarProperty("OffsetY");
+ var scrollPropSet = _scrollProperties.GetSpecializedReference();
+ var expressionAnimation = ExpressionFunctions.Max(ExpressionFunctions.Min(propSetOffset, -scrollPropSet.Translation.Y), 0);
+
+ _headerVisual.StartAnimation("Offset.Y", expressionAnimation);
+
+ return true;
+ }
+
+ ///
+ /// Remove the animation from the UIElement.
+ ///
+ private void RemoveAnimation()
+ {
+ if (_scrollViewer != null)
+ {
+ _scrollViewer.ViewChanged -= ScrollViewer_ViewChanged;
+ _scrollViewer.GotFocus -= ScrollViewer_GotFocus;
+ }
+
+ if (HeaderElement is FrameworkElement element)
+ {
+ element.SizeChanged -= ScrollHeader_SizeChanged;
+ }
+
+ StopAnimation();
+ }
+
+ ///
+ /// Stop the animation of the UIElement.
+ ///
+ private void StopAnimation()
+ {
+ _headerVisual?.StopAnimation("Offset.Y");
+
+ _animationProperties?.InsertScalar("OffsetY", 0.0f);
+
+ if (_headerVisual != null)
+ {
+ var offset = _headerVisual.Offset;
+ offset.Y = 0.0f;
+ _headerVisual.Offset = offset;
+ }
+ }
+
+ private void ScrollHeader_SizeChanged(object sender, SizeChangedEventArgs e)
+ {
+ AssignAnimation();
+ }
+
+ private void ScrollViewer_ViewChanged(object? sender, ScrollViewerViewChangedEventArgs e)
+ {
+ if (_animationProperties != null && _scrollViewer != null)
+ {
+ FrameworkElement header = (FrameworkElement)HeaderElement;
+ var headerHeight = header.ActualHeight;
+ if (_headerPosition + headerHeight < _scrollViewer.VerticalOffset)
+ {
+ // scrolling down: move header down, so it is just above screen
+ _headerPosition = _scrollViewer.VerticalOffset - headerHeight;
+ _animationProperties.InsertScalar("OffsetY", (float)_headerPosition);
+ }
+ else if (_headerPosition > _scrollViewer.VerticalOffset)
+ {
+ // scrolling up: move header up, align with top border.
+ // the expression animation makes sure it never really is shown below border, so no lag effect!
+ _headerPosition = _scrollViewer.VerticalOffset;
+ _animationProperties.InsertScalar("OffsetY", (float)_headerPosition);
+ }
+ }
+ }
+
+ private void ScrollViewer_GotFocus(object sender, RoutedEventArgs e)
+ {
+ var scroller = (ScrollViewer)sender;
+
+ object focusedElement;
+ if (ApiInformationHelper.IsXamlRootAvailable && scroller.XamlRoot != null)
+ {
+ focusedElement = FocusManager.GetFocusedElement(scroller.XamlRoot)!;
+ }
+ else
+ {
+ focusedElement = FocusManager.GetFocusedElement()!;
+ }
+
+ if (focusedElement is UIElement element)
+ {
+ FrameworkElement header = (FrameworkElement)HeaderElement;
+
+ var point = element.TransformToVisual(scroller).TransformPoint(new Point(0, 0));
+
+ if (point.Y < header.ActualHeight)
+ {
+ scroller.ChangeView(0, scroller.VerticalOffset - (header.ActualHeight - point.Y), 1, false);
+ }
+ }
+ }
+}
diff --git a/components/Behaviors/src/Headers/StickyHeaderBehavior.cs b/components/Behaviors/src/Headers/StickyHeaderBehavior.cs
new file mode 100644
index 00000000..cacda590
--- /dev/null
+++ b/components/Behaviors/src/Headers/StickyHeaderBehavior.cs
@@ -0,0 +1,261 @@
+// 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.Animations.Expressions;
+
+#if WINUI3
+using Microsoft.UI.Composition;
+using Microsoft.UI.Xaml.Hosting;
+using ListViewBase = Microsoft.UI.Xaml.Controls.ListViewBase;
+#else
+using Windows.UI.Composition;
+using Windows.UI.Xaml.Hosting;
+using ListViewBase = Windows.UI.Xaml.Controls.ListViewBase;
+#endif
+
+namespace CommunityToolkit.WinUI.Behaviors;
+
+///
+/// Performs an animation on a ListView or GridView Header to make it 'sticky' and remain in view using composition.
+///
+///
+/// Microsoft.Xaml.Interactivity.Behavior{Microsoft.UI.Xaml.UIElement}
+///
+public class StickyHeaderBehavior : BehaviorBase
+{
+ ///
+ /// Attaches the behavior to the associated object.
+ ///
+ ///
+ /// true if attaching succeeded; otherwise false.
+ ///
+ protected override bool Initialize()
+ {
+ var result = AssignAnimation();
+ return result;
+ }
+
+ ///
+ /// Detaches the behavior from the associated object.
+ ///
+ ///
+ /// true if detaching succeeded; otherwise false.
+ ///
+ protected override bool Uninitialize()
+ {
+ RemoveAnimation();
+ return true;
+ }
+
+ ///
+ /// The UIElement that will be 'sticky' and remain in view.
+ ///
+ public static readonly DependencyProperty HeaderElementProperty = DependencyProperty.Register(
+ nameof(HeaderElement), typeof(UIElement), typeof(StickyHeaderBehavior), new PropertyMetadata(null, PropertyChangedCallback));
+
+ private ScrollViewer? _scrollViewer;
+ private double _previousVerticalScrollOffset;
+ private CompositionPropertySet? _scrollProperties;
+ private CompositionPropertySet? _animationProperties;
+ private Visual? _headerVisual;
+
+ ///
+ /// Gets or sets the target element for the ScrollHeader behavior.
+ ///
+ ///
+ /// Set this using the header of a ListView or GridView.
+ ///
+ public UIElement HeaderElement
+ {
+ get { return (UIElement)GetValue(HeaderElementProperty); }
+ set { SetValue(HeaderElementProperty, value); }
+ }
+
+ ///
+ /// Show the header
+ ///
+ public void Show()
+ {
+ if (_headerVisual != null && _scrollViewer != null)
+ {
+ _previousVerticalScrollOffset = _scrollViewer.VerticalOffset;
+
+ _animationProperties?.InsertScalar("OffsetY", 0.0f);
+ }
+ }
+
+ ///
+ /// If any of the properties are changed then the animation is automatically started.
+ ///
+ /// The dependency object.
+ /// The instance containing the event data.
+ private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is StickyHeaderBehavior b)
+ {
+ b.AssignAnimation();
+ }
+ }
+
+ ///
+ /// Uses Composition API to get the UIElement and sets an ExpressionAnimation
+ /// The ExpressionAnimation uses the height of the UIElement to calculate an opacity value
+ /// for the Header as it is scrolling off-screen. The opacity reaches 0 when the Header
+ /// is entirely scrolled off.
+ ///
+ /// true if the assignment was successful; otherwise, false.
+ private bool AssignAnimation()
+ {
+ StopAnimation();
+
+ if (AssociatedObject == null)
+ {
+ return false;
+ }
+
+ // TODO: What if we attach to the 'header element' and look up for the ScrollViewer?
+ if (_scrollViewer == null)
+ {
+ _scrollViewer = AssociatedObject as ScrollViewer ?? AssociatedObject.FindDescendant();
+ }
+
+ if (_scrollViewer == null)
+ {
+ return false;
+ }
+
+ var listView = AssociatedObject as ListViewBase ?? AssociatedObject.FindDescendant();
+
+ // TODO: Is this required?
+ if (listView != null && listView.ItemsPanelRoot != null)
+ {
+ Canvas.SetZIndex(listView.ItemsPanelRoot, -1);
+ }
+
+ if (_scrollProperties == null)
+ {
+ _scrollProperties = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(_scrollViewer);
+ }
+
+ if (_scrollProperties == null)
+ {
+ return false;
+ }
+
+ // Implicit operation: Find the Header object of the control if it uses ListViewBase
+ if (HeaderElement == null && listView != null)
+ {
+ HeaderElement = (listView.Header as UIElement)!;
+ }
+
+ var headerElement = HeaderElement as FrameworkElement;
+ if (headerElement == null || headerElement.RenderSize.Height == 0)
+ {
+ return false;
+ }
+
+ if (_headerVisual == null)
+ {
+ _headerVisual = ElementCompositionPreview.GetElementVisual(headerElement);
+ }
+
+ if (_headerVisual == null)
+ {
+ return false;
+ }
+
+ headerElement.SizeChanged -= ScrollHeader_SizeChanged;
+ headerElement.SizeChanged += ScrollHeader_SizeChanged;
+
+ _scrollViewer.GotFocus -= ScrollViewer_GotFocus;
+ _scrollViewer.GotFocus += ScrollViewer_GotFocus;
+
+ var compositor = _scrollProperties.Compositor;
+
+ if (_animationProperties == null)
+ {
+ _animationProperties = compositor.CreatePropertySet();
+ _animationProperties.InsertScalar("OffsetY", 0.0f);
+ }
+
+ _previousVerticalScrollOffset = _scrollViewer.VerticalOffset;
+
+ var propSetOffset = _animationProperties.GetReference().GetScalarProperty("OffsetY");
+ var scrollPropSet = _scrollProperties.GetSpecializedReference();
+ var expressionAnimation = ExpressionFunctions.Max(propSetOffset - scrollPropSet.Translation.Y, 0);
+
+ _headerVisual.StartAnimation("Offset.Y", expressionAnimation);
+
+ return true;
+ }
+
+ ///
+ /// Remove the animation from the UIElement.
+ ///
+ private void RemoveAnimation()
+ {
+ if (HeaderElement is FrameworkElement element)
+ {
+ element.SizeChanged -= ScrollHeader_SizeChanged;
+ }
+
+ if (_scrollViewer != null)
+ {
+ _scrollViewer.GotFocus -= ScrollViewer_GotFocus;
+ }
+
+ StopAnimation();
+ }
+
+ ///
+ /// Stop the animation of the UIElement.
+ ///
+ private void StopAnimation()
+ {
+ _headerVisual?.StopAnimation("Offset.Y");
+
+ _animationProperties?.InsertScalar("OffsetY", 0.0f);
+
+ if (_headerVisual != null)
+ {
+ var offset = _headerVisual.Offset;
+ offset.Y = 0.0f;
+ _headerVisual.Offset = offset;
+ }
+ }
+
+ private void ScrollHeader_SizeChanged(object sender, SizeChangedEventArgs e)
+ {
+ AssignAnimation();
+ }
+
+ private void ScrollViewer_GotFocus(object sender, RoutedEventArgs e)
+ {
+ var scroller = (ScrollViewer)sender;
+
+ object focusedElement;
+ if (ApiInformationHelper.IsXamlRootAvailable && scroller.XamlRoot != null)
+ {
+ focusedElement = FocusManager.GetFocusedElement(scroller.XamlRoot)!;
+ }
+ else
+ {
+ focusedElement = FocusManager.GetFocusedElement()!;
+ }
+
+ // To prevent Popups (Flyouts...) from triggering the autoscroll, we check if the focused element has a valid parent.
+ // Popups have no parents, whereas a normal Item would have the ListView as a parent.
+ if (focusedElement is UIElement element && VisualTreeHelper.GetParent(element) != null)
+ {
+ FrameworkElement header = (FrameworkElement)HeaderElement;
+
+ var point = element.TransformToVisual(scroller).TransformPoint(new Point(0, 0));
+
+ if (point.Y < header.ActualHeight)
+ {
+ scroller.ChangeView(0, scroller.VerticalOffset - (header.ActualHeight - point.Y), 1, false);
+ }
+ }
+ }
+}
From 3d15aaae0aa9ce76999822f7f7a14b50170b9f58 Mon Sep 17 00:00:00 2001
From: michael-hawker <24302614+michael-hawker@users.noreply.github.com>
Date: Wed, 12 Jul 2023 11:41:35 -0700
Subject: [PATCH 2/9] Consolidate StickyHeaderBehavior and
QuickReturnHeaderBehavior with new abstract HeaderBehaviorBase helper class
Is responsible for finding needed controls and properties for setting up composition animation. 90% of these two classes were doing the same thing, so now only the logic related to setting up/manipulating the composition animation is in each subclass.
Going to investigate if FadeHeaderBehavior is similar enough to reuse as well.
---
.../src/Headers/HeaderBehaviorBase.cs | 218 ++++++++++++++++++
.../src/Headers/QuickReturnHeaderBehavior.cs | 217 ++---------------
.../src/Headers/StickyHeaderBehavior.cs | 217 ++---------------
3 files changed, 257 insertions(+), 395 deletions(-)
create mode 100644 components/Behaviors/src/Headers/HeaderBehaviorBase.cs
diff --git a/components/Behaviors/src/Headers/HeaderBehaviorBase.cs b/components/Behaviors/src/Headers/HeaderBehaviorBase.cs
new file mode 100644
index 00000000..717f41dc
--- /dev/null
+++ b/components/Behaviors/src/Headers/HeaderBehaviorBase.cs
@@ -0,0 +1,218 @@
+// 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.
+
+#if WINUI3
+using Microsoft.UI.Composition;
+using Microsoft.UI.Xaml.Hosting;
+using ListViewBase = Microsoft.UI.Xaml.Controls.ListViewBase;
+#else
+using Windows.UI.Composition;
+using Windows.UI.Xaml.Hosting;
+using ListViewBase = Windows.UI.Xaml.Controls.ListViewBase;
+#endif
+
+namespace CommunityToolkit.WinUI.Behaviors.Internal;
+
+///
+/// Base class helper for header behaviors which manipulate an element within a viewport of a based control.
+///
+public abstract class HeaderBehaviorBase : BehaviorBase
+{
+ protected ScrollViewer? _scrollViewer;
+ protected CompositionPropertySet? _scrollProperties;
+ protected CompositionPropertySet? _animationProperties;
+ protected Visual? _headerVisual;
+
+ ///
+ /// Gets or sets the target element for the Header behavior to be manipulated within the viewport.
+ ///
+ ///
+ /// Set this using the header of a ListView or GridView.
+ ///
+ public UIElement HeaderElement
+ {
+ get { return (UIElement)GetValue(HeaderElementProperty); }
+ set { SetValue(HeaderElementProperty, value); }
+ }
+
+ ///
+ /// Defines the Dependency Property for the property.
+ ///
+ public static readonly DependencyProperty HeaderElementProperty = DependencyProperty.Register(
+ nameof(HeaderElement), typeof(UIElement), typeof(HeaderBehaviorBase), new PropertyMetadata(null, PropertyChangedCallback));
+
+ ///
+ /// If any of the properties are changed then the animation is automatically started.
+ ///
+ /// The dependency object.
+ /// The instance containing the event data.
+ private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is HeaderBehaviorBase @base)
+ {
+ @base.AssignAnimation();
+ }
+ }
+
+ ///
+ /// Attaches the behavior to the associated object.
+ ///
+ ///
+ /// true if attaching succeeded; otherwise false.
+ ///
+ protected override bool Initialize()
+ {
+ var result = AssignAnimation();
+ return result;
+ }
+
+ ///
+ /// Detaches the behavior from the associated object.
+ ///
+ ///
+ /// true if detaching succeeded; otherwise false.
+ ///
+ protected override bool Uninitialize()
+ {
+ RemoveAnimation();
+ return true;
+ }
+
+ ///
+ /// Uses Composition API to get the UIElement and sets an ExpressionAnimation
+ ///
+ /// true if the assignment was successful; otherwise, false.
+ protected virtual bool AssignAnimation()
+ {
+ StopAnimation();
+
+ if (AssociatedObject == null)
+ {
+ return false;
+ }
+
+ // TODO: What if we attach to the 'header element' and look up for the ScrollViewer?
+ if (_scrollViewer == null)
+ {
+ _scrollViewer = AssociatedObject as ScrollViewer ?? AssociatedObject.FindDescendant();
+ }
+
+ if (_scrollViewer == null)
+ {
+ return false;
+ }
+
+ var listView = AssociatedObject as ListViewBase ?? AssociatedObject.FindDescendant();
+
+ // TODO: Is this required?
+ if (listView != null && listView.ItemsPanelRoot != null)
+ {
+ Canvas.SetZIndex(listView.ItemsPanelRoot, -1);
+ }
+
+ if (_scrollProperties == null)
+ {
+ _scrollProperties = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(_scrollViewer);
+ }
+
+ if (_scrollProperties == null)
+ {
+ return false;
+ }
+
+ // Implicit operation: Find the Header object of the control if it uses ListViewBase
+ if (HeaderElement == null && listView != null)
+ {
+ HeaderElement = (listView.Header as UIElement)!;
+ }
+
+ var headerElement = HeaderElement as FrameworkElement;
+ if (headerElement == null || headerElement.RenderSize.Height == 0)
+ {
+ return false;
+ }
+
+ if (_headerVisual == null)
+ {
+ _headerVisual = ElementCompositionPreview.GetElementVisual(headerElement);
+ }
+
+ if (_headerVisual == null)
+ {
+ return false;
+ }
+
+ headerElement.SizeChanged -= ScrollHeader_SizeChanged;
+ headerElement.SizeChanged += ScrollHeader_SizeChanged;
+
+ _scrollViewer.GotFocus -= ScrollViewer_GotFocus;
+ _scrollViewer.GotFocus += ScrollViewer_GotFocus;
+
+ var compositor = _scrollProperties.Compositor;
+
+ if (_animationProperties == null)
+ {
+ _animationProperties = compositor.CreatePropertySet();
+ }
+
+ return true;
+ }
+
+ ///
+ /// Stop the animation of the UIElement.
+ ///
+ protected abstract void StopAnimation();
+
+ ///
+ /// Remove the animation from the UIElement.
+ ///
+ protected virtual void RemoveAnimation()
+ {
+ if (_scrollViewer != null)
+ {
+ _scrollViewer.GotFocus -= ScrollViewer_GotFocus;
+ }
+
+ if (HeaderElement is FrameworkElement element)
+ {
+ element.SizeChanged -= ScrollHeader_SizeChanged;
+ }
+
+ StopAnimation();
+ }
+
+ private void ScrollHeader_SizeChanged(object sender, SizeChangedEventArgs e)
+ {
+ AssignAnimation();
+ }
+
+ private void ScrollViewer_GotFocus(object sender, RoutedEventArgs e)
+ {
+ var scroller = (ScrollViewer)sender;
+
+ object focusedElement;
+ if (ApiInformationHelper.IsXamlRootAvailable && scroller.XamlRoot != null)
+ {
+ focusedElement = FocusManager.GetFocusedElement(scroller.XamlRoot)!;
+ }
+ else
+ {
+ focusedElement = FocusManager.GetFocusedElement()!;
+ }
+
+ // To prevent Popups (Flyouts...) from triggering the autoscroll, we check if the focused element has a valid parent.
+ // Popups have no parents, whereas a normal Item would have the ListView as a parent.
+ if (focusedElement is UIElement element && VisualTreeHelper.GetParent(element) != null)
+ {
+ FrameworkElement header = (FrameworkElement)HeaderElement;
+
+ var point = element.TransformToVisual(scroller).TransformPoint(new Point(0, 0));
+
+ if (point.Y < header.ActualHeight)
+ {
+ scroller.ChangeView(0, scroller.VerticalOffset - (header.ActualHeight - point.Y), 1, false);
+ }
+ }
+ }
+}
diff --git a/components/Behaviors/src/Headers/QuickReturnHeaderBehavior.cs b/components/Behaviors/src/Headers/QuickReturnHeaderBehavior.cs
index 79e4cc94..a1960b0f 100644
--- a/components/Behaviors/src/Headers/QuickReturnHeaderBehavior.cs
+++ b/components/Behaviors/src/Headers/QuickReturnHeaderBehavior.cs
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using CommunityToolkit.WinUI.Animations.Expressions;
+using CommunityToolkit.WinUI.Behaviors.Internal;
#if WINUI3
using Microsoft.UI.Composition;
@@ -22,56 +23,10 @@ namespace CommunityToolkit.WinUI.Behaviors;
///
/// Microsoft.Xaml.Interactivity.Behavior{Windows.UI.Xaml.UIElement}
///
-public class QuickReturnHeaderBehavior : BehaviorBase
+public class QuickReturnHeaderBehavior : HeaderBehaviorBase
{
- ///
- /// Attaches the behavior to the associated object.
- ///
- ///
- /// true if attaching succeeded; otherwise false.
- ///
- protected override bool Initialize()
- {
- var result = AssignAnimation();
- return result;
- }
-
- ///
- /// Detaches the behavior from the associated object.
- ///
- ///
- /// true if detaching succeeded; otherwise false.
- ///
- protected override bool Uninitialize()
- {
- RemoveAnimation();
- return true;
- }
-
- ///
- /// The UIElement that will be faded.
- ///
- public static readonly DependencyProperty HeaderElementProperty = DependencyProperty.Register(
- nameof(HeaderElement), typeof(UIElement), typeof(QuickReturnHeaderBehavior), new PropertyMetadata(null, PropertyChangedCallback));
-
- private ScrollViewer? _scrollViewer;
private double _headerPosition;
- private CompositionPropertySet? _scrollProperties;
- private CompositionPropertySet? _animationProperties;
- private Visual? _headerVisual;
-
- ///
- /// Gets or sets the target element for the ScrollHeader behavior.
- ///
- ///
- /// Set this using the header of a ListView or GridView.
- ///
- public UIElement HeaderElement
- {
- get { return (UIElement)GetValue(HeaderElementProperty); }
- set { SetValue(HeaderElementProperty, value); }
- }
-
+
///
/// Show the header
///
@@ -83,131 +38,30 @@ public void Show()
}
}
- ///
- /// If any of the properties are changed then the animation is automatically started depending on the QuickReturn and IsSticky properties.
- ///
- /// The dependency object.
- /// The instance containing the event data.
- private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ ///
+ protected override bool AssignAnimation()
{
- var b = d as QuickReturnHeaderBehavior;
- b?.AssignAnimation();
- }
-
- ///
- /// Uses Composition API to get the UIElement and sets an ExpressionAnimation
- /// The ExpressionAnimation uses the height of the UIElement to calculate an opacity value
- /// for the Header as it is scrolling off-screen. The opacity reaches 0 when the Header
- /// is entirely scrolled off.
- ///
- /// true if the assignment was successful; otherwise, false.
- private bool AssignAnimation()
- {
- StopAnimation();
-
- if (AssociatedObject == null)
- {
- return false;
- }
-
- if (_scrollViewer == null)
- {
- _scrollViewer = AssociatedObject as ScrollViewer ?? AssociatedObject.FindDescendant();
- }
-
- if (_scrollViewer == null)
- {
- return false;
- }
-
- var listView = AssociatedObject as ListViewBase ?? AssociatedObject.FindDescendant();
-
- if (listView != null && listView.ItemsPanelRoot != null)
- {
- Canvas.SetZIndex(listView.ItemsPanelRoot, -1);
- }
-
- if (_scrollProperties == null)
- {
- _scrollProperties = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(_scrollViewer);
- }
-
- if (_scrollProperties == null)
- {
- return false;
- }
-
- // Implicit operation: Find the Header object of the control if it uses ListViewBase
- if (HeaderElement == null && listView != null)
- {
- HeaderElement = (listView.Header as UIElement)!;
- }
-
- var headerElement = HeaderElement as FrameworkElement;
- if (headerElement == null || headerElement.RenderSize.Height == 0)
+ if (base.AssignAnimation())
{
- return false;
- }
-
- if (_headerVisual == null)
- {
- _headerVisual = ElementCompositionPreview.GetElementVisual(headerElement);
- }
-
- if (_headerVisual == null)
- {
- return false;
- }
-
- _scrollViewer.ViewChanged -= ScrollViewer_ViewChanged;
- _scrollViewer.ViewChanged += ScrollViewer_ViewChanged;
-
- _scrollViewer.GotFocus -= ScrollViewer_GotFocus;
- _scrollViewer.GotFocus += ScrollViewer_GotFocus;
-
- headerElement.SizeChanged -= ScrollHeader_SizeChanged;
- headerElement.SizeChanged += ScrollHeader_SizeChanged;
-
- var compositor = _scrollProperties.Compositor;
-
- if (_animationProperties == null)
- {
- _animationProperties = compositor.CreatePropertySet();
- _animationProperties.InsertScalar("OffsetY", 0.0f);
- }
-
- var propSetOffset = _animationProperties.GetReference().GetScalarProperty("OffsetY");
- var scrollPropSet = _scrollProperties.GetSpecializedReference();
- var expressionAnimation = ExpressionFunctions.Max(ExpressionFunctions.Min(propSetOffset, -scrollPropSet.Translation.Y), 0);
+ _animationProperties?.InsertScalar("OffsetY", 0.0f);
- _headerVisual.StartAnimation("Offset.Y", expressionAnimation);
+ _scrollViewer!.ViewChanged -= ScrollViewer_ViewChanged;
+ _scrollViewer!.ViewChanged += ScrollViewer_ViewChanged;
- return true;
- }
+ var propSetOffset = _animationProperties!.GetReference().GetScalarProperty("OffsetY");
+ var scrollPropSet = _scrollProperties!.GetSpecializedReference();
+ var expressionAnimation = ExpressionFunctions.Max(ExpressionFunctions.Min(propSetOffset, -scrollPropSet.Translation.Y), 0);
- ///
- /// Remove the animation from the UIElement.
- ///
- private void RemoveAnimation()
- {
- if (_scrollViewer != null)
- {
- _scrollViewer.ViewChanged -= ScrollViewer_ViewChanged;
- _scrollViewer.GotFocus -= ScrollViewer_GotFocus;
- }
+ _headerVisual?.StartAnimation("Offset.Y", expressionAnimation);
- if (HeaderElement is FrameworkElement element)
- {
- element.SizeChanged -= ScrollHeader_SizeChanged;
+ return true;
}
- StopAnimation();
+ return false;
}
- ///
- /// Stop the animation of the UIElement.
- ///
- private void StopAnimation()
+ ///
+ protected override void StopAnimation()
{
_headerVisual?.StopAnimation("Offset.Y");
@@ -221,9 +75,15 @@ private void StopAnimation()
}
}
- private void ScrollHeader_SizeChanged(object sender, SizeChangedEventArgs e)
+ ///
+ protected override void RemoveAnimation()
{
- AssignAnimation();
+ if (_scrollViewer != null)
+ {
+ _scrollViewer.ViewChanged -= ScrollViewer_ViewChanged;
+ }
+
+ base.RemoveAnimation();
}
private void ScrollViewer_ViewChanged(object? sender, ScrollViewerViewChangedEventArgs e)
@@ -247,31 +107,4 @@ private void ScrollViewer_ViewChanged(object? sender, ScrollViewerViewChangedEve
}
}
}
-
- private void ScrollViewer_GotFocus(object sender, RoutedEventArgs e)
- {
- var scroller = (ScrollViewer)sender;
-
- object focusedElement;
- if (ApiInformationHelper.IsXamlRootAvailable && scroller.XamlRoot != null)
- {
- focusedElement = FocusManager.GetFocusedElement(scroller.XamlRoot)!;
- }
- else
- {
- focusedElement = FocusManager.GetFocusedElement()!;
- }
-
- if (focusedElement is UIElement element)
- {
- FrameworkElement header = (FrameworkElement)HeaderElement;
-
- var point = element.TransformToVisual(scroller).TransformPoint(new Point(0, 0));
-
- if (point.Y < header.ActualHeight)
- {
- scroller.ChangeView(0, scroller.VerticalOffset - (header.ActualHeight - point.Y), 1, false);
- }
- }
- }
}
diff --git a/components/Behaviors/src/Headers/StickyHeaderBehavior.cs b/components/Behaviors/src/Headers/StickyHeaderBehavior.cs
index cacda590..fbd2f2ef 100644
--- a/components/Behaviors/src/Headers/StickyHeaderBehavior.cs
+++ b/components/Behaviors/src/Headers/StickyHeaderBehavior.cs
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using CommunityToolkit.WinUI.Animations.Expressions;
+using CommunityToolkit.WinUI.Behaviors.Internal;
#if WINUI3
using Microsoft.UI.Composition;
@@ -22,56 +23,8 @@ namespace CommunityToolkit.WinUI.Behaviors;
///
/// Microsoft.Xaml.Interactivity.Behavior{Microsoft.UI.Xaml.UIElement}
///
-public class StickyHeaderBehavior : BehaviorBase
+public class StickyHeaderBehavior : HeaderBehaviorBase
{
- ///
- /// Attaches the behavior to the associated object.
- ///
- ///
- /// true if attaching succeeded; otherwise false.
- ///
- protected override bool Initialize()
- {
- var result = AssignAnimation();
- return result;
- }
-
- ///
- /// Detaches the behavior from the associated object.
- ///
- ///
- /// true if detaching succeeded; otherwise false.
- ///
- protected override bool Uninitialize()
- {
- RemoveAnimation();
- return true;
- }
-
- ///
- /// The UIElement that will be 'sticky' and remain in view.
- ///
- public static readonly DependencyProperty HeaderElementProperty = DependencyProperty.Register(
- nameof(HeaderElement), typeof(UIElement), typeof(StickyHeaderBehavior), new PropertyMetadata(null, PropertyChangedCallback));
-
- private ScrollViewer? _scrollViewer;
- private double _previousVerticalScrollOffset;
- private CompositionPropertySet? _scrollProperties;
- private CompositionPropertySet? _animationProperties;
- private Visual? _headerVisual;
-
- ///
- /// Gets or sets the target element for the ScrollHeader behavior.
- ///
- ///
- /// Set this using the header of a ListView or GridView.
- ///
- public UIElement HeaderElement
- {
- get { return (UIElement)GetValue(HeaderElementProperty); }
- set { SetValue(HeaderElementProperty, value); }
- }
-
///
/// Show the header
///
@@ -79,139 +32,31 @@ public void Show()
{
if (_headerVisual != null && _scrollViewer != null)
{
- _previousVerticalScrollOffset = _scrollViewer.VerticalOffset;
-
_animationProperties?.InsertScalar("OffsetY", 0.0f);
}
}
- ///
- /// If any of the properties are changed then the animation is automatically started.
- ///
- /// The dependency object.
- /// The instance containing the event data.
- private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ ///
+ protected override bool AssignAnimation()
{
- if (d is StickyHeaderBehavior b)
- {
- b.AssignAnimation();
- }
- }
-
- ///
- /// Uses Composition API to get the UIElement and sets an ExpressionAnimation
- /// The ExpressionAnimation uses the height of the UIElement to calculate an opacity value
- /// for the Header as it is scrolling off-screen. The opacity reaches 0 when the Header
- /// is entirely scrolled off.
- ///
- /// true if the assignment was successful; otherwise, false.
- private bool AssignAnimation()
- {
- StopAnimation();
-
- if (AssociatedObject == null)
- {
- return false;
- }
-
- // TODO: What if we attach to the 'header element' and look up for the ScrollViewer?
- if (_scrollViewer == null)
- {
- _scrollViewer = AssociatedObject as ScrollViewer ?? AssociatedObject.FindDescendant();
- }
-
- if (_scrollViewer == null)
- {
- return false;
- }
-
- var listView = AssociatedObject as ListViewBase ?? AssociatedObject.FindDescendant();
-
- // TODO: Is this required?
- if (listView != null && listView.ItemsPanelRoot != null)
- {
- Canvas.SetZIndex(listView.ItemsPanelRoot, -1);
- }
-
- if (_scrollProperties == null)
- {
- _scrollProperties = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(_scrollViewer);
- }
-
- if (_scrollProperties == null)
- {
- return false;
- }
-
- // Implicit operation: Find the Header object of the control if it uses ListViewBase
- if (HeaderElement == null && listView != null)
- {
- HeaderElement = (listView.Header as UIElement)!;
- }
-
- var headerElement = HeaderElement as FrameworkElement;
- if (headerElement == null || headerElement.RenderSize.Height == 0)
- {
- return false;
- }
-
- if (_headerVisual == null)
- {
- _headerVisual = ElementCompositionPreview.GetElementVisual(headerElement);
- }
-
- if (_headerVisual == null)
+ if (base.AssignAnimation())
{
- return false;
- }
-
- headerElement.SizeChanged -= ScrollHeader_SizeChanged;
- headerElement.SizeChanged += ScrollHeader_SizeChanged;
+ _animationProperties?.InsertScalar("OffsetY", 0.0f);
- _scrollViewer.GotFocus -= ScrollViewer_GotFocus;
- _scrollViewer.GotFocus += ScrollViewer_GotFocus;
+ var propSetOffset = _animationProperties!.GetReference().GetScalarProperty("OffsetY");
+ var scrollPropSet = _scrollProperties!.GetSpecializedReference();
+ var expressionAnimation = ExpressionFunctions.Max(propSetOffset - scrollPropSet.Translation.Y, 0);
- var compositor = _scrollProperties.Compositor;
+ _headerVisual?.StartAnimation("Offset.Y", expressionAnimation);
- if (_animationProperties == null)
- {
- _animationProperties = compositor.CreatePropertySet();
- _animationProperties.InsertScalar("OffsetY", 0.0f);
+ return true;
}
- _previousVerticalScrollOffset = _scrollViewer.VerticalOffset;
-
- var propSetOffset = _animationProperties.GetReference().GetScalarProperty("OffsetY");
- var scrollPropSet = _scrollProperties.GetSpecializedReference();
- var expressionAnimation = ExpressionFunctions.Max(propSetOffset - scrollPropSet.Translation.Y, 0);
-
- _headerVisual.StartAnimation("Offset.Y", expressionAnimation);
-
- return true;
+ return false;
}
- ///
- /// Remove the animation from the UIElement.
- ///
- private void RemoveAnimation()
- {
- if (HeaderElement is FrameworkElement element)
- {
- element.SizeChanged -= ScrollHeader_SizeChanged;
- }
-
- if (_scrollViewer != null)
- {
- _scrollViewer.GotFocus -= ScrollViewer_GotFocus;
- }
-
- StopAnimation();
- }
-
- ///
- /// Stop the animation of the UIElement.
- ///
- private void StopAnimation()
+ ///
+ protected override void StopAnimation()
{
_headerVisual?.StopAnimation("Offset.Y");
@@ -224,38 +69,4 @@ private void StopAnimation()
_headerVisual.Offset = offset;
}
}
-
- private void ScrollHeader_SizeChanged(object sender, SizeChangedEventArgs e)
- {
- AssignAnimation();
- }
-
- private void ScrollViewer_GotFocus(object sender, RoutedEventArgs e)
- {
- var scroller = (ScrollViewer)sender;
-
- object focusedElement;
- if (ApiInformationHelper.IsXamlRootAvailable && scroller.XamlRoot != null)
- {
- focusedElement = FocusManager.GetFocusedElement(scroller.XamlRoot)!;
- }
- else
- {
- focusedElement = FocusManager.GetFocusedElement()!;
- }
-
- // To prevent Popups (Flyouts...) from triggering the autoscroll, we check if the focused element has a valid parent.
- // Popups have no parents, whereas a normal Item would have the ListView as a parent.
- if (focusedElement is UIElement element && VisualTreeHelper.GetParent(element) != null)
- {
- FrameworkElement header = (FrameworkElement)HeaderElement;
-
- var point = element.TransformToVisual(scroller).TransformPoint(new Point(0, 0));
-
- if (point.Y < header.ActualHeight)
- {
- scroller.ChangeView(0, scroller.VerticalOffset - (header.ActualHeight - point.Y), 1, false);
- }
- }
- }
}
From c248fe9aab35e15e3d776c716ad2f11dc7b6a4ba Mon Sep 17 00:00:00 2001
From: michael-hawker <24302614+michael-hawker@users.noreply.github.com>
Date: Wed, 12 Jul 2023 12:08:27 -0700
Subject: [PATCH 3/9] Move FadeHeaderBehavior to new base class
Was much more aligned then it looked at first glance. Think it was first, so some things were just done locally in scope vs. cached from the other classes. At least now we can clean things up all together easily if we want to optimize things.
Main difference is some of the event hooks, so if they're not needed we may want flags/options for those in the base class to enable.
---
.../src/Headers/FadeHeaderBehavior.cs | 134 +++---------------
.../src/Headers/HeaderBehaviorBase.cs | 6 +-
.../src/Headers/QuickReturnHeaderBehavior.cs | 4 +-
.../src/Headers/StickyHeaderBehavior.cs | 4 +-
4 files changed, 27 insertions(+), 121 deletions(-)
diff --git a/components/Behaviors/src/Headers/FadeHeaderBehavior.cs b/components/Behaviors/src/Headers/FadeHeaderBehavior.cs
index a093d0d4..f320ac0e 100644
--- a/components/Behaviors/src/Headers/FadeHeaderBehavior.cs
+++ b/components/Behaviors/src/Headers/FadeHeaderBehavior.cs
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using CommunityToolkit.WinUI.Animations.Expressions;
+using CommunityToolkit.WinUI.Behaviors.Internal;
#if WINUI3
using Microsoft.UI.Composition;
@@ -22,134 +23,35 @@ namespace CommunityToolkit.WinUI.Behaviors;
///
/// Microsoft.Xaml.Interactivity.Behavior{Windows.UI.Xaml.UIElement}
///
-public class FadeHeaderBehavior : BehaviorBase
+public class FadeHeaderBehavior : HeaderBehaviorBase
{
- ///
- /// Attaches the behavior to the associated object.
- ///
- ///
- /// true if attaching succeeded; otherwise false.
- ///
- protected override bool Initialize()
+ ///
+ protected override bool AssignAnimation()
{
- var result = AssignFadeAnimation();
- return result;
- }
-
- ///
- /// Detaches the behavior from the associated object.
- ///
- ///
- /// true if detaching succeeded; otherwise false.
- ///
- protected override bool Uninitialize()
- {
- RemoveFadeAnimation();
- return true;
- }
-
- ///
- /// If any of the properties are changed then the animation is automatically started depending on the AutomaticallyStart property.
- ///
- /// The dependency object.
- /// The instance containing the event data.
- private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
- {
- var b = d as FadeHeaderBehavior;
- b?.AssignFadeAnimation();
- }
-
- ///
- /// The UIElement that will be faded.
- ///
- public static readonly DependencyProperty HeaderElementProperty = DependencyProperty.Register(
- nameof(HeaderElement), typeof(UIElement), typeof(FadeHeaderBehavior), new PropertyMetadata(null, PropertyChangedCallback));
-
- ///
- /// Gets or sets the target element for the Fading behavior.
- ///
- ///
- /// Set this using the header of a ListView or GridView. You can use the entire root of the header or an element within the header.
- ///
- /// Using this example Header:
- ///
- ///
- ///
- ///
- ///
- /// The behavior would be implemented like this
- ///
- ///
- public UIElement HeaderElement
- {
- get { return (UIElement)GetValue(HeaderElementProperty); }
- set { SetValue(HeaderElementProperty, value); }
- }
-
- ///
- /// Uses Composition API to get the UIElement and sets an ExpressionAnimation
- /// The ExpressionAnimation uses the height of the UIElement to calculate an opacity value
- /// for the Header as it is scrolling off-screen. The opacity reaches 0 when the Header
- /// is entirely scrolled off.
- ///
- /// true if the assignment was successful; otherwise, false.
- private bool AssignFadeAnimation()
- {
- if (AssociatedObject == null)
+ if (base.AssignAnimation())
{
- return false;
- }
+ // Get the ScrollViewer's ManipulationPropertySet
+ var scrollPropSet = _scrollProperties!.GetSpecializedReference();
- var scroller = AssociatedObject as ScrollViewer ?? AssociatedObject.FindDescendant();
- if (scroller == null)
- {
- return false;
- }
+ // Use the ScrollViewer's Y offset and the header's height to calculate the opacity percentage. Clamp it between 0% and 100%
+ var headerHeight = (float)HeaderElement.RenderSize.Height;
+ var opacityExpression = ExpressionFunctions.Clamp(1 - (-scrollPropSet.Translation.Y / headerHeight), 0, 1);
- var listView = AssociatedObject as ListViewBase ?? AssociatedObject.FindDescendant();
+ // Begin animating
+ _headerVisual?.StartAnimation("Opacity", opacityExpression);
- if (listView != null && listView.ItemsPanelRoot != null)
- {
- Canvas.SetZIndex(listView.ItemsPanelRoot, -1);
- }
-
- // Implicit operation: Find the Header object of the control if it uses ListViewBase
- if (HeaderElement == null && listView != null)
- {
- HeaderElement = (listView.Header as UIElement)!;
+ return true;
}
- // If no header is set or detected, return.
- if (HeaderElement == null || HeaderElement.RenderSize.Height == 0d)
- {
- return false;
- }
-
- // Get the ScrollViewer's ManipulationPropertySet
- var scrollViewerManipulationPropSet = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(scroller);
- var scrollPropSet = scrollViewerManipulationPropSet.GetSpecializedReference();
-
- // Use the ScrollViewer's Y offset and the header's height to calculate the opacity percentage. Clamp it between 0% and 100%
- var headerHeight = (float)HeaderElement.RenderSize.Height;
- var opacityExpression = ExpressionFunctions.Clamp(1 - (-scrollPropSet.Translation.Y / headerHeight), 0, 1);
-
- // Begin animating
- var targetElement = ElementCompositionPreview.GetElementVisual(HeaderElement);
- targetElement.StartAnimation("Opacity", opacityExpression);
-
- return true;
+ return false;
}
- ///
- /// Remove the opacity animation from the UIElement.
- ///
- private void RemoveFadeAnimation()
+ protected override void StopAnimation()
{
- if (HeaderElement != null)
+ if (_headerVisual != null)
{
- var targetElement = ElementCompositionPreview.GetElementVisual(HeaderElement);
- targetElement.StopAnimation("Opacity");
- targetElement.Opacity = 1.0f;
+ _headerVisual.StopAnimation("Opacity");
+ _headerVisual.Opacity = 1.0f;
}
}
}
diff --git a/components/Behaviors/src/Headers/HeaderBehaviorBase.cs b/components/Behaviors/src/Headers/HeaderBehaviorBase.cs
index 717f41dc..06d0cf7a 100644
--- a/components/Behaviors/src/Headers/HeaderBehaviorBase.cs
+++ b/components/Behaviors/src/Headers/HeaderBehaviorBase.cs
@@ -80,8 +80,11 @@ protected override bool Uninitialize()
}
///
- /// Uses Composition API to get the UIElement and sets an ExpressionAnimation
+ /// Uses Composition API to get the UIElement and sets an ExpressionAnimation.
///
+ ///
+ /// If this method returns true, you should have access to all protected fields with assigned components to use.
+ ///
/// true if the assignment was successful; otherwise, false.
protected virtual bool AssignAnimation()
{
@@ -143,6 +146,7 @@ protected virtual bool AssignAnimation()
return false;
}
+ // TODO: Not sure if we need to provide an option to turn these events off, as FadeHeaderBehavior didn't use these two, unlike QuickReturn/Sticky did...
headerElement.SizeChanged -= ScrollHeader_SizeChanged;
headerElement.SizeChanged += ScrollHeader_SizeChanged;
diff --git a/components/Behaviors/src/Headers/QuickReturnHeaderBehavior.cs b/components/Behaviors/src/Headers/QuickReturnHeaderBehavior.cs
index a1960b0f..117169ac 100644
--- a/components/Behaviors/src/Headers/QuickReturnHeaderBehavior.cs
+++ b/components/Behaviors/src/Headers/QuickReturnHeaderBehavior.cs
@@ -63,12 +63,12 @@ protected override bool AssignAnimation()
///
protected override void StopAnimation()
{
- _headerVisual?.StopAnimation("Offset.Y");
-
_animationProperties?.InsertScalar("OffsetY", 0.0f);
if (_headerVisual != null)
{
+ _headerVisual.StopAnimation("Offset.Y");
+
var offset = _headerVisual.Offset;
offset.Y = 0.0f;
_headerVisual.Offset = offset;
diff --git a/components/Behaviors/src/Headers/StickyHeaderBehavior.cs b/components/Behaviors/src/Headers/StickyHeaderBehavior.cs
index fbd2f2ef..c2d634a0 100644
--- a/components/Behaviors/src/Headers/StickyHeaderBehavior.cs
+++ b/components/Behaviors/src/Headers/StickyHeaderBehavior.cs
@@ -58,12 +58,12 @@ protected override bool AssignAnimation()
///
protected override void StopAnimation()
{
- _headerVisual?.StopAnimation("Offset.Y");
-
_animationProperties?.InsertScalar("OffsetY", 0.0f);
if (_headerVisual != null)
{
+ _headerVisual.StopAnimation("Offset.Y");
+
var offset = _headerVisual.Offset;
offset.Y = 0.0f;
_headerVisual.Offset = offset;
From 91a60e5fae57bde40828ba660fee3cea0e5fed3a Mon Sep 17 00:00:00 2001
From: michael-hawker <24302614+michael-hawker@users.noreply.github.com>
Date: Wed, 12 Jul 2023 14:20:53 -0700
Subject: [PATCH 4/9] Make HeaderElement a FrameworkElement as we pretty much
needed it like that everywhere anyway...
---
.../src/Headers/HeaderBehaviorBase.cs | 27 +++++++++----------
.../src/Headers/QuickReturnHeaderBehavior.cs | 3 +--
2 files changed, 13 insertions(+), 17 deletions(-)
diff --git a/components/Behaviors/src/Headers/HeaderBehaviorBase.cs b/components/Behaviors/src/Headers/HeaderBehaviorBase.cs
index 06d0cf7a..2c8be487 100644
--- a/components/Behaviors/src/Headers/HeaderBehaviorBase.cs
+++ b/components/Behaviors/src/Headers/HeaderBehaviorBase.cs
@@ -30,9 +30,9 @@ public abstract class HeaderBehaviorBase : BehaviorBase
///
/// Set this using the header of a ListView or GridView.
///
- public UIElement HeaderElement
+ public FrameworkElement HeaderElement
{
- get { return (UIElement)GetValue(HeaderElementProperty); }
+ get { return (FrameworkElement)GetValue(HeaderElementProperty); }
set { SetValue(HeaderElementProperty, value); }
}
@@ -40,7 +40,7 @@ public UIElement HeaderElement
/// Defines the Dependency Property for the property.
///
public static readonly DependencyProperty HeaderElementProperty = DependencyProperty.Register(
- nameof(HeaderElement), typeof(UIElement), typeof(HeaderBehaviorBase), new PropertyMetadata(null, PropertyChangedCallback));
+ nameof(HeaderElement), typeof(FrameworkElement), typeof(HeaderBehaviorBase), new PropertyMetadata(null, PropertyChangedCallback));
///
/// If any of the properties are changed then the animation is automatically started.
@@ -127,18 +127,17 @@ protected virtual bool AssignAnimation()
// Implicit operation: Find the Header object of the control if it uses ListViewBase
if (HeaderElement == null && listView != null)
{
- HeaderElement = (listView.Header as UIElement)!;
+ HeaderElement = (listView.Header as FrameworkElement)!;
}
- var headerElement = HeaderElement as FrameworkElement;
- if (headerElement == null || headerElement.RenderSize.Height == 0)
+ if (HeaderElement == null || HeaderElement.RenderSize.Height == 0)
{
return false;
}
if (_headerVisual == null)
{
- _headerVisual = ElementCompositionPreview.GetElementVisual(headerElement);
+ _headerVisual = ElementCompositionPreview.GetElementVisual(HeaderElement);
}
if (_headerVisual == null)
@@ -147,8 +146,8 @@ protected virtual bool AssignAnimation()
}
// TODO: Not sure if we need to provide an option to turn these events off, as FadeHeaderBehavior didn't use these two, unlike QuickReturn/Sticky did...
- headerElement.SizeChanged -= ScrollHeader_SizeChanged;
- headerElement.SizeChanged += ScrollHeader_SizeChanged;
+ HeaderElement.SizeChanged -= ScrollHeader_SizeChanged;
+ HeaderElement.SizeChanged += ScrollHeader_SizeChanged;
_scrollViewer.GotFocus -= ScrollViewer_GotFocus;
_scrollViewer.GotFocus += ScrollViewer_GotFocus;
@@ -178,9 +177,9 @@ protected virtual void RemoveAnimation()
_scrollViewer.GotFocus -= ScrollViewer_GotFocus;
}
- if (HeaderElement is FrameworkElement element)
+ if (HeaderElement != null)
{
- element.SizeChanged -= ScrollHeader_SizeChanged;
+ HeaderElement.SizeChanged -= ScrollHeader_SizeChanged;
}
StopAnimation();
@@ -209,13 +208,11 @@ private void ScrollViewer_GotFocus(object sender, RoutedEventArgs e)
// Popups have no parents, whereas a normal Item would have the ListView as a parent.
if (focusedElement is UIElement element && VisualTreeHelper.GetParent(element) != null)
{
- FrameworkElement header = (FrameworkElement)HeaderElement;
-
var point = element.TransformToVisual(scroller).TransformPoint(new Point(0, 0));
- if (point.Y < header.ActualHeight)
+ if (point.Y < HeaderElement.ActualHeight)
{
- scroller.ChangeView(0, scroller.VerticalOffset - (header.ActualHeight - point.Y), 1, false);
+ scroller.ChangeView(0, scroller.VerticalOffset - (HeaderElement.ActualHeight - point.Y), 1, false);
}
}
}
diff --git a/components/Behaviors/src/Headers/QuickReturnHeaderBehavior.cs b/components/Behaviors/src/Headers/QuickReturnHeaderBehavior.cs
index 117169ac..5e2561cc 100644
--- a/components/Behaviors/src/Headers/QuickReturnHeaderBehavior.cs
+++ b/components/Behaviors/src/Headers/QuickReturnHeaderBehavior.cs
@@ -90,8 +90,7 @@ private void ScrollViewer_ViewChanged(object? sender, ScrollViewerViewChangedEve
{
if (_animationProperties != null && _scrollViewer != null)
{
- FrameworkElement header = (FrameworkElement)HeaderElement;
- var headerHeight = header.ActualHeight;
+ var headerHeight = HeaderElement.ActualHeight;
if (_headerPosition + headerHeight < _scrollViewer.VerticalOffset)
{
// scrolling down: move header down, so it is just above screen
From 7675469f0e679baa509d09750fbb9ff7f74a9bdf Mon Sep 17 00:00:00 2001
From: michael-hawker <24302614+michael-hawker@users.noreply.github.com>
Date: Wed, 12 Jul 2023 16:20:42 -0700
Subject: [PATCH 5/9] Initial switch over to inversion of paradigm for Header
behaviors to enable more scenarios
---
.../Headers/FadeHeaderBehaviorSample.xaml | 6 +-
.../samples/Headers/HeaderBehaviors.md | 18 +++--
.../QuickReturnHeaderBehaviorSample.xaml | 6 +-
.../Headers/StickyHeaderBehaviorSample.xaml | 6 +-
.../src/Headers/FadeHeaderBehavior.cs | 2 +-
.../src/Headers/HeaderBehaviorBase.cs | 66 +++++--------------
.../src/Headers/QuickReturnHeaderBehavior.cs | 2 +-
7 files changed, 39 insertions(+), 67 deletions(-)
diff --git a/components/Behaviors/samples/Headers/FadeHeaderBehaviorSample.xaml b/components/Behaviors/samples/Headers/FadeHeaderBehaviorSample.xaml
index 157d378a..0aaac502 100644
--- a/components/Behaviors/samples/Headers/FadeHeaderBehaviorSample.xaml
+++ b/components/Behaviors/samples/Headers/FadeHeaderBehaviorSample.xaml
@@ -10,12 +10,12 @@
-
-
-
+
+
+
diff --git a/components/Behaviors/samples/Headers/HeaderBehaviors.md b/components/Behaviors/samples/Headers/HeaderBehaviors.md
index a698cc67..12311807 100644
--- a/components/Behaviors/samples/Headers/HeaderBehaviors.md
+++ b/components/Behaviors/samples/Headers/HeaderBehaviors.md
@@ -1,7 +1,7 @@
---
title: Header Behaviors
author: michael-hawker
-description: Behaviors modifying the headers of ListView based components when scrolling.
+description: Behaviors for modifying an element's movement/response when scrolling within a ScrollViewer.
keywords: Behaviors
dev_langs:
- csharp
@@ -12,22 +12,30 @@ issue-id: 0
icon: Assets/Behaviors.png
---
-The `FadeHeaderBehavior`, `QuickReturnHeaderBehavior`, and `StickyHeaderBehavior` apply behaviors to `ListView`, `GridView`, and `HeaderedTreeView` Headers.
+The `FadeHeaderBehavior`, `QuickReturnHeaderBehavior`, and `StickyHeaderBehavior` most commonly apply behaviors to `ListView`, `GridView`, and `HeaderedTreeView` elements in their `Header`. It can also be applied to any element contained at the top of a `ScrollViewer`.
+
+They use composition animations to allow the visual of an element of a scrolling viewport to be manipulated for various effects.
+
+To use the behavior, place it on the _element in the header to be manipulated_.
+
+> [!NOTE]
+> This is different in 8.x than it was in 7.x where the behavior was placed on the parent containing element (e.g. `ListView`).
+> This change was made to enable more scenarios for these components.
## FadeHeaderBehavior
-The FadeHeaderBehavior causes the Header of the scrolling collection to fade in and out as the user scrolls at the top of the collection.
+The FadeHeaderBehavior causes the element in the scrolling collection to fade in and out as the user scrolls at the top of the collection.
> [!Sample FadeHeaderBehaviorSample]
## QuickReturnBehavior
-The QuickReturnHeaderBehavior causes the Header of the scrolling collection to return back into view as soon as the user scrolls up even if they are not near the top of the collection.
+The QuickReturnHeaderBehavior causes the element in the scrolling collection to return back into view as soon as the user scrolls up even if they are not near the top of the collection.
> [!Sample QuickReturnHeaderBehaviorSample]
## StickyHeaderBehavior
-The StickyHeaderBehavior causes the Header of the scrolling collection to stay in view as the user scrolls up and down in the collection.
+The StickyHeaderBehavior causes the element in the scrolling collection to stay in view as the user scrolls up and down in the collection.
> [!Sample StickyHeaderBehaviorSample]
diff --git a/components/Behaviors/samples/Headers/QuickReturnHeaderBehaviorSample.xaml b/components/Behaviors/samples/Headers/QuickReturnHeaderBehaviorSample.xaml
index ba494d7c..978a112b 100644
--- a/components/Behaviors/samples/Headers/QuickReturnHeaderBehaviorSample.xaml
+++ b/components/Behaviors/samples/Headers/QuickReturnHeaderBehaviorSample.xaml
@@ -10,12 +10,12 @@
-
-
-
+
+
+
diff --git a/components/Behaviors/samples/Headers/StickyHeaderBehaviorSample.xaml b/components/Behaviors/samples/Headers/StickyHeaderBehaviorSample.xaml
index da6c35b0..40a667e4 100644
--- a/components/Behaviors/samples/Headers/StickyHeaderBehaviorSample.xaml
+++ b/components/Behaviors/samples/Headers/StickyHeaderBehaviorSample.xaml
@@ -14,12 +14,12 @@
-
-
-
+
+
+
diff --git a/components/Behaviors/src/Headers/FadeHeaderBehavior.cs b/components/Behaviors/src/Headers/FadeHeaderBehavior.cs
index f320ac0e..a9ca7c34 100644
--- a/components/Behaviors/src/Headers/FadeHeaderBehavior.cs
+++ b/components/Behaviors/src/Headers/FadeHeaderBehavior.cs
@@ -34,7 +34,7 @@ protected override bool AssignAnimation()
var scrollPropSet = _scrollProperties!.GetSpecializedReference();
// Use the ScrollViewer's Y offset and the header's height to calculate the opacity percentage. Clamp it between 0% and 100%
- var headerHeight = (float)HeaderElement.RenderSize.Height;
+ var headerHeight = (float)AssociatedObject.RenderSize.Height;
var opacityExpression = ExpressionFunctions.Clamp(1 - (-scrollPropSet.Translation.Y / headerHeight), 0, 1);
// Begin animating
diff --git a/components/Behaviors/src/Headers/HeaderBehaviorBase.cs b/components/Behaviors/src/Headers/HeaderBehaviorBase.cs
index 2c8be487..8cc42021 100644
--- a/components/Behaviors/src/Headers/HeaderBehaviorBase.cs
+++ b/components/Behaviors/src/Headers/HeaderBehaviorBase.cs
@@ -24,37 +24,6 @@ public abstract class HeaderBehaviorBase : BehaviorBase
protected CompositionPropertySet? _animationProperties;
protected Visual? _headerVisual;
- ///
- /// Gets or sets the target element for the Header behavior to be manipulated within the viewport.
- ///
- ///
- /// Set this using the header of a ListView or GridView.
- ///
- public FrameworkElement HeaderElement
- {
- get { return (FrameworkElement)GetValue(HeaderElementProperty); }
- set { SetValue(HeaderElementProperty, value); }
- }
-
- ///
- /// Defines the Dependency Property for the property.
- ///
- public static readonly DependencyProperty HeaderElementProperty = DependencyProperty.Register(
- nameof(HeaderElement), typeof(FrameworkElement), typeof(HeaderBehaviorBase), new PropertyMetadata(null, PropertyChangedCallback));
-
- ///
- /// If any of the properties are changed then the animation is automatically started.
- ///
- /// The dependency object.
- /// The instance containing the event data.
- private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
- {
- if (d is HeaderBehaviorBase @base)
- {
- @base.AssignAnimation();
- }
- }
-
///
/// Attaches the behavior to the associated object.
///
@@ -95,10 +64,10 @@ protected virtual bool AssignAnimation()
return false;
}
- // TODO: What if we attach to the 'header element' and look up for the ScrollViewer?
if (_scrollViewer == null)
{
- _scrollViewer = AssociatedObject as ScrollViewer ?? AssociatedObject.FindDescendant();
+ // TODO: We probably want checks which provide better guidance if we detect we're not attached correctly?
+ _scrollViewer = AssociatedObject.FindAscendant();
}
if (_scrollViewer == null)
@@ -106,12 +75,12 @@ protected virtual bool AssignAnimation()
return false;
}
- var listView = AssociatedObject as ListViewBase ?? AssociatedObject.FindDescendant();
+ var itemsControl = AssociatedObject.FindAscendant();
- // TODO: Is this required?
- if (listView != null && listView.ItemsPanelRoot != null)
+ if (itemsControl != null && itemsControl.ItemsPanelRoot != null)
{
- Canvas.SetZIndex(listView.ItemsPanelRoot, -1);
+ // This appears to be important to force the items within the ScrollViewer of an ItemsControl behind our header element.
+ Canvas.SetZIndex(itemsControl.ItemsPanelRoot, -1);
}
if (_scrollProperties == null)
@@ -124,20 +93,15 @@ protected virtual bool AssignAnimation()
return false;
}
- // Implicit operation: Find the Header object of the control if it uses ListViewBase
- if (HeaderElement == null && listView != null)
- {
- HeaderElement = (listView.Header as FrameworkElement)!;
- }
-
- if (HeaderElement == null || HeaderElement.RenderSize.Height == 0)
+ // Implicit operation: Double-check that we have an element associated with us (we should) and that it has size
+ if (AssociatedObject == null || AssociatedObject.RenderSize.Height == 0)
{
return false;
}
if (_headerVisual == null)
{
- _headerVisual = ElementCompositionPreview.GetElementVisual(HeaderElement);
+ _headerVisual = ElementCompositionPreview.GetElementVisual(AssociatedObject);
}
if (_headerVisual == null)
@@ -146,8 +110,8 @@ protected virtual bool AssignAnimation()
}
// TODO: Not sure if we need to provide an option to turn these events off, as FadeHeaderBehavior didn't use these two, unlike QuickReturn/Sticky did...
- HeaderElement.SizeChanged -= ScrollHeader_SizeChanged;
- HeaderElement.SizeChanged += ScrollHeader_SizeChanged;
+ AssociatedObject.SizeChanged -= ScrollHeader_SizeChanged;
+ AssociatedObject.SizeChanged += ScrollHeader_SizeChanged;
_scrollViewer.GotFocus -= ScrollViewer_GotFocus;
_scrollViewer.GotFocus += ScrollViewer_GotFocus;
@@ -177,9 +141,9 @@ protected virtual void RemoveAnimation()
_scrollViewer.GotFocus -= ScrollViewer_GotFocus;
}
- if (HeaderElement != null)
+ if (AssociatedObject != null)
{
- HeaderElement.SizeChanged -= ScrollHeader_SizeChanged;
+ AssociatedObject.SizeChanged -= ScrollHeader_SizeChanged;
}
StopAnimation();
@@ -210,9 +174,9 @@ private void ScrollViewer_GotFocus(object sender, RoutedEventArgs e)
{
var point = element.TransformToVisual(scroller).TransformPoint(new Point(0, 0));
- if (point.Y < HeaderElement.ActualHeight)
+ if (point.Y < AssociatedObject.ActualHeight)
{
- scroller.ChangeView(0, scroller.VerticalOffset - (HeaderElement.ActualHeight - point.Y), 1, false);
+ scroller.ChangeView(0, scroller.VerticalOffset - (AssociatedObject.ActualHeight - point.Y), 1, false);
}
}
}
diff --git a/components/Behaviors/src/Headers/QuickReturnHeaderBehavior.cs b/components/Behaviors/src/Headers/QuickReturnHeaderBehavior.cs
index 5e2561cc..ffaf3cf5 100644
--- a/components/Behaviors/src/Headers/QuickReturnHeaderBehavior.cs
+++ b/components/Behaviors/src/Headers/QuickReturnHeaderBehavior.cs
@@ -90,7 +90,7 @@ private void ScrollViewer_ViewChanged(object? sender, ScrollViewerViewChangedEve
{
if (_animationProperties != null && _scrollViewer != null)
{
- var headerHeight = HeaderElement.ActualHeight;
+ var headerHeight = AssociatedObject.ActualHeight;
if (_headerPosition + headerHeight < _scrollViewer.VerticalOffset)
{
// scrolling down: move header down, so it is just above screen
From 9d7e8735f6805b4ccc1490d452bc6afacbe3d416 Mon Sep 17 00:00:00 2001
From: michael-hawker <24302614+michael-hawker@users.noreply.github.com>
Date: Wed, 12 Jul 2023 17:23:47 -0700
Subject: [PATCH 6/9] Clean-up base check for AssociatedObject and add two new
samples for new scenarios enabled by changes
Show a HeaderedItemsControl with a StickyHeader
Show a QuickReturn of an element that's at the top of a ScrollViewer (doesn't load properly on first load though??)
---
.../samples/Headers/HeaderBehaviors.md | 10 ++-
.../QuickReturnScrollViewerSample.xaml | 27 ++++++++
.../QuickReturnScrollViewerSample.xaml.cs | 19 ++++++
.../StickyHeaderItemsControlSample.xaml | 61 +++++++++++++++++++
.../StickyHeaderItemsControlSample.xaml.cs | 20 ++++++
.../src/Headers/HeaderBehaviorBase.cs | 18 +++---
6 files changed, 147 insertions(+), 8 deletions(-)
create mode 100644 components/Behaviors/samples/Headers/QuickReturnScrollViewerSample.xaml
create mode 100644 components/Behaviors/samples/Headers/QuickReturnScrollViewerSample.xaml.cs
create mode 100644 components/Behaviors/samples/Headers/StickyHeaderItemsControlSample.xaml
create mode 100644 components/Behaviors/samples/Headers/StickyHeaderItemsControlSample.xaml.cs
diff --git a/components/Behaviors/samples/Headers/HeaderBehaviors.md b/components/Behaviors/samples/Headers/HeaderBehaviors.md
index 12311807..c607f541 100644
--- a/components/Behaviors/samples/Headers/HeaderBehaviors.md
+++ b/components/Behaviors/samples/Headers/HeaderBehaviors.md
@@ -12,7 +12,7 @@ issue-id: 0
icon: Assets/Behaviors.png
---
-The `FadeHeaderBehavior`, `QuickReturnHeaderBehavior`, and `StickyHeaderBehavior` most commonly apply behaviors to `ListView`, `GridView`, and `HeaderedTreeView` elements in their `Header`. It can also be applied to any element contained at the top of a `ScrollViewer`.
+The `FadeHeaderBehavior`, `QuickReturnHeaderBehavior`, and `StickyHeaderBehavior` most commonly apply behaviors to `ListView`, `GridView`, `HeaderedItemsControl`, and `HeaderedTreeView` elements in their `Header`. It can also be applied to any element contained at the top of a `ScrollViewer`.
They use composition animations to allow the visual of an element of a scrolling viewport to be manipulated for various effects.
@@ -34,8 +34,16 @@ The QuickReturnHeaderBehavior causes the element in the scrolling collection to
> [!Sample QuickReturnHeaderBehaviorSample]
+It can also be used to have content quickly re-appear in any `ScrollViewer`:
+
+> [!Sample QuickReturnScrollViewerSample]
+
## StickyHeaderBehavior
The StickyHeaderBehavior causes the element in the scrolling collection to stay in view as the user scrolls up and down in the collection.
> [!Sample StickyHeaderBehaviorSample]
+
+Or similarly, it can be used with a `HeaderedItemsControl` to maintain context at the top:
+
+> [!Sample StickyHeaderItemsControlSample]
diff --git a/components/Behaviors/samples/Headers/QuickReturnScrollViewerSample.xaml b/components/Behaviors/samples/Headers/QuickReturnScrollViewerSample.xaml
new file mode 100644
index 00000000..a9c2fcea
--- /dev/null
+++ b/components/Behaviors/samples/Headers/QuickReturnScrollViewerSample.xaml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/Behaviors/samples/Headers/QuickReturnScrollViewerSample.xaml.cs b/components/Behaviors/samples/Headers/QuickReturnScrollViewerSample.xaml.cs
new file mode 100644
index 00000000..6d6f7427
--- /dev/null
+++ b/components/Behaviors/samples/Headers/QuickReturnScrollViewerSample.xaml.cs
@@ -0,0 +1,19 @@
+// 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.Behaviors;
+
+namespace BehaviorsExperiment.Samples;
+
+///
+/// An empty page that can be used on its own or navigated to within a Frame.
+///
+[ToolkitSample(id: nameof(QuickReturnScrollViewerSample), $"{nameof(QuickReturnHeaderBehavior)} in a ScrollViewer", description: $"A sample for showing how to use the {nameof(QuickReturnHeaderBehavior)} in a ScrollViewer.")]
+public sealed partial class QuickReturnScrollViewerSample : Page
+{
+ public QuickReturnScrollViewerSample()
+ {
+ this.InitializeComponent();
+ }
+}
diff --git a/components/Behaviors/samples/Headers/StickyHeaderItemsControlSample.xaml b/components/Behaviors/samples/Headers/StickyHeaderItemsControlSample.xaml
new file mode 100644
index 00000000..d28982f3
--- /dev/null
+++ b/components/Behaviors/samples/Headers/StickyHeaderItemsControlSample.xaml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ One
+ Two
+ Three
+ Four
+ Five
+ Six
+ Seven
+ Eight
+ Nine
+ Ten
+ Eleven
+ Twelve
+ Thirteen
+ Fourteen
+ Fifteen
+ Sixteen
+ Seventeen
+ Eighteen
+ Nineteen
+ Twenty
+
+
+
+
diff --git a/components/Behaviors/samples/Headers/StickyHeaderItemsControlSample.xaml.cs b/components/Behaviors/samples/Headers/StickyHeaderItemsControlSample.xaml.cs
new file mode 100644
index 00000000..3ed9ea45
--- /dev/null
+++ b/components/Behaviors/samples/Headers/StickyHeaderItemsControlSample.xaml.cs
@@ -0,0 +1,20 @@
+// 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.Behaviors;
+using CommunityToolkit.WinUI.Controls;
+
+namespace BehaviorsExperiment.Samples;
+
+///
+/// An empty page that can be used on its own or navigated to within a Frame.
+///
+[ToolkitSample(id: nameof(StickyHeaderItemsControlSample), $"{nameof(StickyHeaderBehavior)} with {nameof(HeaderedItemsControl)}", description: $"A sample for showing how to use the {nameof(StickyHeaderBehavior)} with a {nameof(HeaderedItemsControl)}.")]
+public sealed partial class StickyHeaderItemsControlSample : Page
+{
+ public StickyHeaderItemsControlSample()
+ {
+ this.InitializeComponent();
+ }
+}
diff --git a/components/Behaviors/src/Headers/HeaderBehaviorBase.cs b/components/Behaviors/src/Headers/HeaderBehaviorBase.cs
index 8cc42021..126223ac 100644
--- a/components/Behaviors/src/Headers/HeaderBehaviorBase.cs
+++ b/components/Behaviors/src/Headers/HeaderBehaviorBase.cs
@@ -19,6 +19,9 @@ namespace CommunityToolkit.WinUI.Behaviors.Internal;
///
public abstract class HeaderBehaviorBase : BehaviorBase
{
+ // From Doc: https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.canvas.zindex
+ private const int CanvasZIndexMax = 1_000_000;
+
protected ScrollViewer? _scrollViewer;
protected CompositionPropertySet? _scrollProperties;
protected CompositionPropertySet? _animationProperties;
@@ -59,7 +62,8 @@ protected virtual bool AssignAnimation()
{
StopAnimation();
- if (AssociatedObject == null)
+ // Double-check that we have an element associated with us (we should) and that it has size
+ if (AssociatedObject == null || AssociatedObject.RenderSize.Height == 0)
{
return false;
}
@@ -82,19 +86,19 @@ protected virtual bool AssignAnimation()
// This appears to be important to force the items within the ScrollViewer of an ItemsControl behind our header element.
Canvas.SetZIndex(itemsControl.ItemsPanelRoot, -1);
}
-
- if (_scrollProperties == null)
+ else
{
- _scrollProperties = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(_scrollViewer);
+ // If we're not part of a collection panel, then we're probably just in the ScrollViewer,
+ // And we should ensure our 'header' element is on top of any other content within the ScrollViewer.
+ Canvas.SetZIndex(AssociatedObject, CanvasZIndexMax);
}
if (_scrollProperties == null)
{
- return false;
+ _scrollProperties = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(_scrollViewer);
}
- // Implicit operation: Double-check that we have an element associated with us (we should) and that it has size
- if (AssociatedObject == null || AssociatedObject.RenderSize.Height == 0)
+ if (_scrollProperties == null)
{
return false;
}
From 603343625ffc97ba2d1a18610016978dd969dbbe Mon Sep 17 00:00:00 2001
From: michael-hawker <24302614+michael-hawker@users.noreply.github.com>
Date: Wed, 12 Jul 2023 17:38:29 -0700
Subject: [PATCH 7/9] Rev Behaviors Package Version
---
.../Behaviors/src/CommunityToolkit.WinUI.Behaviors.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/components/Behaviors/src/CommunityToolkit.WinUI.Behaviors.csproj b/components/Behaviors/src/CommunityToolkit.WinUI.Behaviors.csproj
index 930be82d..c00f146b 100644
--- a/components/Behaviors/src/CommunityToolkit.WinUI.Behaviors.csproj
+++ b/components/Behaviors/src/CommunityToolkit.WinUI.Behaviors.csproj
@@ -2,7 +2,7 @@
BehaviorsThis package contains Behaviors.
- 8.0.0-beta.2
+ 8.0.0-beta.3CommunityToolkit.WinUI.BehaviorsRns
From 99b83b2b4215ddad37a62f17ca5a9c4006a6e229 Mon Sep 17 00:00:00 2001
From: Niels Laute
Date: Fri, 14 Jul 2023 20:04:28 +0200
Subject: [PATCH 8/9] Minor design tweaks header behaviors samples (#142)
* Design tweaks
* Removing background theme brushes
---
.../samples/AutoSelectBehaviorSample.xaml | 4 +-
.../samples/FocusBehaviorButtonSample.xaml | 6 +-
.../Headers/FadeHeaderBehaviorSample.xaml | 12 +--
.../QuickReturnHeaderBehaviorSample.xaml | 12 +--
.../QuickReturnScrollViewerSample.xaml | 14 +--
.../Headers/StickyHeaderBehaviorSample.xaml | 9 +-
.../StickyHeaderBehaviorSample.xaml.cs | 1 -
.../StickyHeaderItemsControlSample.xaml | 9 +-
.../samples/InvokeActionsSample.xaml | 4 +-
.../samples/KeyDownTriggerBehaviorSample.xaml | 5 +-
.../samples/StartAnimationActivitySample.xaml | 10 +-
.../samples/ViewportBehaviorSample.xaml | 99 +++++++++----------
12 files changed, 93 insertions(+), 92 deletions(-)
diff --git a/components/Behaviors/samples/AutoSelectBehaviorSample.xaml b/components/Behaviors/samples/AutoSelectBehaviorSample.xaml
index f681e837..731655fd 100644
--- a/components/Behaviors/samples/AutoSelectBehaviorSample.xaml
+++ b/components/Behaviors/samples/AutoSelectBehaviorSample.xaml
@@ -1,10 +1,10 @@
-
-
diff --git a/components/Behaviors/samples/FocusBehaviorButtonSample.xaml b/components/Behaviors/samples/FocusBehaviorButtonSample.xaml
index 1e57af3e..8872a365 100644
--- a/components/Behaviors/samples/FocusBehaviorButtonSample.xaml
+++ b/components/Behaviors/samples/FocusBehaviorButtonSample.xaml
@@ -1,4 +1,4 @@
-
+ Content="Button 2" />
diff --git a/components/Behaviors/samples/Headers/FadeHeaderBehaviorSample.xaml b/components/Behaviors/samples/Headers/FadeHeaderBehaviorSample.xaml
index 0aaac502..af2f4ae8 100644
--- a/components/Behaviors/samples/Headers/FadeHeaderBehaviorSample.xaml
+++ b/components/Behaviors/samples/Headers/FadeHeaderBehaviorSample.xaml
@@ -5,26 +5,26 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
-
+
+ Background="{ThemeResource AccentFillColorDefaultBrush}">
+ Foreground="{ThemeResource TextOnAccentFillColorPrimaryBrush}"
+ Text="Header" />
-
+
diff --git a/components/Behaviors/samples/Headers/QuickReturnHeaderBehaviorSample.xaml b/components/Behaviors/samples/Headers/QuickReturnHeaderBehaviorSample.xaml
index 978a112b..39f95e6f 100644
--- a/components/Behaviors/samples/Headers/QuickReturnHeaderBehaviorSample.xaml
+++ b/components/Behaviors/samples/Headers/QuickReturnHeaderBehaviorSample.xaml
@@ -5,27 +5,27 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
-
+
+ Background="{ThemeResource AccentFillColorDefaultBrush}">
+ Foreground="{ThemeResource TextOnAccentFillColorPrimaryBrush}"
+ Text="Header" />
+ Height="200">
diff --git a/components/Behaviors/samples/Headers/QuickReturnScrollViewerSample.xaml b/components/Behaviors/samples/Headers/QuickReturnScrollViewerSample.xaml
index a9c2fcea..5528ff53 100644
--- a/components/Behaviors/samples/Headers/QuickReturnScrollViewerSample.xaml
+++ b/components/Behaviors/samples/Headers/QuickReturnScrollViewerSample.xaml
@@ -5,20 +5,20 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
-
+
-
+
+
-
+
diff --git a/components/Behaviors/samples/Headers/StickyHeaderBehaviorSample.xaml b/components/Behaviors/samples/Headers/StickyHeaderBehaviorSample.xaml
index 40a667e4..61438908 100644
--- a/components/Behaviors/samples/Headers/StickyHeaderBehaviorSample.xaml
+++ b/components/Behaviors/samples/Headers/StickyHeaderBehaviorSample.xaml
@@ -1,4 +1,4 @@
-
+ Background="{ThemeResource AccentFillColorDefaultBrush}">
+ Foreground="{ThemeResource TextOnAccentFillColorPrimaryBrush}"
+ Text="Header" />
diff --git a/components/Behaviors/samples/Headers/StickyHeaderBehaviorSample.xaml.cs b/components/Behaviors/samples/Headers/StickyHeaderBehaviorSample.xaml.cs
index 7e041180..49c1ba53 100644
--- a/components/Behaviors/samples/Headers/StickyHeaderBehaviorSample.xaml.cs
+++ b/components/Behaviors/samples/Headers/StickyHeaderBehaviorSample.xaml.cs
@@ -20,7 +20,6 @@ public StickyHeaderBehaviorSample()
Items = GetData();
}
-
private ObservableCollection GetData()
{
var list = new ObservableCollection();
diff --git a/components/Behaviors/samples/Headers/StickyHeaderItemsControlSample.xaml b/components/Behaviors/samples/Headers/StickyHeaderItemsControlSample.xaml
index d28982f3..c594d072 100644
--- a/components/Behaviors/samples/Headers/StickyHeaderItemsControlSample.xaml
+++ b/components/Behaviors/samples/Headers/StickyHeaderItemsControlSample.xaml
@@ -8,21 +8,22 @@
xmlns:local="using:BehaviorsExperiment.Samples"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
- Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
-
+
+ Background="{ThemeResource AccentFillColorDefaultBrush}">
+ Foreground="{ThemeResource TextOnAccentFillColorPrimaryBrush}"
+ Text="Header" />
diff --git a/components/Behaviors/samples/InvokeActionsSample.xaml b/components/Behaviors/samples/InvokeActionsSample.xaml
index ac9cc08f..47f1e748 100644
--- a/components/Behaviors/samples/InvokeActionsSample.xaml
+++ b/components/Behaviors/samples/InvokeActionsSample.xaml
@@ -11,8 +11,8 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
-