// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Net;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms.Layout;
using static Interop;
namespace System.Windows.Forms
{
///
/// Displays an image that can be a graphic from a bitmap, icon, or metafile, as well as from
/// an enhanced metafile, JPEG, or GIF files.
///
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDispatch)]
[DefaultProperty(nameof(Image))]
[DefaultBindingProperty(nameof(Image))]
[Docking(DockingBehavior.Ask)]
[Designer("System.Windows.Forms.Design.PictureBoxDesigner, " + AssemblyRef.SystemDesign)]
[SRDescription(nameof(SR.DescriptionPictureBox))]
public class PictureBox : Control, ISupportInitialize
{
///
/// The type of border this control will have.
///
private BorderStyle _borderStyle = BorderStyle.None;
///
/// The image being displayed.
///
private Image _image;
///
/// Controls how the image is placed within our bounds, or how we are sized to fit said image.
///
private PictureBoxSizeMode _sizeMode = PictureBoxSizeMode.Normal;
private Size _savedSize;
private bool _currentlyAnimating;
// Instance members for asynchronous behavior
private AsyncOperation _currentAsyncLoadOperation;
private string _imageLocation;
private Image _initialImage;
private Image errorImage;
private int _contentLength;
private int _totalBytesRead;
private MemoryStream _tempDownloadStream;
private const int ReadBlockSize = 4096;
private byte[] _readBuffer;
private ImageInstallationType _imageInstallationType;
private SendOrPostCallback _loadCompletedDelegate;
private SendOrPostCallback _loadProgressDelegate = null;
private bool _handleValid;
private readonly object _internalSyncObject = new object();
// These default images will be demand loaded.
private Image _defaultInitialImage = null;
private Image _defaultErrorImage = null;
[ThreadStatic]
private static Image t_defaultInitialImageForThread = null;
[ThreadStatic]
private static Image t_defaultErrorImageForThread = null;
private static readonly object s_defaultInitialImageKey = new object();
private static readonly object s_defaultErrorImageKey = new object();
private static readonly object s_loadCompletedKey = new object();
private static readonly object s_loadProgressChangedKey = new object();
private const int AsyncOperationInProgressState = 0x00000001;
private const int CancellationPendingState = 0x00000002;
private const int UseDefaultInitialImageState = 0x00000004;
private const int UseDefaultErrorImageState = 0x00000008;
private const int WaitOnLoadState = 0x00000010;
private const int NeedToLoadImageLocationState = 0x00000020;
private const int InInitializationState = 0x00000040;
// PERF: take all the bools and put them into a state variable
private BitVector32 _pictureBoxState; // see PICTUREBOXSTATE_ consts above
///
/// http://msdn.microsoft.com/en-us/library/93z9ee4x(v=VS.100).aspx
/// if we load an image from a stream, we must keep the stream open for the lifetime of the Image
///
private StreamReader _localImageStreamReader = null;
private Stream _uriImageStream = null;
///
/// Creates a new picture with all default properties and no Image. The default PictureBox.SizeMode
/// will be PictureBoxSizeMode.NORMAL.
///
public PictureBox()
{
// this class overrides GetPreferredSizeCore, let Control automatically cache the result
SetExtendedState(ExtendedStates.UserPreferredSizeCache, true);
_pictureBoxState = new BitVector32(UseDefaultErrorImageState | UseDefaultInitialImageState);
SetStyle(ControlStyles.Opaque | ControlStyles.Selectable, false);
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.SupportsTransparentBackColor, true);
TabStop = false;
_savedSize = Size;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool AllowDrop
{
get => base.AllowDrop;
set => base.AllowDrop = value;
}
///
/// Indicates the border style for the control.
///
[DefaultValue(BorderStyle.None)]
[SRCategory(nameof(SR.CatAppearance))]
[DispId((int)Ole32.DispatchID.BORDERSTYLE)]
[SRDescription(nameof(SR.PictureBoxBorderStyleDescr))]
public BorderStyle BorderStyle
{
get => _borderStyle;
set
{
if (!ClientUtils.IsEnumValid(value, (int)value, (int)BorderStyle.None, (int)BorderStyle.Fixed3D))
{
throw new InvalidEnumArgumentException(nameof(value), (int)value, typeof(BorderStyle));
}
if (_borderStyle != value)
{
_borderStyle = value;
RecreateHandle();
AdjustSize();
}
}
}
///
/// Try to build a URI, but if that fails, that means it's a relative path, and we treat it as
/// relative to the working directory (which is what GetFullPath uses).
///
private Uri CalculateUri(string path)
{
try
{
return new Uri(path);
}
catch (UriFormatException)
{
// It's a relative pathname, get its full path as a file.
path = Path.GetFullPath(path);
return new Uri(path);
}
}
[SRCategory(nameof(SR.CatAsynchronous))]
[SRDescription(nameof(SR.PictureBoxCancelAsyncDescr))]
public void CancelAsync()
{
_pictureBoxState[CancellationPendingState] = true;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new bool CausesValidation
{
get => base.CausesValidation;
set => base.CausesValidation = value;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event EventHandler CausesValidationChanged
{
add => base.CausesValidationChanged += value;
remove => base.CausesValidationChanged -= value;
}
///
/// Returns the parameters needed to create the handle.
///
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
switch (_borderStyle)
{
case BorderStyle.Fixed3D:
cp.ExStyle |= (int)User32.WS_EX.CLIENTEDGE;
break;
case BorderStyle.FixedSingle:
cp.Style |= (int)User32.WS.BORDER;
break;
}
return cp;
}
}
protected override ImeMode DefaultImeMode => ImeMode.Disable;
///
/// Deriving classes can override this to configure a default size for their control.
/// This is more efficient than setting the size in the control's constructor.
///
protected override Size DefaultSize => new Size(100, 50);
[SRCategory(nameof(SR.CatAsynchronous))]
[Localizable(true)]
[RefreshProperties(RefreshProperties.All)]
[SRDescription(nameof(SR.PictureBoxErrorImageDescr))]
public Image ErrorImage
{
get
{
// Strange pictureBoxState[PICTUREBOXSTATE_useDefaultErrorImage] approach used
// here to avoid statically loading the default bitmaps from resources at
// runtime when they're never used.
if (errorImage == null && _pictureBoxState[UseDefaultErrorImageState])
{
if (_defaultErrorImage == null)
{
// Can't share images across threads.
if (t_defaultErrorImageForThread == null)
{
t_defaultErrorImageForThread = DpiHelper.GetBitmapFromIcon(typeof(PictureBox), "ImageInError");
}
_defaultErrorImage = t_defaultErrorImageForThread;
}
errorImage = _defaultErrorImage;
}
return errorImage;
}
set
{
if (ErrorImage != value)
{
_pictureBoxState[UseDefaultErrorImageState] = false;
}
errorImage = value;
}
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public override Color ForeColor
{
get => base.ForeColor;
set => base.ForeColor = value;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event EventHandler ForeColorChanged
{
add => base.ForeColorChanged += value;
remove => base.ForeColorChanged -= value;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public override Font Font
{
get => base.Font;
set => base.Font = value;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event EventHandler FontChanged
{
add => base.FontChanged += value;
remove => base.FontChanged -= value;
}
///
/// Retrieves the Image that the is currently displaying.
///
[SRCategory(nameof(SR.CatAppearance))]
[Localizable(true)]
[Bindable(true)]
[SRDescription(nameof(SR.PictureBoxImageDescr))]
public Image Image
{
get => _image;
set => InstallNewImage(value, ImageInstallationType.DirectlySpecified);
}
// The area occupied by the image
[SRCategory(nameof(SR.CatAsynchronous))]
[Localizable(true)]
[DefaultValue(null)]
[RefreshProperties(RefreshProperties.All)]
[SRDescription(nameof(SR.PictureBoxImageLocationDescr))]
public string ImageLocation
{
get => _imageLocation;
set
{
// Reload even if value hasn't changed, since Image itself may have changed.
_imageLocation = value;
_pictureBoxState[NeedToLoadImageLocationState] = !string.IsNullOrEmpty(_imageLocation);
// Reset main image if it hasn't been directly specified.
if (string.IsNullOrEmpty(_imageLocation) && _imageInstallationType != ImageInstallationType.DirectlySpecified)
{
InstallNewImage(null, ImageInstallationType.DirectlySpecified);
}
if (WaitOnLoad && !_pictureBoxState[InInitializationState] && !string.IsNullOrEmpty(_imageLocation))
{
// Load immediately, so any error will occur synchronously
Load();
}
Invalidate();
}
}
private Rectangle ImageRectangle => ImageRectangleFromSizeMode(_sizeMode);
private Rectangle ImageRectangleFromSizeMode(PictureBoxSizeMode mode)
{
Rectangle result = LayoutUtils.DeflateRect(ClientRectangle, Padding);
if (_image != null)
{
switch (mode)
{
case PictureBoxSizeMode.Normal:
case PictureBoxSizeMode.AutoSize:
// Use image's size rather than client size.
result.Size = _image.Size;
break;
case PictureBoxSizeMode.StretchImage:
// Do nothing, was initialized to the available dimensions.
break;
case PictureBoxSizeMode.CenterImage:
// Center within the available space.
result.X += (result.Width - _image.Width) / 2;
result.Y += (result.Height - _image.Height) / 2;
result.Size = _image.Size;
break;
case PictureBoxSizeMode.Zoom:
Size imageSize = _image.Size;
float ratio = Math.Min((float)ClientRectangle.Width / (float)imageSize.Width, (float)ClientRectangle.Height / (float)imageSize.Height);
result.Width = (int)(imageSize.Width * ratio);
result.Height = (int)(imageSize.Height * ratio);
result.X = (ClientRectangle.Width - result.Width) / 2;
result.Y = (ClientRectangle.Height - result.Height) / 2;
break;
default:
Debug.Fail("Unsupported PictureBoxSizeMode value: " + mode);
break;
}
}
return result;
}
[SRCategory(nameof(SR.CatAsynchronous))]
[Localizable(true)]
[RefreshProperties(RefreshProperties.All)]
[SRDescription(nameof(SR.PictureBoxInitialImageDescr))]
public Image InitialImage
{
get
{
// Strange pictureBoxState[PICTUREBOXSTATE_useDefaultInitialImage] approach
// used here to avoid statically loading the default bitmaps from resources at
// runtime when they're never used.
if (_initialImage == null && _pictureBoxState[UseDefaultInitialImageState])
{
if (_defaultInitialImage == null)
{
// Can't share images across threads.
if (t_defaultInitialImageForThread == null)
{
t_defaultInitialImageForThread = DpiHelper.GetBitmapFromIcon(typeof(PictureBox), "PictureBox.Loading");
}
_defaultInitialImage = t_defaultInitialImageForThread;
}
_initialImage = _defaultInitialImage;
}
return _initialImage;
}
set
{
if (InitialImage != value)
{
_pictureBoxState[UseDefaultInitialImageState] = false;
}
_initialImage = value;
}
}
private void InstallNewImage(Image value, ImageInstallationType installationType)
{
StopAnimate();
_image = value;
LayoutTransaction.DoLayoutIf(AutoSize, this, this, PropertyNames.Image);
Animate();
if (installationType != ImageInstallationType.ErrorOrInitial)
{
AdjustSize();
}
_imageInstallationType = installationType;
Invalidate();
CommonProperties.xClearPreferredSizeCache(this);
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new ImeMode ImeMode
{
get => base.ImeMode;
set => base.ImeMode = value;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event EventHandler ImeModeChanged
{
add => base.ImeModeChanged += value;
remove => base.ImeModeChanged -= value;
}
///
/// Synchronous load
///
[SRCategory(nameof(SR.CatAsynchronous))]
[SRDescription(nameof(SR.PictureBoxLoad0Descr))]
public void Load()
{
if (string.IsNullOrEmpty(_imageLocation))
{
throw new InvalidOperationException(SR.PictureBoxNoImageLocation);
}
// If the load and install fails, pictureBoxState[PICTUREBOXSTATE_needToLoadImageLocation] will be
// false to prevent subsequent attempts.
_pictureBoxState[NeedToLoadImageLocationState] = false;
Image img;
ImageInstallationType installType = ImageInstallationType.FromUrl;
try
{
DisposeImageStream();
Uri uri = CalculateUri(_imageLocation);
if (uri.IsFile)
{
_localImageStreamReader = new StreamReader(uri.LocalPath);
img = Image.FromStream(_localImageStreamReader.BaseStream);
}
else
{
using (WebClient wc = new WebClient())
{
_uriImageStream = wc.OpenRead(uri.ToString());
img = Image.FromStream(_uriImageStream);
}
}
}
catch
{
if (!DesignMode)
{
throw;
}
else
{
// In design mode, just replace with Error bitmap.
img = ErrorImage;
installType = ImageInstallationType.ErrorOrInitial;
}
}
InstallNewImage(img, installType);
}
[SRCategory(nameof(SR.CatAsynchronous))]
[SRDescription(nameof(SR.PictureBoxLoad1Descr))]
public void Load(string url)
{
ImageLocation = url;
Load();
}
[SRCategory(nameof(SR.CatAsynchronous))]
[SRDescription(nameof(SR.PictureBoxLoadAsync0Descr))]
public void LoadAsync()
{
if (string.IsNullOrEmpty(_imageLocation))
{
throw new InvalidOperationException(SR.PictureBoxNoImageLocation);
}
if (_pictureBoxState[AsyncOperationInProgressState])
{
// We shouldn't throw here: just return.
return;
}
_pictureBoxState[AsyncOperationInProgressState] = true;
if ((Image == null || (_imageInstallationType == ImageInstallationType.ErrorOrInitial)) && InitialImage != null)
{
InstallNewImage(InitialImage, ImageInstallationType.ErrorOrInitial);
}
_currentAsyncLoadOperation = AsyncOperationManager.CreateOperation(null);
if (_loadCompletedDelegate == null)
{
_loadCompletedDelegate = new SendOrPostCallback(LoadCompletedDelegate);
_loadProgressDelegate = new SendOrPostCallback(LoadProgressDelegate);
_readBuffer = new byte[ReadBlockSize];
}
_pictureBoxState[NeedToLoadImageLocationState] = false;
_pictureBoxState[CancellationPendingState] = false;
_contentLength = -1;
_tempDownloadStream = new MemoryStream();
WebRequest req = WebRequest.Create(CalculateUri(_imageLocation));
Task.Run(() =>
{
// Invoke BeginGetResponse on a threadpool thread, as it has unpredictable latency
req.BeginGetResponse(new AsyncCallback(GetResponseCallback), req);
});
}
private void PostCompleted(Exception error, bool cancelled)
{
AsyncOperation temp = _currentAsyncLoadOperation;
_currentAsyncLoadOperation = null;
if (temp != null)
{
temp.PostOperationCompleted(_loadCompletedDelegate, new AsyncCompletedEventArgs(error, cancelled, null));
}
}
private void LoadCompletedDelegate(object arg)
{
AsyncCompletedEventArgs e = (AsyncCompletedEventArgs)arg;
Image img = ErrorImage;
ImageInstallationType installType = ImageInstallationType.ErrorOrInitial;
if (!e.Cancelled && e.Error == null)
{
// successful completion
try
{
img = Image.FromStream(_tempDownloadStream);
installType = ImageInstallationType.FromUrl;
}
catch (Exception error)
{
e = new AsyncCompletedEventArgs(error, false, null);
}
}
// If cancelled, don't change the image
if (!e.Cancelled)
{
InstallNewImage(img, installType);
}
_tempDownloadStream = null;
_pictureBoxState[CancellationPendingState] = false;
_pictureBoxState[AsyncOperationInProgressState] = false;
OnLoadCompleted(e);
}
private void LoadProgressDelegate(object arg) => OnLoadProgressChanged((ProgressChangedEventArgs)arg);
private void GetResponseCallback(IAsyncResult result)
{
if (_pictureBoxState[CancellationPendingState])
{
PostCompleted(null, true);
return;
}
try
{
WebRequest req = (WebRequest)result.AsyncState;
WebResponse webResponse = req.EndGetResponse(result);
_contentLength = (int)webResponse.ContentLength;
_totalBytesRead = 0;
Stream responseStream = webResponse.GetResponseStream();
// Continue on with asynchronous reading.
responseStream.BeginRead(
_readBuffer,
0,
ReadBlockSize,
new AsyncCallback(ReadCallBack),
responseStream);
}
catch (Exception error)
{
// Since this is on a non-UI thread, we catch any exceptions and
// pass them back as data to the UI-thread.
PostCompleted(error, false);
}
}
private void ReadCallBack(IAsyncResult result)
{
if (_pictureBoxState[CancellationPendingState])
{
PostCompleted(null, true);
return;
}
Stream responseStream = (Stream)result.AsyncState;
try
{
int bytesRead = responseStream.EndRead(result);
if (bytesRead > 0)
{
_totalBytesRead += bytesRead;
_tempDownloadStream.Write(_readBuffer, 0, bytesRead);
responseStream.BeginRead(
_readBuffer,
0,
ReadBlockSize,
new AsyncCallback(ReadCallBack),
responseStream);
// Report progress thus far, but only if we know total length.
if (_contentLength != -1)
{
int progress = (int)(100 * (((float)_totalBytesRead) / ((float)_contentLength)));
if (_currentAsyncLoadOperation != null)
{
_currentAsyncLoadOperation.Post(_loadProgressDelegate,
new ProgressChangedEventArgs(progress, null));
}
}
}
else
{
_tempDownloadStream.Seek(0, SeekOrigin.Begin);
if (_currentAsyncLoadOperation != null)
{
_currentAsyncLoadOperation.Post(_loadProgressDelegate,
new ProgressChangedEventArgs(100, null));
}
PostCompleted(null, false);
// Do this so any exception that Close() throws will be
// dealt with ok.
Stream rs = responseStream;
responseStream = null;
rs.Close();
}
}
catch (Exception error)
{
// Since this is on a non-UI thread, we catch any exceptions and
// pass them back as data to the UI-thread.
PostCompleted(error, false);
responseStream?.Close();
}
}
[SRCategory(nameof(SR.CatAsynchronous))]
[SRDescription(nameof(SR.PictureBoxLoadAsync1Descr))]
public void LoadAsync(string url)
{
ImageLocation = url;
LoadAsync();
}
[SRCategory(nameof(SR.CatAsynchronous))]
[SRDescription(nameof(SR.PictureBoxLoadCompletedDescr))]
public event AsyncCompletedEventHandler LoadCompleted
{
add => Events.AddHandler(s_loadCompletedKey, value);
remove => Events.RemoveHandler(s_loadCompletedKey, value);
}
[SRCategory(nameof(SR.CatAsynchronous))]
[SRDescription(nameof(SR.PictureBoxLoadProgressChangedDescr))]
public event ProgressChangedEventHandler LoadProgressChanged
{
add => Events.AddHandler(s_loadProgressChangedKey, value);
remove => Events.RemoveHandler(s_loadProgressChangedKey, value);
}
private void ResetInitialImage()
{
_pictureBoxState[UseDefaultInitialImageState] = true;
_initialImage = _defaultInitialImage;
}
private void ResetErrorImage()
{
_pictureBoxState[UseDefaultErrorImageState] = true;
errorImage = _defaultErrorImage;
}
private void ResetImage()
{
InstallNewImage(null, ImageInstallationType.DirectlySpecified);
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public override RightToLeft RightToLeft
{
get => base.RightToLeft;
set => base.RightToLeft = value;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event EventHandler RightToLeftChanged
{
add => base.RightToLeftChanged += value;
remove => base.RightToLeftChanged -= value;
}
///
/// Be sure not to re-serialized initial image if it's the default.
///
private bool ShouldSerializeInitialImage()
{
return !_pictureBoxState[UseDefaultInitialImageState];
}
///
/// Be sure not to re-serialized error image if it's the default.
///
private bool ShouldSerializeErrorImage()
{
return !_pictureBoxState[UseDefaultErrorImageState];
}
///
/// Be sure not to serialize image if it wasn't directly specified
/// through the Image property (e.g., if it's a download, or an initial
/// or error image)
///
private bool ShouldSerializeImage()
{
return (_imageInstallationType == ImageInstallationType.DirectlySpecified) && (Image != null);
}
///
/// Indicates how the image is displayed.
///
[DefaultValue(PictureBoxSizeMode.Normal)]
[SRCategory(nameof(SR.CatBehavior))]
[Localizable(true)]
[SRDescription(nameof(SR.PictureBoxSizeModeDescr))]
[RefreshProperties(RefreshProperties.Repaint)]
public PictureBoxSizeMode SizeMode
{
get => _sizeMode;
set
{
if (!ClientUtils.IsEnumValid(value, (int)value, (int)PictureBoxSizeMode.Normal, (int)PictureBoxSizeMode.Zoom))
{
throw new InvalidEnumArgumentException(nameof(value), (int)value, typeof(PictureBoxSizeMode));
}
if (_sizeMode != value)
{
if (value == PictureBoxSizeMode.AutoSize)
{
AutoSize = true;
SetStyle(ControlStyles.FixedHeight | ControlStyles.FixedWidth, true);
}
if (value != PictureBoxSizeMode.AutoSize)
{
AutoSize = false;
SetStyle(ControlStyles.FixedHeight | ControlStyles.FixedWidth, false);
_savedSize = Size;
}
_sizeMode = value;
AdjustSize();
Invalidate();
OnSizeModeChanged(EventArgs.Empty);
}
}
}
private static readonly object EVENT_SIZEMODECHANGED = new object();
[SRCategory(nameof(SR.CatPropertyChanged)), SRDescription(nameof(SR.PictureBoxOnSizeModeChangedDescr))]
public event EventHandler SizeModeChanged
{
add => Events.AddHandler(EVENT_SIZEMODECHANGED, value);
remove => Events.RemoveHandler(EVENT_SIZEMODECHANGED, value);
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new bool TabStop
{
get => base.TabStop;
set => base.TabStop = value;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event EventHandler TabStopChanged
{
add => base.TabStopChanged += value;
remove => base.TabStopChanged -= value;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new int TabIndex
{
get => base.TabIndex;
set => base.TabIndex = value;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event EventHandler TabIndexChanged
{
add => base.TabIndexChanged += value;
remove => base.TabIndexChanged -= value;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never), Bindable(false)]
public override string Text
{
get => base.Text;
set => base.Text = value;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event EventHandler TextChanged
{
add => base.TextChanged += value;
remove => base.TextChanged -= value;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event EventHandler Enter
{
add => base.Enter += value;
remove => base.Enter -= value;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event KeyEventHandler KeyUp
{
add => base.KeyUp += value;
remove => base.KeyUp -= value;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event KeyEventHandler KeyDown
{
add => base.KeyDown += value;
remove => base.KeyDown -= value;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event KeyPressEventHandler KeyPress
{
add => base.KeyPress += value;
remove => base.KeyPress -= value;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event EventHandler Leave
{
add => base.Leave += value;
remove => base.Leave -= value;
}
///
/// If the PictureBox has the SizeMode property set to AutoSize, this makes sure that the
/// picturebox is large enough to hold the image.
///
private void AdjustSize()
{
if (_sizeMode == PictureBoxSizeMode.AutoSize)
{
Size = PreferredSize;
}
else
{
Size = _savedSize;
}
}
private void Animate() => Animate(animate: !DesignMode && Visible && Enabled && ParentInternal != null);
private void StopAnimate() => Animate(animate: false);
private void Animate(bool animate)
{
if (animate != _currentlyAnimating)
{
if (animate)
{
if (_image != null)
{
ImageAnimator.Animate(_image, new EventHandler(OnFrameChanged));
_currentlyAnimating = animate;
}
}
else
{
if (_image != null)
{
ImageAnimator.StopAnimate(_image, new EventHandler(OnFrameChanged));
_currentlyAnimating = animate;
}
}
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
StopAnimate();
}
DisposeImageStream();
base.Dispose(disposing);
}
private void DisposeImageStream()
{
if (_localImageStreamReader != null)
{
_localImageStreamReader.Dispose();
_localImageStreamReader = null;
}
if (_uriImageStream != null)
{
_uriImageStream.Dispose();
_localImageStreamReader = null;
}
}
///
/// Overriding this method allows us to get the caching and clamping the proposedSize/output to
/// MinimumSize / MaximumSize from GetPreferredSize for free.
///
internal override Size GetPreferredSizeCore(Size proposedSize)
{
if (_image == null)
{
return CommonProperties.GetSpecifiedBounds(this).Size;
}
else
{
Size bordersAndPadding = SizeFromClientSize(Size.Empty) + Padding.Size;
return _image.Size + bordersAndPadding;
}
}
protected override void OnEnabledChanged(EventArgs e)
{
base.OnEnabledChanged(e);
Animate();
}
private void OnFrameChanged(object o, EventArgs e)
{
if (Disposing || IsDisposed)
{
return;
}
// Handle should be created, before calling the BeginInvoke.
if (InvokeRequired && IsHandleCreated)
{
lock (_internalSyncObject)
{
if (_handleValid)
{
BeginInvoke(new EventHandler(OnFrameChanged), o, e);
}
return;
}
}
Invalidate();
}
[EditorBrowsable(EditorBrowsableState.Advanced)]
protected override void OnHandleDestroyed(EventArgs e)
{
lock (_internalSyncObject)
{
_handleValid = false;
}
base.OnHandleDestroyed(e);
}
[EditorBrowsable(EditorBrowsableState.Advanced)]
protected override void OnHandleCreated(EventArgs e)
{
lock (_internalSyncObject)
{
_handleValid = true;
}
base.OnHandleCreated(e);
}
protected virtual void OnLoadCompleted(AsyncCompletedEventArgs e)
{
((AsyncCompletedEventHandler)(Events[s_loadCompletedKey]))?.Invoke(this, e);
}
protected virtual void OnLoadProgressChanged(ProgressChangedEventArgs e)
{
((ProgressChangedEventHandler)(Events[s_loadProgressChangedKey]))?.Invoke(this, e);
}
///
/// Overridden onPaint to make sure that the image is painted correctly.
///
protected override void OnPaint(PaintEventArgs pe)
{
if (_pictureBoxState[NeedToLoadImageLocationState])
{
try
{
if (WaitOnLoad)
{
Load();
}
else
{
LoadAsync();
}
}
catch (Exception ex) when (!ClientUtils.IsCriticalException(ex))
{
_image = ErrorImage;
}
}
if (_image != null && pe != null)
{
Animate();
ImageAnimator.UpdateFrames(Image);
// Error and initial image are drawn centered, non-stretched.
Rectangle drawingRect =
(_imageInstallationType == ImageInstallationType.ErrorOrInitial)
? ImageRectangleFromSizeMode(PictureBoxSizeMode.CenterImage)
: ImageRectangle;
pe.Graphics.DrawImage(_image, drawingRect);
}
// Windows draws the border for us (see CreateParams)
base.OnPaint(pe);
}
protected override void OnVisibleChanged(EventArgs e)
{
base.OnVisibleChanged(e);
Animate();
}
protected override void OnParentChanged(EventArgs e)
{
base.OnParentChanged(e);
Animate();
}
///
/// OnResize override to invalidate entire control in Stetch mode
///
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
if (_sizeMode == PictureBoxSizeMode.Zoom || _sizeMode == PictureBoxSizeMode.StretchImage || _sizeMode == PictureBoxSizeMode.CenterImage || BackgroundImage != null)
{
Invalidate();
}
_savedSize = Size;
}
protected virtual void OnSizeModeChanged(EventArgs e)
{
if (Events[EVENT_SIZEMODECHANGED] is EventHandler eh)
{
eh(this, e);
}
}
///
/// Returns a string representation for this control.
///
public override string ToString()
{
string s = base.ToString();
return s + ", SizeMode: " + _sizeMode.ToString("G");
}
[SRCategory(nameof(SR.CatAsynchronous))]
[Localizable(true)]
[DefaultValue(false)]
[SRDescription(nameof(SR.PictureBoxWaitOnLoadDescr))]
public bool WaitOnLoad
{
get => _pictureBoxState[WaitOnLoadState];
set => _pictureBoxState[WaitOnLoadState] = value;
}
void ISupportInitialize.BeginInit()
{
_pictureBoxState[InInitializationState] = true;
}
void ISupportInitialize.EndInit()
{
if (!_pictureBoxState[InInitializationState])
{
return;
}
// Need to do this in EndInit since there's no guarantee of the
// order in which ImageLocation and WaitOnLoad will be set.
if (ImageLocation != null && ImageLocation.Length != 0 && WaitOnLoad)
{
// Load when initialization completes, so any error will occur synchronously
Load();
}
_pictureBoxState[InInitializationState] = false;
}
private enum ImageInstallationType
{
DirectlySpecified,
ErrorOrInitial,
FromUrl
}
}
}