using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Shapes; namespace PCL; /// /// 轻量折叠栏:一行可点击标题 + 三角,点击切换其下内容区的显示。 /// 无卡片外观(无阴影/边框/背景),带高度折叠动画。 /// public class MyCollapseBar : StackPanel { public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(nameof(Title), typeof(string), typeof(MyCollapseBar), new PropertyMetadata("", (d, e) => ((MyCollapseBar)d)._titleBlock.Text = (string)e.NewValue)); private const double HeaderHeight = 30d; private readonly int _uuid = ModBase.GetUuid(); private readonly TextBlock _titleBlock; private readonly Path _triangle; private readonly StackPanel _contentPanel; private (MyCard card, bool useAnimation)? _parentCardState; public MyCollapseBar() { Orientation = Orientation.Vertical; ClipToBounds = true; _titleBlock = new TextBlock { FontSize = 14d, FontWeight = FontWeights.Bold, VerticalAlignment = VerticalAlignment.Center, HorizontalAlignment = HorizontalAlignment.Left, Margin = new Thickness(6d, 0d, 0d, 0d), IsHitTestVisible = false }; _titleBlock.SetResourceReference(TextBlock.ForegroundProperty, "ColorBrush1"); _triangle = new Path { HorizontalAlignment = HorizontalAlignment.Right, VerticalAlignment = VerticalAlignment.Center, Stretch = Stretch.Uniform, Height = 6d, Width = 10d, Margin = new Thickness(0d, 0d, 12d, 0d), IsHitTestVisible = false, Data = (Geometry)new GeometryConverter().ConvertFromString("M2,4 l-2,2 10,10 10,-10 -2,-2 -8,8 -8,-8 z"), RenderTransform = new RotateTransform(180d), RenderTransformOrigin = new Point(0.5d, 0.5d) }; _triangle.SetResourceReference(Shape.FillProperty, "ColorBrush1"); var header = new Grid { Height = HeaderHeight, Background = Brushes.Transparent, Cursor = Cursors.Hand }; header.Children.Add(_titleBlock); header.Children.Add(_triangle); header.MouseLeftButtonUp += (_, _) => IsCollapsed = !IsCollapsed; _contentPanel = new StackPanel { Margin = new Thickness(6d, 2d, 0d, 0d) }; Children.Add(header); Children.Add(_contentPanel); } /// 标题文本。 public string Title { get => (string)GetValue(TitleProperty); set => SetValue(TitleProperty, value); } /// 内容区,调用方向其 Children 添加要折叠的内容。 public StackPanel ContentPanel => _contentPanel; /// 开合状态实际改变时触发。 public event EventHandler? Toggled; /// 是否收起。 public bool IsCollapsed { get; set { if (field == value) return; field = value; var target = value ? 0d : 180d; if (IsLoaded) ModAnimation.AniStart( ModAnimation.AaRotateTransform(_triangle, target - ((RotateTransform)_triangle.RenderTransform).Angle, 250, ease: new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.ExtraStrong)), "MyCollapseBar " + _uuid, true); else ((RotateTransform)_triangle.RenderTransform).Angle = target; if (IsLoaded && ActualHeight > 0) { if (value) CollapseWithAnimation(); else ExpandWithAnimation(); } else { _contentPanel.Visibility = value ? Visibility.Collapsed : Visibility.Visible; } Toggled?.Invoke(this, EventArgs.Empty); } } private void CollapseWithAnimation() { ModAnimation.AniStop("MyCollapseBar Height " + _uuid); RestoreParentCardOnInterrupt(); SilenceParentCard(); var fullHeight = ActualHeight; Height = fullHeight; ModAnimation.AniStart(new List { ModAnimation.AaHeight(this, HeaderHeight - fullHeight, 200, ease: new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.ExtraStrong)), ModAnimation.AaCode(() => { _contentPanel.Visibility = Visibility.Collapsed; Height = double.NaN; RestoreParentCard(); }, after: true) }, "MyCollapseBar Height " + _uuid); } private void ExpandWithAnimation() { ModAnimation.AniStop("MyCollapseBar Height " + _uuid); RestoreParentCardOnInterrupt(); SilenceParentCard(); _contentPanel.Visibility = Visibility.Visible; Height = double.NaN; Measure(new Size(ActualWidth, double.PositiveInfinity)); var fullHeight = DesiredSize.Height; Height = HeaderHeight; ModAnimation.AniStart(new List { ModAnimation.AaHeight(this, fullHeight - HeaderHeight, 200, ease: new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.ExtraStrong)), ModAnimation.AaCode(() => { Height = double.NaN; RestoreParentCard(); }, after: true) }, "MyCollapseBar Height " + _uuid); } /// 若上一个动画被中断且它的 RestoreParentCard 未执行,则先恢复。 private void RestoreParentCardOnInterrupt() { if (_parentCardState is { } s) { s.card.UseAnimation = s.useAnimation; _parentCardState = null; } } private void SilenceParentCard() { if (_parentCardState is not null) return; var current = Parent as FrameworkElement; while (current is not null) { if (current is MyCard card) { _parentCardState = (card, card.UseAnimation); card.UseAnimation = false; return; } current = current.Parent as FrameworkElement; } } private void RestoreParentCard() { if (_parentCardState is { } s) { s.card.UseAnimation = s.useAnimation; _parentCardState = null; } } }