/// <summary> /// Callback method invoked from worker threads to indicate that another /// thumbnail has been downloaded /// </summary> private void ProcessCompletedDownload(IVideo iVideo, VideoThumbnail thumbnail) { try { // ensure we don't invoke on 'dead' controls (could happen if a background // thread completes a download after the parent dialog has been dismisssed) if (!_workQueue.Terminated && !_owner.IsDisposed) { // marshal to the UI thread if necessary if (_owner.InvokeRequired) { _owner.BeginInvoke(new CompletedHandler(ProcessCompletedDownload), new object[] { iVideo, thumbnail }); } else { // cache the thumbnail _thumbnails[iVideo] = thumbnail; // notify listeners if (ThumbnailDownloadCompleted != null) { ThumbnailDownloadCompleted(iVideo); } } } } catch (Exception ex) { Trace.Fail("Unexpected exception in DownloaderCompletedHandler: " + ex); } }
/// <summary> /// Main worker thread logic for thumbnailer /// </summary> private void ThumbnailDownloaderMain() { try { // keep waiting for new work items until the queue is terminated // via the ThreadSafeQueueTerminatedException while (true) { // get the next video to download IVideo ivideo = _workQueue.Dequeue() as IVideo; // attempt to download it Stream videoStream = SafeDownloadThumbnail(ivideo); // either get the downloaded thumbnail or if we failed due to a // timeout or other error then put in a special 'Not Available' // thumbnail image VideoThumbnail thumbnail; if (videoStream != null) { thumbnail = new VideoThumbnail(videoStream); } else { thumbnail = new NoAvailableVideoThumbnail(); } // notification that the download is completed ProcessCompletedDownload(ivideo, thumbnail); } } catch (ThreadSafeQueueTerminatedException) { } catch (Exception ex) { Trace.Fail("Unexpected exception in ThumbnailDownloader: " + ex); } }
/// <summary> /// Download the thumbnail for the specified video (only if it isn't already in our cache) /// </summary> public void DownloadThumbnail(IVideo video) { if (!_thumbnails.Contains(video)) { // if we can get a version of the thumbnail from the cache // then just save this version PluginHttpRequest pluginHttpRequest = new PluginHttpRequest(video.ThumbnailUrl, HttpRequestCacheLevel.CacheOnly); using (Stream cachedStream = pluginHttpRequest.GetResponse()) { if (cachedStream != null) { MemoryStream memoryStream = new MemoryStream(); StreamHelper.Transfer(cachedStream, memoryStream); _thumbnails[video] = new VideoThumbnail(memoryStream); } // otherwise mark it as 'downloading' and enque the download else { _thumbnails[video] = new DownloadingVideoThumbnail(); _workQueue.Enqueue(video); } } } }
/// <summary> /// Callback method invoked from worker threads to indicate that another /// thumbnail has been downloaded /// </summary> private void ProcessCompletedDownload(IVideo iVideo, VideoThumbnail thumbnail) { try { // ensure we don't invoke on 'dead' controls (could happen if a background // thread completes a download after the parent dialog has been dismisssed) if (!_workQueue.Terminated && !_owner.IsDisposed) { // marshal to the UI thread if necessary if (_owner.InvokeRequired) { _owner.BeginInvoke(new CompletedHandler(ProcessCompletedDownload), new object[] { iVideo, thumbnail }); } else { // cache the thumbnail _thumbnails[iVideo] = thumbnail; // notify listeners if (ThumbnailDownloadCompleted != null) ThumbnailDownloadCompleted(iVideo); } } } catch (Exception ex) { Trace.Fail("Unexpected exception in DownloaderCompletedHandler: " + ex); } }
/// <summary> /// Main worker thread logic for thumbnailer /// </summary> private void ThumbnailDownloaderMain() { try { // keep waiting for new work items until the queue is terminated // via the ThreadSafeQueueTerminatedException while (true) { // get the next video to download IVideo ivideo = _workQueue.Dequeue() as IVideo; // attempt to download it Stream videoStream = SafeDownloadThumbnail(ivideo); // either get the downloaded thumbnail or if we failed due to a // timeout or other error then put in a special 'Not Available' // thumbnail image VideoThumbnail thumbnail; if (videoStream != null) thumbnail = new VideoThumbnail(videoStream); else thumbnail = new NoAvailableVideoThumbnail(); // notification that the download is completed ProcessCompletedDownload(ivideo, thumbnail); } } catch (ThreadSafeQueueTerminatedException) { } catch (Exception ex) { Trace.Fail("Unexpected exception in ThumbnailDownloader: " + ex); } }
protected override void OnDrawItem(DrawItemEventArgs e) { // screen invalid drawing states if (DesignMode || e.Index == -1) { return; } // get video we are rendering IVideo video = Items[e.Index] as IVideo; // determine state bool selected = ((e.State & DrawItemState.Selected) > 0) && !_preventSelectionPainting; // calculate colors Color textColor; if (selected) { if (Focused) { textColor = SystemColors.HighlightText; } else { textColor = SystemColors.ControlText; } } else { textColor = SystemColors.ControlText; } Color previewColor = Color.FromArgb(200, textColor); BidiGraphics g = new BidiGraphics(e.Graphics, e.Bounds); // setup standard string format TextFormatFlags ellipsesStringFormat = TextFormatFlags.WordBreak | TextFormatFlags.TextBoxControl | TextFormatFlags.ExpandTabs | TextFormatFlags.WordEllipsis; // draw background e.DrawBackground(); //using (SolidBrush solidBrush = new SolidBrush(backColor)) // g.FillRectangle(solidBrush, e.Bounds); // draw the thumbnail image Rectangle thumbnailRect = new Rectangle(e.Bounds.Left + ScaleX(HORIZONTAL_INSET), e.Bounds.Top + ScaleY(VERTICAL_INSET), THUMBNAIL_IMAGE_WIDTH, THUMBNAIL_IMAGE_HEIGHT); VideoThumbnail thumbnail = _thumbnailManager.GetThumbnail(video); thumbnail.Draw(g, e.Font, thumbnailRect); // calculate standard text drawing metrics int leftMargin = ScaleX(HORIZONTAL_INSET) + THUMBNAIL_IMAGE_WIDTH + ScaleX(HORIZONTAL_INSET); int topMargin = e.Bounds.Top + ScaleY(VERTICAL_INSET); int fontHeight = g.MeasureText(video.Title, e.Font).Height; // draw title and duration int titleWidth; Rectangle durationRectangle; using (Font hyperlinkFont = new Font(e.Font, FontStyle.Underline)) { titleWidth = e.Bounds.Right - ScaleX(HORIZONTAL_INSET) - leftMargin; Rectangle titleRectangle = new Rectangle(leftMargin, topMargin, titleWidth, fontHeight); string title = video.Title ?? ""; g.DrawText(title, hyperlinkFont, titleRectangle, selected ? textColor : SystemColors.HotTrack, ellipsesStringFormat); } using (Font durationFont = new Font(e.Font, FontStyle.Regular)) // was bold { durationRectangle = new Rectangle(leftMargin, topMargin + fontHeight + 3, titleWidth, fontHeight); string duration = String.Format(CultureInfo.InvariantCulture, "{0:00}:{1:00}", video.LengthSeconds / 60, video.LengthSeconds % 60); g.DrawText( duration, durationFont, durationRectangle, textColor, ellipsesStringFormat); } // draw description // calculate layout rectangle Rectangle layoutRectangle = new Rectangle( leftMargin, durationRectangle.Bottom + ScaleY(VERTICAL_INSET), e.Bounds.Width - leftMargin - ScaleX(HORIZONTAL_INSET), e.Bounds.Bottom - ScaleY(VERTICAL_INSET) - durationRectangle.Bottom - ScaleY(VERTICAL_INSET)); // draw description g.DrawText( video.Description, e.Font, layoutRectangle, previewColor, ellipsesStringFormat); // draw bottom-line if necessary if (!selected) { using (Pen pen = new Pen(SystemColors.ControlLight)) g.DrawLine(pen, e.Bounds.Left, e.Bounds.Bottom - ScaleY(1), e.Bounds.Right, e.Bounds.Bottom - ScaleY(1)); } // focus rectange if necessary e.DrawFocusRectangle(); }