实例介绍
【实例截图】
【核心代码】
using System;
using System.ComponentModel;
using System.Collections;
using System.Diagnostics ;
using System.Drawing ;
using System.Drawing.Drawing2D ;
using System.Drawing.Imaging ;
using System.Windows.Forms ;
using System.Runtime.InteropServices ;
namespace UIComponents {
/// <summary>
/// ImagePanel Control displays a set of source images allowing the
/// user to select an image using the mouse or keyboard
/// </summary>
/// <remarks>
/// The <c>ImagePanel</c> can be run in two different modes:
/// <list type="bullet">
/// <item>Static Control</item>
/// <item>Popup (like a context menu)</item>
/// </list>
/// <para>
/// Overall functionality is the same but when in popup mode, various actions/events
/// cause the <c>ImagePanel</c> to be hidden
/// </para>
/// <para>
/// <c>ImagePanel</c> has the following primary properties:
/// <list type="table">
/// <listheader>
/// <term>Property</term>
/// <description>Purpose</description>
/// </listheader>
/// <item>
/// <term><see cref="Dimensions"/></term>
/// <description>Determines the row/column arrangement for the image grid</description>
/// </item>
/// <item>
/// <term><see cref="HighlightColor"/></term>
/// <description></description>
/// </item>
/// <item>
/// <term><see cref="SelectedColor"/></term>
/// <description></description>
/// </item>
/// <item>
/// <term><see cref="GridColor"/></term>
/// <description></description>
/// </item>
/// <item>
/// <term><see cref="PanelGridSize"/></term>
/// <description></description>
/// </item>
/// <item>
/// <term><see cref="AutoSelect"/></term>
/// <description></description>
/// </item>
/// <item>
/// <term><see cref="Images"/></term>
/// <description></description>
/// </item>
/// </list>
/// </para>
/// <para>
/// <c>ImagePanel</c> has the following primary events:
/// <list type="table">
/// <listheader>
/// <term>Event</term>
/// <description>Purpose</description>
/// </listheader>
/// <item>
/// <term><see cref="PropertyChange"/></term>
/// <description></description>
/// </item>
/// <item>
/// <term><see cref="ImageSelected"/></term>
/// <description></description>
/// </item>
/// </list>
/// </para>
/// <para>
/// <c>ImagePanel</c> has the following primary methods:
/// <list type="table">
/// <listheader>
/// <term>Method</term>
/// <description>Purpose</description>
/// </listheader>
/// <item>
/// <term><see cref="SetImages(Bitmap,int)"/></term>
/// <description></description>
/// </item>
/// <item>
/// <term><see cref="Popup(int,int,Control)"/></term>
/// <description></description>
/// </item>
/// </list>
/// </para>
/// </remarks>
public class ImagePanel : ScrollableControl {
#region ImagePanel enums
/// <summary>
/// Enumeration providing hint on how <see cref="ImagePanel"/> layout
/// should appear
/// </summary>
public enum PanelSizeHints {
/// <summary>
/// Minimize the number of columns
/// </summary>
/// <remarks>
/// <c>ImagePanel</c> will be vertically oriented
/// </remarks>
MinimizeColumns=0,
/// <summary>
/// Minimize the number of rows
/// </summary>
/// <remarks>
/// <c>ImagePanel</c> will be horizontally oriented
/// </remarks>
MinimizeRows=2,
/// <summary>
/// Minimize both the number of rows and columns
/// </summary>
/// <remarks>
/// <c>ImagePanel</c> will be a square
/// </remarks>
MinimizeBoth
}
/// <summary>
/// Enumeration used to define X/Y coordinates
/// for Popup mode
/// </summary>
public enum ImagePanelPlacement {
/// <summary>
/// Coordinates specify Top/Left
/// </summary>
TopLeft,
/// <summary>
/// Coordinates specify Top/Right
/// </summary>
TopRight,
/// <summary>
/// Coordinates specify Bottom/Left
/// </summary>
BottomLeft,
/// <summary>
/// Coordinates specify Bottom/Right
/// </summary>
BottomRight
}
/// <summary>
/// Enumeration of property arguments for <see cref="ImagePanel.PropertyChange"/>
/// </summary>
public enum ImagePanelProperties {
/// <summary>
/// <see cref="ImagePanel.OnBackColorChanged"/>
/// </summary>
BackColorProperty,
/// <summary>
/// <see cref="ImagePanel.PanelGridSize"/>
/// </summary>
PanelGridSizeProperty,
/// <summary>
/// <see cref="ImagePanel.GridColor"/>
/// </summary>
GridColorProperty,
/// <summary>
/// <see cref="ImagePanel.SelectedColor"/>
/// </summary>
SelectedColorsProperty,
/// <summary>
/// <see cref="ImagePanel.SetImages(Bitmap,int)"/>
/// </summary>
ImagesProperty,
/// <summary>
/// <see cref="ImagePanel.AutoSelect"/>
/// </summary>
AutoSelectProperty,
/// <summary>
/// <see cref="ImagePanel.Dimensions"/>
/// </summary>
DimensionsProperty,
/// <summary>
/// <see cref="ImagePanel.DefaultImage"/>
/// </summary>
DefaultImageProperty,
/// <summary>
/// <see cref="BounceFactor"/>
/// </summary>
BounceFactorProperty,
/// <summary>
/// <see cref="MaxBounceRatio"/>
/// </summary>
MaxBounceRatioProperty,
/// <summary>
/// <see cref="MinBounceRatio"/>
/// </summary>
MinBounceRatioProperty
}
#endregion ImagePanel enums
#region class PropertyChangeEventArgs
/// <summary>
/// <see cref="XPPanelGroup.PropertyChange"/> event arguments
/// </summary>
public class PropertyChangeEventArgs : System.EventArgs {
/// <summary>
/// The enumeration for the property that changed
/// </summary>
private readonly ImagePanelProperties property ;
/// <summary>
/// Create a <c>PropertyChangeEventArgs</c>
/// </summary>
/// <param name="property">The enumeration for the property that changed</param>
public PropertyChangeEventArgs(ImagePanelProperties property) {
this.property = property ;
}
/// <summary>
/// Get the enumeration for the property that changed
/// </summary>
public ImagePanelProperties Property {
get {
return property ;
}
}
}
#endregion class PropertyChangeEventArgs
#region Win32 Stuff
/// <summary>
/// Win32 function that allows us to 'sleep' if there are no application level
/// events to be fired. This way we dont take up all the CPU cycles while
/// in a tight loop during popup.
/// </summary>
/// <param name="nCount">The number of wait handles</param>
/// <param name="pHandles">The array of wait handles</param>
/// <param name="bWaitAll">Wait option</param>
/// <param name="dwMilliseconds">Wait timeout</param>
/// <param name="dwWakeMask">Event types mask</param>
/// <returns>
/// Various values. Primarily the wait handle that signaled, but could
/// indicate timeout or abandonment. See Win32 API docs for complete
/// info.
/// </returns>
[DllImport("user32.dll")]
private static extern int MsgWaitForMultipleObjects(
int nCount, // number of wait handles in the array
int pHandles, // wait handle array
bool bWaitAll, // wait option
int dwMilliseconds, // time-out
int dwWakeMask // input event type(s)
);
#endregion Win32 Stuff
#region Constants
private static readonly ColorPair DefaultSelectedColors = new ColorPair(Color.White,Color.DodgerBlue) ;
#endregion Constants
#region Fields
/// <summary>
/// Standard Container Control collection
/// </summary>
private System.ComponentModel.IContainer components;
/// <summary>
/// Source image strip
/// </summary>
/// <remarks>
/// Contains <c>imageCount</c> images
/// </remarks>
private Bitmap sourceImages = null ;
/// <summary>
/// Number of images in the sourceImage bitmap
/// </summary>
private int imageCount = 0 ;
/// <summary>
/// Size of source images
/// </summary>
private Size imageSize = Size.Empty ;
/// <summary>
/// Size of the grid
/// </summary>
private Size gridSize = new Size(1,1) ;
/// <summary>
/// Pre-drawn, internal representation of grid w/ images
/// </summary>
private Bitmap panelImage = null ;
/// <summary>
/// Dimensions of columns and rows
/// </summary>
private Size dimensions = new Size(5,2) ;
/// <summary>
/// Combination of imageSize and gridSize values
/// </summary>
private Size imageUnits = Size.Empty ;
/// <summary>
/// True if mouse hover automatically selects an image
/// </summary>
private bool isAutoSelect = false ;
/// <summary>
/// Frame/Background color for selected image
/// </summary>
private ColorPair selectedColors = DefaultSelectedColors ;
/// <summary>
/// Color of the image grid
/// </summary>
private Color gridColor = Color.Black ;
/// <summary>
/// The default image to be selected
/// </summary>
private int defaultImage = -1 ;
#region Bouncing
/// <summary>
/// Base increment for bouncing
/// </summary>
/// <remarks>
/// See <see cref="BounceFactor"/>
/// </remarks>
private float baseBounceFactorAdjust = .10f ;
/// <summary>
/// Maximum image bounce size (120% of org. image)
/// </summary>
/// <remarks>
/// See <see cref="MaxBounceRatio"/>
/// </remarks>
private float maxBounceRatio = 1.2f ;
/// <summary>
/// Minimum image bounce size (80% of org. image)
/// </summary>
/// <remarks>
/// See <see cref="MinBounceRatio"/>
/// </remarks>
private float minBounceRatio = .80f ;
/// <summary>
/// Dynamic bounce factor. Moves between min and max
/// </summary>
private float bounceFactor = 1.0f ;
/// <summary>
/// Dynamic bounce factor. Goes /- depending on shrinking/growing
/// </summary>
private float bounceFactorAdjust = .10f ;
#endregion Bouncing
#region Events
/// <summary>
/// Listeners for property change events
/// </summary>
private EventHandler propertyChangeListeners = null ;
/// <summary>
/// Event handler triggered when an image is selected
/// </summary>
private EventHandler imageSelectedListeners = null ;
/// <summary>
/// Control to receive focus when a popup completes
/// </summary>
private Control focusControl = null ;
#endregion Events
#region Dynamic Fields
/// <summary>
/// True if the image panel is being shown as a popup
/// </summary>
private bool isPopup = false ;
/// <summary>
/// When columns are fixed, represents the "actual" number of rows in the
/// bitmap representation of the image panel (although Rows # of rows are visible)
/// </summary>
private int virtualRows = 0 ;
/// <summary>
/// Last column selected
/// </summary>
private int selectedColumn = -1 ;
/// <summary>
/// Last row selected
/// </summary>
private int selectedRow = -1 ;
/// <summary>
/// True if the mouse is down
/// </summary>
private bool isMouseDown = false ;
private System.Windows.Forms.Timer bounceTimer;
/// <summary>
/// True if the mouse is hovering
/// </summary>
private bool isMouseHover = false ;
#endregion Dynamic Fields
#endregion Fields
#region Dispose (and related)
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing ) {
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#endregion Dispose (and related)
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
this.components = new System.ComponentModel.Container();
this.bounceTimer = new System.Windows.Forms.Timer(this.components);
//
// bounceTimer
//
this.bounceTimer.Interval = 100 ;
this.bounceTimer.Tick = new System.EventHandler(this.bounceTimer_Tick);
}
#endregion
#region Constructor(s)
/// <summary>
/// Create an empty <c>ImagePanel</c>
/// </summary>
public ImagePanel() {
//
// Required for Windows.Forms Class Composition Designer support
//
InitializeComponent();
this.AutoScroll = true ;
// by default, panel types are not tabstops. We change this, but the
// user can override using TabStop property
this.TabStop = true ;
this.isPopup = false ;
}
/// <summary>
/// Create an <c>ImagePanel</c> with the specified sourc image
/// and grid dimensions
/// </summary>
/// <param name="sourceImages">The source image strip</param>
/// <param name="imageCount">The number of images in the strip</param>
/// <param name="rows">Number of rows in the grid</param>
/// <param name="columns">Number of columns in the grid</param>
public ImagePanel(Bitmap sourceImages,int imageCount,int rows,int columns) {
//
// Required for Windows.Forms Class Composition Designer support
//
InitializeComponent();
this.AutoScroll = true ;
// by default, panel types are not tabstops. We change this, but the
// user can override using TabStop property
this.TabStop = true ;
this.isPopup = false ;
this.sourceImages = sourceImages ;
Dimensions = new Size(columns,rows) ;
this.imageCount = imageCount ;
this.Width = PanelImage.Width ;
this.Height = PanelImage.Height ;
}
/// <summary>
/// Create an ImagePanel using the specified <see cref="ImageSet"/> for the
/// source images
/// </summary>
/// <param name="imageSet">Source images</param>
/// <remarks>
/// The best dimensions (rows/cols) are calculated using <see cref="PanelSizeHints.MinimizeBoth"/>
/// which produces the best square. The client rectangle for the <c>ImagePanel</c> is calculated based
/// upon the dimensions
/// </remarks>
public ImagePanel(ImageSet imageSet) {
if (imageSet == null) {
throw new ArgumentNullException("imageSet") ;
}
//
// Required for Windows.Forms Class Composition Designer support
//
InitializeComponent();
this.AutoScroll = true ;
// by default, panel types are not tabstops. We change this, but the
// user can override using TabStop property
this.TabStop = true ;
this.isPopup = false ;
this.sourceImages = (Bitmap) imageSet.Preview ;
this.imageCount = imageSet.Count ;
Dimensions = ImagePanel.CalculateBestDimensions(imageSet.Count,PanelSizeHints.MinimizeBoth) ;
Size bestSize = this.CalculateBestClientSize() ;
this.Width = bestSize.Width ;
this.Height = bestSize.Height ;
}
#endregion Constructor(s)
#region Properties
/// <summary>
/// Get/Set the <see cref="ColorPair"/> which describes the Frame and
/// Background <see cref="Color"/> values for selected images
/// </summary>
public ColorPair SelectedColors {
get {
return selectedColors ;
}
set {
if (selectedColors != value) {
selectedColors = value ;
OnPropertyChange(ImagePanelProperties.SelectedColorsProperty) ;
}
}
}
/// <summary>
/// Get/Set the image highlight background color used when an
/// image is pre-selected (mouse over, keyboard) and the frame color
/// when selected (mouse down, mouse hover)
/// </summary>
/// <remarks>
/// The <c>HighightColor</c> is used for both the frame of the image as
/// well as the background color. The background color is only visible
/// when if the image has transparency and the background color of
/// the image panel is not equivalent. The background/frame color
/// used is always determined in conjunction with <see cref="SelectedColor"/>
/// so that the frame/background colors alternates. (i.e, whats the point
/// of a frame that is the same color as the background?)
/// <para>
/// This property (indirectly) sends a <see cref="PropertyChange"/> event with
/// <see cref="ImagePanelProperties.SelectedColorsProperty"/>
/// </para>
/// <para>
/// <seealso cref="SelectedColor"/>
/// </para>
/// </remarks>
[Category("Appearance"),Description("Background color of image when highlighted"),
DefaultValue(typeof(System.Drawing.Color),"Color.White")]
[Obsolete]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Color HighlightColor {
get {
return selectedColors.Foreground ;
}
set {
if (selectedColors.Foreground != value) {
selectedColors = new ColorPair(value,SelectedColor) ;
}
}
}
/// <summary>
/// Get/Set the image selected frame/background color used when an
/// image is selected (mouse down, mouse hover)
/// </summary>
/// <remarks>
/// The <c>SelectedColor</c> is used for both the frame of the image as
/// well as the background color. The background color is only visible
/// when if the image has transparency and the background color of
/// the image panel is not equivalent. The background/frame color
/// used is always determined in conjunction with <see cref="HighlightColor"/>
/// so that the frame/background colors alternates. (i.e, whats the point
/// of a frame that is the same color as the background?)
/// <para>
/// This property (indirectly) sends a <see cref="PropertyChange"/> event with
/// <see cref="ImagePanelProperties.SelectedColorsProperty"/>
/// </para>
/// <para>
/// <seealso cref="HighlightColor"/>
/// </para>
/// </remarks>
[Category("Appearance"),Description("Background color of image when selected"),
DefaultValue(typeof(System.Drawing.Color),"Color.DodgerBlue")]
[Obsolete]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Color SelectedColor {
get {
return selectedColors.Background ;
}
set {
if (selectedColors.Background != value) {
selectedColors = new ColorPair(HighlightColor,value) ;
}
}
}
/// <summary>
/// Get/Set the <see cref="Color"/> value of the grid lines
/// </summary>
/// <para>
/// This property sends a <see cref="PropertyChange"/> event with
/// <see cref="ImagePanelProperties.GridColorProperty"/>
/// </para>
[Category("Appearance"),Description("Color of the image grid"),
DefaultValue(typeof(System.Drawing.Color),"Color.Black")]
public Color GridColor {
get {
return gridColor ;
}
set {
if (gridColor != value) {
gridColor = value ;
OnPropertyChange(ImagePanelProperties.GridColorProperty) ;
}
}
}
/// <summary>
/// Get/Set the line thickness of the <c>ImagePanel</c> grid
/// </summary>
/// <remarks>
/// <para>This method fires a <see cref="PropertyChange"/> event with the
/// <see cref="ImagePanelProperties.PanelGridSizeProperty"/></para>
/// </remarks>
[Category("Appearance"),Description("Size of image grid lines")]
public Size PanelGridSize {
get {
return gridSize ;
}
set {
if (gridSize != value) {
gridSize = value ;
OnPropertyChange(ImagePanelProperties.PanelGridSizeProperty) ;
}
}
}
/// <summary>
/// Determine if this property should be serialized by designer
/// code generation
/// </summary>
/// <returns>
/// <see langword="true"/> if the property does not equal its default value
/// </returns>
protected bool ShouldSerializePanelGridSize() {
return (gridSize.Width != 1) || (gridSize.Height != 1) ;
}
/// <summary>
/// Reset this property to its default value
/// </summary>
protected void ResetPanelGridSize() {
gridSize.Width =1 ;
gridSize.Height =1 ;
}
/// <summary>
/// Get/Set the dimensions of the <c>ImagePanel</c> grid
/// </summary>
/// <remarks>
/// The ImagePanel grid is define in terms of rows/columns where
/// <see cref="System.Drawing.Size.Width"/> is the number of columns, and
/// <see cref="System.Drawing.Size.Height"/> is the number of rows
/// <para>This method fires a <see cref="PropertyChange"/> event with the
/// <see cref="ImagePanelProperties.DimensionsProperty"/></para>
/// </remarks>
[Category("Appearance"),Description("Number of rows/columns in the image grid"),DefaultValue(2)]
public Size Dimensions {
get {
return dimensions ;
}
set {
if (value.Width < 1) {
throw new ArgumentException("Columns must be >= 1") ;
}
if (value.Height < 1) {
throw new ArgumentException("Rows must be >= 1") ;
}
if (dimensions != value) {
dimensions = value ;
OnPropertyChange(ImagePanelProperties.DimensionsProperty) ;
}
}
}
/// <summary>
/// Determine if this property should be serialized by designer
/// code generation
/// </summary>
/// <returns>
/// <see langword="true"/> if the property does not equal its default value
/// </returns>
protected bool ShouldSerializeDimensions() {
return (dimensions.Height != 2) || (dimensions.Width != 5) ;
}
/// <summary>
/// Reset this property to its default value
/// </summary>
protected void ResetDimensions() {
dimensions.Width = 5 ;
dimensions.Height = 2 ;
}
/// <summary>
/// Get/Set automatic selection of image on mouse hover
/// </summary>
/// <remarks>
/// <para>This method fires a <see cref="PropertyChange"/> event with the
/// <see cref="ImagePanelProperties.AutoSelectProperty"/></para>
/// </remarks>
[Category("Behaviour"),Description("True if mouse hover automatically selects image"),DefaultValue(false)]
public bool AutoSelect {
get {
return isAutoSelect ;
}
set {
if (value != isAutoSelect) {
isAutoSelect = value ;
OnPropertyChange(ImagePanelProperties.AutoSelectProperty) ;
}
}
}
/// <summary>
/// Get the <see cref="Bitmap"/> for the source images
/// </summary>
[Browsable(false)]
public Bitmap Images {
get {
return sourceImages ;
}
}
/// <summary>
/// Get/Set the default image selection
/// </summary>
/// <remarks>
/// <para>This method fires a <see cref="PropertyChange"/> event with the
/// <see cref="ImagePanelProperties.DefaultImageProperty"/></para>
/// </remarks>
public int DefaultImage {
get {
return defaultImage ;
}
set {
if ((value != defaultImage) && (value < imageCount)) {
defaultImage = Math.Max(value,-1) ;
OnPropertyChange(ImagePanelProperties.DefaultImageProperty) ;
}
}
}
/// <summary>
/// Get/Set the increment/decrement unit when bouncing an image
/// </summary>
/// <remarks>
/// <c>BounceFactor</c> must be between 0.0 and 1.0 inclusive
/// </remarks>
[Category("Behaviour"),Description("Image size increment/decrement during image bounce"),
DefaultValue(.10f)]
public float BounceFactor {
get {
return baseBounceFactorAdjust ;
}
set {
if ((value < 0.0f) || (value > 1.0)) {
throw new ArgumentException("BounceFactor must be between 0.0 and 1.0 (inclusive)") ;
}
if (baseBounceFactorAdjust != value) {
baseBounceFactorAdjust = value ;
OnPropertyChange(ImagePanelProperties.BounceFactorProperty) ;
}
}
}
/// <summary>
/// Get/Set the maximum image scaling during bouncing
/// </summary>
/// <remarks>
/// <c>MaxBounceRatio</c> must be >= 1.0
/// </remarks>
[Category("Behaviour"),Description("Maximum percentage increase of image during bounce"),
DefaultValue(1.3f)]
public float MaxBounceRatio {
get {
return maxBounceRatio ;
}
set {
if (value < 1.0f) {
throw new ArgumentException("MaxBounceRatio must be >= 1.0") ;
}
if (maxBounceRatio != value) {
maxBounceRatio = value ;
OnPropertyChange(ImagePanelProperties.MaxBounceRatioProperty) ;
}
}
}
/// <summary>
/// Get/Set the minimum image scaling during bouncing
/// </summary>
/// <remarks>
/// <c>MinBounceRatio</c> must be <= 1.0
/// </remarks>
[Category("Behaviour"),Description("Minimum percentage increase of image during bounce"),
DefaultValue(0.8f)]
public float MinBounceRatio {
get {
return minBounceRatio ;
}
set {
if (value > 1.0f) {
throw new ArgumentException("MinBounceRatio must be <= 1.0") ;
}
if (minBounceRatio != value) {
minBounceRatio = value ;
OnPropertyChange(ImagePanelProperties.MinBounceRatioProperty) ;
}
}
}
#endregion Properties
#region Events
/// <summary>
/// Add/Remove <c>PropertyChange</c> event listeners
/// </summary>
public event EventHandler PropertyChange {
add {
propertyChangeListeners = value ;
}
remove {
propertyChangeListeners -= value ;
}
}
/// <summary>
/// Add/Remove <c>ImageSelected</c> event listeners
/// </summary>
public event EventHandler ImageSelected {
add {
imageSelectedListeners = value ;
}
remove {
imageSelectedListeners -= value ;
}
}
#endregion Events
#region Methods
/// <summary>
/// Set the source images for the <c>ImagePanel</c>
/// </summary>
/// <param name="images">The source image strip</param>
/// <param name="imageCount">The number of images in the image strip</param>
/// <remarks>
/// <para>This method fires a <see cref="PropertyChange"/> event with the
/// <see cref="ImagePanelProperties.ImagesProperty"/></para>
/// </remarks>
public void SetImages(Bitmap images, int imageCount) {
if ((images != null) && (imageCount <= 0)) {
throw new ArgumentException("imageCount must be > 0") ;
}
sourceImages = images ;
this.imageCount = imageCount ;
OnPropertyChange(ImagePanelProperties.ImagesProperty) ;
}
/// <summary>
/// Initializes the source images from the specified <see cref="ImageSet"/>
/// </summary>
/// <param name="imageSet">The source image collection</param>
/// <remarks>
/// <para>This method (indirectly) fires a <see cref="PropertyChange"/> event with the
/// <see cref="ImagePanelProperties.ImagesProperty"/></para>
/// </remarks>
public void SetImages(ImageSet imageSet) {
SetImages((Bitmap) imageSet.Preview,imageSet.Count) ;
}
/// <summary>
/// Initializes the source images from the specified <see cref="ImageList"/>
/// </summary>
/// <param name="imageList">The source image list</param>
/// <remarks>
/// <para>This method (indirectly) fires a <see cref="PropertyChange"/> event with the
/// <see cref="ImagePanelProperties.ImagesProperty"/></para>
/// </remarks>
public void SetImages(ImageList imageList) {
ImageSet imageSet = new ImageSet(imageList.ImageSize,imageList.TransparentColor) ;
foreach(Image image in imageList.Images) {
imageSet.Images.Add(image) ;
}
SetImages(imageSet) ;
}
/// <summary>
/// Show the <c>ImagePanel</c> as a popup with its origin
/// at the specified X,Y coordinates (relative to its parent)
/// </summary>
/// <param name="x">Left</param>
/// <param name="y">Top</param>
/// <param name="placement">Specifies relative meaning of X/Y coordinates</param>
/// <param name="focusMe">Control to receive the focus when the
/// popup is complete (or <see langword="null"/>)</param>
/// <returns>
/// The image index selected, or -1 if canceled
/// </returns>
public int Popup(int x, int y, ImagePanelPlacement placement,Control focusMe) {
if (Visible) {
return -1 ;
}
return DoPopup(x,y,placement,focusMe) ;
}
/// <summary>
/// Show the <c>ImagePanel</c> as a popup with its origin
/// at the specified X,Y coordinates (relative to its parent)
/// </summary>
/// <param name="x">Left</param>
/// <param name="y">Top</param>
/// <param name="focusMe">Control to receive the focus when the
/// popup is complete (or <see langword="null"/>)</param>
/// <returns>
/// The image index selected, or -1 if canceled
/// </returns>
/// <remarks>
/// X/Y coordinates are <see cref="ImagePanelPlacement.TopLeft"/>
/// </remarks>
public int Popup(int x,int y,Control focusMe) {
if (Visible) {
return -1 ;
}
return DoPopup(x,y,ImagePanelPlacement.TopLeft,focusMe) ;
}
/// <summary>
/// Show the <c>ImagePanel</c> as a popup with its origin
/// at the specified X,Y coordinates (relative to its parent)
/// with dimensions suggested by the <see cref="PanelSizeHints"/>
/// </summary>
/// <param name="x">Left</param>
/// <param name="y">Top</param>
/// <param name="placement">Relative meaning of X/Y coordinates</param>
/// <param name="panelSizeHint">Hint about panel layout</param>
/// <param name="focusMe">Control to receive the focus when the
/// popup is complete (or <see langword="null"/>)</param>
/// <returns>
/// The image index selected, or -1 if canceled
/// </returns>
public int Popup(int x,int y,ImagePanelPlacement placement,Control focusMe,PanelSizeHints panelSizeHint) {
if (Visible) {
return -1 ;
}
Dimensions = CalculateBestDimensions(imageCount,panelSizeHint) ;
return DoPopup(x,y,placement,focusMe) ;
}
/// <summary>
/// Show the <c>ImagePanel</c> as a popup using the specified
/// client <see cref="Rectangle"/> (relative to its parent)
/// </summary>
/// <param name="rect">The client rectangle for the pop-up</param>
/// <param name="focusMe">Control to receive the focus when the
/// popup is complete (or <see langword="null"/>)</param>
/// <returns>
/// The image index selected, or -1 if canceled
/// </returns>
public int Popup(Rectangle rect,Control focusMe) {
if (Visible) {
return -1 ;
}
// force this to be created
Bitmap image = PanelImage ;
int maxCols = Math.Min((rect.Width / imageUnits.Width) 1,8) ;
int bestCols = 1 ;
int bestRows = 1 ;
if (rect.Width > rect.Height) {
bestRows = Math.Min((imageCount / maxCols) 1,8) ;
} else {
bestRows = Math.Min((rect.Height / imageUnits.Height) 1,8) ;
bestCols = Math.Max(1,Math.Min((imageCount / bestRows),8)) ;
}
if ((bestCols * bestRows) > imageCount) {
Dimensions = new Size(bestCols,bestRows) ;
} else {
bestCols = Math.Max(1,Math.Min(((rect.Width-SystemInformation.VerticalScrollBarWidth)/imageUnits.Width),8)) ;
Dimensions = new Size(bestCols,bestRows) ;
}
return DoPopup(rect,focusMe) ;
}
#endregion Methods
#region Implementation
/// <summary>
/// Get the number of rows in the <c>ImagePanel</c>
/// </summary>
private int Rows {
get {
return dimensions.Height ;
}
}
/// <summary>
/// Get the number of columns in the <c>ImagePanel</c>
/// </summary>
private int Columns {
get {
return dimensions.Width ;
}
}
/// <summary>
/// Get the cached <c>ImagePanel</c> <see cref="Bitmap"/>, creating
/// it if necessary
/// </summary>
private Bitmap PanelImage {
get {
if (panelImage == null) {
panelImage = CreateBitmap(sourceImages,imageCount,Rows,Columns) ;
}
return panelImage ;
}
}
/// <summary>
/// Get/Set the selected row coordinate
/// </summary>
private int SelectedRow {
get {
return selectedRow ;
}
set {
selectedRow = value ;
}
}
/// <summary>
/// Get/Set the selected column coordinate
/// </summary>
private int SelectedColumn {
get {
return selectedColumn ;
}
set {
selectedColumn = value ;
}
}
/// <summary>
/// Determine if the <c>ImagePanel</c> has a selected image
/// </summary>
private bool HasSelection {
get {
return (SelectedRow != -1) && (SelectedColumn != -1) ;
}
}
/// <summary>
/// Clear any selection
/// </summary>
private void ClearSelection() {
SelectedColumn = SelectedRow = -1 ;
Animate(false) ;
Invalidate() ;
}
/// <summary>
/// Ensure that a given row/column coordinate is fully visible
/// by scrolling as necessary
/// </summary>
/// <param name="row">The row</param>
/// <param name="column">The column</param>
private void EnsureVisible(int row, int column) {
if ((ClientRectangle.Width < PanelImage.Width) ||
(ClientRectangle.Height < PanelImage.Height))
{
Rectangle rect = GetPanelFrameRect(row,column) ;
Rectangle temp = ClientRectangle ;
temp.Inflate(-1,-1) ;
temp.Offset(-AutoScrollPosition.X,-AutoScrollPosition.Y) ;
if (!temp.Contains(rect)) {
int xOffset = Math.Min((rect.Right - ClientRectangle.Right) gridSize.Width << 1,PanelImage.Width - ClientRectangle.Width) ;
int yOffset = Math.Min((rect.Bottom - ClientRectangle.Bottom) gridSize.Width << 1,PanelImage.Height - ClientRectangle.Height) ;
AutoScrollPosition = new Point(xOffset,yOffset) ;
}
}
}
/// <summary>
/// Set the row/column of the selected image
/// </summary>
/// <param name="row">The row index (or -1)</param>
/// <param name="column">The column index (or -1)</param>
private void SetSelection(int row,int column) {
if ((row == -1) || (column==-1)) {
// no selection
ClearSelection() ;
} else if (((row != SelectedRow) || (column != SelectedColumn))) {
// if its a valid image, set the selection and ensure
// that the item is fully visible
if (IsImage(row,column)) {
SelectedRow = row ;
SelectedColumn = column ;
EnsureVisible(row,column) ;
Animate(Visible && Enabled) ;
} else {
// not a valid image, no selection
SelectedRow = -1 ;
SelectedColumn = -1 ;
Animate(false) ;
}
Invalidate() ;
}
}
/// <summary>
/// React to property changes, including firing <see cref="PropertyChange"/> events
/// </summary>
/// <param name="imagePanelProperty">Enumeration for the property that changed</param>
protected virtual void OnPropertyChange(ImagePanelProperties imagePanelProperty) {
switch(imagePanelProperty) {
case ImagePanelProperties.BackColorProperty:
case ImagePanelProperties.PanelGridSizeProperty:
case ImagePanelProperties.GridColorProperty:
case ImagePanelProperties.ImagesProperty:
case ImagePanelProperties.DimensionsProperty:
if (panelImage != null) {
panelImage.Dispose() ;
panelImage = null ;
}
if (Visible) {
Invalidate() ;
}
break ;
case ImagePanelProperties.SelectedColorsProperty:
case ImagePanelProperties.DefaultImageProperty:
if (Visible) {
Invalidate() ;
}
break ;
// Note: We dont do anything if these change dynamically
// If we are animating they will be picked up and nothing bad
// will happen
case ImagePanelProperties.MinBounceRatioProperty:
case ImagePanelProperties.MaxBounceRatioProperty:
case ImagePanelProperties.BounceFactorProperty:
case ImagePanelProperties.AutoSelectProperty:
break ;
}
if (propertyChangeListeners != null) {
propertyChangeListeners(this,new PropertyChangeEventArgs(imagePanelProperty)) ;
}
}
/// <summary>
/// Fire an <see cref="ImageSelected"/> event
/// </summary>
/// <param name="imageIndex">The image index selected or (-1 for cancel)</param>
protected virtual void OnImageSelectedEvent(int imageIndex) {
if (imageSelectedListeners != null) {
imageSelectedListeners(this,new ImageSelectedEventArgs(imageIndex)) ;
}
// selecting an image closes the control when in popup mode
if (isPopup) {
this.Hide() ;
}
}
/// <summary>
/// Given the <see cref="PanelSizeHints"/>, calculate the best dimensions
/// for a given number of images
/// </summary>
/// <param name="imageCount">Number of images</param>
/// <param name="sizeHints">hints about the layout</param>
/// <returns>
/// The suggested Width (cols) and Height (rows) of the panel as type
/// <see cref="System.Drawing.Size"/>
/// </returns>
public static Size CalculateBestDimensions(int imageCount,PanelSizeHints sizeHints) {
// in this case we will always scroll (vertically)
if (imageCount > (8*8)) {
return new Size(8,8) ;
}
if (sizeHints == PanelSizeHints.MinimizeColumns) {
// divide by max rows
return new Size(Math.Min((imageCount >> 3) 1,8),Math.Min(8,imageCount)) ;
} else if (sizeHints == PanelSizeHints.MinimizeRows) {
return new Size(Math.Min(8,imageCount),Math.Min((imageCount >> 3) 1,8)) ;
}
int adjust1 = 1 ;
int adjust2 = 0 ;
do {
adjust1 ;
adjust2 = (imageCount (adjust1-1)) / adjust1 ;
} while(Math.Abs(adjust1-adjust2) > 1) ;
return new Size(Math.Min(Math.Max(adjust1,adjust2),8),Math.Min(Math.Min(adjust1,adjust2),8)) ;
}
/// <summary>
/// Given a rectangle, calculate the best dimensions for the <c>ImagePanel</c>
/// </summary>
/// <param name="rect">The bounding rectangle</param>
/// <returns>
/// The suggested Width (cols) and Height (rows) of the panel as type
/// <see cref="System.Drawing.Size"/>
/// </returns>
public Size CalculateBestDimensions(Rectangle rect) {
Size result ;
int maxCols = Math.Min((rect.Width / imageUnits.Width) 1,8) ;
int bestCols = 1 ;
int bestRows = 1 ;
if (rect.Width > rect.Height) {
bestRows = Math.Min((imageCount / maxCols) 1,8) ;
} else {
bestRows = Math.Min((rect.Height / imageUnits.Height) 1,8) ;
bestCols = Math.Max(1,Math.Min((imageCount / bestRows),8)) ;
}
if ((bestCols * bestRows) > imageCount) {
result = new Size(bestCols,bestRows) ;
} else {
bestCols = Math.Max(1,Math.Min(((rect.Width-SystemInformation.VerticalScrollBarWidth)/imageUnits.Width),8)) ;
result = new Size(bestCols,bestRows) ;
}
return result ;
}
/// <summary>
/// Calculate the best <see cref="Rectangle"/> given the current
/// row/column configuration
/// </summary>
/// <returns>
/// A suggested client rectangle
/// </returns>
private Size CalculateBestClientSize() {
if (imageCount > (Columns * Rows)) {
int width = PanelImage.Width SystemInformation.VerticalScrollBarWidth ;
int height = imageUnits.Height * Rows ;
return new Size(width,height) ;
}
return new Size(PanelImage.Width,PanelImage.Height) ;
}
/// <summary>
/// Returns the image <see cref="Rectangle"/> for a given row/column
/// excluding the frame
/// </summary>
/// <param name="row">The row</param>
/// <param name="column">The column</param>
/// <returns>
/// The rectangle for a given row/column (does not include frame of image)
/// </returns>
private Rectangle GetPanelImageRect(int row,int column) {
return new Rectangle(
(column*imageSize.Width) ((column 1)*gridSize.Width),
(row*imageSize.Height) ((row 1)*gridSize.Height),
imageSize.Width,
imageSize.Height
) ;
}
/// <summary>
/// Returns the image <see cref="Rectangle"/> for a given row/column
/// including the frame
/// </summary>
/// <param name="row">The row</param>
/// <param name="column">The column</param>
/// <returns>
/// The rectangle for a given row/column including the frame
/// </returns>
private Rectangle GetPanelFrameRect(int row,int column) {
Rectangle frameRect = GetPanelImageRect(row,column) ;
frameRect.Offset(-((gridSize.Width 1)>>1),-((gridSize.Width 1)>>1)) ;
frameRect.Height = 1 ;
frameRect.Width = 1 ;
return frameRect ;
}
/// <summary>
/// Returns the image <see cref="Rectangle"/> for a given row/column
/// in the source image
/// </summary>
/// <param name="row">The row</param>
/// <param name="column">The column</param>
/// <returns>
/// The rectangle for the image that corresponds to the row/column
/// </returns>
private Rectangle GetSourceImageRect(int row,int column) {
return new Rectangle(
((row*Columns) column) * imageSize.Width,
0,
imageSize.Width,
imageSize.Height
) ;
}
/// <summary>
/// Gets the index of the image for the specified row/column
/// </summary>
/// <param name="row">The row</param>
/// <param name="column">The column</param>
/// <returns>
/// The image index for the specified row/column or -1 if
/// the coordinates do specify valid coordinates
/// </returns>
private int GetImageIndex(int row,int column) {
if ((row >= virtualRows) || (column >= Columns))
return -1 ;
int imageIndex = (row*Columns) column ;
return (imageIndex >=0) ? imageIndex : -1 ;
}
/// <summary>
/// Determine if the given row/column coordinates contains
/// an image
/// </summary>
/// <param name="row">The row</param>
/// <param name="column">The column</param>
/// <returns>
/// <see langword="true"/> if the coordinates contain an image
/// </returns>
private bool IsImage(int row,int column) {
int imageIndex = GetImageIndex(row,column) ;
return (imageIndex >= 0) && (imageIndex < imageCount) ;
}
/// <summary>
/// Create the cached <see cref="Bitmap"/> representation of the
/// <c>ImagePanel</c>
/// </summary>
/// <param name="sourceImages">The source images 'strip'</param>
/// <param name="imageCount">The number of source images</param>
/// <param name="rows">The number of rows in the panel grid</param>
/// <param name="columns">The number of cols in the panel grid</param>
/// <returns>
/// The <see cref="Bitmap"/> representation of the <c>ImagePanel</c>
/// </returns>
private Bitmap CreateBitmap(Bitmap sourceImages,int imageCount,int rows,int columns) {
Bitmap bitmap = null ;
if ((sourceImages != null) && (imageCount > 0)) {
imageSize = new Size(sourceImages.Width / imageCount,sourceImages.Height) ;
imageUnits = new Size(imageSize.Width gridSize.Width,imageSize.Height gridSize.Height) ;
virtualRows = ((imageCount-1) / columns) 1 ;
bitmap = new Bitmap(
(imageUnits.Width* columns) gridSize.Width,
(imageUnits.Height * virtualRows) gridSize.Height,
PixelFormat.Format32bppArgb
) ;
AutoScrollMinSize = new Size(bitmap.Width,bitmap.Height) ;
using(Graphics g = Graphics.FromImage(bitmap)) {
using(SolidBrush b = new SolidBrush(this.BackColor)) {
g.FillRectangle(b,0,0,bitmap.Width,bitmap.Height) ;
}
using(Pen pen = new Pen(GridColor,gridSize.Height)) {
for(int i=0; i < columns ; i ) {
for(int j=0 ; j < virtualRows ; j ) {
if (IsImage(j,i)) {
#if !DEBUG_DRAWING
g.DrawImage(sourceImages,GetPanelImageRect(j,i),GetSourceImageRect(j,i),GraphicsUnit.Pixel) ;
#endif
g.DrawRectangle(pen,GetPanelFrameRect(j,i)) ;
} else {
break ;
}
}
}
}
}
} else {
if ((ClientRectangle.Width == 0) || (ClientRectangle.Height==0))
return null ;
bitmap = new Bitmap(
ClientRectangle.Width,
ClientRectangle.Height,
PixelFormat.Format32bppArgb
) ;
using(Graphics g = Graphics.FromImage(bitmap)) {
using(SolidBrush b = new SolidBrush(this.BackColor)) {
g.FillRectangle(b,0,0,bitmap.Width,bitmap.Height) ;
}
using(Pen pen = new Pen(GridColor,gridSize.Width)) {
g.DrawRectangle(pen,gridSize.Width>>1,gridSize.Height>>1,bitmap.Width-gridSize.Width,bitmap.Height-gridSize.Height) ;
}
}
}
return bitmap ;
}
/// <summary>
/// Hook for sub-classes. Called when popping up
/// </summary>
/// <remarks>
/// <seealso cref="DoPopup"/>
/// </remarks>
protected virtual void OnPopup() {}
/// <summary>
/// Popup the <c>ImagePanel</c>
/// </summary>
/// <param name="rect">The <see cref="Rectangle"/> for the popup</param>
/// <param name="focusMe">The control to receive focus when the popup completes</param>
/// <returns>
/// The index of the image selected, or -1 if canceled
/// </returns>
protected int DoPopup(Rectangle rect,Control focusMe) {
int result = - 1 ;
ResetPopup() ;
this.Left = rect.Left ;
this.Top = rect.Top ;
this.Width = rect.Width ;
this.Height = rect.Height ;
focusControl = focusMe ;
OnPopup() ;
this.BringToFront() ;
Visible = true ;
Focus() ;
while(Visible) {
Application.DoEvents() ;
MsgWaitForMultipleObjects(0, 0, true, 250, 255);
}
if (IsImage(SelectedRow,SelectedColumn)) {
result = GetImageIndex(SelectedRow,SelectedColumn) ;
}
return result ;
}
/// <summary>
/// Popup the <c>ImagePanel</c> at the specified coordinates
/// </summary>
/// <param name="x">The X origin</param>
/// <param name="y">The Y origin</param>
/// <param name="placement">Specifies meaning of X/Y coordinates</param>
/// <param name="focusMe">The control to receive focus when the popup completes</param>
/// <returns>
/// The index of the image selected, or -1 if canceled
/// </returns>
public int DoPopup(int x,int y,ImagePanelPlacement placement,Control focusMe) {
Size client = CalculateBestClientSize() ;
Rectangle popupRect ;
switch(placement) {
case ImagePanelPlacement.BottomLeft:
popupRect = new Rectangle(x,y-client.Height,client.Width,client.Height) ;
break ;
case ImagePanelPlacement.BottomRight:
popupRect = new Rectangle(x-client.Width,y-client.Height,client.Width,client.Height) ;
break ;
case ImagePanelPlacement.TopRight:
popupRect = new Rectangle(x-client.Width,y,client.Width,client.Height) ;
break ;
case ImagePanelPlacement.TopLeft:
default:
popupRect = new Rectangle(x,y,client.Width,client.Height) ;
break ;
}
return DoPopup(popupRect,focusMe) ;
}
/// <summary>
/// Reset the <c>ImagePanel</c> in preperation for Popup
/// </summary>
private void ResetPopup() {
ClearSelection() ;
AutoScrollPosition = new Point(0,0) ;
isMouseDown = false ;
isMouseHover = false ;
isPopup = true ;
}
#endregion Implementation
#region Overrides
/// <summary>
/// Override to supress in designer property grid
/// </summary>
/// <remarks>
/// We always want this value to be <see langword="true"/>
/// </remarks>
[Browsable(false)]
public override bool AutoScroll {
get {
return base.AutoScroll;
}
set {
if (value == true) {
base.AutoScroll = value;
}
}
}
/// <summary>
/// Override to forward to our <see cref="PropertyChange"/> event
/// </summary>
/// <param name="e"></param>
protected override void OnBackColorChanged(EventArgs e) {
base.OnBackColorChanged (e);
OnPropertyChange(ImagePanelProperties.BackColorProperty) ;
}
/// <summary>
/// Handle keyboard navigation
/// </summary>
/// <param name="msg">The <see cref="Message"/></param>
/// <param name="keyData">The <see cref="Keys"/> data</param>
/// <returns>
/// <see langword="true"/> if the message was processed, <see langword="false"/> otherwise
/// </returns>
protected override bool ProcessCmdKey(ref Message msg, Keys keyData) {
bool fProcessed = true ;
int hitCol = SelectedColumn ;
int hitRow = SelectedRow ;
switch(keyData) {
case Keys.Down:
hitRow = Math.Min(hitRow 1,virtualRows-1) ;
hitCol = Math.Max(hitCol,0) ;
break ;
case Keys.Up:
hitRow = Math.Max(hitRow-1,0) ;
hitCol = Math.Max(hitCol,0) ;
break ;
case Keys.Left:
hitCol = Math.Max(hitCol-1,0) ;
hitRow = Math.Max(hitRow,0) ;
break ;
case Keys.Right:
hitCol = Math.Min(hitCol 1,Columns-1) ;
hitRow = Math.Max(hitRow,0) ;
break ;
case Keys.Escape:
hitCol = -1 ;
hitRow = -1 ;
OnImageSelectedEvent(-1) ;
break ;
case Keys.Enter:
case Keys.Space:
if (!HasSelection) {
SelectedRow = 0 ;
SelectedColumn = 0 ;
}
OnImageSelectedEvent(GetImageIndex(SelectedRow,SelectedColumn)) ;
break ;
default:
fProcessed = false ;
break ;
}
SetSelection(hitRow,hitCol) ;
return fProcessed ? true : base.ProcessCmdKey (ref msg, keyData);
}
/// <summary>
/// Handle various conditions when the <c>ImagePanel</c>
/// is shown/hidden
/// </summary>
/// <param name="e"><see cref="EventArgs"/></param>
protected override void OnVisibleChanged(EventArgs e) {
if (!Visible) {
if (focusControl!=null) {
focusControl.Focus() ;
}
} else {
if (DefaultImage != -1) {
SetSelection(DefaultImage / dimensions.Width,DefaultImage % dimensions.Width) ;
} else {
ClearSelection() ;
}
}
base.OnVisibleChanged (e);
}
/// <summary>
/// React to losing the focus
/// </summary>
/// <param name="e"><see cref="EventArgs"/></param>
protected override void OnLostFocus(EventArgs e) {
if (Visible && isPopup) {
Hide() ;
}
base.OnLostFocus (e);
}
/// <summary>
/// Handle mouse down
/// </summary>
/// <param name="e"><see cref="MouseEventArgs"/></param>
protected override void OnMouseDown(MouseEventArgs e) {
Rectangle temp = ClientRectangle ;
temp.Width = VScroll ? SystemInformation.VerticalScrollBarWidth : 0 ;
temp.Height = HScroll ? SystemInformation.HorizontalScrollBarHeight : 0 ;
if (temp.Contains(e.X,e.Y)) {
int hitCol = (e.X-AutoScrollPosition.X) / imageUnits.Width ;
int hitRow = (e.Y-AutoScrollPosition.Y) / imageUnits.Height ;
this.SetSelection(hitRow,hitCol) ;
isMouseDown = true ;
Invalidate() ;
} else {
// This code is useless when the mouse is not captured. It is designed
// to work like a menu where clicking outside the menu area closes it. When
// we dont capture the mouse, we never get a mouse move that is outside the
// client rectangle. Unfourtunately, I had small mouse issues when capturing
// so I left it uncaptured
OnImageSelectedEvent(-1) ;
}
}
/// <summary>
/// Handle mouse hover for highlight and auto-select
/// </summary>
/// <param name="e"><see cref="EventArgs"/></param>
protected override void OnMouseHover(EventArgs e) {
if (HasSelection && IsImage(SelectedRow,SelectedColumn)) {
if (isAutoSelect) {
OnImageSelectedEvent(GetImageIndex(SelectedRow,SelectedColumn)) ;
}
isMouseHover = true ;
Invalidate() ;
}
base.OnMouseHover(e) ;
}
/// <summary>
/// Handle mouse up
/// </summary>
/// <param name="e"><see cref="MouseEventArgs"/></param>
/// <remarks>
/// MouseUp triggers <see cref="ImageSelected"/> event if the
/// mouse down was on a valid image
/// </remarks>
protected override void OnMouseUp(MouseEventArgs e) {
isMouseDown = false ;
if (IsImage(SelectedRow,SelectedColumn)) {
OnImageSelectedEvent(GetImageIndex(SelectedRow,SelectedColumn)) ;
} else {
Invalidate() ;
}
}
/// <summary>
/// Handle mouse movement for image selection
/// </summary>
/// <param name="e"><see cref="MouseEventArgs"/></param>
protected override void OnMouseMove(MouseEventArgs e) {
int hitCol = -1 ;
int hitRow = -1 ;
if (imageCount > 0) {
if (ClientRectangle.Contains(e.X,e.Y)) {
hitCol = (e.X-AutoScrollPosition.X) / imageUnits.Width ;
hitRow = (e.Y-AutoScrollPosition.Y) / imageUnits.Height ;
}
if ((hitCol != SelectedColumn) || (hitRow != SelectedRow)) {
if (isMouseHover) {
isMouseHover = false ;
// Undocumented. May have unknown side-effects, but does the trick of
// restarting the mouse hover timer without leaving the control
base.ResetMouseEventArgs() ;
}
SetSelection(hitRow,hitCol) ;
}
}
base.OnMouseMove (e);
}
/// <summary>
/// Grab focus on mouse enter
/// </summary>
/// <param name="e"><see cref="EventArgs"/></param>
protected override void OnMouseEnter(EventArgs e) {
this.Focus() ;
base.OnMouseEnter (e);
}
/// <summary>
/// Clear selection when mouse leaves
/// </summary>
/// <param name="e"></param>
/// <remarks>
/// Note that we don't hide if in popup mode when the mouse leaves
/// </remarks>
protected override void OnMouseLeave(EventArgs e) {
base.OnMouseLeave (e);
if (Visible) {
ClearSelection() ;
}
}
#endregion Overrides
#region Paint code
/// <summary>
/// Draw the <c>ImagePanel</c>
/// </summary>
/// <param name="pevent"><see cref="PaintEventArgs"/></param>
protected override void OnPaintBackground(PaintEventArgs pevent) {
Bitmap doubleBuffer = new Bitmap(ClientRectangle.Width,ClientRectangle.Height) ;
Graphics offscreen = Graphics.FromImage(doubleBuffer) ;
offscreen.PageUnit = GraphicsUnit.Pixel ;
using(SolidBrush b = new SolidBrush(BackColor)) {
offscreen.FillRectangle(b,ClientRectangle) ;
}
offscreen.DrawImageUnscaled(PanelImage,AutoScrollPosition.X,AutoScrollPosition.Y) ;
if (HasSelection && IsImage(SelectedRow,SelectedColumn)) {
Rectangle imageRect = GetPanelImageRect(SelectedRow,SelectedColumn) ;
Rectangle frameRect = GetPanelFrameRect(SelectedRow,SelectedColumn) ;
imageRect.Offset(AutoScrollPosition.X,AutoScrollPosition.Y) ;
frameRect.Offset(AutoScrollPosition.X,AutoScrollPosition.Y) ;
Color fillColor = SelectedColors.Background ;
Color frameColor = SelectedColors.Foreground ;
if (!fillColor.IsEmpty) {
using(SolidBrush b = new SolidBrush(fillColor)) {
offscreen.FillRectangle(b,imageRect) ;
}
}
if (!frameColor.IsEmpty) {
using(Pen pen = new Pen(frameColor,gridSize.Width)) {
offscreen.DrawRectangle(pen,frameRect) ;
}
}
RectangleF imageRectF = new RectangleF(imageRect.Left,imageRect.Top,imageRect.Width,imageRect.Height) ;
imageRectF.Offset(
-((imageRectF.Width * bounceFactor) - imageRectF.Width) / 2,
-((imageRectF.Height * bounceFactor) - imageRectF.Height) / 2
) ;
imageRectF.Width = imageRectF.Width * bounceFactor ;
imageRectF.Height = imageRectF.Width * bounceFactor ;
#if !DEBUG_DRAWING
offscreen.DrawImage(sourceImages,imageRectF,GetSourceImageRect(SelectedRow,SelectedColumn),GraphicsUnit.Pixel) ;
#endif
}
// pevent.Graphics.PageUnit = GraphicsUnit.Pixel ;
pevent.Graphics.DrawImage(doubleBuffer,0,0) ;
offscreen.Dispose() ;
doubleBuffer.Dispose() ;
}
#endregion Paint code
#region Animation
private void Animate(bool fAnimationHint) {
if (fAnimationHint != bounceTimer.Enabled) {
if (fAnimationHint == false) {
bounceTimer.Enabled = false ;
// Make sure image is shown normal
bounceFactor = 1.0f ;
} else {
// we dont animate if either of these conditions is true
if ((BounceFactor != 0.0f) && ((MinBounceRatio != 1.0) && (MaxBounceRatio != 1.0))) {
bounceFactor = 1.0f ;
bounceFactorAdjust = baseBounceFactorAdjust ;
bounceTimer.Enabled = true ;
} else {
// here we wont bounce, but we will show a larger image :)
bounceFactor = MaxBounceRatio ;
}
}
}
}
private void bounceTimer_Tick(object sender, System.EventArgs e) {
bounceFactor = bounceFactorAdjust ;
if (bounceFactor >= MaxBounceRatio) {
bounceFactor = MaxBounceRatio ;
bounceFactorAdjust = -bounceFactorAdjust ;
} else if (bounceFactor <= MinBounceRatio) {
bounceFactor = MinBounceRatio ;
bounceFactorAdjust = -bounceFactorAdjust ;
}
Invalidate() ;
}
#endregion Animation
}
#region class ImageSelectedEventArgs
/// <summary>
/// <see cref="ImagePanel.ImageSelected"/> event arguments
/// </summary>
public class ImageSelectedEventArgs : System.EventArgs {
/// <summary>
/// The image index selected (or -1 if canceled)
/// </summary>
private readonly int imageIndex ;
/// <summary>
/// Create a <c>ImageSelectedEventArgs</c> with the specified
/// image index
/// </summary>
/// <param name="imageIndex">The index of the image selected or -1 if canceled</param>
public ImageSelectedEventArgs(int imageIndex) {
this.imageIndex = imageIndex ;
}
/// <summary>
/// Get the selected image index
/// </summary>
public int ImageIndex {
get {
return imageIndex ;
}
}
}
#endregion class ImageSelectedEventArgs
}
标签:
小贴士
感谢您为本站写下的评论,您的评论对其它用户来说具有重要的参考价值,所以请认真填写。
- 类似“顶”、“沙发”之类没有营养的文字,对勤劳贡献的楼主来说是令人沮丧的反馈信息。
- 相信您也不想看到一排文字/表情墙,所以请不要反馈意义不大的重复字符,也请尽量不要纯表情的回复。
- 提问之前请再仔细看一遍楼主的说明,或许是您遗漏了。
- 请勿到处挖坑绊人、招贴广告。既占空间让人厌烦,又没人会搭理,于人于己都无利。
关于好例子网
本站旨在为广大IT学习爱好者提供一个非营利性互相学习交流分享平台。本站所有资源都可以被免费获取学习研究。本站资源来自网友分享,对搜索内容的合法性不具有预见性、识别性、控制性,仅供学习研究,请务必在下载后24小时内给予删除,不得用于其他任何用途,否则后果自负。基于互联网的特殊性,平台无法对用户传输的作品、信息、内容的权属或合法性、安全性、合规性、真实性、科学性、完整权、有效性等进行实质审查;无论平台是否已进行审查,用户均应自行承担因其传输的作品、信息、内容而可能或已经产生的侵权或权属纠纷等法律责任。本站所有资源不代表本站的观点或立场,基于网友分享,根据中国法律《信息网络传播权保护条例》第二十二与二十三条之规定,若资源存在侵权或相关问题请联系本站客服人员,点此联系我们。关于更多版权及免责申明参见 版权及免责申明


网友评论
我要评论