当前位置:首页 > 技术知识 > 正文内容

Grid 动态横向动画显示 Item(datagrid横向滚动条)

maynowei8个月前 (08-16)技术知识82

Grid 动态横向动画显示 Item

控件名:AnimationGrid

作 者:WPFDevelopersOrg - 驚鏵

原文链接[1]
https://github.com/WPFDevelopersOrg/WPFDevelopers

码云链接[2]
https://gitee.com/WPFDevelopersOrg/WPFDevelopers

  • 框架支持 .NET4 至 .NET8
  • Visual Studio 2022 ;

欢迎各位开发者下载并体验。如果在使用过程中遇到任何问题,欢迎随时向我们反馈[3]

控件功能
  • AnimationGrid 控件通过动画效果动态展示和隐藏数据项。默认控件会显示一个内容项。当添加第二个内容时,第一个内容的宽度会自动变小,第二个内容则从右侧滑入并展示。

1. 新增 AnimationGrid.cs
  • ItemsSource : 绑定到控件的数据集合,当 ItemsSource 变化时,会触发 OnItemsSourceChanged 方法来重新初始化项。每个数据项通过 ItemTemplate 加载并渲染为 FrameworkElement
  • ItemTemplate : 数据项模板。
  • InitializeItems :方法清空当前控件中的 Items ,重新添加新的内容,每个 Item 的宽度设置为 0 ,并且 Visibility 设置为 Collapsed ,初始不可见,第一个 Item 设置立即显示,并调用 UpdateLayoutAnimated 来更新布局。
  • ShowItem :切换 Item 数据项的显示状态。如果数据项未在 VisibleItems 中,就添加到集合中并显示。如果它已经在 VisibleItems 中,则移除并隐藏。
  • AnimateWidth :当 Item 发生变化时,控件会通过 DoubleAnimation 动画来修改 ItemWidth 动画时长设置 300 毫秒。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;

namespaceWPFDevelopers.Controls
{
publicclassAnimationGrid : Grid
{
privatereadonlyobject _syncLock = newobject();

publicstaticreadonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register(nameof(ItemsSource), typeof(IEnumerable), typeof(AnimationGrid),
new PropertyMetadata(null, OnItemsSourceChanged));

publicstaticreadonly DependencyProperty ItemTemplateProperty =
DependencyProperty.Register(nameof(ItemTemplate), typeof(DataTemplate), typeof(AnimationGrid),
new PropertyMetadata(null));

privatereadonly Dictionaryobject, FrameworkElement> _itemMap = new Dictionaryobject, FrameworkElement>();
privatereadonly HashSetobject> _visibleItems = new HashSetobject>();

public IEnumerable ItemsSource
{
get => (IEnumerable)GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}

public DataTemplate ItemTemplate
{
get => (DataTemplate)GetValue(ItemTemplateProperty);
set => SetValue(ItemTemplateProperty, value);
}
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is AnimationGrid panel)
{
panel.InitializeItems();
}
}

private void InitializeItems()
{
Children.Clear();
ColumnDefinitions.Clear();
_itemMap.Clear();
_visibleItems.Clear();

if (ItemsSource == null || ItemTemplate == null) return;

foreach (var item in ItemsSource)
{
var content = (FrameworkElement)ItemTemplate.LoadContent();
content.DataContext = item;
content.Visibility = Visibility.Collapsed;
content.Width = 0;
_itemMap[item] = content;
Children.Add(content);
}
if (_itemMap.Count > 0)
{
var first = _itemMap.First();
_visibleItems.Add(first.Key);
first.Value.Visibility = Visibility.Visible;
UpdateLayoutAnimated();
}
}

public void ShowItem(object item)
{
lock (_syncLock)
{
if (!_itemMap.ContainsKey(item))
return;
if (_visibleItems.Contains(item))
_visibleItems.Remove(item);
else
_visibleItems.Add(item);
_itemMap[item].Visibility = Visibility.Visible;
UpdateLayoutAnimated();
}
}

