OSDN Git Service

OTPictureBox コントロールを追加
authorKimura Youichi <kim.upsilon@bucyou.net>
Sat, 12 Jan 2013 18:31:20 +0000 (03:31 +0900)
committerKimura Youichi <kim.upsilon@bucyou.net>
Sat, 19 Jan 2013 11:21:20 +0000 (20:21 +0900)
今後 LoadAsync() の内部をいじり易くする目的で実装

OpenTween/OTPictureBox.cs [new file with mode: 0644]
OpenTween/OTWebClient.cs
OpenTween/OpenTween.csproj
OpenTween/TweetThumbnail.cs

diff --git a/OpenTween/OTPictureBox.cs b/OpenTween/OTPictureBox.cs
new file mode 100644 (file)
index 0000000..ee7501d
--- /dev/null
@@ -0,0 +1,207 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Windows.Forms;
+using System.ComponentModel;
+using System.Drawing;
+using System.Threading.Tasks;
+using System.Threading;
+using System.Net;
+using System.IO;
+
+namespace OpenTween
+{
+    public class OTPictureBox : PictureBox
+    {
+        [Localizable(true)]
+        public new string ImageLocation
+        {
+            get { return this._ImageLocation; }
+            set
+            {
+                if (value == null)
+                {
+                    this.Image = null;
+                    return;
+                }
+                this.LoadAsync(value);
+            }
+        }
+        private string _ImageLocation;
+
+        private Task loadAsyncTask = null;
+        private CancellationTokenSource loadAsyncCancelTokenSource = null;
+
+        public new Task LoadAsync(string url)
+        {
+            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();
+
+            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 => {
+                if (t.IsFaulted) throw t.Exception;
+
+                var bytes = t.Result;
+                using (var stream = new MemoryStream(bytes))
+                {
+                    stream.Write(bytes, 0, bytes.Length);
+
+                    return Image.FromStream(stream, true, true);
+                }
+            }, cancelToken)
+            .ContinueWith(t =>
+            {
+                if (!t.IsCanceled)
+                {
+                    if (t.IsFaulted)
+                        this.Image = this.expandedErrorImage;
+                    else
+                        this.Image = t.Result;
+                }
+
+                var exp = t.Exception != null ? t.Exception.Flatten() : null;
+                this.OnLoadCompleted(new AsyncCompletedEventArgs(exp, t.IsCanceled, null));
+            }, uiScheduler);
+        }
+
+        public new void CancelAsync()
+        {
+            if (this.loadAsyncTask == null || this.loadAsyncTask.IsCompleted)
+                return;
+
+            this.loadAsyncCancelTokenSource.Cancel();
+
+            try
+            {
+                this.loadAsyncTask.Wait();
+            }
+            catch (AggregateException ae)
+            {
+                ae.Handle(e =>
+                {
+                    if (e is OperationCanceledException)
+                        return true;
+                    if (e is WebException)
+                        return true;
+
+                    return false;
+                });
+            }
+        }
+
+        public new Image ErrorImage
+        {
+            get { return base.ErrorImage; }
+            set
+            {
+                base.ErrorImage = value;
+                this.UpdateStatusImages();
+            }
+        }
+
+        public new Image InitialImage
+        {
+            get { return base.InitialImage; }
+            set
+            {
+                base.InitialImage = value;
+                this.UpdateStatusImages();
+            }
+        }
+
+        private Image expandedErrorImage = null;
+        private Image expandedInitialImage = null;
+
+        /// <summary>
+        /// ErrorImage と InitialImage の表示用の画像を生成する
+        /// </summary>
+        /// <remarks>
+        /// ErrorImage と InitialImage は SizeMode の値に依らず中央等倍に表示する必要があるため、
+        /// 事前にコントロールのサイズに合わせた画像を生成しておく
+        /// </remarks>
+        private void UpdateStatusImages()
+        {
+            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);
+        }
+
+        private Image ExpandImage(Image image)
+        {
+            if (image == null) return null;
+
+            var bitmap = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);
+
+            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;
+
+                g.DrawImage(image,
+                    new Rectangle(posx, posy, image.Width, image.Height),
+                    new Rectangle(0, 0, image.Width, image.Height),
+                    GraphicsUnit.Pixel);
+            }
+
+            return bitmap;
+        }
+
+        protected override void OnResize(EventArgs e)
+        {
+            base.OnResize(e);
+            this.UpdateStatusImages();
+        }
+
+        protected override void Dispose(bool disposing)
+        {
+            base.Dispose(disposing);
+
+            if (this.expandedErrorImage != null)
+                this.expandedErrorImage.Dispose();
+
+            if (this.expandedInitialImage != null)
+                this.expandedInitialImage.Dispose();
+        }
+    }
+}
index 2e601bd..9e006f8 100644 (file)
@@ -24,6 +24,9 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Net;
+using System.Threading.Tasks;
+using System.Threading;
+using System.ComponentModel;
 
 namespace OpenTween
 {
@@ -51,5 +54,53 @@ namespace OpenTween
 
             return req;
         }
+
+        public new Task<byte[]> DownloadDataAsync(Uri address)
+        {
+            return this.DownloadDataAsync(address, CancellationToken.None);
+        }
+
+        public Task<byte[]> DownloadDataAsync(Uri address, CancellationToken token)
+        {
+            var tcs = new TaskCompletionSource<byte[]>();
+
+            token.Register(() =>
+            {
+                this.CancelAsync();
+            });
+
+            DownloadDataCompletedEventHandler onCompleted = null;
+
+            onCompleted = (s, e) =>
+            {
+                this.DownloadDataCompleted -= onCompleted;
+
+                if (e.Cancelled)
+                {
+                    tcs.TrySetCanceled();
+                    return;
+                }
+                if (e.Error != null)
+                {
+                    tcs.TrySetException(e.Error);
+                    return;
+                }
+
+                tcs.SetResult(e.Result);
+            };
+
+            this.DownloadDataCompleted += onCompleted;
+            base.DownloadDataAsync(address);
+
+            return tcs.Task;
+        }
+
+        [Browsable(false)]
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public new event DownloadDataCompletedEventHandler DownloadDataCompleted
+        {
+            add { base.DownloadDataCompleted += value; }
+            remove { base.DownloadDataCompleted -= value; }
+        }
     }
 }
index 5f6ce75..904657b 100644 (file)
     <Compile Include="DetailsListView.cs">
       <SubType>Component</SubType>
     </Compile>
+    <Compile Include="OTPictureBox.cs">
+      <SubType>Component</SubType>
+    </Compile>
     <Compile Include="OTWebClient.cs">
       <SubType>Component</SubType>
     </Compile>
index 5a008c4..85b07cf 100644 (file)
@@ -36,7 +36,7 @@ namespace OpenTween
 {
     public partial class TweetThumbnail : UserControl
     {
-        protected internal List<PictureBox> pictureBox = new List<PictureBox>();
+        protected internal List<OTPictureBox> pictureBox = new List<OTPictureBox>();
 
         private Task task = null;
         private CancellationTokenSource cancelTokenSource;
@@ -156,9 +156,9 @@ namespace OpenTween
             this.ResumeLayout(false);
         }
 
-        protected virtual PictureBox CreatePictureBox(string name)
+        protected virtual OTPictureBox CreatePictureBox(string name)
         {
-            return new PictureBox()
+            return new OTPictureBox()
             {
                 Name = name,
                 SizeMode = PictureBoxSizeMode.Zoom,