这听起来像是一项有趣的任务,所以我决定按照你想要的方式实现一些东西。我想我可以分享它,以便您可以改进它或根据自己的喜好使用它。首先,这项任务超出了我仅在XAML 中实现的技能。我不是说做不到,只是有点难。相反,我实现了自己的 Panel 类型(称为GapPanel
)。这听起来可能更糟,但它仍然有一些好处,例如实现RoutedEvent
s 以在 XAML 中响应动画的可能性。
所以,这是代码的负载。首先是 XAML
<Window x:Class="SlidingWrapPanel.SecondAttempt"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SlidingWrapPanel"
Title="Wrapped items with details pane" Height="250" Width="600">
<Window.Resources>
<ControlTemplate TargetType="Button" x:Key="ItemButtonTemplate">
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="ButtonBorder"
Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
To="#999999" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="ButtonBorder"
Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
To="#3e3e3e" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>
<Border x:Name="ButtonBorder" Background="#3e3e3e" BorderBrush="#222" BorderThickness="1">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Content="{TemplateBinding Content}" Margin="0" />
</Border>
</ControlTemplate>
<Style x:Key="ItemGridStyle" TargetType="{x:Type Grid}">
<Setter Property="Background" Value="#3e3e3e"/>
<Setter Property="Width" Value="150"/>
<Setter Property="Height" Value="100"/>
<Setter Property="Margin" Value="1"/>
</Style>
<Style x:Key="ItemButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Foreground" Value="White"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<Binding Source="{StaticResource ItemButtonTemplate}"/>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="DetailsGridStyle" TargetType="{x:Type Grid}">
<Setter Property="Background" Value="#3e3e3e"/>
<Setter Property="Width" Value="160"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="RenderTransform">
<Setter.Value>
<TranslateTransform X="-160" />
</Setter.Value>
</Setter>
</Style>
<Style x:Key="DetailsTextStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontFamily" Value="Segoe WP Light"/>
<Setter Property="Margin" Value="15"/>
</Style>
<Storyboard x:Key="ExpandColumnAnimation">
<DoubleAnimation Storyboard.TargetProperty="GapWidth" Storyboard.TargetName="ItemsPanel"
From="0" To="{Binding ActualWidth, ElementName=DetailsPanel}" Duration="0:0:0.75">
<DoubleAnimation.EasingFunction>
<QuinticEase EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)"
Storyboard.TargetName="DetailsPanel">
<DiscreteDoubleKeyFrame KeyTime="0" Value="{Binding GapX, ElementName=ItemsPanel}"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="CollapseColumnAnimation">
<DoubleAnimation Storyboard.TargetProperty="GapWidth" Storyboard.TargetName="ItemsPanel"
To="0" Duration="0:0:0.5">
<DoubleAnimation.EasingFunction>
<QuinticEase EasingMode="EaseIn"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)"
Storyboard.TargetName="DetailsPanel">
<DiscreteDoubleKeyFrame KeyTime="0:0:0.5" Value="-160"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Grid>
<Grid>
<Grid x:Name="DetailsPanel" Style="{StaticResource DetailsGridStyle}">
<ScrollViewer>
<TextBlock Style="{StaticResource DetailsTextStyle}">
<Run Text="Details" FontSize="18"/>
<LineBreak />
<Run Text="Some text"/>
</TextBlock>
</ScrollViewer>
</Grid>
</Grid>
<local:GapPanel x:Name="ItemsPanel">
<local:GapPanel.Triggers>
<EventTrigger RoutedEvent="local:GapPanel.ColumnChanged">
<BeginStoryboard Storyboard="{StaticResource ExpandColumnAnimation}"/>
</EventTrigger>
<EventTrigger RoutedEvent="local:GapPanel.CloseGap">
<BeginStoryboard Storyboard="{StaticResource CollapseColumnAnimation}"/>
</EventTrigger>
</local:GapPanel.Triggers>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 1" />
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 2" />
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 3" />
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 4"/>
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 5"/>
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 6"/>
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 7"/>
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 8"/>
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 9"/>
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 10"/>
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 11"/>
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 12"/>
</Grid>
</local:GapPanel>
</Grid>
</Window>
和面板的(可憎的)..
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace SlidingWrapPanel {
public class GapPanel : Panel, INotifyPropertyChanged {
private readonly IDictionary<UIElement, int> columns;
private readonly IDictionary<int, double> gapCoordinates;
private object opened;
public static readonly DependencyProperty GapColumnProperty =
DependencyProperty.Register("GapColumn", typeof(int), typeof(GapPanel), new FrameworkPropertyMetadata(default(int), FrameworkPropertyMetadataOptions.AffectsRender, columnChanged));
public static readonly DependencyProperty GapWidthProperty =
DependencyProperty.Register("GapWidth", typeof(double), typeof(GapPanel), new FrameworkPropertyMetadata(default(double), FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly RoutedEvent ColumnChangedEvent;
public static readonly RoutedEvent CloseGapEvent;
static GapPanel() {
ColumnChangedEvent = EventManager.RegisterRoutedEvent("ColumnChanged", RoutingStrategy.Bubble, typeof(RoutedEvent), typeof(GapPanel));
CloseGapEvent = EventManager.RegisterRoutedEvent("CloseGap", RoutingStrategy.Bubble, typeof(RoutedEvent), typeof(GapPanel));
}
public GapPanel() {
columns = new Dictionary<UIElement, int>();
gapCoordinates = new Dictionary<int, double>();
GapWidth = 0;
GapColumn = -1;
}
public int GapColumn {
get { return (int)GetValue(GapColumnProperty); }
set { SetValue(GapColumnProperty, value); }
}
public double GapWidth {
get { return (double)GetValue(GapWidthProperty); }
set { SetValue(GapWidthProperty, value); }
}
public double GapX {
get {
double value;
gapCoordinates.TryGetValue(GapColumn, out value);
return value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public event RoutedEventHandler ColumnChanged {
add { AddHandler(ColumnChangedEvent, value); }
remove { RemoveHandler(ColumnChangedEvent, value); }
}
public event RoutedEventHandler CloseGap {
add { AddHandler(CloseGapEvent, value); }
remove { RemoveHandler(CloseGapEvent, value); }
}
protected override Size ArrangeOverride(Size finalSize) {
Point location = new Point();
double position = 0;
double columnWidth = 0;
int col = 0;
foreach (UIElement child in Children) {
columnWidth = Math.Max(columnWidth, child.DesiredSize.Width);
position += child.DesiredSize.Height;
if (position > finalSize.Height && columnWidth > 0) {
location.X += columnWidth;
if (col == GapColumn) {
location.X += GapWidth;
}
++col;
columnWidth = 0;
position = child.DesiredSize.Height;
location.Y = 0;
}
columns[child] = col;
child.Arrange(new Rect(location, child.DesiredSize));
location.Y = position;
}
return finalSize;
}
protected override Size MeasureOverride(Size availableSize) {
double width = 0, height = 0;
double position = 0;
double columnWidth = 0;
int col = 0;
foreach (UIElement child in Children) {
child.Measure(availableSize);
columnWidth = Math.Max(columnWidth, child.DesiredSize.Width);
position += child.DesiredSize.Height;
if (position > availableSize.Height && columnWidth > 0) {
width += columnWidth;
++col;
columnWidth = child.DesiredSize.Width;
position = child.DesiredSize.Height;
height = Math.Max(height, child.DesiredSize.Height);
}
gapCoordinates[col] = width + columnWidth;
}
return new Size(width + GapWidth, height);
}
protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved) {
base.OnVisualChildrenChanged(visualAdded, visualRemoved);
UIElement element = visualAdded as UIElement;
if (element != null) {
element.PreviewMouseLeftButtonDown += expandAtVisual;
}
element = visualRemoved as UIElement;
if (element != null) {
element.PreviewMouseLeftButtonDown -= expandAtVisual;
}
}
private void expandAtVisual(object sender, MouseButtonEventArgs e) {
// find element column
int column = columns[(UIElement)sender];
GapWidth = 0;
GapColumn = column;
if (opened == sender) {
RaiseEvent(new RoutedEventArgs(CloseGapEvent, this));
}
opened = sender;
}
private void onPropertyChanged(string propertyName) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
private static void columnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
((GapPanel)d).onPropertyChanged("GapX");
((GapPanel)d).RaiseEvent(new RoutedEventArgs(ColumnChangedEvent, d));
}
}
}
如果您觉得有什么需要我解释的,请发表评论。
另外,我不禁要宣传 Adam Nathan 的《WPF4 Unleashed》一书。本书对我在上面所做的所有事情进行了详细的解释,因此对于任何想要了解更多关于 WPF 的人来说,它都是一个很好的资源。