private void UpdateLayoutAnimated()
{
ColumnDefinitions.Clear();
var visibleCount = Math.Max(1, _visibleItems.Count);
var width = this.Width;
var targetWidth = ActualWidth / visibleCount;
int index = 0;
foreach (var item in _itemMap.Keys)
{
ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
var element = _itemMap[item];
SetColumn(element, index);
if (_visibleItems.Contains(item))
{
AnimateWidth(element, targetWidth);
}
else
{
AnimateWidth(element, 0, () => element.Visibility = Visibility.Collapsed);
}
index++;
}
}

private void AnimateWidth(FrameworkElement element, double targetWidth, Action completed = null)
{
var anim = new DoubleAnimation
{
To = targetWidth,
Duration = TimeSpan.FromMilliseconds(300),
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut }
};
if (completed != null)
{
anim.Completed += delegate { completed(); };
}
element.BeginAnimation(WidthProperty, anim);
}

protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
if (_visibleItems.Count > 0)
{
UpdateLayoutAnimated();
}
}
}
}

2. 新增 AnimationGridExample.xaml
  • GridItemTemplate :新增数据模板;
  • 新增 ToggleButton 样式;
  • IsSelectedCommand :绑定选择命令;
UserControl
x:Class="WPFDevelopers.Samples.ExampleViews.AnimationGridExample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:wd="https://github.com/WPFDevelopersOrg/WPFDevelopers"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
UserControl.Resources>
DataTemplate x:Key="GridItemTemplate">
Button Content="{Binding Content}" />
DataTemplate>
Style TargetType="ToggleButton">
Setter Property="Width" Value="30" />
Setter Property="Height" Value="20" />
Setter Property="Template">
Setter.Value>
ControlTemplate TargetType="ToggleButton">
Border
x:Name="border"
Background="Transparent"
BorderBrush="Transparent"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="4">
wd:PathIcon x:Name="pathIcon" Data="{Binding Data}" />
Border>
ControlTemplate.Triggers>
Trigger Property="IsChecked" Value="True">
Setter Property="Foreground" Value="{DynamicResource WD.PrimaryBrush}" />
Trigger>
Trigger Property="IsMouseOver" Value="True">
Setter TargetName="border" Property="BorderBrush" Value="{DynamicResource WD.PrimaryBrush}" />
Trigger>
ControlTemplate.Triggers>
ControlTemplate>
Setter.Value>
Setter>
Style>
DataTemplate x:Key="ToggleItemTemplate">
ToggleButton
Command="{Binding IsSelectedCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding .}"
IsChecked="{Binding IsSelected}"
Tag="{Binding Content}" />
DataTemplate>
UserControl.Resources>
Grid>
wd:AnimationGrid
x:Name="MyPanel"
ItemTemplate="{StaticResource GridItemTemplate}"
ItemsSource="{Binding GridItems, RelativeSource={RelativeSource AncestorType=UserControl}}" />
Border
Margin="0,10"
Padding="6"
HorizontalAlignment="Center"
VerticalAlignment="Top"
Background="{DynamicResource WD.BackgroundBrush}"
CornerRadius="3"
Effect="{StaticResource WD.PrimaryShadowDepth}">
ItemsControl ItemTemplate="{StaticResource ToggleItemTemplate}" ItemsSource="{Binding GridItems, RelativeSource={RelativeSource AncestorType=UserControl}}">
ItemsControl.ItemsPanel>
ItemsPanelTemplate>
StackPanel Orientation="Horizontal" />
ItemsPanelTemplate>
ItemsControl.ItemsPanel>
ItemsControl>
Border>
Grid>
UserControl>

3. 新增 AnimationGridExample.xaml.cs
  • GridItemsAnimationGrid 的数据源;
  • IsSelectedCommand :当点击按钮时调用 AnimationGrid 的 ShowItem。
