Coverflow-Control in Silverlight inkl. Sourcecode
Ich war lange auf der Suche nach einem Coverflow-Control, welches wiederverwertbar ist. Leider absolute Fehlanzeige, jedes Control das ich gefunden habe war nett, aber nicht wirklich brauchbar um damit einen Designer einigermaßen Glücklich zu machen.
Was heißt für mich “einen Designer glücklich machen”?
Er will das Control verwenden und was darin anzeigen, er will nicht stundenlang Sourcecode durchforsten und dort händisch die Bilder austauschen. Dafür gibt es doch Komponenten.
Also habe ich mich ran gemacht und so ein Coverflow-Control einfach selber geschrieben.
Feature 1: Design-Time Support
Das Control stellt sich in Blend bereits dar. Man muss nicht die ganze Anwendung erst starten. Damit unterscheidet es sich schon von allen Controls die ich gefunden habe.
Feature 2: Das Coverflow wurde von Panel abgeleitet.
Panels haben die schöne Eigenschaft, das sie Children haben können. Und das bedeutet für den Designer, man kann einfach via Drag & Drop Elemente in das Coverflow ziehen und wieder entfernen.
Feature 3: Konfigurieren über Eigenschaften
Ist es zu viel verlangt, das ich meine Controls ein individuelles Aussehen geben möchte? Warum bietet man den Blend-Benutzer nicht direkt eine Fülle von Eigenschaften zum Konfigurieren an? Keine Ahnung. Ich tu es
Der Sourcecode ist lediglich 427 Zeilen lang. Für die Beispiele die ich zu Coverflow gefunden habe, ein absoluter Rekord (Eigenlob ist an dieser Stelle mal erlaubt)
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Effects;
using System.ComponentModel;
namespace Oliver.Controls
{
public class Coverflow : Panel
{
ContentControl _contentControl;
public Coverflow()
{
#region Trick for Keyevents, Panel is not focusable, but contentcontrol is
_contentControl = new ContentControl();
_contentControl.IsTabStop = true;
_contentControl.KeyDown += new KeyEventHandler(Coverflow_KeyDown);
this.Children.Add(_contentControl);
_contentControl.Focus();
#endregion
this.GotFocus += new RoutedEventHandler(Coverflow_GotFocus);
}
void Coverflow_GotFocus(object sender, RoutedEventArgs e)
{
_contentControl.Focus();
}
void Coverflow_KeyDown(object sender, KeyEventArgs e)
{
Debug.WriteLine("Key pressed");
// Move Back
if (e.Key == Key.Left)
{
if (CurrentItemIndex > 0)
{
CurrentItemIndex–;
}
}
// Move forward
if (e.Key == Key.Right)
{
if (CurrentItemIndex + 1 < this.Children.Count)
{
CurrentItemIndex++;
}
}
}
#region Properties
[Category("Oliver.Controls")]
public double OpacityShift
{
get { return (double)GetValue(OpacityShiftProperty); }
set { SetValue(OpacityShiftProperty, value); }
}
// Using a DependencyProperty as the backing store for OpacityShift. This enables animation, styling, binding, etc…
public static readonly DependencyProperty OpacityShiftProperty =
DependencyProperty.Register(
"OpacityShift",
typeof(double),
typeof(Coverflow),
new PropertyMetadata(0.1, OnPropertyChanged));
[Category("Oliver.Controls")]
public bool DropShadow
{
get { return (bool)GetValue(DropShadowProperty); }
set { SetValue(DropShadowProperty, value); }
}
// Using a DependencyProperty as the backing store for DropShadow. This enables animation, styling, binding, etc…
public static readonly DependencyProperty DropShadowProperty =
DependencyProperty.Register(
"DropShadow",
typeof(bool),
typeof(Coverflow),
new PropertyMetadata(false, OnPropertyChanged));
[Category("Oliver.Controls")]
public int SpaceBetweenCovers
{
get { return (int)GetValue(SpaceBetweenCoversProperty); }
set { SetValue(SpaceBetweenCoversProperty, value); }
}
// Using a DependencyProperty as the backing store for SpaceBetweenCover. This enables animation, styling, binding, etc…
public static readonly DependencyProperty SpaceBetweenCoversProperty =
DependencyProperty.Register(
"SpaceBetweenCovers",
typeof(int),
typeof(Coverflow),
new PropertyMetadata(65, OnPropertyChanged));
[Category("Oliver.Controls")]
public int MoveDuration
{
get { return (int)GetValue(MoveDurationProperty); }
set { SetValue(MoveDurationProperty, value); }
}
// Using a DependencyProperty as the backing store for MoveDuration. This enables animation, styling, binding, etc…
public static readonly DependencyProperty MoveDurationProperty =
DependencyProperty.Register(
"MoveDuration",
typeof(int),
typeof(Coverflow),
new PropertyMetadata(500, OnPropertyChanged));
[Category("Oliver.Controls")]
public double ItemWidth
{
get { return (double)GetValue(ItemWidthProperty); }
set { SetValue(ItemWidthProperty, value); }
}
// Using a DependencyProperty as the backing store for ItemWidth. This enables animation, styling, binding, etc…
public static readonly DependencyProperty ItemWidthProperty =
DependencyProperty.Register(
"ItemWidth",
typeof(double),
typeof(Coverflow),
new PropertyMetadata(200.0, OnPropertyChanged));
[Category("Oliver.Controls")]
public double ItemHeight
{
get { return (double)GetValue(ItemHeightProperty); }
set { SetValue(ItemHeightProperty, value); }
}
// Using a DependencyProperty as the backing store for ItemHeight. This enables animation, styling, binding, etc…
public static readonly DependencyProperty ItemHeightProperty =
DependencyProperty.Register(
"ItemHeight",
typeof(double),
typeof(Coverflow),
new PropertyMetadata(400.0, OnPropertyChanged));
[Category("Oliver.Controls")]
public int CurrentItemIndex
{
get { return (int)GetValue(CurrentItemIndexProperty); }
set { SetValue(CurrentItemIndexProperty, value); }
}
// Using a DependencyProperty as the backing store for CurrentItemIndex. This enables animation, styling, binding, etc…
public static readonly DependencyProperty CurrentItemIndexProperty =
DependencyProperty.Register(
"CurrentItemIndex",
typeof(int),
typeof(Coverflow),
new PropertyMetadata(0, OnPropertyChanged));
[Category("Oliver.Controls")]
public double XShift
{
get { return (double)GetValue(XShiftProperty); }
set { SetValue(XShiftProperty, value); }
}
// Using a DependencyProperty as the backing store for XShift. This enables animation, styling, binding, etc…
public static readonly DependencyProperty XShiftProperty =
DependencyProperty.Register(
"XShift",
typeof(double),
typeof(Coverflow),
new PropertyMetadata(0.0, OnPropertyChanged));
[Category("Oliver.Controls")]
public double YShift
{
get { return (double)GetValue(YShiftProperty); }
set { SetValue(YShiftProperty, value); }
}
// Using a DependencyProperty as the backing store for YShift. This enables animation, styling, binding, etc…
public static readonly DependencyProperty YShiftProperty =
DependencyProperty.Register(
"YShift",
typeof(double),
typeof(Coverflow),
new PropertyMetadata(0.0, OnPropertyChanged));
[Category("Oliver.Controls")]
public double ZShift
{
get { return (double)GetValue(ZShiftProperty); }
set { SetValue(ZShiftProperty, value); }
}
// Using a DependencyProperty as the backing store for ZShift. This enables animation, styling, binding, etc…
public static readonly DependencyProperty ZShiftProperty =
DependencyProperty.Register(
"ZShift",
typeof(double),
typeof(Coverflow),
new PropertyMetadata(-50.0, OnPropertyChanged));
[Category("Oliver.Controls")]
public double RotationAngle
{
get { return (double)GetValue(RotationAngleProperty); }
set { SetValue(RotationAngleProperty, value); }
}
// Using a DependencyProperty as the backing store for RotationAngle. This enables animation, styling, binding, etc…
public static readonly DependencyProperty RotationAngleProperty =
DependencyProperty.Register(
"RotationAngle",
typeof(double),
typeof(Coverflow),
new PropertyMetadata(50.0, OnPropertyChanged));
[Category("Oliver.Controls")]
public double SpaceBetweenCenter
{
get { return (double)GetValue(SpaceBetweenCenterProperty); }
set { SetValue(SpaceBetweenCenterProperty, value); }
}
// Using a DependencyProperty as the backing store for SpaceBetweenCenter. This enables animation, styling, binding, etc…
public static readonly DependencyProperty SpaceBetweenCenterProperty =
DependencyProperty.Register(
"SpaceBetweenCenter",
typeof(double),
typeof(Coverflow),
new PropertyMetadata(10.0, OnPropertyChanged));
private static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
((Coverflow)sender).InvalidateArrange();
}
#endregion
protected override Size ArrangeOverride(Size finalSize)
{
InsertElements();
return base.ArrangeOverride(finalSize);
}
private void InsertElements()
{
foreach (var item in this.Children)
{
if (item.Visibility == Visibility.Visible)
{
if (this.DropShadow)
{
DropShadowEffect dse = new DropShadowEffect();
dse.BlurRadius = 5;
item.Effect = dse;
}
else
{
item.Effect = null;
}
RenderItem(item);
item.MouseLeftButtonDown += libItem_MouseLeftButtonDown;
}
}
}
private void RenderItem(UIElement item)
{
int itemIdx = this.Children.IndexOf(item);
// Größe
FrameworkElement fe = item as FrameworkElement;
fe.Width = this.ItemWidth;
fe.Height = this.ItemHeight;
// Center
double centerX = (double)this.Width / 2 – ItemWidth / 2;
double centerY = (double)this.Height / 2 – ItemHeight / 2;
TranslateTransform tt = new TranslateTransform();
tt.X = centerX;
tt.Y = centerY;
item.RenderTransform = tt;
// Opacity
double opacity = 1;
// Angle
PlaneProjection pp = new PlaneProjection();
if (itemIdx < this.CurrentItemIndex)
{
// Item is Left of center
pp.RotationY = -this.RotationAngle;
pp.LocalOffsetX =
(XShift + SpaceBetweenCovers) * (itemIdx – this.CurrentItemIndex)
– (ItemWidth / 2)
– SpaceBetweenCenter
– ZShift;
pp.LocalOffsetZ = (this.CurrentItemIndex – itemIdx) * -ZShift;
pp.LocalOffsetY = (this.CurrentItemIndex – itemIdx) * -YShift;
opacity = 1-(this.CurrentItemIndex – itemIdx) * OpacityShift;
}
else if (itemIdx > this.CurrentItemIndex)
{
// Item is Right of center
int pos = itemIdx – this.CurrentItemIndex;
pp.RotationY = this.RotationAngle;
pp.LocalOffsetX =
(XShift + SpaceBetweenCovers) * pos
+ (ItemWidth / 2)
+ SpaceBetweenCenter
+ ZShift;
pp.LocalOffsetZ = pos * -ZShift;
pp.LocalOffsetY = pos * -YShift;
opacity = 1- pos * OpacityShift;
}
else
{
// Item is Center
pp.RotationY = 0;
pp.LocalOffsetX = 0;
pp.LocalOffsetY = 0;
opacity = 1;
}
if (item.Projection == null)
{
item.Projection = new PlaneProjection();
}
// Create a duration of x seconds.
TimeSpan ts = TimeSpan.FromMilliseconds(this.MoveDuration);
Duration duration = new Duration(ts);
Storyboard sb = new Storyboard();
#region Rotation Y
// Rotation Y
DoubleAnimation daRotationY = new DoubleAnimation();
daRotationY.Duration = duration;
PropertyPath propertyPathRotationY = new PropertyPath("(UIElement.Projection).(PlaneProjection.RotationY)");
Storyboard.SetTarget(daRotationY, item);
Storyboard.SetTargetProperty(daRotationY, propertyPathRotationY);
daRotationY.To = pp.RotationY;
sb.Children.Add(daRotationY);
#endregion
#region Local Offset X
DoubleAnimation daLocalOffsetX = new DoubleAnimation();
daLocalOffsetX.Duration = duration;
PropertyPath propertyPathLocalOffsetX = new PropertyPath("(UIElement.Projection).(PlaneProjection.LocalOffsetX)");
Storyboard.SetTarget(daLocalOffsetX, item);
Storyboard.SetTargetProperty(daLocalOffsetX, propertyPathLocalOffsetX);
daLocalOffsetX.To = pp.LocalOffsetX;
sb.Children.Add(daLocalOffsetX);
#endregion
#region Local Offset Z
DoubleAnimation daLocalOffsetZ = new DoubleAnimation();
daLocalOffsetZ.Duration = duration;
PropertyPath propertyPathLocalOffsetZ = new PropertyPath("(UIElement.Projection).(PlaneProjection.LocalOffsetZ)");
Storyboard.SetTarget(daLocalOffsetZ, item);
Storyboard.SetTargetProperty(daLocalOffsetZ, propertyPathLocalOffsetZ);
daLocalOffsetZ.To = pp.LocalOffsetZ;
sb.Children.Add(daLocalOffsetZ);
#endregion
#region Local Offset Y
DoubleAnimation daLocalOffsetY = new DoubleAnimation();
daLocalOffsetY.Duration = duration;
PropertyPath propertyPathLocalOffsetY = new PropertyPath("(UIElement.Projection).(PlaneProjection.LocalOffsetY)");
Storyboard.SetTarget(daLocalOffsetY, item);
Storyboard.SetTargetProperty(daLocalOffsetY, propertyPathLocalOffsetY);
daLocalOffsetY.To = pp.LocalOffsetY;
sb.Children.Add(daLocalOffsetY);
#endregion
#region Opacity
DoubleAnimation daOpacity = new DoubleAnimation();
daOpacity.Duration = duration;
PropertyPath propertyPathOpacity = new PropertyPath("(UIElement.Opacity)");
Storyboard.SetTarget(daOpacity, item);
Storyboard.SetTargetProperty(daOpacity, propertyPathOpacity);
daOpacity.To = opacity;
sb.Children.Add(daOpacity);
#endregion
// Begin the animation.
sb.Completed += sb_Completed;
sb.Begin();
}
void sb_Completed(object sender, EventArgs e)
{
Storyboard sb = sender as Storyboard;
sb.Completed -= sb_Completed;
}
void libItem_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
int idx = this.Children.IndexOf(sender as UIElement);
this.CurrentItemIndex = idx;
this.InvalidateArrange();
}
}
}
Das Projekt kann hier herunter geladen werden.
Feedback ist natürlich immer gerne gesehen



