feat(Splitter): introduce SplitterDragBar for improved drag functionality

This commit is contained in:
chinboy
2026-05-09 22:40:38 +08:00
parent f58dd7ce00
commit 9edd9b3ebe
7 changed files with 278 additions and 278 deletions
+1 -4
View File
@@ -1,5 +1,4 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
@@ -119,13 +118,11 @@ public class Thumb : TemplatedControl
var ev = new VectorEventArgs
{
RoutedEvent = DragStartedEvent,
Vector = (Vector)_dragStartPoint.Value,
Vector = _dragStartPoint.Value,
};
PseudoClasses.Add(StdPseudoClass.Pressed);
e.PreventGestureRecognition();
RaiseEvent(ev);
}
@@ -0,0 +1,79 @@
using AtomUIThumb = AtomUI.Controls.Primitives.Thumb;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Media;
namespace AtomUI.Desktop.Controls;
internal class SplitterDragBar : AtomUIThumb
{
public static readonly StyledProperty<Orientation> OrientationProperty =
AvaloniaProperty.Register<SplitterDragBar, Orientation>(nameof(Orientation), Orientation.Vertical);
public static readonly StyledProperty<IBrush?> LineBrushProperty =
AvaloniaProperty.Register<SplitterDragBar, IBrush?>(nameof(LineBrush));
public static readonly StyledProperty<double> LineThicknessProperty =
AvaloniaProperty.Register<SplitterDragBar, double>(nameof(LineThickness));
public static readonly StyledProperty<bool> IsDragEnabledProperty =
AvaloniaProperty.Register<SplitterDragBar, bool>(nameof(IsDragEnabled), true);
public Orientation Orientation
{
get => GetValue(OrientationProperty);
set => SetValue(OrientationProperty, value);
}
public IBrush? LineBrush
{
get => GetValue(LineBrushProperty);
set => SetValue(LineBrushProperty, value);
}
public double LineThickness
{
get => GetValue(LineThicknessProperty);
set => SetValue(LineThicknessProperty, value);
}
public bool IsDragEnabled
{
get => GetValue(IsDragEnabledProperty);
set => SetValue(IsDragEnabledProperty, value);
}
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
if (!IsDragEnabled)
{
return;
}
base.OnPointerPressed(e);
}
protected override void OnPointerMoved(PointerEventArgs e)
{
if (!IsDragEnabled)
{
return;
}
base.OnPointerMoved(e);
}
protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
if (!IsDragEnabled)
{
return;
}
base.OnPointerReleased(e);
}
internal void SetDragging(bool isDragging)
{
PseudoClasses.Set(StdPseudoClass.Dragging, isDragging);
}
}
@@ -1,23 +1,21 @@
using AtomUI.Controls;
using AtomUI.Data;
using AtomUI.Desktop.Controls.DesignTokens;
using AtomUI.Icons.AntDesign;
using AtomUIThumb = AtomUI.Controls.Primitives.Thumb;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Media;
namespace AtomUI.Desktop.Controls;
[TemplatePart("PART_HandleLine", typeof(Border))]
[TemplatePart("PART_HandleGrip", typeof(Border))]
[TemplatePart("PART_DragBar", typeof(SplitterDragBar))]
[TemplatePart("PART_CollapsePrevButton", typeof(IconButton))]
[TemplatePart("PART_CollapseNextButton", typeof(IconButton))]
internal class SplitterHandle : AtomUIThumb
internal class SplitterHandle : TemplatedControl
{
public static readonly StyledProperty<Orientation> OrientationProperty =
AvaloniaProperty.Register<SplitterHandle, Orientation>(nameof(Orientation), Orientation.Vertical);
@@ -55,26 +53,8 @@ internal class SplitterHandle : AtomUIThumb
set => SetValue(IsDragEnabledProperty, value);
}
public int HandleIndex { get; set; }
private IconButton? _collapsePrevButton;
private IconButton? _collapseNextButton;
private bool _isPointerOver;
private bool _isPointerPressed;
private HoverSide _hoverSide = HoverSide.None;
private Point _lastPointerPosition;
private bool _hasPointerPosition;
private bool _isDragging;
internal bool IsPointerInDragZone { get; private set; }
private enum HoverSide
{
None,
Previous,
Next
}
internal bool IsPreviousCollapsible { get; set; }
internal bool IsNextCollapsible { get; set; }
internal bool IsPreviousCollapsed { get; set; }
@@ -86,37 +66,53 @@ internal class SplitterHandle : AtomUIThumb
internal bool NextButtonControlsPrevious { get; set; }
internal IIconTemplate? PreviousIconTemplate { get; set; }
internal IIconTemplate? NextIconTemplate { get; set; }
internal SplitterCollapsibleIconDisplayMode PreviousShowMode { get; set; } =
SplitterCollapsibleIconDisplayMode.Hover;
internal SplitterCollapsibleIconDisplayMode NextShowMode { get; set; } =
SplitterCollapsibleIconDisplayMode.Hover;
internal bool IsPointerInDragZone => _dragBar?.IsPointerOver == true;
public event EventHandler<VectorEventArgs>? DragStarted;
public event EventHandler<VectorEventArgs>? DragDelta;
public event EventHandler<VectorEventArgs>? DragCompleted;
public event EventHandler? CollapsePreviousRequested;
public event EventHandler? CollapseNextRequested;
private SplitterDragBar? _dragBar;
private IconButton? _collapsePrevButton;
private IconButton? _collapseNextButton;
private HoverSide _hoverSide = HoverSide.None;
private enum HoverSide
{
None,
Previous,
Next
}
static SplitterHandle()
{
OrientationProperty.Changed.AddClassHandler<SplitterHandle>((x, _) => x.OnOrientationChanged());
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
if (_collapsePrevButton != null)
{
_collapsePrevButton.Click -= HandleCollapsePrevClick;
}
if (_collapseNextButton != null)
{
_collapseNextButton.Click -= HandleCollapseNextClick;
}
DetachDragBar();
DetachCollapseButton(ref _collapsePrevButton, HandleCollapsePrevClick);
DetachCollapseButton(ref _collapseNextButton, HandleCollapseNextClick);
_dragBar = e.NameScope.Find<SplitterDragBar>("PART_DragBar");
_collapsePrevButton = e.NameScope.Find<IconButton>("PART_CollapsePrevButton");
_collapseNextButton = e.NameScope.Find<IconButton>("PART_CollapseNextButton");
if (_collapsePrevButton != null)
{
_collapsePrevButton.Click += HandleCollapsePrevClick;
}
if (_collapseNextButton != null)
{
_collapseNextButton.Click += HandleCollapseNextClick;
}
AttachDragBar();
AttachCollapseButton(_collapsePrevButton, HandleCollapsePrevClick);
AttachCollapseButton(_collapseNextButton, HandleCollapseNextClick);
UpdateCollapseButtons();
}
@@ -124,182 +120,149 @@ internal class SplitterHandle : AtomUIThumb
protected override void OnPointerEntered(PointerEventArgs e)
{
base.OnPointerEntered(e);
_isPointerOver = true;
CachePointerPosition(e);
UpdateHoverSide(e);
UpdateCursor(_lastPointerPosition);
UpdateCollapseButtons();
}
protected override void OnPointerMoved(PointerEventArgs e)
{
base.OnPointerMoved(e);
UpdateHoverSide(e);
}
protected override void OnPointerExited(PointerEventArgs e)
{
base.OnPointerExited(e);
_isPointerOver = false;
if (!_isPointerPressed && !_isDragging)
{
_hoverSide = HoverSide.None;
}
_hasPointerPosition = false;
IsPointerInDragZone = false;
if (!_isDragging)
{
Cursor = Cursor.Default;
}
_hoverSide = HoverSide.None;
UpdateCollapseButtons();
}
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
if (!IsDragEnabled)
{
return;
}
CachePointerPosition(e);
if (!IsPointerInDragArea(e))
{
UpdateCursor(_lastPointerPosition);
return;
}
_isPointerPressed = true;
UpdateCursor(_lastPointerPosition);
base.OnPointerPressed(e);
}
protected override void OnPointerMoved(PointerEventArgs e)
{
CachePointerPosition(e);
UpdateHoverSide(e);
UpdateCursor(_lastPointerPosition);
if (IsDragEnabled)
{
base.OnPointerMoved(e);
}
}
protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
if (!IsDragEnabled)
{
return;
}
CachePointerPosition(e);
UpdateCursor(_lastPointerPosition);
base.OnPointerReleased(e);
_isPointerPressed = false;
if (!_isPointerOver)
{
_hoverSide = HoverSide.None;
}
UpdateCollapseButtons();
}
protected override void OnPointerCaptureLost(PointerCaptureLostEventArgs e)
{
if (!IsDragEnabled)
{
return;
}
_hasPointerPosition = false;
IsPointerInDragZone = false;
_isPointerPressed = false;
Cursor = Cursor.Default;
base.OnPointerCaptureLost(e);
if (!_isPointerOver)
{
_hoverSide = HoverSide.None;
}
UpdateCollapseButtons();
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == IsDragEnabledProperty || change.Property == OrientationProperty)
{
if (_isPointerOver && _hasPointerPosition)
{
UpdateCursor(_lastPointerPosition);
}
else if (!IsDragEnabled)
{
Cursor = Cursor.Default;
}
}
}
private void HandleCollapsePrevClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
if (PreviousButtonControlsNext)
{
CollapseNextRequested?.Invoke(this, EventArgs.Empty);
return;
}
CollapsePreviousRequested?.Invoke(this, EventArgs.Empty);
}
private void HandleCollapseNextClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
if (NextButtonControlsPrevious)
{
CollapsePreviousRequested?.Invoke(this, EventArgs.Empty);
return;
}
CollapseNextRequested?.Invoke(this, EventArgs.Empty);
}
internal void SetDragging(bool isDragging)
{
_isDragging = isDragging;
PseudoClasses.Set(StdPseudoClass.Dragging, isDragging);
Cursor = isDragging ? GetDragCursor() : Cursor.Default;
_dragBar?.SetDragging(isDragging);
}
internal void AdjustDrag(Vector vector)
{
_dragBar?.AdjustDrag(vector);
}
internal void UpdateCollapseButtons()
{
var hasPrevious = ShowPreviousButton && IsPreviousCollapsible;
var hasNext = ShowNextButton && IsNextCollapsible;
var hasPrevious = ShowPreviousButton && IsPreviousCollapsible;
var hasNext = ShowNextButton && IsNextCollapsible;
var onlyPrevious = hasPrevious && !hasNext;
var onlyNext = hasNext && !hasPrevious;
var onlyNext = hasNext && !hasPrevious;
if (_collapsePrevButton != null)
{
ApplyCustomIconBackground(_collapsePrevButton, PreviousIconTemplate != null);
_collapsePrevButton.IsVisible = hasPrevious &&
ShouldShowIcon(PreviousShowMode, true, onlyPrevious);
_collapsePrevButton.Icon = CreatePrevIcon();
_collapsePrevButton.IsVisible = hasPrevious && ShouldShowIcon(PreviousShowMode, true, onlyPrevious);
_collapsePrevButton.Icon = CreatePrevIcon();
}
if (_collapseNextButton != null)
{
ApplyCustomIconBackground(_collapseNextButton, NextIconTemplate != null);
_collapseNextButton.IsVisible = hasNext &&
ShouldShowIcon(NextShowMode, false, onlyNext);
_collapseNextButton.Icon = CreateNextIcon();
_collapseNextButton.IsVisible = hasNext && ShouldShowIcon(NextShowMode, false, onlyNext);
_collapseNextButton.Icon = CreateNextIcon();
}
}
private void OnOrientationChanged()
{
UpdateCollapseButtons();
}
private void AttachDragBar()
{
if (_dragBar == null)
{
return;
}
_dragBar.DragStarted += HandleDragBarStarted;
_dragBar.DragDelta += HandleDragBarDelta;
_dragBar.DragCompleted += HandleDragBarCompleted;
}
private void DetachDragBar()
{
if (_dragBar == null)
{
return;
}
_dragBar.DragStarted -= HandleDragBarStarted;
_dragBar.DragDelta -= HandleDragBarDelta;
_dragBar.DragCompleted -= HandleDragBarCompleted;
}
private void HandleDragBarStarted(object? sender, VectorEventArgs e)
{
DragStarted?.Invoke(this, new VectorEventArgs { RoutedEvent = e.RoutedEvent, Vector = e.Vector });
}
private void HandleDragBarDelta(object? sender, VectorEventArgs e)
{
DragDelta?.Invoke(this, new VectorEventArgs { RoutedEvent = e.RoutedEvent, Vector = e.Vector });
}
private void HandleDragBarCompleted(object? sender, VectorEventArgs e)
{
DragCompleted?.Invoke(this, new VectorEventArgs { RoutedEvent = e.RoutedEvent, Vector = e.Vector });
}
private static void AttachCollapseButton(IconButton? button, EventHandler<RoutedEventArgs> handler)
{
if (button != null)
{
button.Click += handler;
}
}
private static void DetachCollapseButton(ref IconButton? button, EventHandler<RoutedEventArgs> handler)
{
if (button != null)
{
button.Click -= handler;
}
button = null;
}
private void HandleCollapsePrevClick(object? sender, RoutedEventArgs e)
{
var target = PreviousButtonControlsNext ? CollapseNextRequested : CollapsePreviousRequested;
target?.Invoke(this, EventArgs.Empty);
}
private void HandleCollapseNextClick(object? sender, RoutedEventArgs e)
{
var target = NextButtonControlsPrevious ? CollapsePreviousRequested : CollapseNextRequested;
target?.Invoke(this, EventArgs.Empty);
}
private static void ApplyCustomIconBackground(IconButton button, bool isCustom)
{
if (isCustom)
{
button.SetCurrentValue(TemplatedControl.BackgroundProperty, Brushes.Transparent);
button.SetCurrentValue(BackgroundProperty, Brushes.Transparent);
}
else
{
button.ClearValue(TemplatedControl.BackgroundProperty);
button.ClearValue(BackgroundProperty);
}
}
private bool ShouldShowIcon(SplitterCollapsibleIconDisplayMode mode, bool isPrevious, bool isOnly)
{
var isPointerOver = _isPointerOver || _isPointerPressed;
return mode switch
{
SplitterCollapsibleIconDisplayMode.Always => true,
SplitterCollapsibleIconDisplayMode.Hidden => false,
SplitterCollapsibleIconDisplayMode.Hover => isPointerOver &&
SplitterCollapsibleIconDisplayMode.Hover => IsPointerOver &&
(ShowBothButtonsOnHover ||
isOnly ||
_hoverSide == (isPrevious ? HoverSide.Previous : HoverSide.Next)),
@@ -309,15 +272,16 @@ internal class SplitterHandle : AtomUIThumb
private void UpdateHoverSide(PointerEventArgs e)
{
if (!_isPointerOver)
if (!IsPointerOver)
{
return;
}
var position = e.GetPosition(this);
var compare = Orientation == Orientation.Vertical ? position.X : position.Y;
var center = Orientation == Orientation.Vertical ? Bounds.Width * 0.5 : Bounds.Height * 0.5;
var newSide = compare < center ? HoverSide.Previous : HoverSide.Next;
var isVertical = Orientation == Orientation.Vertical;
var compare = isVertical ? position.X : position.Y;
var center = isVertical ? Bounds.Width * 0.5 : Bounds.Height * 0.5;
var newSide = compare < center ? HoverSide.Previous : HoverSide.Next;
if (_hoverSide == newSide)
{
@@ -332,38 +296,6 @@ internal class SplitterHandle : AtomUIThumb
}
}
private void CachePointerPosition(PointerEventArgs e)
{
var currentPosition = e.GetPosition(this);
_lastPointerPosition = currentPosition;
_hasPointerPosition = true;
IsPointerInDragZone = IsPointerInDragArea(_lastPointerPosition);
}
private void UpdateCursor(Point position)
{
if (_isDragging || _isPointerPressed)
{
Cursor = GetDragCursor();
return;
}
if (!IsDragEnabled)
{
Cursor = Cursor.Default;
return;
}
Cursor = Cursor.Default;
}
private Cursor GetDragCursor()
{
return Orientation == Orientation.Vertical
? new Cursor(StandardCursorType.SizeWestEast)
: new Cursor(StandardCursorType.SizeNorthSouth);
}
private PathIcon? CreatePrevIcon()
{
if (PreviousIconTemplate != null)
@@ -373,9 +305,9 @@ internal class SplitterHandle : AtomUIThumb
return Orientation switch
{
Orientation.Vertical => new LeftOutlined(),
Orientation.Vertical => new LeftOutlined(),
Orientation.Horizontal => new UpOutlined(),
_ => new LeftOutlined()
_ => new LeftOutlined()
};
}
@@ -388,53 +320,9 @@ internal class SplitterHandle : AtomUIThumb
return Orientation switch
{
Orientation.Vertical => new RightOutlined(),
Orientation.Vertical => new RightOutlined(),
Orientation.Horizontal => new DownOutlined(),
_ => new RightOutlined()
_ => new RightOutlined()
};
}
private bool IsPointerInDragArea(PointerEventArgs e)
{
return IsPointerInDragArea(e.GetPosition(this));
}
private bool IsPointerInDragArea(Point position)
{
var triggerSize = GetDragTriggerSize();
if (triggerSize <= 0)
{
return true;
}
var half = triggerSize * 0.5;
if (Orientation == Orientation.Vertical)
{
return Math.Abs(position.X - Bounds.Width * 0.5) <= half;
}
return Math.Abs(position.Y - Bounds.Height * 0.5) <= half;
}
private double GetDragTriggerSize()
{
var value = TokenResourceUtils.FindTokenResource(this, SplitterTokenKind.SplitTriggerSize);
if (value is double size && !double.IsNaN(size) && !double.IsInfinity(size))
{
return Math.Max(0, size);
}
value = TokenResourceUtils.FindTokenResource(this, SplitterTokenKind.SplitBarSize);
if (value is double fallback && !double.IsNaN(fallback) && !double.IsInfinity(fallback))
{
return Math.Max(0, fallback);
}
if (!double.IsNaN(LineThickness) && !double.IsInfinity(LineThickness))
{
return Math.Max(0.0, LineThickness);
}
return 0.0;
}
}
@@ -272,11 +272,6 @@ internal class SplitterPanel : Panel
}
}
handle.IsDragEnabled = canDrag;
handle.Cursor = canDrag
? Orientation == Orientation.Vertical
? new Cursor(StandardCursorType.SizeWestEast)
: new Cursor(StandardCursorType.SizeNorthSouth)
: Cursor.Default;
handle.PreviousButtonControlsNext = false;
handle.NextButtonControlsPrevious = false;
@@ -0,0 +1,44 @@
<ResourceDictionary
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:atom="https://atomui.net"
x:ClassModifier="internal">
<ControlTheme x:Key="{x:Type atom:SplitterDragBar}" TargetType="atom:SplitterDragBar">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}">
<Border Name="PART_Grip"
Background="{TemplateBinding LineBrush}"
CornerRadius="{atom:SharedTokenResource BorderRadiusXS}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
</ControlTemplate>
</Setter>
<Style Selector="^[Orientation=Vertical]">
<Setter Property="Cursor" Value="SizeWestEast" />
<Setter Property="Width" Value="{atom:SplitterTokenResource SplitTriggerSize}" />
<Style Selector="^ /template/ Border#PART_Grip">
<Setter Property="Width" Value="{atom:SplitterTokenResource SplitBarSize}" />
<Setter Property="Height" Value="{atom:SplitterTokenResource SplitBarDraggableSize}" />
</Style>
</Style>
<Style Selector="^[Orientation=Horizontal]">
<Setter Property="Cursor" Value="SizeNorthSouth" />
<Setter Property="Height" Value="{atom:SplitterTokenResource SplitTriggerSize}" />
<Style Selector="^ /template/ Border#PART_Grip">
<Setter Property="Height" Value="{atom:SplitterTokenResource SplitBarSize}" />
<Setter Property="Width" Value="{atom:SplitterTokenResource SplitBarDraggableSize}" />
</Style>
</Style>
<Style Selector="^[IsDragEnabled=False]">
<Style Selector="^ /template/ Border#PART_Grip">
<Setter Property="IsVisible" Value="False" />
</Style>
</Style>
</ControlTheme>
</ResourceDictionary>
@@ -10,10 +10,13 @@
ClipToBounds="False">
<Grid>
<Border Name="PART_HandleLine"
Background="{TemplateBinding LineBrush}" />
<Border Name="PART_HandleGrip"
Background="{atom:SharedTokenResource ColorFill}"
CornerRadius="{atom:SharedTokenResource BorderRadiusXS}" />
Background="{TemplateBinding LineBrush}"
IsHitTestVisible="False" />
<atom:SplitterDragBar Name="PART_DragBar"
Orientation="{TemplateBinding Orientation}"
LineBrush="{atom:SharedTokenResource ColorFill}"
LineThickness="{TemplateBinding LineThickness}"
IsDragEnabled="{TemplateBinding IsDragEnabled}" />
<Canvas Name="PART_CollapseIconsHost"
Width="0"
Height="0"
@@ -98,11 +101,9 @@
<Setter Property="VerticalAlignment" Value="Stretch" />
</Style>
<Style Selector="^[Orientation=Vertical] /template/ Border#PART_HandleGrip">
<Setter Property="Width" Value="{atom:SplitterTokenResource SplitBarSize}" />
<Setter Property="Height" Value="{atom:SplitterTokenResource SplitBarDraggableSize}" />
<Style Selector="^[Orientation=Vertical] /template/ atom|SplitterDragBar#PART_DragBar">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Stretch" />
</Style>
<Style Selector="^[Orientation=Horizontal] /template/ Border#PART_HandleLine">
@@ -111,11 +112,9 @@
<Setter Property="HorizontalAlignment" Value="Stretch" />
</Style>
<Style Selector="^[Orientation=Horizontal] /template/ Border#PART_HandleGrip">
<Setter Property="Height" Value="{atom:SplitterTokenResource SplitBarSize}" />
<Setter Property="Width" Value="{atom:SplitterTokenResource SplitBarDraggableSize}" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Style Selector="^[Orientation=Horizontal] /template/ atom|SplitterDragBar#PART_DragBar">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
</Style>
<Style Selector="^[Orientation=Vertical] /template/ atom|IconButton#PART_CollapsePrevButton">
@@ -156,9 +155,6 @@
<Style Selector="^[IsDragEnabled=False]">
<Setter Property="LineBrush" Value="{atom:SplitterTokenResource HandleLineColor}" />
<Style Selector="^ /template/ Border#PART_HandleGrip">
<Setter Property="IsVisible" Value="False" />
</Style>
</Style>
<Style Selector="^[IsDragEnabled=False]:pointerover">
@@ -3,6 +3,7 @@
x:ClassModifier="internal">
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="SplitterTheme.axaml" />
<ResourceInclude Source="SplitterDragBarTheme.axaml" />
<ResourceInclude Source="SplitterHandleTheme.axaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</ResourceDictionary>