protected override void OnPaint(PaintEventArgs e) { //Assume anything Vista or later has the same issues. if (IsVistaOrLater()) { //Windows Vista is opportunistic when it comes to wait conditions (e.g. locks, Mutexes, etc) //in that it will actually process WM_PAINT messages on the current thread, even though //it is supposed to be blocking in a WaitSleepJoin state. This behaviour can actually //break rendering for a couple of reasons: // 1. We do custom double-buffering, and it's possible that we could process a paint message // for an image that hasn't actually been rendered to the back buffer yet. // 2. The renderer itself accesses the pixel data of the ImageSops, which is a synchronized operation. // In the case where 2 threads try to load the pixel data of an image simultaneously, the renderer can end up // blocking execution on the main UI thread in the middle of a rendering operation. If we // allow another tile to paint in this situation, it actually causes some GDI errors because // the previous rendering operation has not yet completed. if (_isDrawing || _painting) { e.Graphics.Clear(Color.Black); //Queue this tile up for deferred painting and return. if (!_tilesToRepaint.Contains(this)) { _tilesToRepaint.Add(this); } return; } //We're about to paint this tile, so remove it from the queue. _tilesToRepaint.Remove(this); } if (_tile.PresentationImage == null) { DisposeSurface(); } if (this.Surface == null) { // Make sure tile gets blacked out if there's // no presentation image in it e.Graphics.Clear(Color.Black); } else { this.Surface.WindowID = this.Handle; this.Surface.ContextID = e.Graphics.GetHdc(); this.Surface.ClientRectangle = this.ClientRectangle; this.Surface.ClipRectangle = e.ClipRectangle; DrawArgs args = new DrawArgs(this.Surface, new WinFormsScreenProxy(Screen.FromControl(this)), DrawMode.Refresh); _painting = true; try { _tile.Draw(args); // if an exception was encountered the last time we rendered the buffer, refresh the error text now if (!string.IsNullOrEmpty(_lastRenderExceptionMessage)) { // we cannot simply pass the Graphics because we haven't released its hDC yet // if we do, we'll get a "Object is currently in use elsewhere" exception DrawErrorMessage(_lastRenderExceptionMessage, Surface.ContextID, ClientRectangle); } } catch (Exception ex) { Platform.Log(LogLevel.Error, ex, "An error has occured while refreshing the contents of a tile."); var exceptionMessage = ex is RenderingException ? ((RenderingException)ex).UserMessage : ex.Message; // we cannot simply pass the existing Graphics because we haven't released its hDC yet // if we do, we'll get a "Object is currently in use elsewhere" exception DrawErrorMessage(exceptionMessage, Surface.ContextID, ClientRectangle); } finally { e.Graphics.ReleaseHdc(this.Surface.ContextID); _painting = false; } } // Now that we've finished painting this tile, we can process the deferred paint jobs. // The code below is self-fulfilling, in that we remove one tile from the queue and // invalidate it, causing it to paint. When it's done painting, it will remove and // invalidate the next one, and so on. if (IsVistaOrLater() && _tilesToRepaint.Count > 0) { TileControl tileToRepaint = _tilesToRepaint[0]; _tilesToRepaint.RemoveAt(0); tileToRepaint.Invalidate(); tileToRepaint.Update(); } //base.OnPaint(e); }