• 【转】WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表


    一.前言

      申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接。

      本文主要针对WPF项目开发中图片的各种使用问题,经过总结,把一些经验分享一下。内容包括:

    • WPF常用图像数据源ImageSource的创建;
    • 自定义缩略图控件ThumbnailImage,支持网络图片、大图片、图片异步加载等特性;
    • 动态图片gif播放控件;
    • 图片列表样式,支持大数据量的虚拟化;

    二. WPF常用图像数据源ImageSource的创建

    <Image Source="../Images/qq.png"></Image> 

    这是一个普通Image控件的使用,Source的数据类型是ImageSource,在XAML中可以使用文件绝对路径或相对路径,ImageSource是一个抽象类,我们一般使用BitmapSource、BitmapImage等。

      但在实际项目中,有各种各样的需求,比如:

      • 从Bitmap创建ImageSource对象;
      • 从数据流byte[]创建ImageSource对象;
      • 从System.Drawing.Image创建ImageSource对象;
      • 从一个大图片文件创建一个指定大小的ImageSource对象;

    2.1 从System.Drawing.Image创建指定大小ImageSource对象  

    /// <summary>
            /// 使用System.Drawing.Image创建WPF使用的ImageSource类型缩略图(不放大小图)
            /// </summary>
            /// <param name="sourceImage">System.Drawing.Image 对象</param>
            /// <param name="width">指定宽度</param>
            /// <param name="height">指定高度</param>
            public static ImageSource CreateImageSourceThumbnia(System.Drawing.Image sourceImage, double width, double height)
            {
                if (sourceImage == null) return null;
                double rw = width / sourceImage.Width;
                double rh = height / sourceImage.Height;
                var aspect = (float)Math.Min(rw, rh);
                int w = sourceImage.Width, h = sourceImage.Height;
                if (aspect < 1)
                {
                    w = (int)Math.Round(sourceImage.Width * aspect); h = (int)Math.Round(sourceImage.Height * aspect);
                }
                Bitmap sourceBmp = new Bitmap(sourceImage, w, h);
                IntPtr hBitmap = sourceBmp.GetHbitmap();
                BitmapSource bitmapSource = Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, Int32Rect.Empty,
                       BitmapSizeOptions.FromEmptyOptions());
                bitmapSource.Freeze();
                System.Utility.Win32.Win32.DeleteObject(hBitmap);
                sourceImage.Dispose();
                sourceBmp.Dispose();
                return bitmapSource;
            }

    2.2 从一个大图片文件创建一个指定大小的ImageSource对象

    /// <summary>
            /// 创建WPF使用的ImageSource类型缩略图(不放大小图)
            /// </summary>
            /// <param name="fileName">本地图片路径</param>
            /// <param name="width">指定宽度</param>
            /// <param name="height">指定高度</param>
            public static ImageSource CreateImageSourceThumbnia(string fileName, double width, double height)
            {
                System.Drawing.Image sourceImage = System.Drawing.Image.FromFile(fileName);
                double rw = width / sourceImage.Width;
                double rh = height / sourceImage.Height;
                var aspect = (float)Math.Min(rw, rh);
                int w = sourceImage.Width, h = sourceImage.Height;
                if (aspect < 1)
                {
                    w = (int)Math.Round(sourceImage.Width * aspect); h = (int)Math.Round(sourceImage.Height * aspect);
                }
                Bitmap sourceBmp = new Bitmap(sourceImage, w, h);
                IntPtr hBitmap = sourceBmp.GetHbitmap();
                BitmapSource bitmapSource = Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, Int32Rect.Empty,
                       BitmapSizeOptions.FromEmptyOptions());
    
                bitmapSource.Freeze();
                System.Utility.Win32.Win32.DeleteObject(hBitmap);
                sourceImage.Dispose();
                sourceBmp.Dispose();
                return bitmapSource;
            }

    2.3 从Bitmap创建指定大小的ImageSource对象  

    /// <summary>
            /// 从一个Bitmap创建ImageSource
            /// </summary>
            /// <param name="image">Bitmap对象</param>
            /// <returns></returns>
            public static ImageSource CreateImageSourceFromImage(Bitmap image)
            {
                if (image == null) return null;
                try
                {
                    IntPtr ptr = image.GetHbitmap();
                    BitmapSource bs = Imaging.CreateBitmapSourceFromHBitmap(ptr, IntPtr.Zero, Int32Rect.Empty,
                                                                            BitmapSizeOptions.FromEmptyOptions());
                    bs.Freeze();
                    image.Dispose();
                    System.Utility.Win32.Win32.DeleteObject(ptr);
                    return bs;
                }
                catch (Exception)
                {
                    return null;
                }
            }

    2.4 从数据流byte[]创建指定大小的ImageSource对象  

    /// <summary>
            /// 从数据流创建缩略图
            /// </summary>
            public static ImageSource CreateImageSourceThumbnia(byte[] data, double width, double height)
            {
                using (Stream stream = new MemoryStream(data, true))
                {
                    using (Image img = Image.FromStream(stream))
                    {
                        return CreateImageSourceThumbnia(img, width, height);
                    }
                }
            }

    三.自定义缩略图控件ThumbnailImage

      ThumbnailImage控件的主要解决的问题:

      为了能扩展支持多种类型的缩略图,设计了一个简单的模式,用VS自带的工具生成的代码视图:

    3.1 多种类型的缩略图扩展

      首先定义一个图片类型枚举:  

    /// <summary>
        /// 缩略图数据源源类型
        /// </summary>
        public enum EnumThumbnail
        {
            Image,
            Vedio,
            WebImage,
            Auto,
            FileX,
        }

    然后定义了一个接口,生成图片数据源ImageSource  

    /// <summary>
        /// 缩略图创建服务接口
        /// </summary>
        public interface IThumbnailProvider
        {
            /// <summary>
            /// 创建缩略图。fileName:文件路径;图片宽度;height:高度
            /// </summary>
            ImageSource GenereateThumbnail(object fileSource, double width, double height);
        }

    如上面的代码视图,有三个实现,视频缩略图VedioThumbnailProvider没有实现完成,基本方法是利用一个第三方工具ffmpeg来获取第一帧图像然后创建ImageSource。

      ImageThumbnailProvider:普通图片缩略图实现(调用的2.2方法):

    /// <summary>
        /// 本地图片缩略图创建服务
        /// </summary>
        internal class ImageThumbnailProvider : IThumbnailProvider
        {
            /// <summary>
            /// 创建缩略图。fileName:文件路径;图片宽度;height:高度
            /// </summary>
            public ImageSource GenereateThumbnail(object fileName, double width, double height)
            {
                try
                {
                    var path = fileName.ToSafeString();
                    if (path.IsInvalid()) return null;
                    return System.Utility.Helper.Images.CreateImageSourceThumbnia(path, width, height);
                }
                catch
                {
                    return null;
                }
            }
        }

    WebImageThumbnailProvider:网络图片缩略图实现(下载图片数据后调用2.1方法):  

    /// <summary>
        /// 网络图片缩略图创建服务
        /// </summary>
        internal class WebImageThumbnailProvider : IThumbnailProvider
        {
            /// <summary>
            /// 创建缩略图。fileName:文件路径;图片宽度;height:高度
            /// </summary>
            public ImageSource GenereateThumbnail(object fileName, double width, double height)
            {
                try
                {
                    var path = fileName.ToSafeString();
                    if (path.IsInvalid()) return null;
                    var request = WebRequest.Create(path);
                    request.Timeout = 20000;
                    var stream = request.GetResponse().GetResponseStream();
                    var img = System.Drawing.Image.FromStream(stream);
                    return System.Utility.Helper.Images.CreateImageSourceThumbnia(img, width, height);
                }
                catch
                {
                    return null;
                }
            }
        }

    简单工厂ThumbnailProviderFactory实现:  

    /// <summary>
        /// 缩略图创建服务简单工厂
        /// </summary>
        public class ThumbnailProviderFactory : System.Utility.Patterns.ISimpleFactory<EnumThumbnail, IThumbnailProvider>
        {
            /// <summary>
            /// 根据key获取实例
            /// </summary>
            public virtual IThumbnailProvider GetInstance(EnumThumbnail key)
            {
                switch (key)
                {
                    case EnumThumbnail.Image:
                        return Singleton<ImageThumbnailProvider>.GetInstance();
                    case EnumThumbnail.Vedio:
                        return Singleton<VedioThumbnailProvider>.GetInstance();
                    case EnumThumbnail.WebImage:
                        return Singleton<WebImageThumbnailProvider>.GetInstance();
                }
                return null;
            }
        }

    3.2 缩略图控件ThumbnailImage

      先看看效果图吧,下面三张图片,图1是本地图片,图2是网络图片,图3也是网络图片,为什么没显示呢,这张图片用的是国外的图片链接地址,异步加载(加载比较慢,还没出来的!)

      ThumbnailImage实际是继承在微软的图片控件Image,因此没有样式代码,继承之后,主要的目的就是重写Imagesource的处理过程,详细代码:

    /*
         * 较大的图片,视频,网络图片要做缓存处理:缓存缩略图为本地文件,或内存缩略图对象。
         */
    
        /// <summary>
        /// 缩略图图片显示控件,同时支持图片和视频缩略图
        /// </summary>
        public class ThumbnailImage : Image
        {
            /// <summary>
            /// 是否启用缓存,默认false不启用
            /// </summary>
            public bool CacheEnable
            {
                get { return (bool)GetValue(CacheEnableProperty); }
                set { SetValue(CacheEnableProperty, value); }
            }
            /// <summary>
            /// 是否启用缓存,默认false不启用.默认缓存时间是180秒
            /// </summary>
            public static readonly DependencyProperty CacheEnableProperty =
                DependencyProperty.Register("CacheEnable", typeof(bool), typeof(ThumbnailImage), new PropertyMetadata(false));
    
            /// <summary>
            /// 缓存时间,单位秒。默认180秒
            /// </summary>
            public int CacheTime
            {
                get { return (int)GetValue(CacheTimeProperty); }
                set { SetValue(CacheTimeProperty, value); }
            }
            public static readonly DependencyProperty CacheTimeProperty =
                DependencyProperty.Register("CacheTime", typeof(int), typeof(ThumbnailImage), new PropertyMetadata(180));
    
            /// <summary>
            /// 是否启用异步加载,网络图片建议启用,本地图可以不需要。默认不起用异步
            /// </summary>
            public bool AsyncEnable
            {
                get { return (bool)GetValue(AsyncEnableProperty); }
                set { SetValue(AsyncEnableProperty, value); }
            }
            public static readonly DependencyProperty AsyncEnableProperty =
                DependencyProperty.Register("AsyncEnable", typeof(bool), typeof(ThumbnailImage), new PropertyMetadata(false));
    
            /// <summary>
            /// 缩略图类型,默认Image图片
            /// </summary>
            public EnumThumbnail ThumbnailType
            {
                get { return (EnumThumbnail)GetValue(ThumbnailTypeProperty); }
                set { SetValue(ThumbnailTypeProperty, value); }
            }
            public static readonly DependencyProperty ThumbnailTypeProperty =
                DependencyProperty.Register("ThumbnailType", typeof(EnumThumbnail), typeof(ThumbnailImage), new PropertyMetadata(EnumThumbnail.Image));
    
            /// <summary>
            /// 缩略图数据源:文件物理路径
            /// </summary>
            public object ThumbnailSource
            {
                get { return GetValue(ThumbnailSourceProperty); }
                set { SetValue(ThumbnailSourceProperty, value); }
            }
            public static readonly DependencyProperty ThumbnailSourceProperty = DependencyProperty.Register("ThumbnailSource", typeof(object),
                typeof(ThumbnailImage), new PropertyMetadata(OnSourcePropertyChanged));
    
            /// <summary>
            /// 缩略图
            /// </summary>
            protected static ThumbnailProviderFactory ThumbnailProviderFactory = new ThumbnailProviderFactory();
    
            protected override void OnInitialized(EventArgs e)
            {
                base.OnInitialized(e);
                this.Loaded += ThumbnailImage_Loaded;
            }
    
            void ThumbnailImage_Loaded(object sender, RoutedEventArgs e)
            {
                BindSource(this);
            }
    
            /// <summary>
            /// 属性更改处理事件
            /// </summary>
            private static void OnSourcePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
            {
                ThumbnailImage img = sender as ThumbnailImage;
                if (img == null) return;
                if (!img.IsLoaded) return;
                BindSource(img);
            }
            private static void BindSource(ThumbnailImage image)
            {
                var w = image.Width;
                var h = image.Height;
                object source = image.ThumbnailSource;
                //bind
                if (image.AsyncEnable)
                {
                    BindThumbnialAync(image, source, w, h);
                }
                else
                {
                    BindThumbnial(image, source, w, h);
                }
            }
    
            /// <summary>
            /// 绑定缩略图
            /// </summary>
            private static void BindThumbnial(ThumbnailImage image, object fileSource, double w, double h)
            {
                IThumbnailProvider thumbnailProvider = ThumbnailProviderFactory.GetInstance(image.ThumbnailType);
                image.Dispatcher.BeginInvoke(new Action(() =>
                {
                    var cache = image.CacheEnable;
                    var time = image.CacheTime;
                    ImageSource img = null;
                    if (cache)
                    {
                        img = CacheManager.GetCache<ImageSource>(fileSource.GetHashCode().ToString(), time, () =>
                        {
                            return thumbnailProvider.GenereateThumbnail(fileSource, w, h);
                        });
                    }
                    else img = thumbnailProvider.GenereateThumbnail(fileSource, w, h);
                    image.Source = img;
                }), DispatcherPriority.ApplicationIdle);
            }
    
            /// <summary>
            /// 异步线程池绑定缩略图
            /// </summary>
            private static void BindThumbnialAync(ThumbnailImage image, object fileSource, double w, double h)
            {
                IThumbnailProvider thumbnailProvider = ThumbnailProviderFactory.GetInstance(image.ThumbnailType);
                var cache = image.CacheEnable;
                var time = image.CacheTime;
                System.Utility.Executer.TryRunByThreadPool(() =>
                {
                    ImageSource img = null;
                    if (cache)
                    {
                        img = CacheManager.GetCache<ImageSource>(fileSource.GetHashCode().ToString(), time, () =>
                        {
                            return thumbnailProvider.GenereateThumbnail(fileSource, w, h);
                        });
                    }
                    else img = thumbnailProvider.GenereateThumbnail(fileSource, w, h);
                    image.Dispatcher.BeginInvoke(new Action(() => { image.Source = img; }), DispatcherPriority.ApplicationIdle);
                });
            }
        }

    其中异步用的线程池执行图片加载, Executer.TryRunByThreadPool是一个辅助方法,用于在线程池中执行一个委托方法。缓存的实现用的是另外一个轻量级内存缓存组建(使用微软HttpRuntime.Cache的缓存机制),关于缓存的方案网上很多,这里就不介绍了。

      示例代码:  

    <core:ThumbnailImage Width="120" Height="120" Margin="3" ThumbnailSource="Images/qq.png" />
                <core:ThumbnailImage Width="120" Height="120" Margin="3" ThumbnailType="WebImage" AsyncEnable="True" ThumbnailSource="http://img0.bdstatic.com/img/image/shouye/fsxzqnghbxzzzz.jpg" />
                <core:ThumbnailImage Width="160" Height="120" Margin="3" CacheEnable="True" ThumbnailType="WebImage" AsyncEnable="True" ThumbnailSource="http://www.wallsave.com/wallpapers/1920x1080/beautiful-girl/733941/beautiful-girl-girls-hd-733941.jpg" />
                <core:ThumbnailImage Width="160" Height="120" Margin="3" ThumbnailType="WebImage" AsyncEnable="True" ThumbnailSource="http://wallpaperpassion.com/upload_puzzle_thumb/16047/hot-girl-hd-wallpaper.jpg" />
                <core:FButton Width="120" Click="FButton_Click">CacheEnable</core:FButton>
                <core:ThumbnailImage x:Name="ImageCache" Width="160" CacheEnable="True" Height="120" Margin="3" ThumbnailType="WebImage" AsyncEnable="True" />

    四.动态图片gif播放控件

      由于WPF没有提供Gif的播放控件,网上有不少开源的方案,这里实现的Gif播放也是来自网上的开源代码(代码地址:http://1code.codeplex.com/)。效果不错哦!:

    实现代码:  

    /// <summary>
        /// 支持GIF动画图片播放的图片控件,GIF图片源GIFSource
        /// </summary>
        public class AnimatedGIF : Image
        {
            public static readonly DependencyProperty GIFSourceProperty = DependencyProperty.Register(
                "GIFSource", typeof(string), typeof(AnimatedGIF), new PropertyMetadata(OnSourcePropertyChanged));
    
            /// <summary>
            /// GIF图片源,支持相对路径、绝对路径
            /// </summary>
            public string GIFSource
            {
                get { return (string)GetValue(GIFSourceProperty); }
                set { SetValue(GIFSourceProperty, value); }
            }
    
            internal Bitmap Bitmap; // Local bitmap member to cache image resource
            internal BitmapSource BitmapSource;
            public delegate void FrameUpdatedEventHandler();
    
            /// <summary>
            /// Delete local bitmap resource
            /// Reference: http://msdn.microsoft.com/en-us/library/dd183539(VS.85).aspx
            /// </summary>
            [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            static extern bool DeleteObject(IntPtr hObject);
    
            protected override void OnInitialized(EventArgs e)
            {
                base.OnInitialized(e);
                this.Loaded += AnimatedGIF_Loaded;
                this.Unloaded += AnimatedGIF_Unloaded;
            }
    
            void AnimatedGIF_Unloaded(object sender, RoutedEventArgs e)
            {
                this.StopAnimate();
            }
    
            void AnimatedGIF_Loaded(object sender, RoutedEventArgs e)
            {
                BindSource(this);
            }
    
            /// <summary>
            /// Start animation
            /// </summary>
            public void StartAnimate()
            {
                ImageAnimator.Animate(Bitmap, OnFrameChanged);
            }
    
            /// <summary>
            /// Stop animation
            /// </summary>
            public void StopAnimate()
            {
                ImageAnimator.StopAnimate(Bitmap, OnFrameChanged);
            }
    
            /// <summary>
            /// Event handler for the frame changed
            /// </summary>
            private void OnFrameChanged(object sender, EventArgs e)
            {
                Dispatcher.BeginInvoke(DispatcherPriority.Normal,
                                       new FrameUpdatedEventHandler(FrameUpdatedCallback));
            }
    
            private void FrameUpdatedCallback()
            {
                ImageAnimator.UpdateFrames();
    
                if (BitmapSource != null)
                    BitmapSource.Freeze();
    
                // Convert the bitmap to BitmapSource that can be display in WPF Visual Tree
                BitmapSource = GetBitmapSource(this.Bitmap, this.BitmapSource);
                Source = BitmapSource;
                InvalidateVisual();
            }
    
            /// <summary>
            /// 属性更改处理事件
            /// </summary>
            private static void OnSourcePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
            {
                AnimatedGIF gif = sender as AnimatedGIF;
                if (gif == null) return;
                if (!gif.IsLoaded) return;
                BindSource(gif);
            }
            private static void BindSource(AnimatedGIF gif)
            {
                gif.StopAnimate();
                if (gif.Bitmap != null) gif.Bitmap.Dispose();
                var path = gif.GIFSource;
                if (path.IsInvalid()) return;
                if (!Path.IsPathRooted(path))
                {
                    path = File.GetPhysicalPath(path);
                }
                gif.Bitmap = new Bitmap(path);
                gif.BitmapSource = GetBitmapSource(gif.Bitmap, gif.BitmapSource);
                gif.StartAnimate();
            }
    
            private static BitmapSource GetBitmapSource(Bitmap bmap, BitmapSource bimg)
            {
                IntPtr handle = IntPtr.Zero;
    
                try
                {
                    handle = bmap.GetHbitmap();
                    bimg = Imaging.CreateBitmapSourceFromHBitmap(
                        handle, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
                }
                finally
                {
                    if (handle != IntPtr.Zero)
                        DeleteObject(handle);
                }
    
                return bimg;
            }
        }

    五.图片列表样式,支持大数据量的虚拟化

      先看看效果图(gif图,有点大):

     

      用的是ListView作为列表容器,因为Listview支持灵活的扩展,为了实现上面的效果,集合容器ItemsPanel只能使用WrapPanel,样式本身并不复杂:  

    <Page.Resources>
            <DataTemplate x:Key="ThumbImageItem">
                <Grid Width="140" Height="120" ToolTip="{Binding Path=DataContext.FullPath}">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="20"/>
                    </Grid.RowDefinitions>
                    <core:ThumbnailImage ThumbnailSource="{Binding File}" Width="140" Height="100" CacheEnable="True" AsyncEnable="True"  VerticalAlignment="Center" HorizontalAlignment="Center" Stretch="None"/>
                    <TextBlock Grid.Row="1" Text="{Binding Name}" FontSize="12" Height="20" HorizontalAlignment="Center" VerticalAlignment="Center" TextAlignment="Center" TextTrimming="CharacterEllipsis"/>
                    <!--<CheckBox VerticalAlignment="Top" HorizontalAlignment="Right" xly:ControlAttachProperty.FIconSize="20"/>-->
                </Grid>
            </DataTemplate>
    
            <Style x:Key="ImageListViewItem" TargetType="{x:Type ListViewItem}">
                <Setter Property="Foreground" Value="{StaticResource TextForeground}" />
                <Setter Property="HorizontalContentAlignment" Value="Stretch" />
                <Setter Property="VerticalContentAlignment" Value="Center" />
                <Setter Property="Margin" Value="2" />
                <Setter Property="SnapsToDevicePixels" Value="True" />
                <Setter Property="Background" Value="Transparent"></Setter>
                <Setter Property="Padding" Value="2,0,2,0"></Setter>
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type ListViewItem}">
                            <Border x:Name="Bd" Background="{TemplateBinding Background}" SnapsToDevicePixels="true" BorderThickness="1"
                                    BorderBrush="Transparent" Margin="{TemplateBinding Margin}">
                                <ContentPresenter x:Name="contentPresenter" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Margin="{TemplateBinding Padding}" />
                            </Border>
                            <ControlTemplate.Triggers>
                                <Trigger Property="IsSelected" Value="true">
                                    <Setter TargetName="Bd" Property="Background" Value="{StaticResource ItemSelectedBackground}" />
                                    <Setter Property="Foreground" Value="{StaticResource ItemSelectedForeground}" />
                                    <Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource FocusBorderBrush}" />
                                </Trigger>
                                <Trigger Property="IsMouseOver" Value="True">
                                    <Setter TargetName="Bd" Property="Background" Value="{StaticResource ItemMouseOverBackground}" />
                                    <Setter Property="Foreground" Value="{StaticResource ItemMouseOverForeground}" />
                                    <Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource MouseOverBorderBrush}" />
                                </Trigger>
                                <MultiTrigger>
                                    <MultiTrigger.Conditions>
                                        <Condition Property="IsSelected" Value="true" />
                                        <Condition Property="Selector.IsSelectionActive" Value="True" />
                                    </MultiTrigger.Conditions>
                                    <Setter Property="Background" Value="{StaticResource ItemSelectedBackground}" />
                                    <Setter Property="Foreground" Value="{StaticResource ItemSelectedForeground}" />
                                    <Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource FocusBorderBrush}" />
                                </MultiTrigger>
                            </ControlTemplate.Triggers>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
    
        </Page.Resources>
    
        <Grid Margin="3">
            <Grid.RowDefinitions>
                <RowDefinition Height="50"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <StackPanel Orientation="Horizontal">
                <TextBox x:Name="txtFolder"  Style="{StaticResource LabelOpenFolderTextBox}" Height="30" Width="400" Margin="5">D:DocResource</TextBox>
                <core:FButton Content="绑定" Margin="5" Click="FButton_Click"></core:FButton>
            </StackPanel>
    
            <ListView Grid.Row="1" x:Name="timgViewer" AlternationCount="0" ScrollViewer.IsDeferredScrollingEnabled="True" SelectionMode="Multiple"
                      ItemTemplate="{StaticResource ThumbImageItem}" ItemContainerStyle="{StaticResource ImageListViewItem}">
                <ListView.ItemsPanel>
                    <ItemsPanelTemplate>
                        <core:VirtualizingWrapPanel  ItemHeight="200" ItemWidth="240" Orientation="Horizontal" 
                                                    VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling"
                                                    CanVerticallyScroll="True" CanHorizontallyScroll="False" />
                    </ItemsPanelTemplate>
                </ListView.ItemsPanel>
            </ListView>
        </Grid>

    主要难道在于 WrapPanel是不支持虚拟化的,网上找了一个开源的WrapPanel虚拟化实现=VirtualizingWrapPanel,它有点小bug(滑动条长度计算有时候不是很准确),不过完全不影响使用,代码: 

    public class VirtualizingWrapPanel : VirtualizingPanel, IScrollInfo
        {
    
            #region Fields
    
            UIElementCollection _children;
            ItemsControl _itemsControl;
            IItemContainerGenerator _generator;
            private Point _offset = new Point(0, 0);
            private Size _extent = new Size(0, 0);
            private Size _viewport = new Size(0, 0);
            private int firstIndex = 0;
            private Size childSize;
            private Size _pixelMeasuredViewport = new Size(0, 0);
            Dictionary<UIElement, Rect> _realizedChildLayout = new Dictionary<UIElement, Rect>();
            WrapPanelAbstraction _abstractPanel;
    
    
            #endregion
    
            #region Properties
    
            private Size ChildSlotSize
            {
                get
                {
                    return new Size(ItemWidth, ItemHeight);
                }
            }
    
            #endregion
    
            #region Dependency Properties
    
            [TypeConverter(typeof(LengthConverter))]
            public double ItemHeight
            {
                get
                {
                    return (double)base.GetValue(ItemHeightProperty);
                }
                set
                {
                    base.SetValue(ItemHeightProperty, value);
                }
            }
    
            [TypeConverter(typeof(LengthConverter))]
            public double ItemWidth
            {
                get
                {
                    return (double)base.GetValue(ItemWidthProperty);
                }
                set
                {
                    base.SetValue(ItemWidthProperty, value);
                }
            }
    
            public Orientation Orientation
            {
                get { return (Orientation)GetValue(OrientationProperty); }
                set { SetValue(OrientationProperty, value); }
            }
    
            public static readonly DependencyProperty ItemHeightProperty = DependencyProperty.Register("ItemHeight", typeof(double), typeof(VirtualizingWrapPanel), new FrameworkPropertyMetadata(double.PositiveInfinity));
            public static readonly DependencyProperty ItemWidthProperty = DependencyProperty.Register("ItemWidth", typeof(double), typeof(VirtualizingWrapPanel), new FrameworkPropertyMetadata(double.PositiveInfinity));
            public static readonly DependencyProperty OrientationProperty = StackPanel.OrientationProperty.AddOwner(typeof(VirtualizingWrapPanel), new FrameworkPropertyMetadata(Orientation.Horizontal));
    
            #endregion
    
            #region Methods
    
            public void SetFirstRowViewItemIndex(int index)
            {
                SetVerticalOffset((index) / Math.Floor((_viewport.Width) / childSize.Width));
                SetHorizontalOffset((index) / Math.Floor((_viewport.Height) / childSize.Height));
            }
    
            private void Resizing(object sender, EventArgs e)
            {
                if (_viewport.Width != 0)
                {
                    int firstIndexCache = firstIndex;
                    _abstractPanel = null;
                    MeasureOverride(_viewport);
                    SetFirstRowViewItemIndex(firstIndex);
                    firstIndex = firstIndexCache;
                }
            }
    
            public int GetFirstVisibleSection()
            {
                int section;
                if (_abstractPanel == null) return 0;
                var maxSection = _abstractPanel.Max(x => x.Section);
                if (Orientation == Orientation.Horizontal)
                {
                    section = (int)_offset.Y;
                }
                else
                {
                    section = (int)_offset.X;
                }
                if (section > maxSection)
                    section = maxSection;
                return section;
            }
    
            public int GetFirstVisibleIndex()
            {
                if (_abstractPanel == null) return 0;
                int section = GetFirstVisibleSection();
                var item = _abstractPanel.Where(x => x.Section == section).FirstOrDefault();
                if (item != null)
                    return item._index;
                return 0;
            }
    
            private void CleanUpItems(int minDesiredGenerated, int maxDesiredGenerated)
            {
                for (int i = _children.Count - 1; i >= 0; i--)
                {
                    GeneratorPosition childGeneratorPos = new GeneratorPosition(i, 0);
                    int itemIndex = _generator.IndexFromGeneratorPosition(childGeneratorPos);
                    if (itemIndex < minDesiredGenerated || itemIndex > maxDesiredGenerated)
                    {
                        _generator.Remove(childGeneratorPos, 1);
                        RemoveInternalChildRange(i, 1);
                    }
                }
            }
    
            private void ComputeExtentAndViewport(Size pixelMeasuredViewportSize, int visibleSections)
            {
                if (Orientation == Orientation.Horizontal)
                {
                    _viewport.Height = visibleSections;
                    _viewport.Width = pixelMeasuredViewportSize.Width;
                }
                else
                {
                    _viewport.Width = visibleSections;
                    _viewport.Height = pixelMeasuredViewportSize.Height;
                }
    
                if (Orientation == Orientation.Horizontal)
                {
                    _extent.Height = _abstractPanel.SectionCount + ViewportHeight - 1;
    
                }
                else
                {
                    _extent.Width = _abstractPanel.SectionCount + ViewportWidth - 1;
                }
                _owner.InvalidateScrollInfo();
            }
    
            private void ResetScrollInfo()
            {
                _offset.X = 0;
                _offset.Y = 0;
            }
    
            private int GetNextSectionClosestIndex(int itemIndex)
            {
                var abstractItem = _abstractPanel[itemIndex];
                if (abstractItem.Section < _abstractPanel.SectionCount - 1)
                {
                    var ret = _abstractPanel.
                        Where(x => x.Section == abstractItem.Section + 1).
                        OrderBy(x => Math.Abs(x.SectionIndex - abstractItem.SectionIndex)).
                        First();
                    return ret._index;
                }
                else
                    return itemIndex;
            }
    
            private int GetLastSectionClosestIndex(int itemIndex)
            {
                var abstractItem = _abstractPanel[itemIndex];
                if (abstractItem.Section > 0)
                {
                    var ret = _abstractPanel.
                        Where(x => x.Section == abstractItem.Section - 1).
                        OrderBy(x => Math.Abs(x.SectionIndex - abstractItem.SectionIndex)).
                        First();
                    return ret._index;
                }
                else
                    return itemIndex;
            }
    
            private void NavigateDown()
            {
                var gen = _generator.GetItemContainerGeneratorForPanel(this);
                UIElement selected = (UIElement)Keyboard.FocusedElement;
                int itemIndex = gen.IndexFromContainer(selected);
                int depth = 0;
                while (itemIndex == -1)
                {
                    selected = (UIElement)VisualTreeHelper.GetParent(selected);
                    itemIndex = gen.IndexFromContainer(selected);
                    depth++;
                }
                DependencyObject next = null;
                if (Orientation == Orientation.Horizontal)
                {
                    int nextIndex = GetNextSectionClosestIndex(itemIndex);
                    next = gen.ContainerFromIndex(nextIndex);
                    while (next == null)
                    {
                        SetVerticalOffset(VerticalOffset + 1);
                        UpdateLayout();
                        next = gen.ContainerFromIndex(nextIndex);
                    }
                }
                else
                {
                    if (itemIndex == _abstractPanel._itemCount - 1)
                        return;
                    next = gen.ContainerFromIndex(itemIndex + 1);
                    while (next == null)
                    {
                        SetHorizontalOffset(HorizontalOffset + 1);
                        UpdateLayout();
                        next = gen.ContainerFromIndex(itemIndex + 1);
                    }
                }
                while (depth != 0)
                {
                    next = VisualTreeHelper.GetChild(next, 0);
                    depth--;
                }
                (next as UIElement).Focus();
            }
    
            private void NavigateLeft()
            {
                var gen = _generator.GetItemContainerGeneratorForPanel(this);
    
                UIElement selected = (UIElement)Keyboard.FocusedElement;
                int itemIndex = gen.IndexFromContainer(selected);
                int depth = 0;
                while (itemIndex == -1)
                {
                    selected = (UIElement)VisualTreeHelper.GetParent(selected);
                    itemIndex = gen.IndexFromContainer(selected);
                    depth++;
                }
                DependencyObject next = null;
                if (Orientation == Orientation.Vertical)
                {
                    int nextIndex = GetLastSectionClosestIndex(itemIndex);
                    next = gen.ContainerFromIndex(nextIndex);
                    while (next == null)
                    {
                        SetHorizontalOffset(HorizontalOffset - 1);
                        UpdateLayout();
                        next = gen.ContainerFromIndex(nextIndex);
                    }
                }
                else
                {
                    if (itemIndex == 0)
                        return;
                    next = gen.ContainerFromIndex(itemIndex - 1);
                    while (next == null)
                    {
                        SetVerticalOffset(VerticalOffset - 1);
                        UpdateLayout();
                        next = gen.ContainerFromIndex(itemIndex - 1);
                    }
                }
                while (depth != 0)
                {
                    next = VisualTreeHelper.GetChild(next, 0);
                    depth--;
                }
                (next as UIElement).Focus();
            }
    
            private void NavigateRight()
            {
                var gen = _generator.GetItemContainerGeneratorForPanel(this);
                UIElement selected = (UIElement)Keyboard.FocusedElement;
                int itemIndex = gen.IndexFromContainer(selected);
                int depth = 0;
                while (itemIndex == -1)
                {
                    selected = (UIElement)VisualTreeHelper.GetParent(selected);
                    itemIndex = gen.IndexFromContainer(selected);
                    depth++;
                }
                DependencyObject next = null;
                if (Orientation == Orientation.Vertical)
                {
                    int nextIndex = GetNextSectionClosestIndex(itemIndex);
                    next = gen.ContainerFromIndex(nextIndex);
                    while (next == null)
                    {
                        SetHorizontalOffset(HorizontalOffset + 1);
                        UpdateLayout();
                        next = gen.ContainerFromIndex(nextIndex);
                    }
                }
                else
                {
                    if (itemIndex == _abstractPanel._itemCount - 1)
                        return;
                    next = gen.ContainerFromIndex(itemIndex + 1);
                    while (next == null)
                    {
                        SetVerticalOffset(VerticalOffset + 1);
                        UpdateLayout();
                        next = gen.ContainerFromIndex(itemIndex + 1);
                    }
                }
                while (depth != 0)
                {
                    next = VisualTreeHelper.GetChild(next, 0);
                    depth--;
                }
                (next as UIElement).Focus();
            }
    
            private void NavigateUp()
            {
                var gen = _generator.GetItemContainerGeneratorForPanel(this);
                UIElement selected = (UIElement)Keyboard.FocusedElement;
                int itemIndex = gen.IndexFromContainer(selected);
                int depth = 0;
                while (itemIndex == -1)
                {
                    selected = (UIElement)VisualTreeHelper.GetParent(selected);
                    itemIndex = gen.IndexFromContainer(selected);
                    depth++;
                }
                DependencyObject next = null;
                if (Orientation == Orientation.Horizontal)
                {
                    int nextIndex = GetLastSectionClosestIndex(itemIndex);
                    next = gen.ContainerFromIndex(nextIndex);
                    while (next == null)
                    {
                        SetVerticalOffset(VerticalOffset - 1);
                        UpdateLayout();
                        next = gen.ContainerFromIndex(nextIndex);
                    }
                }
                else
                {
                    if (itemIndex == 0)
                        return;
                    next = gen.ContainerFromIndex(itemIndex - 1);
                    while (next == null)
                    {
                        SetHorizontalOffset(HorizontalOffset - 1);
                        UpdateLayout();
                        next = gen.ContainerFromIndex(itemIndex - 1);
                    }
                }
                while (depth != 0)
                {
                    next = VisualTreeHelper.GetChild(next, 0);
                    depth--;
                }
                (next as UIElement).Focus();
            }
    
    
            #endregion
    
            #region Override
    
            protected override void OnKeyDown(KeyEventArgs e)
            {
                switch (e.Key)
                {
                    case Key.Down:
                        NavigateDown();
                        e.Handled = true;
                        break;
                    case Key.Left:
                        NavigateLeft();
                        e.Handled = true;
                        break;
                    case Key.Right:
                        NavigateRight();
                        e.Handled = true;
                        break;
                    case Key.Up:
                        NavigateUp();
                        e.Handled = true;
                        break;
                    default:
                        base.OnKeyDown(e);
                        break;
                }
            }
    
    
            protected override void OnItemsChanged(object sender, ItemsChangedEventArgs args)
            {
                base.OnItemsChanged(sender, args);
                _abstractPanel = null;
                ResetScrollInfo();
            }
    
            protected override void OnInitialized(EventArgs e)
            {
                this.SizeChanged += new SizeChangedEventHandler(this.Resizing);
                base.OnInitialized(e);
                _itemsControl = ItemsControl.GetItemsOwner(this);
                _children = InternalChildren;
                _generator = ItemContainerGenerator;
            }
    
            protected override Size MeasureOverride(Size availableSize)
            {
                if (_itemsControl == null || _itemsControl.Items.Count == 0)
                    return availableSize;
                if (_abstractPanel == null)
                    _abstractPanel = new WrapPanelAbstraction(_itemsControl.Items.Count);
    
                _pixelMeasuredViewport = availableSize;
    
                _realizedChildLayout.Clear();
    
                Size realizedFrameSize = availableSize;
    
                int itemCount = _itemsControl.Items.Count;
                int firstVisibleIndex = GetFirstVisibleIndex();
    
                GeneratorPosition startPos = _generator.GeneratorPositionFromIndex(firstVisibleIndex);
    
                int childIndex = (startPos.Offset == 0) ? startPos.Index : startPos.Index + 1;
                int current = firstVisibleIndex;
                int visibleSections = 1;
                using (_generator.StartAt(startPos, GeneratorDirection.Forward, true))
                {
                    bool stop = false;
                    bool isHorizontal = Orientation == Orientation.Horizontal;
                    double currentX = 0;
                    double currentY = 0;
                    double maxItemSize = 0;
                    int currentSection = GetFirstVisibleSection();
                    while (current < itemCount)
                    {
                        bool newlyRealized;
    
                        // Get or create the child                    
                        UIElement child = _generator.GenerateNext(out newlyRealized) as UIElement;
                        if (newlyRealized)
                        {
                            // Figure out if we need to insert the child at the end or somewhere in the middle
                            if (childIndex >= _children.Count)
                            {
                                base.AddInternalChild(child);
                            }
                            else
                            {
                                base.InsertInternalChild(childIndex, child);
                            }
                            _generator.PrepareItemContainer(child);
                            child.Measure(ChildSlotSize);
                        }
                        else
                        {
                            // The child has already been created, let's be sure it's in the right spot
                            Debug.Assert(child == _children[childIndex], "Wrong child was generated");
                        }
                        childSize = child.DesiredSize;
                        Rect childRect = new Rect(new Point(currentX, currentY), childSize);
                        if (isHorizontal)
                        {
                            maxItemSize = Math.Max(maxItemSize, childRect.Height);
                            if (childRect.Right > realizedFrameSize.Width) //wrap to a new line
                            {
                                currentY = currentY + maxItemSize;
                                currentX = 0;
                                maxItemSize = childRect.Height;
                                childRect.X = currentX;
                                childRect.Y = currentY;
                                currentSection++;
                                visibleSections++;
                            }
                            if (currentY > realizedFrameSize.Height)
                                stop = true;
                            currentX = childRect.Right;
                        }
                        else
                        {
                            maxItemSize = Math.Max(maxItemSize, childRect.Width);
                            if (childRect.Bottom > realizedFrameSize.Height) //wrap to a new column
                            {
                                currentX = currentX + maxItemSize;
                                currentY = 0;
                                maxItemSize = childRect.Width;
                                childRect.X = currentX;
                                childRect.Y = currentY;
                                currentSection++;
                                visibleSections++;
                            }
                            if (currentX > realizedFrameSize.Width)
                                stop = true;
                            currentY = childRect.Bottom;
                        }
                        _realizedChildLayout.Add(child, childRect);
                        _abstractPanel.SetItemSection(current, currentSection);
    
                        if (stop)
                            break;
                        current++;
                        childIndex++;
                    }
                }
                CleanUpItems(firstVisibleIndex, current - 1);
    
                ComputeExtentAndViewport(availableSize, visibleSections);
    
                return availableSize;
            }
            protected override Size ArrangeOverride(Size finalSize)
            {
                if (_children != null)
                {
                    foreach (UIElement child in _children)
                    {
                        var layoutInfo = _realizedChildLayout[child];
                        child.Arrange(layoutInfo);
                    }
                }
                return finalSize;
            }
    
            #endregion
    
            #region IScrollInfo Members
    
            private bool _canHScroll = false;
            public bool CanHorizontallyScroll
            {
                get { return _canHScroll; }
                set { _canHScroll = value; }
            }
    
            private bool _canVScroll = false;
            public bool CanVerticallyScroll
            {
                get { return _canVScroll; }
                set { _canVScroll = value; }
            }
    
            public double ExtentHeight
            {
                get { return _extent.Height; }
            }
    
            public double ExtentWidth
            {
                get { return _extent.Width; }
            }
    
            public double HorizontalOffset
            {
                get { return _offset.X; }
            }
    
            public double VerticalOffset
            {
                get { return _offset.Y; }
            }
    
            public void LineDown()
            {
                if (Orientation == Orientation.Vertical)
                    SetVerticalOffset(VerticalOffset + 20);
                else
                    SetVerticalOffset(VerticalOffset + 1);
            }
    
            public void LineLeft()
            {
                if (Orientation == Orientation.Horizontal)
                    SetHorizontalOffset(HorizontalOffset - 20);
                else
                    SetHorizontalOffset(HorizontalOffset - 1);
            }
    
            public void LineRight()
            {
                if (Orientation == Orientation.Horizontal)
                    SetHorizontalOffset(HorizontalOffset + 20);
                else
                    SetHorizontalOffset(HorizontalOffset + 1);
            }
    
            public void LineUp()
            {
                if (Orientation == Orientation.Vertical)
                    SetVerticalOffset(VerticalOffset - 20);
                else
                    SetVerticalOffset(VerticalOffset - 1);
            }
    
            public Rect MakeVisible(Visual visual, Rect rectangle)
            {
                var gen = (ItemContainerGenerator)_generator.GetItemContainerGeneratorForPanel(this);
                var element = (UIElement)visual;
                int itemIndex = gen.IndexFromContainer(element);
                while (itemIndex == -1)
                {
                    element = (UIElement)VisualTreeHelper.GetParent(element);
                    itemIndex = gen.IndexFromContainer(element);
                }
                int section = _abstractPanel[itemIndex].Section;
                Rect elementRect = _realizedChildLayout[element];
                if (Orientation == Orientation.Horizontal)
                {
                    double viewportHeight = _pixelMeasuredViewport.Height;
                    if (elementRect.Bottom > viewportHeight)
                        _offset.Y += 1;
                    else if (elementRect.Top < 0)
                        _offset.Y -= 1;
                }
                else
                {
                    double viewportWidth = _pixelMeasuredViewport.Width;
                    if (elementRect.Right > viewportWidth)
                        _offset.X += 1;
                    else if (elementRect.Left < 0)
                        _offset.X -= 1;
                }
                InvalidateMeasure();
                return elementRect;
            }
    
            public void MouseWheelDown()
            {
                PageDown();
            }
    
            public void MouseWheelLeft()
            {
                PageLeft();
            }
    
            public void MouseWheelRight()
            {
                PageRight();
            }
    
            public void MouseWheelUp()
            {
                PageUp();
            }
    
            public void PageDown()
            {
                SetVerticalOffset(VerticalOffset + _viewport.Height * 0.8);
            }
    
            public void PageLeft()
            {
                SetHorizontalOffset(HorizontalOffset - _viewport.Width * 0.8);
            }
    
            public void PageRight()
            {
                SetHorizontalOffset(HorizontalOffset + _viewport.Width * 0.8);
            }
    
            public void PageUp()
            {
                SetVerticalOffset(VerticalOffset - _viewport.Height * 0.8);
            }
    
            private ScrollViewer _owner;
            public ScrollViewer ScrollOwner
            {
                get { return _owner; }
                set { _owner = value; }
            }
    
            public void SetHorizontalOffset(double offset)
            {
                if (offset < 0 || _viewport.Width >= _extent.Width)
                {
                    offset = 0;
                }
                else
                {
                    if (offset + _viewport.Width >= _extent.Width)
                    {
                        offset = _extent.Width - _viewport.Width;
                    }
                }
    
                _offset.X = offset;
    
                if (_owner != null)
                    _owner.InvalidateScrollInfo();
    
                InvalidateMeasure();
                firstIndex = GetFirstVisibleIndex();
            }
    
            public void SetVerticalOffset(double offset)
            {
                if (offset < 0 || _viewport.Height >= _extent.Height)
                {
                    offset = 0;
                }
                else
                {
                    if (offset + _viewport.Height >= _extent.Height)
                    {
                        offset = _extent.Height - _viewport.Height;
                    }
                }
    
                _offset.Y = offset;
    
                if (_owner != null)
                    _owner.InvalidateScrollInfo();
    
                //_trans.Y = -offset;
    
                InvalidateMeasure();
                firstIndex = GetFirstVisibleIndex();
            }
    
            public double ViewportHeight
            {
                get { return _viewport.Height; }
            }
    
            public double ViewportWidth
            {
                get { return _viewport.Width; }
            }
    
            #endregion
    
            #region helper data structures
    
            class ItemAbstraction
            {
                public ItemAbstraction(WrapPanelAbstraction panel, int index)
                {
                    _panel = panel;
                    _index = index;
                }
    
                WrapPanelAbstraction _panel;
    
                public readonly int _index;
    
                int _sectionIndex = -1;
                public int SectionIndex
                {
                    get
                    {
                        if (_sectionIndex == -1)
                        {
                            return _index % _panel._averageItemsPerSection - 1;
                        }
                        return _sectionIndex;
                    }
                    set
                    {
                        if (_sectionIndex == -1)
                            _sectionIndex = value;
                    }
                }
    
                int _section = -1;
                public int Section
                {
                    get
                    {
                        if (_section == -1)
                        {
                            return _index / _panel._averageItemsPerSection;
                        }
                        return _section;
                    }
                    set
                    {
                        if (_section == -1)
                            _section = value;
                    }
                }
            }
    
            class WrapPanelAbstraction : IEnumerable<ItemAbstraction>
            {
                public WrapPanelAbstraction(int itemCount)
                {
                    List<ItemAbstraction> items = new List<ItemAbstraction>(itemCount);
                    for (int i = 0; i < itemCount; i++)
                    {
                        ItemAbstraction item = new ItemAbstraction(this, i);
                        items.Add(item);
                    }
    
                    Items = new ReadOnlyCollection<ItemAbstraction>(items);
                    _averageItemsPerSection = itemCount;
                    _itemCount = itemCount;
                }
    
                public readonly int _itemCount;
                public int _averageItemsPerSection;
                private int _currentSetSection = -1;
                private int _currentSetItemIndex = -1;
                private int _itemsInCurrentSecction = 0;
                private object _syncRoot = new object();
    
                public int SectionCount
                {
                    get
                    {
                        int ret = _currentSetSection + 1;
                        if (_currentSetItemIndex + 1 < Items.Count)
                        {
                            int itemsLeft = Items.Count - _currentSetItemIndex;
                            ret += itemsLeft / _averageItemsPerSection + 1;
                        }
                        return ret;
                    }
                }
    
                private ReadOnlyCollection<ItemAbstraction> Items { get; set; }
    
                public void SetItemSection(int index, int section)
                {
                    lock (_syncRoot)
                    {
                        if (section <= _currentSetSection + 1 && index == _currentSetItemIndex + 1)
                        {
                            _currentSetItemIndex++;
                            Items[index].Section = section;
                            if (section == _currentSetSection + 1)
                            {
                                _currentSetSection = section;
                                if (section > 0)
                                {
                                    _averageItemsPerSection = (index) / (section);
                                }
                                _itemsInCurrentSecction = 1;
                            }
                            else
                                _itemsInCurrentSecction++;
                            Items[index].SectionIndex = _itemsInCurrentSecction - 1;
                        }
                    }
                }
    
                public ItemAbstraction this[int index]
                {
                    get { return Items[index]; }
                }
    
                #region IEnumerable<ItemAbstraction> Members
    
                public IEnumerator<ItemAbstraction> GetEnumerator()
                {
                    return Items.GetEnumerator();
                }
    
                #endregion
    
                #region IEnumerable Members
    
                System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
                {
                    return GetEnumerator();
                }
    
                #endregion
            }
    
            #endregion
        }

    原文地址:https://www.cnblogs.com/anding/p/5009120.html

  • 相关阅读:
    Codeforces Round #260 (Div. 2)
    面试题:给定数组a,找到最大的j-i, 使a[j]>a[i]
    ssh自动输入密码脚本 切换目录脚本
    make工作时的执行步骤
    Codeforces Round #259 (Div. 2)
    Codeforces Round #258 (Div. 2)
    如何在半径为1的圆中随机选取一个点
    面试中常问的有关随机选取k个数的总结
    topcoder SRM 628 DIV2 BracketExpressions
    topcoder SRM 628 DIV2 BishopMove
  • 原文地址:https://www.cnblogs.com/mqxs/p/10142496.html
Copyright © 2020-2023  润新知