2013-03-27 104 views
9

我需要創建格式正確的按鈕,像在Windows 8開始頁面瓷磚。是否有任何工具包可用於自定義ListView,它可以支持平鋪視圖或網格視圖,具有一些格式並可以是一些動畫選項。WPF工具包磚的ListView

我試圖創建自己的自定義列表視圖,但它似乎是一個複雜的任務。

回答

32

我不知道一個不錯的任意瓦片控制的。 DevExpress有一個不錯的商業版本。

如果要指定您的具體要求(即做什麼屬性,你需要配置什麼樣的動畫,...),我發現的時候,我會雖然給它一個旋轉。

編輯:我已經創建了一個帶有WrapPanel作爲ItemsPanel一個ItemsControl。使用MVVM模式,將控件擴展到您的需求和數據對象應該不會太困難。很難做到的是DragDrop行爲部分 - 當然還有一些改進空間。我沒有包含這些圖片。

enter image description here

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> 

視圖模型,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(); 
    } 
} 
+0

動畫 - >可能是一些延遲後出現的列表項,拖放功能的瓷磚項目,添加圖像或複選框的功能。 – 2013-04-04 12:28:37

+0

瓷磚總是相同的大小,相同的邊距?您需要配置哪些圖塊(例如顏色,文本,圖標,類型:複選框/常規按鈕,...)?調整後的控件應該如何運作? – 2013-04-04 12:40:29

+0

是相同的大小,相同的邊距和可配置 - >一切(背景圖像,文本,圖標),當調整大小時,它應該適應其他瓷磚項目的空間,其他瓷磚項目應該在調整大小時平穩移動。 – 2013-04-04 12:46:54

1

您可以簡單地使用項目控制, 我)。在項目面板中給出你想要的行數和列數。如果你想在這裏使用的按鈕是動態生成的,只需將按鈕列表分配給它。

<ItemsControl x:Name="lstButtons" 
       Grid.Row="0" 
       Grid.Column="1"> 
    <ItemsControl.ItemsPanel> 
    <ItemsPanelTemplate> 
     <UniformGrid Columns="4" 
        Rows="4" /> 
    </ItemsPanelTemplate> 
    </ItemsControl.ItemsPanel> 
    <ItemsControl.ItemTemplate> 
    <DataTemplate> 
     <Button Click="CLICK_EVENT_HERE" 
       Style="Use metro Button Style here" /> 
    </DataTemplate> 
    </ItemsControl.ItemTemplate> 
</ItemsControl>