using System;
using System.Collections.Generic;
using System.Linq;
+using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Threading;
using System.Net;
+using System.Net.Http;
using System.IO;
+using OpenTween.Thumbnail;
namespace OpenTween
{
public class OTPictureBox : PictureBox
{
- [Localizable(true)]
- public new Image Image
+ [Browsable(false)]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
+ public new MemoryImage Image
{
- get { return base.Image; }
+ get { return this.memoryImage; }
set
{
- if (this.memoryImage != null)
- {
- this.memoryImage.Dispose();
- this.memoryImage = null;
- }
- base.Image = value;
+ this.memoryImage = value;
+ base.Image = value?.Image;
+
+ this.RestoreSizeMode();
}
}
+ private MemoryImage memoryImage;
[Localizable(true)]
- public new string ImageLocation
+ [DefaultValue(PictureBoxSizeMode.Normal)]
+ public new PictureBoxSizeMode SizeMode
{
- get { return this._ImageLocation; }
+ get { return this.currentSizeMode; }
set
{
- if (value == null)
+ this.currentSizeMode = value;
+
+ if (base.Image != base.InitialImage && base.Image != base.ErrorImage)
{
- this.Image = null;
- return;
+ base.SizeMode = value;
}
- this.LoadAsync(value);
}
}
- private string _ImageLocation;
/// <summary>
- /// ImageLocation によりロードされた画像
+ /// InitialImage や ErrorImage の表示に SizeMode を一時的に変更するため、
+ /// 現在の SizeMode を記憶しておくためのフィールド
/// </summary>
- private MemoryImage memoryImage = null;
+ private PictureBoxSizeMode currentSizeMode;
- private Task loadAsyncTask = null;
- private CancellationTokenSource loadAsyncCancelTokenSource = null;
-
- public new Task LoadAsync(string url)
+ public void ShowInitialImage()
{
- this._ImageLocation = url;
-
- if (this.loadAsyncTask != null && !this.loadAsyncTask.IsCompleted)
- this.CancelAsync();
-
- if (this.expandedInitialImage != null)
- this.Image = this.expandedInitialImage;
-
- Uri uri;
- try
- {
- uri = new Uri(url);
- }
- catch (UriFormatException)
- {
- uri = new Uri(Path.GetFullPath(url));
- }
-
- var client = new OTWebClient();
+ base.Image = base.InitialImage;
- client.DownloadProgressChanged += (s, e) =>
- {
- this.OnLoadProgressChanged(new ProgressChangedEventArgs(e.ProgressPercentage, e.UserState));
- };
-
- this.loadAsyncCancelTokenSource = new CancellationTokenSource();
- var cancelToken = this.loadAsyncCancelTokenSource.Token;
- var loadImageTask = client.DownloadDataAsync(uri, cancelToken);
-
- // UnobservedTaskException イベントを発生させないようにする
- loadImageTask.ContinueWith(t => { var ignore = t.Exception; }, TaskContinuationOptions.OnlyOnFaulted);
-
- var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
-
- return loadImageTask.ContinueWith(t => {
- client.Dispose();
-
- if (t.IsFaulted) throw t.Exception;
+ // InitialImage は SizeMode の値に依らず中央等倍に表示する必要がある
+ base.SizeMode = PictureBoxSizeMode.CenterImage;
+ }
- return MemoryImage.CopyFromBytes(t.Result);
- }, cancelToken)
- .ContinueWith(t =>
- {
- if (!t.IsCanceled)
- {
- if (t.IsFaulted)
- {
- this.Image = this.expandedErrorImage;
- }
- else
- {
- this.Image = t.Result.Image;
- this.memoryImage = t.Result;
- }
- }
+ public void ShowErrorImage()
+ {
+ base.Image = base.ErrorImage;
- var exp = t.Exception != null ? t.Exception.Flatten() : null;
- this.OnLoadCompleted(new AsyncCompletedEventArgs(exp, t.IsCanceled, null));
- }, uiScheduler);
+ // ErrorImage は SizeMode の値に依らず中央等倍に表示する必要がある
+ base.SizeMode = PictureBoxSizeMode.CenterImage;
}
- public new void CancelAsync()
+ private void RestoreSizeMode()
{
- if (this.loadAsyncTask == null || this.loadAsyncTask.IsCompleted)
- return;
-
- this.loadAsyncCancelTokenSource.Cancel();
+ base.SizeMode = this.currentSizeMode;
+ }
+ public async Task SetImageFromTask(Func<Task<MemoryImage>> imageTask)
+ {
try
{
- this.loadAsyncTask.Wait();
+ this.ShowInitialImage();
+ this.Image = await imageTask();
}
- catch (AggregateException ae)
+ catch (Exception)
{
- ae.Handle(e =>
+ this.ShowErrorImage();
+ try
{
- if (e is OperationCanceledException)
- return true;
- if (e is WebException)
- return true;
-
- return false;
- });
+ throw;
+ }
+ catch (HttpRequestException) { }
+ catch (InvalidImageException) { }
+ catch (OperationCanceledException) { }
+ catch (WebException) { }
}
}
- public new Image ErrorImage
+ protected override void OnPaint(PaintEventArgs pe)
{
- get { return base.ErrorImage; }
- set
+ try
{
- base.ErrorImage = value;
- this.UpdateStatusImages();
- }
- }
+ base.OnPaint(pe);
- public new Image InitialImage
- {
- get { return base.InitialImage; }
- set
- {
- base.InitialImage = value;
- this.UpdateStatusImages();
+ // 動画なら再生ボタンを上から描画
+ DrawPlayableMark(pe);
}
- }
-
- private Image expandedErrorImage = null;
- private Image expandedInitialImage = null;
-
- /// <summary>
- /// ErrorImage と InitialImage の表示用の画像を生成する
- /// </summary>
- /// <remarks>
- /// ErrorImage と InitialImage は SizeMode の値に依らず中央等倍に表示する必要があるため、
- /// 事前にコントロールのサイズに合わせた画像を生成しておく
- /// </remarks>
- private void UpdateStatusImages()
- {
- var isError = false;
- var isInit = false;
-
- if (this.Image != null)
+ catch (ExternalException)
{
- // ErrorImage か InitialImage を使用中であれば記憶しておく
- isError = (this.Image == this.expandedErrorImage);
- isInit = (this.Image == this.expandedInitialImage);
+ // アニメーション GIF 再生中に発生するエラーの対策
+ // 参照: https://sourceforge.jp/ticket/browse.php?group_id=6526&tid=32894
+ this.ShowErrorImage();
}
-
- if (isError || isInit)
- this.Image = null;
-
- if (this.expandedErrorImage != null)
- this.expandedErrorImage.Dispose();
-
- if (this.expandedInitialImage != null)
- this.expandedInitialImage.Dispose();
-
- this.expandedErrorImage = this.ExpandImage(this.ErrorImage);
- this.expandedInitialImage = this.ExpandImage(this.InitialImage);
-
- if (isError)
- this.Image = this.expandedErrorImage;
-
- if (isInit)
- this.Image = this.expandedInitialImage;
}
- private Image ExpandImage(Image image)
+ private void DrawPlayableMark(PaintEventArgs pe)
{
- if (image == null) return null;
+ var thumb = this.Tag as ThumbnailInfo;
+ if (thumb == null || !thumb.IsPlayable) return;
+ if (base.Image == base.InitialImage || base.Image == base.ErrorImage) return;
- var bitmap = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);
+ var overlayImage = Properties.Resources.PlayableOverlayImage;
- using (var g = this.CreateGraphics())
- {
- bitmap.SetResolution(g.DpiX, g.DpiY);
- }
-
- using (var g = Graphics.FromImage(bitmap))
- {
- var posx = (bitmap.Width - image.Width) / 2;
- var posy = (bitmap.Height - image.Height) / 2;
+ var overlaySize = Math.Min(this.Width, this.Height) / 4;
+ var destRect = new Rectangle(
+ (this.Width - overlaySize) / 2,
+ (this.Height - overlaySize) / 2,
+ overlaySize,
+ overlaySize);
- g.DrawImage(image,
- new Rectangle(posx, posy, image.Width, image.Height),
- new Rectangle(0, 0, image.Width, image.Height),
- GraphicsUnit.Pixel);
- }
-
- return bitmap;
+ pe.Graphics.DrawImage(overlayImage, destRect, 0, 0, overlayImage.Width, overlayImage.Height, GraphicsUnit.Pixel);
}
- protected override void OnResize(EventArgs e)
+ [Browsable(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
+ public new string ImageLocation
{
- base.OnResize(e);
- this.UpdateStatusImages();
+ get { throw new NotSupportedException(); }
+ set { throw new NotSupportedException(); }
}
- protected override void Dispose(bool disposing)
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public new void Load(string url)
{
- base.Dispose(disposing);
-
- if (this.expandedErrorImage != null)
- this.expandedErrorImage.Dispose();
-
- if (this.expandedInitialImage != null)
- this.expandedInitialImage.Dispose();
-
- if (this.memoryImage != null)
- this.memoryImage.Dispose();
-
- if (this.loadAsyncCancelTokenSource != null)
- this.loadAsyncCancelTokenSource.Dispose();
+ throw new NotSupportedException();
}
}
}