博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表
阅读量:6672 次
发布时间:2019-06-25

本文共 45094 字,大约阅读时间需要 150 分钟。

原文:

一.前言

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

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

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

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

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

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

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

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

///         /// 使用System.Drawing.Image创建WPF使用的ImageSource类型缩略图(不放大小图)        ///         /// System.Drawing.Image 对象        /// 指定宽度        /// 指定高度        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对象  

///         /// 创建WPF使用的ImageSource类型缩略图(不放大小图)        ///         /// 本地图片路径        /// 指定宽度        /// 指定高度        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对象  

///         /// 从一个Bitmap创建ImageSource        ///         /// Bitmap对象        /// 
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对象  

///         /// 从数据流创建缩略图        ///  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 多种类型的缩略图扩展

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

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

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

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

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

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

///     /// 本地图片缩略图创建服务    ///     internal class ImageThumbnailProvider : IThumbnailProvider    {        ///         /// 创建缩略图。fileName:文件路径;width:图片宽度;height:高度        ///         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方法):  

///     /// 网络图片缩略图创建服务    ///     internal class WebImageThumbnailProvider : IThumbnailProvider    {        ///         /// 创建缩略图。fileName:文件路径;width:图片宽度;height:高度        ///         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实现:  

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

 

3.2 缩略图控件ThumbnailImage

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

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

/*     * 较大的图片,视频,网络图片要做缓存处理:缓存缩略图为本地文件,或内存缩略图对象。     */    ///     /// 缩略图图片显示控件,同时支持图片和视频缩略图    ///     public class ThumbnailImage : Image    {        ///         /// 是否启用缓存,默认false不启用        ///         public bool CacheEnable        {            get { return (bool)GetValue(CacheEnableProperty); }            set { SetValue(CacheEnableProperty, value); }        }        ///         /// 是否启用缓存,默认false不启用.默认缓存时间是180秒        ///         public static readonly DependencyProperty CacheEnableProperty =            DependencyProperty.Register("CacheEnable", typeof(bool), typeof(ThumbnailImage), new PropertyMetadata(false));        ///         /// 缓存时间,单位秒。默认180秒        ///         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));        ///         /// 是否启用异步加载,网络图片建议启用,本地图可以不需要。默认不起用异步        ///         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));        ///         /// 缩略图类型,默认Image图片        ///         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));        ///         /// 缩略图数据源:文件物理路径        ///         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));        ///         /// 缩略图        ///         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);        }        ///         /// 属性更改处理事件        ///         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);            }        }        ///         /// 绑定缩略图        ///         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
(fileSource.GetHashCode().ToString(), time, () => { return thumbnailProvider.GenereateThumbnail(fileSource, w, h); }); } else img = thumbnailProvider.GenereateThumbnail(fileSource, w, h); image.Source = img; }), DispatcherPriority.ApplicationIdle); } ///
/// 异步线程池绑定缩略图 /// 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
(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); }); } }
View Code

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

  示例代码:  

CacheEnable

 

四.动态图片gif播放控件

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

实现代码:  

///     /// 支持GIF动画图片播放的图片控件,GIF图片源GIFSource    ///     public class AnimatedGIF : Image    {        public static readonly DependencyProperty GIFSourceProperty = DependencyProperty.Register(            "GIFSource", typeof(string), typeof(AnimatedGIF), new PropertyMetadata(OnSourcePropertyChanged));        ///         /// GIF图片源,支持相对路径、绝对路径        ///         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();        ///         /// Delete local bitmap resource        /// Reference: http://msdn.microsoft.com/en-us/library/dd183539(VS.85).aspx        ///         [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);        }        ///         /// Start animation        ///         public void StartAnimate()        {            ImageAnimator.Animate(Bitmap, OnFrameChanged);        }        ///         /// Stop animation        ///         public void StopAnimate()        {            ImageAnimator.StopAnimate(Bitmap, OnFrameChanged);        }        ///         /// Event handler for the frame changed        ///         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();        }        ///         /// 属性更改处理事件        ///         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;        }    }
View Code

 

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

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

 

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

D:\Doc\Resource

  主要难道在于 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
_realizedChildLayout = new Dictionary
(); 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
{ public WrapPanelAbstraction(int itemCount) { List
items = new List
(itemCount); for (int i = 0; i < itemCount; i++) { ItemAbstraction item = new ItemAbstraction(this, i); items.Add(item); } Items = new ReadOnlyCollection
(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
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
Members public IEnumerator
GetEnumerator() { return Items.GetEnumerator(); } #endregion #region IEnumerable Members System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion } #endregion }
View Code

 

附录:参考引用  

 

 

版权所有,文章来源:

个人能力有限,本文内容仅供学习、探讨,欢迎指正、交流。

转载地址:http://uhrxo.baihongyu.com/

你可能感兴趣的文章
C#:CodeSmith根据数据库中的表创建C#数据模型Model + 因为没有钱买正版,所以附加自己写的小代码...
查看>>
乐视4.14硬件免费日de用户体验
查看>>
有选择的复制对象,即根据客户端传值来复制对象属性值
查看>>
随机输入一个数字,判断这个数字是不是5的倍数
查看>>
C — 对C语言的认识
查看>>
linkin大话数据结构--Set
查看>>
接口測试-HAR
查看>>
$.each 和$(selector).each()的区别
查看>>
45435
查看>>
JSON格式自动解析遇到的调用方法问题.fromJson() ..readValue()
查看>>
Crystal Reports for Visual Studio 2015 安装
查看>>
iOS UI 15 网络编程下载 图片 音乐 大文件 视频 get/ post方法
查看>>
linux文件系统 - 初始化(二)
查看>>
Python的可视化图表工具集
查看>>
《条目二十九:对于逐个字符的输入请考虑istreambuf_iterator》
查看>>
Python的优点与功能
查看>>
三个文件,
查看>>
webpack的总结
查看>>
hibernate 一级缓存和二级缓存
查看>>
javac不是内部或外部命令
查看>>