Skip to content

Commit 69cd648

Browse files
committed
Fixed virtualization issues and handled segment size changing in Marquee
1 parent e4791c5 commit 69cd648

File tree

4 files changed

+74
-6
lines changed

4 files changed

+74
-6
lines changed

components/Marquee/samples/MarqueeSample.xaml

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
55
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
66
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
7-
xmlns:local="MarqueeExperiment.Samples"
7+
xmlns:local="using:MarqueeExperiment.Samples"
88
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
99
mc:Ignorable="d">
1010

@@ -14,12 +14,25 @@
1414
FontSize="18"
1515
RepeatBehavior="Forever"
1616
Speed="{x:Bind MQSpeed, Mode=OneWay}"
17-
Content="This is a demonstration of using templating to added non-text content to the Marquee control.">
17+
Content="{x:Bind Data}">
1818
<controls:Marquee.ContentTemplate>
19-
<DataTemplate x:DataType="x:String">
20-
<Button Content="{x:Bind}"/>
19+
<DataTemplate x:DataType="local:MarqueeSampleItems">
20+
<ItemsRepeater ItemsSource="{x:Bind Items}">
21+
<ItemsRepeater.Layout>
22+
<StackLayout Orientation="Horizontal" Spacing="8"/>
23+
</ItemsRepeater.Layout>
24+
<ItemsRepeater.ItemTemplate>
25+
<DataTemplate x:DataType="local:MarqueeSampleItem">
26+
<Border Width="100" Height="48" Background="{x:Bind Brush}">
27+
<TextBlock Text="{x:Bind Name}" />
28+
</Border>
29+
</DataTemplate>
30+
</ItemsRepeater.ItemTemplate>
31+
</ItemsRepeater>
2132
</DataTemplate>
2233
</controls:Marquee.ContentTemplate>
2334
</controls:Marquee>
35+
36+
<Button Content="Add something" Click="AddItem_Click"/>
2437
</StackPanel>
2538
</Page>

components/Marquee/samples/MarqueeSample.xaml.cs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
// See the LICENSE file in the project root for more information.
44

55
using CommunityToolkit.WinUI.Controls;
6+
using Windows.UI;
67

78
namespace MarqueeExperiment.Samples;
89

910
[ToolkitSample(id: nameof(MarqueeSample), "Marquee", description: "A control for scrolling content in a marquee fashion.")]
1011
[ToolkitSampleNumericOption("MQSpeed", initial: 96, min: 48, max: 196, step: 1, Title = "Speed")]
1112
[ToolkitSampleMultiChoiceOption("MQDirection", "Left", "Right", "Up", "Down", Title = "Marquee Direction")]
13+
[ToolkitSampleTextOption("MQText", "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.")]
1214
//[ToolkitSampleMultiChoiceOption("MarqueeRepeat", "Repeat", "Forever", "1x", "2x")]
1315
#if !HAS_UNO
1416
[ToolkitSampleMultiChoiceOption("MQBehavior", "Ticker", "Looping", "Bouncing", Title = "Marquee Behavior")]
@@ -20,6 +22,11 @@ public sealed partial class MarqueeSample : Page
2022
public MarqueeSample()
2123
{
2224
this.InitializeComponent();
25+
26+
for (int i = 0; i < 15; i++)
27+
{
28+
AddItem_Click(this, null);
29+
}
2330
}
2431

2532
private MarqueeBehavior ConvertStringToMarqueeBehavior(string str) => str switch
@@ -29,15 +36,38 @@ public MarqueeSample()
2936
#if !HAS_UNO
3037
"Bouncing" => MarqueeBehavior.Bouncing,
3138
#endif
32-
_ => throw new System.NotImplementedException(),
39+
_ => throw new NotImplementedException(),
3340
};
3441

