Non sono a conoscenza di un controllo di tile gratuito. DevExpress ha una bella versione commerciale.
Se dovessi specificare i tuoi requisiti esatti (cioè quali proprietà hai bisogno di configurare, che tipo di animazione, ...) e trovo il tempo, vorrei dargli un vortice però.
MODIFICA: ho creato un ItemsControl con un WrapPanel come ItemsPanel. Utilizzando il pattern MVVM, l'espansione dei controlli alle tue esigenze e ai tuoi oggetti dati non dovrebbe essere troppo difficile. Piuttosto difficile da fare è la parte comportamentale di DragDrop - c'è sicuramente ancora un margine di miglioramento. Non ho incluso le immagini.
TileControl.xaml:
<UserControl x:Class="WpfApplication1.TileControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:WpfApplication1"
xmlns:beh="clr-namespace:WpfApplication1.Behavior"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<local:ViewModel />
</UserControl.DataContext>
<UserControl.Resources>
<local:TileTypeToColorConverter x:Key="TileTypeToColorConverter" />
</UserControl.Resources>
<Grid>
<Image Source="/WpfApplication1;component/Themes/background.png" Stretch="UniformToFill" />
<Border x:Name="darkenBorder" Background="Black" Opacity="0.6" />
<ItemsControl ItemsSource="{Binding Tiles}" Background="Transparent" Margin="5">
<i:Interaction.Behaviors>
<beh:ItemsControlDragDropBehavior />
</i:Interaction.Behaviors>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="local:TileModel">
<Button Content="{Binding Text}" Background="{Binding TileType, Converter={StaticResource TileTypeToColorConverter}}"
Command="{Binding ClickCommand}" Width="120" Height="110" Padding="5" RenderTransformOrigin="0.5, 0.5" >
<Button.RenderTransform>
<TransformGroup>
<ScaleTransform />
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</Button.RenderTransform>
<Button.Template>
<ControlTemplate TargetType="Button">
<Border Padding="5" Background="Transparent">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border x:Name="tileBackground" Grid.RowSpan="2" Background="{TemplateBinding Background}" Opacity="0.9" />
<Image Source="{Binding Image}" HorizontalAlignment="Center" VerticalAlignment="Bottom" Height="50" />
<ContentPresenter TextElement.Foreground="White" Grid.Row="1" HorizontalAlignment="Center" Margin="3,10" />
</Grid>
</Border>
</ControlTemplate>
</Button.Template>
<Button.Resources>
<ElasticEase x:Key="easeOutBounce" EasingMode="EaseOut" Springiness="6" Oscillations="4" />
</Button.Resources>
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard Duration="00:00:00.05" AutoReverse="True">
<DoubleAnimation To="0.1" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"/>
<DoubleAnimation To="0.1" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)" From="0.1" To="1.0" EasingFunction="{StaticResource easeOutBounce}" />
<DoubleAnimation Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)" From="0.1" To="1.0" EasingFunction="{StaticResource easeOutBounce}" />
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="0.1" To="1.0" EasingFunction="{StaticResource easeOutBounce}" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
ViewModel, TileModel, TileType, ActionCommand:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Input;
using System.Windows.Media.Imaging;
namespace WpfApplication1
{
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private ObservableCollection<TileModel> _tiles;
public ObservableCollection<TileModel> Tiles { get { return _tiles; } set { _tiles = value; OnPropertyChanged("Tiles"); } }
public ViewModel()
{
Tiles= new ObservableCollection<TileModel>()
{
new TileModel() { Text = "Facebook", Image = Properties.Resources.Facebook.ToBitmapImage(), TileType = TileType.Website },
new TileModel() { Text = "Skype", Image = Properties.Resources.Skype.ToBitmapImage(), TileType = TileType.Application },
new TileModel() { Text = "Ask.com", Image = Properties.Resources.AskCom.ToBitmapImage(), TileType = TileType.Website },
new TileModel() { Text = "Amazon", Image = Properties.Resources.Amazon.ToBitmapImage(), TileType = TileType.Website },
new TileModel() { Text = "Evernote", Image = Properties.Resources.Evernote.ToBitmapImage(), TileType = TileType.Application },
new TileModel() { Text = "Twitter", Image = Properties.Resources.Twitter.ToBitmapImage(), TileType = TileType.Website },
new TileModel() { Text = "Internet Explorer", Image = Properties.Resources.InterneExplorer.ToBitmapImage(), TileType = TileType.Browser },
new TileModel() { Text = "Android", Image = Properties.Resources.Android.ToBitmapImage(), TileType = TileType.Application },
new TileModel() { Text = "Winamp", Image = Properties.Resources.Winamp.ToBitmapImage(), TileType = TileType.Application },
new TileModel() { Text = "YouTube", Image = Properties.Resources.YouTube.ToBitmapImage(), TileType = TileType.Website },
};
}
}
public class TileModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private string _text;
public string Text { get { return _text; } set { _text = value; OnPropertyChanged("Text"); } }
private BitmapSource _image;
public BitmapSource Image { get { return _image; } set { _image = value; OnPropertyChanged("Image"); } }
private TileType _tileType;
public TileType TileType { get { return _tileType; } set { _tileType = value; OnPropertyChanged("TileType"); } }
public ICommand ClickCommand { get; private set; }
public TileModel()
{
ClickCommand = new ActionCommand(Click);
}
private void Click()
{
// execute appropriate action
}
}
public enum TileType
{
Browser,
Website,
Application
}
public class ActionCommand : ICommand
{
public event EventHandler CanExecuteChanged;
private Action _action;
public ActionCommand(Action action)
{
_action = action;
}
public bool CanExecute(object parameter) { return true; }
public void Execute(object parameter)
{
if (_action != null)
_action();
}
}
}
ItemsControlDragDropBehavior:
using System;
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Media;
namespace WpfApplication1.Behavior
{
public class ItemsControlDragDropBehavior : Behavior<ItemsControl>
{
private bool _isMouseDown;
private bool _isDragging;
private Point _dragStartPosition;
private UIElement _dragItem;
private UIElement _dragContainer;
private IDataObject _dataObject;
private int _currentDropIndex;
private Point _lastCheckPoint;
protected override void OnAttached()
{
this.AssociatedObject.AllowDrop = true;
this.AssociatedObject.PreviewMouseLeftButtonDown += AssociatedObject_PreviewMouseLeftButtonDown;
this.AssociatedObject.PreviewMouseMove += AssociatedObject_PreviewMouseMove;
this.AssociatedObject.PreviewDragOver += AssociatedObject_PreviewDragOver;
this.AssociatedObject.PreviewDrop += AssociatedObject_PreviewDrop;
this.AssociatedObject.PreviewMouseLeftButtonUp += AssociatedObject_PreviewMouseLeftButtonUp;
base.OnAttached();
}
void AssociatedObject_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
ItemsControl itemsControl = (ItemsControl)sender;
Point p = e.GetPosition(itemsControl);
object data = itemsControl.GetDataObjectFromPoint(p);
_dataObject = data != null ? new DataObject(data.GetType(), data) : null;
_dragContainer = itemsControl.GetItemContainerFromPoint(p);
if (_dragContainer != null)
_dragItem = GetItemFromContainer(_dragContainer);
if (data != null)
{
_isMouseDown = true;
_dragStartPosition = p;
}
}
void AssociatedObject_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (_isMouseDown)
{
ItemsControl itemsControl = (ItemsControl)sender;
Point currentPosition = e.GetPosition(itemsControl);
if ((_isDragging == false) && (Math.Abs(currentPosition.X - _dragStartPosition.X) > SystemParameters.MinimumHorizontalDragDistance) ||
(Math.Abs(currentPosition.Y - _dragStartPosition.Y) > SystemParameters.MinimumVerticalDragDistance))
{
DragStarted(e.GetPosition(itemsControl));
}
e.Handled = true;
}
}
void AssociatedObject_PreviewDragOver(object sender, DragEventArgs e)
{
UpdateDropIndex(e.GetPosition(this.AssociatedObject));
}
void AssociatedObject_PreviewDrop(object sender, DragEventArgs e)
{
UpdateDropIndex(e.GetPosition(this.AssociatedObject));
}
void AssociatedObject_PreviewMouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
_isMouseDown = false;
}
private void DragStarted(Point p)
{
if (!_isDragging)
{
_isDragging = true;
if (_dragContainer != null)
_dragContainer.Opacity = 0.3;
_currentDropIndex = FindDropIndex(p);
DragDropEffects e = DragDrop.DoDragDrop(this.AssociatedObject, _dataObject, DragDropEffects.Copy | DragDropEffects.Move);
ResetState();
}
}
private void ResetState()
{
if (_dragContainer != null)
_dragContainer.Opacity = 1.0;
_isMouseDown = false;
_isDragging = false;
_dataObject = null;
_dragItem = null;
_dragContainer = null;
_currentDropIndex = -1;
}
private void UpdateDropIndex(Point p)
{
if ((_lastCheckPoint - p).Length > SystemParameters.MinimumHorizontalDragDistance) // prevent too frequent call
{
int dropIndex = FindDropIndex(p);
if (dropIndex != _currentDropIndex && dropIndex > -1)
{
this.AssociatedObject.RemoveItem(_dataObject);
this.AssociatedObject.AddItem(_dataObject, dropIndex);
_currentDropIndex = dropIndex;
}
_lastCheckPoint = p;
}
}
private int FindDropIndex(Point p)
{
ItemsControl itemsControl = this.AssociatedObject;
UIElement dropTargetContainer = null;
dropTargetContainer = itemsControl.GetItemContainerFromPoint(p);
int index = -1;
if (dropTargetContainer != null)
{
index = itemsControl.ItemContainerGenerator.IndexFromContainer(dropTargetContainer);
if (!IsPointInTopHalf(p))
index = index++; // in second half of item, add after
}
else if (IsPointAfterAllItems(itemsControl, p))
{
// still within itemscontrol, but after all items
index = itemsControl.Items.Count - 1;
}
return index;
}
public bool IsPointInTopHalf(Point p)
{
ItemsControl itemsControl = this.AssociatedObject;
bool isInTopHalf = false;
UIElement selectedItemContainer = itemsControl.GetItemContainerFromPoint(p);
Point relativePosition = Mouse.GetPosition(selectedItemContainer);
if (IsItemControlOrientationHorizontal())
isInTopHalf = relativePosition.X < ((FrameworkElement)selectedItemContainer).ActualWidth/2;
else
isInTopHalf = relativePosition.Y < ((FrameworkElement)selectedItemContainer).ActualHeight/2;
return isInTopHalf;
}
private bool IsItemControlOrientationHorizontal()
{
bool isHorizontal = false;
Panel panel = GetItemsPanel();
if (panel is WrapPanel)
isHorizontal = ((WrapPanel)panel).Orientation == Orientation.Horizontal;
else if (panel is StackPanel)
isHorizontal = ((StackPanel)panel).Orientation == Orientation.Horizontal;
return isHorizontal;
}
private UIElement GetItemFromContainer(UIElement container)
{
UIElement item = null;
if (container != null)
item = VisualTreeHelper.GetChild(container, 0) as UIElement;
return item;
}
private Panel GetItemsPanel()
{
ItemsPresenter itemsPresenter = GetVisualChild<ItemsPresenter>(this.AssociatedObject);
Panel itemsPanel = VisualTreeHelper.GetChild(itemsPresenter, 0) as Panel;
return itemsPanel;
}
private static T GetVisualChild<T>(DependencyObject parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
{
child = GetVisualChild<T>(v);
}
if (child != null)
{
break;
}
}
return child;
}
/// still needs some work
private static bool IsPointAfterAllItems(ItemsControl itemsControl, Point point)
{
bool isAfter = false;
UIElement target = itemsControl.GetLastItemContainer();
Point targetPos = target.TransformToAncestor(itemsControl).Transform(new Point(0, 0));
Point relativeToTarget = new Point(point.X - targetPos.X, point.Y - targetPos.Y);
if (relativeToTarget.X >= 0 && relativeToTarget.Y >= 0)
{
var bounds = VisualTreeHelper.GetDescendantBounds(target);
isAfter = !bounds.Contains(relativeToTarget);
}
return isAfter;
}
}
public static class ItemsControlExtensions
{
public static object GetDataObjectFromPoint(this ItemsControl itemsControl, Point p)
{
UIElement element = itemsControl.InputHitTest(p) as UIElement;
while (element != null)
{
if (element == itemsControl)
return null;
object data = itemsControl.ItemContainerGenerator.ItemFromContainer(element);
if (data != DependencyProperty.UnsetValue)
return data;
else
element = VisualTreeHelper.GetParent(element) as UIElement;
}
return null;
}
public static UIElement GetItemContainerFromPoint(this ItemsControl itemsControl, Point p)
{
UIElement element = itemsControl.InputHitTest(p) as UIElement;
while (element != null)
{
object data = itemsControl.ItemContainerGenerator.ItemFromContainer(element);
if (data != DependencyProperty.UnsetValue)
return element;
else
element = VisualTreeHelper.GetParent(element) as UIElement;
}
return element;
}
public static UIElement GetLastItemContainer(this ItemsControl itemsControl)
{
UIElement container = null;
if (itemsControl.HasItems)
container = itemsControl.GetItemContainerAtIndex(itemsControl.Items.Count - 1);
return container;
}
public static UIElement GetItemContainerAtIndex(this ItemsControl itemsControl, int index)
{
UIElement container = null;
if (itemsControl != null && itemsControl.Items.Count > index && itemsControl.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
container = itemsControl.ItemContainerGenerator.ContainerFromIndex(index) as UIElement;
else
container = itemsControl;
return container;
}
public static void AddItem(this ItemsControl itemsControl, IDataObject item, int insertIndex)
{
if (itemsControl.ItemsSource != null)
{
foreach (string format in item.GetFormats())
{
object data = item.GetData(format);
IList iList = itemsControl.ItemsSource as IList;
if (iList != null)
iList.Insert(insertIndex, data);
else
{
Type type = itemsControl.ItemsSource.GetType();
Type genericList = type.GetInterface("IList`1");
if (genericList != null)
type.GetMethod("Insert").Invoke(itemsControl.ItemsSource, new object[] { insertIndex, data });
}
}
}
else
itemsControl.Items.Insert(insertIndex, item);
}
public static void RemoveItem(this ItemsControl itemsControl, IDataObject itemToRemove)
{
if (itemToRemove != null)
{
foreach (string format in itemToRemove.GetFormats())
{
object data = itemToRemove.GetData(format);
int index = itemsControl.Items.IndexOf(data);
if (index > -1)
itemsControl.RemoveItemAt(index);
}
}
}
public static void RemoveItemAt(this ItemsControl itemsControl, int removeIndex)
{
if (removeIndex != -1 && removeIndex < itemsControl.Items.Count)
{
if (itemsControl.ItemsSource != null)
{
IList iList = itemsControl.ItemsSource as IList;
if (iList != null)
{
iList.RemoveAt(removeIndex);
}
else
{
Type type = itemsControl.ItemsSource.GetType();
Type genericList = type.GetInterface("IList`1");
if (genericList != null)
type.GetMethod("RemoveAt").Invoke(itemsControl.ItemsSource, new object[] { removeIndex });
}
}
else
itemsControl.Items.RemoveAt(removeIndex);
}
}
}
}
TileTypeToColorConverter:
public class TileTypeToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
SolidColorBrush brush = new SolidColorBrush();
TileType type = (TileType)value;
switch (type)
{
case TileType.Browser: brush.Color = Colors.Maroon; break;
case TileType.Application: brush.Color = Colors.DodgerBlue; break;
case TileType.Website: brush.Color = Colors.DarkGoldenrod; break;
}
return brush;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
animazioni-> possono essere voci di elenco che appaiono una dopo l'altra dopo un po 'di ritardo, trascinare la funzione per gli elementi della piastrella, possibilità di aggiungere immagini o caselle di controllo. Le tessere –
hanno sempre le stesse dimensioni, lo stesso margine? quale delle piastrelle hai bisogno di configurare (ad esempio colore, testo, icona, tipo: casella di controllo/pulsante normale, ...)? come dovrebbe comportarsi il controllo quando viene ridimensionato? –
sì stessa dimensione, stesso margine e configurabile-> tutto (immagine di sfondo, testo, icona), quando ridimensionato, dovrebbe contenere spazio per altri elementi della tessera e gli altri elementi della tessera dovrebbero spostarsi senza problemi quando ridimensionati. –