public partialclassAnimationGridExample : UserControl
{
public ObservableCollection GridItems
{
get { return (ObservableCollection)GetValue(GridItemsProperty); }
set { SetValue(GridItemsProperty, value); }
}

publicstaticreadonly DependencyProperty GridItemsProperty =
DependencyProperty.Register("GridItems", typeof(ObservableCollection), typeof(AnimationGridExample), new PropertyMetadata(null));
public AnimationGridExample()
{
InitializeComponent();
Loaded += OnAnimatedGridExample_Loaded;
}

private void OnAnimatedGridExample_Loaded(object sender, RoutedEventArgs e)
{
var list = new List();
list.Add(new GridItem { Content = "Single", Data = "M0.5,0.5 L60.5,0.5 L60.5,43.26 L0.5,43.26 z", IsSelected = true });
list.Add(new GridItem { Content = "Dual", Data = "M0,0 L61,0 L61,43.760002 L0,43.760002 z M25.5,0 L35.5,0 L35.5,43.760002 L25.5,43.760002 z" });
list.Add(new GridItem { Content = "Three", Data = "M0,0 L61,0 L61,43.760002 L0,43.760002 z M17,0.5 L22,0.5 L22,43.260002 L17,43.260002 z M39,0.5 L44,0.5 L44,43.260002 L39,43.260002 z" });
GridItems = new ObservableCollection(list);
}

public ICommand IsSelectedCommand => new RelayCommand(param =>
{
if (param == null) return;
var item = (GridItem)param;
if (item == null) return;
MyPanel.ShowItem(item);
});

}
publicclassGridItem : ViewModelBase
{
publicstring Content { get; set; }
publicstring Data { get; set; }

privatebool _isSelected;
publicbool IsSelected
{
get => _isSelected;
set { _isSelected = value; NotifyPropertyChange("IsSelected"); }
}
}

GitHub 源码地址[4]

Gitee 源码地址[5]

参考资料

[1]

原文链接:
https://github.com/WPFDevelopersOrg/WPFDevelopers

[2]

码云链接:
https://gitee.com/WPFDevelopersOrg/WPFDevelopers

[3]

反馈:
https://github.com/WPFDevelopersOrg/WPFDevelopers/issues/new

[4]

GitHub 源码地址:
https://github.com/WPFDevelopersOrg/WPFDevelopers/blob/dev/src/WPFDevelopers.Shared/Controls/AnimationGrid/AnimationGrid.cs

[5]

Gitee 源码地址:
https://gitee.com/WPFDevelopersOrg/WPFDevelopers/blob/dev/src/WPFDevelopers.Shared/Controls/AnimationGrid/AnimationGrid.cs

相关文章

一个快要被忘记的数据库开发岗位,但应该被尊重

数据库测试,似乎是被人遗忘的数据库职业,但依然是不错的选择。底下是我在某站找的招聘启事,就连蚂蚁金服都在积极寻找数据库测试人:要说我经历的项目,大大小小也有几十个,从 C/S, B/S, 再到 B/C...

python-oracledb——利用python连接Oracle数据库的好用方法

这篇文章最早发布在CSDN了,最近想尝试使用一下头条,重新转移过来了。背景介绍之前使用的数据库一直是MySql,偶尔使用PostgreSQL,都是利用的数据库连接池使用;最近需要在Oracle数据库取...

本地配置plsql远程连接oracle数据库

由于Oracle的庞大,有时候我们需要在只安装Oracle客户端如plsql、toad等的情况下去连接远程数据库,可是没有安装Oracle就没有一切的配置文件去支持。最后终于发现一个很有效的方法,O...

Oracle数据库云服务系列新增前所未有的企业级功能

新推出的关键任务型功能包括:实现容错可用性和按需可扩展性的集群;零数据丢失灾难恢复;Oracle数据库Exadata云服务。甲骨文还宣布推出一项最新免费数据库云服务,数据库管理员和开发人员通过该服务可...

你可能疏忽的plsql和navicat连接Oracle注意点

在日常开发中,我们总是少不了要连接数据库,你是否遇到过填写的账号、密码、连接地址都对,但就是连接不上Oracle的情况?这里说一下其中一种连接不上Oracle的原因,这种情况简单,但很可能被疏忽。记下...

面试官:说说Oracle数据库result cache的原理是什么?

概述前面已经用实验给大家介绍了Result Cache相关内容,今天主要讨论一下Oracle 11g Result Cache的深层原理。从参数看,Oracle提供了Client Result Cac...