42+
public MarqueeSampleItems Data = new();
43+
3544
private MarqueeDirection ConvertStringToMarqueeDirection(string str) => str switch
3645
{
3746
"Left" => MarqueeDirection.Left,
3847
"Up" => MarqueeDirection.Up,
3948
"Right" => MarqueeDirection.Right,
4049
"Down" => MarqueeDirection.Down,
41-
_ => throw new System.NotImplementedException(),
50+
_ => throw new NotImplementedException(),
4251
};
52+
53+
private void AddItem_Click(object sender, RoutedEventArgs? e)
54+
{
55+
Data.Items.Add(new MarqueeSampleItem()
56+
{
57+
Name = $"Item {Data.Items.Count + 1}",
58+
Brush = new SolidColorBrush(Color.FromArgb(255, (byte)Random.Shared.Next(256), (byte)Random.Shared.Next(256), (byte)Random.Shared.Next(256))),
59+
});
60+
}
61+
}
62+
63+
public class MarqueeSampleItems
64+
{
65+
public ObservableCollection<MarqueeSampleItem> Items { get; } = new();
66+
}
67+
68+
public record MarqueeSampleItem
69+
{
70+
public required string Name { get; set; }
71+
72+
public required Brush Brush { get; set; }
4373
}

components/Marquee/src/Marquee.Events.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,18 @@ private void Container_SizeChanged(object sender, SizeChangedEventArgs e)
7676
StartMarquee();
7777
}
7878

79+
private void Segment_SizeChanged(object sender, SizeChangedEventArgs e)
80+
{
81+
if (_segment1 is null)
82+
{
83+
return;
84+
}
85+
86+
// If the segment size changes, we need to update the storyboard,
87+
// and seek to the correct position to maintain a smooth animation.
88+
UpdateAnimation(true);
89+
}
90+
7991
private void StoryBoard_Completed(object? sender, object e)
8092
{
8193
StopMarquee(true);

components/Marquee/src/Marquee.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ protected override void OnApplyTemplate()
6767
_marqueeTransform = (TranslateTransform)GetTemplateChild(MarqueeTransformPartName);
6868

6969
_marqueeContainer.SizeChanged += Container_SizeChanged;
70+
_segment1.SizeChanged += Segment_SizeChanged;
7071

7172
// Swapping tabs in TabView caused errors where the control would unload and never reattach events.
7273
// Hotfix: Track the loaded event. This should be fine because the GC will handle detaching the Loaded
@@ -174,6 +175,7 @@ _segment1 is null ||
174175
double containerSize;
175176
double segmentSize;
176177
double value;
178+
DependencyProperty dp;
177179
string targetProperty;
178180

179181
if (IsDirectionHorizontal)
@@ -183,6 +185,7 @@ _segment1 is null ||
183185
containerSize = _marqueeContainer.ActualWidth;
184186
segmentSize = _segment1.ActualWidth;
185187
value = _marqueeTransform.X;
188+
dp = TranslateTransform.XProperty;
186189
targetProperty = "(TranslateTransform.X)";
187190
}
188191
else
@@ -192,6 +195,7 @@ _segment1 is null ||
192195
containerSize = _marqueeContainer.ActualHeight;
193196
segmentSize = _segment1.ActualHeight;
194197
value = _marqueeTransform.Y;
198+
dp = TranslateTransform.YProperty;
195199
targetProperty = "(TranslateTransform.Y)";
196200
}
197201

@@ -261,6 +265,15 @@ _segment1 is null ||
261265
double progress = Math.Abs(start - value) / distance;
262266
_marqueeStoryboard.Seek(TimeSpan.FromTicks((long)(duration.Ticks * progress)));
263267
}
268+
269+
// NOTE: Can this be optimized to remove or reduce the need for this callback?
270+
// Invalidate the segment measures when the transform changes.
271+
// This forces virtualized panels to re-measure the segments
272+
_marqueeTransform.RegisterPropertyChangedCallback(dp, (sender, dp) =>
273+
{
274+
_segment1.InvalidateMeasure();
275+
_segment2.InvalidateMeasure();
276+
});
264277

265278
return true;
266279
}

0 commit comments

Comments
 (0)