用于平铺列表视图的 WPF 工具包

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15652597/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 08:20:21  来源:igfitidea点击:

WPF toolkit for tile listview

wpfwpf-controlswpftoolkit

提问by Tanuj Wadhwa

I need to create tiles of well formatted buttons, something like the Windows 8 start page. Is there any toolkit available for a custom ListView which may support tile view or grid view, with some formatting and may be some animation options.

我需要创建格式良好的按钮图块,例如 Windows 8 起始页。是否有任何可用于自定义 ListView 的工具包,它可能支持平铺视图或网格视图,具有一些格式和一些动画选项。

I tried creating my own custom listview but it seemed to be a complicated task.

我尝试创建自己的自定义列表视图,但这似乎是一项复杂的任务。

回答by Mike Fuchs

I am not aware of a nice free tile control. DevExpresshas a nice looking commercial version.

我不知道一个不错的免费磁贴控件。DevExpress有一个漂亮的商业版本。

If you'd specify your exact requirements (i.e. what properties do you need configurable, what kind of animation,...) and I find the time, I would give it a whirl though.

如果您要指定您的确切要求(即您需要配置哪些属性,什么样的动画,...)并且我有时间,我会试一试。

EDIT: I've created an ItemsControl with a WrapPanel as ItemsPanel. Using the MVVM pattern, expanding the controls to your needs and your data objects should not be too difficult. Rather hard to do is the DragDrop behavior part - there certainly is still some room for improvement. I didn't include the images.

编辑:我创建了一个带有 WrapPanel 作为 ItemsPanel 的 ItemsControl。使用 MVVM 模式,根据您的需要和数据对象扩展控件应该不会太困难。比较难的是 DragDrop 行为部分 - 当然还有一些改进的空间。我没有包括图像。

enter image description here

在此处输入图片说明

TileControl.xaml:

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:

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:

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:

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();
    }
}

回答by YOusaFZai

You can simply use Item Control, i). In Item Panel just give the number of rows and columns you want ii). If button you want here are to be generated dynamically just assign the list of Buttons to it.

您可以简单地使用项目控制,i)。在项目面板中,只需给出您想要的行数和列数 ii)。如果您想要的按钮是动态生成的,只需将按钮列表分配给它。

<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>