【问题标题】:WPF toolkit for tile listview用于平铺列表视图的 WPF 工具包
【发布时间】:2013-03-27 05:43:03
【问题描述】:

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

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

【问题讨论】:

    标签: wpf wpf-controls wpftoolkit


    【解决方案1】:

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

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

    编辑:我创建了一个带有 WrapPanel 作为 ItemsPanel 的 ItemsControl。使用 MVVM 模式,将控件扩展到您的需求和数据对象应该不会太困难。比较难做的是 DragDrop 行为部分——当然还有一些改进的余地。我没有包含图片。

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

    【讨论】:

    • animation-> 可能是列表项在延迟一段时间后一一出现,图块项的拖放功能,添加图像或复选框的功能。
    • 瓷砖总是相同的大小,相同的边距?您需要配置哪些图块(例如颜色、文本、图标、类型:复选框/普通按钮,...)?控件在调整大小时应该如何表现?
    • 是的,相同的大小、相同的边距和可配置-> 一切(背景图像、文本、图标),当调整大小时,它应该为其他平铺项目提供空间,并且其他平铺项目在调整大小时应该平滑移动。
    • 这个问题是它的性能很差。 (原子处理器)
    • @JesseSeger:抱歉回复晚了,我已经离开一年了。您的问题的答案很大程度上取决于您的应用程序。最有可能的是,您会想要使用独立存储。你坚持它的那一刻就是你退出应用程序的时候,所以找到一个适合你的事件,很可能是MainWindow_OnClosingApplication_Exit。您需要做的就是获取DataContext,然后保存磁贴集合的序列——甚至不需要直接在TileModel 上保留索引属性。
    【解决方案2】:

    您可以简单地使用项目控制, 一世)。在项目面板中,只需给出您想要的行数和列数 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>
    

    【讨论】:

      猜你喜欢
      • 2018-08-11
      • 1970-01-01
      • 1970-01-01
      • 2019-11-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-03
      相关资源
      最近更新 更多