一.前言

  申明: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 < )
{
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 < )
{
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:文件路径;width:图片宽度;height:高度
/// </summary>
ImageSource GenereateThumbnail(object fileSource, double width, double height);
}

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

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

/// <summary>
/// 本地图片缩略图创建服务
/// </summary>
internal class ImageThumbnailProvider : IThumbnailProvider
{
/// <summary>
/// 创建缩略图。fileName:文件路径;width:图片宽度;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:文件路径;width:图片宽度;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 = ;
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()); /// <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:\Doc\Resource</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(, );
private Size _extent = new Size(, );
private Size _viewport = new Size(, );
private int firstIndex = ;
private Size childSize;
private Size _pixelMeasuredViewport = new Size(, );
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 != )
{
int firstIndexCache = firstIndex;
_abstractPanel = null;
MeasureOverride(_viewport);
SetFirstRowViewItemIndex(firstIndex);
firstIndex = firstIndexCache;
}
} public int GetFirstVisibleSection()
{
int section;
if (_abstractPanel == null) return ;
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 ;
int section = GetFirstVisibleSection();
var item = _abstractPanel.Where(x => x.Section == section).FirstOrDefault();
if (item != null)
return item._index;
return ;
} private void CleanUpItems(int minDesiredGenerated, int maxDesiredGenerated)
{
for (int i = _children.Count - ; i >= ; i--)
{
GeneratorPosition childGeneratorPos = new GeneratorPosition(i, );
int itemIndex = _generator.IndexFromGeneratorPosition(childGeneratorPos);
if (itemIndex < minDesiredGenerated || itemIndex > maxDesiredGenerated)
{
_generator.Remove(childGeneratorPos, );
RemoveInternalChildRange(i, );
}
}
} 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 - ; }
else
{
_extent.Width = _abstractPanel.SectionCount + ViewportWidth - ;
}
_owner.InvalidateScrollInfo();
} private void ResetScrollInfo()
{
_offset.X = ;
_offset.Y = ;
} private int GetNextSectionClosestIndex(int itemIndex)
{
var abstractItem = _abstractPanel[itemIndex];
if (abstractItem.Section < _abstractPanel.SectionCount - )
{
var ret = _abstractPanel.
Where(x => x.Section == abstractItem.Section + ).
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 > )
{
var ret = _abstractPanel.
Where(x => x.Section == abstractItem.Section - ).
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 = ;
while (itemIndex == -)
{
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 + );
UpdateLayout();
next = gen.ContainerFromIndex(nextIndex);
}
}
else
{
if (itemIndex == _abstractPanel._itemCount - )
return;
next = gen.ContainerFromIndex(itemIndex + );
while (next == null)
{
SetHorizontalOffset(HorizontalOffset + );
UpdateLayout();
next = gen.ContainerFromIndex(itemIndex + );
}
}
while (depth != )
{
next = VisualTreeHelper.GetChild(next, );
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 = ;
while (itemIndex == -)
{
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 - );
UpdateLayout();
next = gen.ContainerFromIndex(nextIndex);
}
}
else
{
if (itemIndex == )
return;
next = gen.ContainerFromIndex(itemIndex - );
while (next == null)
{
SetVerticalOffset(VerticalOffset - );
UpdateLayout();
next = gen.ContainerFromIndex(itemIndex - );
}
}
while (depth != )
{
next = VisualTreeHelper.GetChild(next, );
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 = ;
while (itemIndex == -)
{
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 + );
UpdateLayout();
next = gen.ContainerFromIndex(nextIndex);
}
}
else
{
if (itemIndex == _abstractPanel._itemCount - )
return;
next = gen.ContainerFromIndex(itemIndex + );
while (next == null)
{
SetVerticalOffset(VerticalOffset + );
UpdateLayout();
next = gen.ContainerFromIndex(itemIndex + );
}
}
while (depth != )
{
next = VisualTreeHelper.GetChild(next, );
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 = ;
while (itemIndex == -)
{
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 - );
UpdateLayout();
next = gen.ContainerFromIndex(nextIndex);
}
}
else
{
if (itemIndex == )
return;
next = gen.ContainerFromIndex(itemIndex - );
while (next == null)
{
SetHorizontalOffset(HorizontalOffset - );
UpdateLayout();
next = gen.ContainerFromIndex(itemIndex - );
}
}
while (depth != )
{
next = VisualTreeHelper.GetChild(next, );
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 == )
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 == ) ? startPos.Index : startPos.Index + ;
int current = firstVisibleIndex;
int visibleSections = ;
using (_generator.StartAt(startPos, GeneratorDirection.Forward, true))
{
bool stop = false;
bool isHorizontal = Orientation == Orientation.Horizontal;
double currentX = ;
double currentY = ;
double maxItemSize = ;
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 = ;
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 = ;
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 - ); 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 + );
else
SetVerticalOffset(VerticalOffset + );
} public void LineLeft()
{
if (Orientation == Orientation.Horizontal)
SetHorizontalOffset(HorizontalOffset - );
else
SetHorizontalOffset(HorizontalOffset - );
} public void LineRight()
{
if (Orientation == Orientation.Horizontal)
SetHorizontalOffset(HorizontalOffset + );
else
SetHorizontalOffset(HorizontalOffset + );
} public void LineUp()
{
if (Orientation == Orientation.Vertical)
SetVerticalOffset(VerticalOffset - );
else
SetVerticalOffset(VerticalOffset - );
} public Rect MakeVisible(Visual visual, Rect rectangle)
{
var gen = (ItemContainerGenerator)_generator.GetItemContainerGeneratorForPanel(this);
var element = (UIElement)visual;
int itemIndex = gen.IndexFromContainer(element);
while (itemIndex == -)
{
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 += ;
else if (elementRect.Top < )
_offset.Y -= ;
}
else
{
double viewportWidth = _pixelMeasuredViewport.Width;
if (elementRect.Right > viewportWidth)
_offset.X += ;
else if (elementRect.Left < )
_offset.X -= ;
}
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 < || _viewport.Width >= _extent.Width)
{
offset = ;
}
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 < || _viewport.Height >= _extent.Height)
{
offset = ;
}
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 = -;
public int SectionIndex
{
get
{
if (_sectionIndex == -)
{
return _index % _panel._averageItemsPerSection - ;
}
return _sectionIndex;
}
set
{
if (_sectionIndex == -)
_sectionIndex = value;
}
} int _section = -;
public int Section
{
get
{
if (_section == -)
{
return _index / _panel._averageItemsPerSection;
}
return _section;
}
set
{
if (_section == -)
_section = value;
}
}
} class WrapPanelAbstraction : IEnumerable<ItemAbstraction>
{
public WrapPanelAbstraction(int itemCount)
{
List<ItemAbstraction> items = new List<ItemAbstraction>(itemCount);
for (int i = ; 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 = -;
private int _currentSetItemIndex = -;
private int _itemsInCurrentSecction = ;
private object _syncRoot = new object(); public int SectionCount
{
get
{
int ret = _currentSetSection + ;
if (_currentSetItemIndex + < Items.Count)
{
int itemsLeft = Items.Count - _currentSetItemIndex;
ret += itemsLeft / _averageItemsPerSection + ;
}
return ret;
}
} private ReadOnlyCollection<ItemAbstraction> Items { get; set; } public void SetItemSection(int index, int section)
{
lock (_syncRoot)
{
if (section <= _currentSetSection + && index == _currentSetItemIndex + )
{
_currentSetItemIndex++;
Items[index].Section = section;
if (section == _currentSetSection + )
{
_currentSetSection = section;
if (section > )
{
_averageItemsPerSection = (index) / (section);
}
_itemsInCurrentSecction = ;
}
else
_itemsInCurrentSecction++;
Items[index].SectionIndex = _itemsInCurrentSecction - ;
}
}
} public ItemAbstraction this[int index]
{
get { return Items[index]; }
} #region IEnumerable<ItemAbstraction> Members public IEnumerator<ItemAbstraction> GetEnumerator()
{
return Items.GetEnumerator();
} #endregion #region IEnumerable Members System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
} #endregion
} #endregion
}

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

