zoukankan      html  css  js  c++  java
  • 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
        }
    复制代码

    附录:参考引用  

    WPF自定义控件与样式(1)-矢量字体图标(iconfont)

    WPF自定义控件与样式(2)-自定义按钮FButton

    WPF自定义控件与样式(3)-TextBox & RichTextBox & PasswordBox样式、水印、Label标签、功能扩展

    WPF自定义控件与样式(4)-CheckBox/RadioButton自定义样式

    WPF自定义控件与样式(5)-Calendar/DatePicker日期控件自定义样式及扩展

    WPF自定义控件与样式(6)-ScrollViewer与ListBox自定义样式

    WPF自定义控件与样式(7)-列表控件DataGrid与ListView自定义样式

    WPF自定义控件与样式(8)-ComboBox与自定义多选控件MultComboBox

    WPF自定义控件与样式(9)-树控件TreeView与菜单Menu-ContextMenu

    WPF自定义控件与样式(10)-进度控件ProcessBar自定义样 

    WPF自定义控件与样式(11)-等待/忙/正在加载状态-控件实现

    版权所有,文章来源:http://www.cnblogs.com/anding

  • 相关阅读:
    【转+补充】在OpenCV for Android 2.4.5中使用SURF(nonfree module)
    Delphi StarOffice Framework Beta 1.0 发布
    Angular ngIf相关问题
    angularjs文档下载
    公众号微信支付开发
    公众号第三方平台开发 教程六 代公众号使用JS SDK说明
    公众号第三方平台开发 教程五 代公众号处理消息和事件
    公众号第三方平台开发 教程四 代公众号发起网页授权说明
    公众号第三方平台开发 教程三 微信公众号授权第三方平台
    公众号第三方平台开发 教程二 component_verify_ticket和accessToken的获取
  • 原文地址:https://www.cnblogs.com/ljdong7/p/12115241.html
Copyright © 2011-2022 走看看