【转】WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表的更多相关文章

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

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

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

    一.图标字体 图标字体在网页开发上运用非常广泛,具体可以网络搜索了解,网页上的运用有很多例子,如Bootstrap.但在C/S程序中使用还不多,字体图标其实就是把矢量图形打包到字体文件里,就像使用一般 ...

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

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

  4. WPF自定义控件与样式(15)-终结篇 & 系列文章索引 & 源码共享

    系列文章目录  WPF自定义控件与样式(1)-矢量字体图标(iconfont) WPF自定义控件与样式(2)-自定义按钮FButton WPF自定义控件与样式(3)-TextBox & Ric ...

  5. WPF自定义控件与样式(13)-自定义窗体Window & 自适应内容大小消息框MessageBox

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

  6. WPF自定义控件与样式(14)-轻量MVVM模式实践

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

  7. WPF自定义控件与样式(15)-终结篇

    原文:WPF自定义控件与样式(15)-终结篇 系列文章目录  WPF自定义控件与样式(1)-矢量字体图标(iconfont) WPF自定义控件与样式(2)-自定义按钮FButton WPF自定义控件与 ...

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

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

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

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

随机推荐

  1. Apache JMeter5 设置中文

    Apache JMeter5 下载: apache-jmeter-5.0.zip apache-jmeter-5.0.tgz 注意:JMeter5需要Java8 以上,本文环境是Win7 64位 1. ...

  2. Expedition [POJ2431] [贪心]

    题目大意: 有n个加油站,每个加油站的加油的油量有限,距离终点都有一个距离. 一个卡车的油箱无限,每走一个单元要消耗一单元的油,问卡车到达终点的最少加多少次油. 分析: 我们希望的是走到没油的时候就尽 ...

  3. Dijkstra求次短路

    #10076.「一本通 3.2 练习 2」Roadblocks:https://loj.ac/problem/10076 解法: 次短路具有一种性质:次短路一定是由起点到点x的最短路 + x到y的距离 ...

  4. PHP中new self()和new static()的区别

    1.new static()是在PHP5.3版本中引入的新特性. 2.无论是new static()还是new self(),都是new了一个新的对象. 3.这两个方法new出来的对象有什么区别呢,说 ...

  5. python实现23种设计模式

    本文源码寄方于github:https://github.com/w392807287/Design_pattern_of_python 参考文献: <大话设计模式>——吴强 <Py ...

  6. PAT Basic 1004

    1004 成绩排名 (20 分) 读入 n(>0)名学生的姓名.学号.成绩,分别输出成绩最高和成绩最低学生的姓名和学号. 输入格式: 每个测试输入包含 1 个测试用例,格式为 第 1 行:正整数 ...

  7. python之socket编程3

    1 什么是粘包 只有TCP有粘包现象,UDP永远不会粘包 应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向连接的,面向流的,收 ...

  8. mysql:Cannot proceed because system tables used by Event Scheduler were found damaged at server start

    mysql 5.7.18 sqlyog访问数据库,查看表数据时,出现 Cannot proceed because system tables used by Event Scheduler were ...

  9. python asyncio

    3. 真-官网文档    ----超级全 http://aiohttp.readthedocs.io/en/stable/client.html#make-a-request 2. 官网文档: htt ...

  10. 8、jsのBOM对象与DOM对象

    javascript的Bom和Dom对象使我们学习的重点,这篇随笔可以重点阅读 本篇导航: BOM对象 DOM对象 DOM Event(事件) 实例练习 一.BOM对象 1.window对象 所有浏览 ...