0

I have the following code that takes a Control (as a Visual object), uses a Visual Brush to pain the control into a RenderTargetBitmap which can then be saved to disk. This is successful.

I would like to use the same code to place the image in the clipboard. This does not seem to work; although the clipboard accepts the data, it does not accept that the data is an image. This is clearly a formatting issue but I have no idea how to sort it...

The code follows:-

public void CopyToClipBoard(Visual forDrawing)
{           
    RenderTargetBitmap bmp = ControlToImage(forDrawing, 96, 96);
    CopyToClipBoard(bmp);
}
private void CopyToClipBoard(BitmapSource bmp)
{
   Thread thread = new Thread(() =>
   {                
      Clipboard.SetImage(bmp);
   });
   thread.SetApartmentState(ApartmentState.STA); //Set the thread to STA
   thread.Start();
}
private static RenderTargetBitmap ControlToImage(Visual target, double dpiX, double dpiY)
{
   if (target == null)
   {
      return null;
   }
   // render control content
   Rect bounds = VisualTreeHelper.GetDescendantBounds(target);
   RenderTargetBitmap rtb = new RenderTargetBitmap((int)(bounds.Width * dpiX / 96.0),
                                                   (int)(bounds.Height * dpiY / 96.0),
                                                  dpiX, dpiY,
                                                  PixelFormats.Pbgra32);
   DrawingVisual dv = new DrawingVisual();
   using (DrawingContext ctx = dv.RenderOpen())
   {
       VisualBrush vb = new VisualBrush(target);
       ctx.DrawRectangle(vb, null, new Rect(new System.Windows.Point(), bounds.Size));
    }
    rtb.Render(dv);
    return rtb;           
}
drG_Bristol
  • 165
  • 9
  • 1
    The clipboard operation must take place on the application's main thread. There is no need to spawn a new UI thread for the clipboard set operation. – BionicCode Nov 22 '20 at 14:06
  • 1
    If you have a background thread, you should at least make sure the RenderTargetBitmap is accessible from that thread (or from any thread other than the one in which it was created) by calling its Freeze() method. – Clemens Nov 22 '20 at 15:37

1 Answers1

2

After reviewing your code, I would expect a cross thread exception to be thrown in your thread delegate (CopyToClipBoard(BitmapSource):void). BitmapSource is a DispatcherObject and created on a different dispatcher thread (on the main thread) than it is handled on. You can't pass DispatcherObject instances between threads. Except the instance extends Freezable and Freezable.Freeze() was called on this instance.

This means in order to pass the bmp reference to the thread that handles the clipboard (or any other thread than the associated thread in general), you must call bmp.Freeze() before.

I assume the exception is swallowed by the Thread and you never ran your application in debug mode. That's why you never recognized the exception.

As I wrote before, you don't need to spawn a new STA thread to access the clipboard. I don't recommend this.

In case you keep using a dedicated thread, simply freeze the Freezable before passing it to the thread:

public void CopyToClipBoard(Visual forDrawing)
{           
    RenderTargetBitmap bmp = ControlToImage(forDrawing, 96, 96);
   
    // Freeze the freezable DispatcherObject so that it can be consumed by other threads
    bmp.Freeze();

    CopyToClipBoard(bmp);
}
BionicCode
  • 1
  • 4
  • 28
  • 44
  • Freezing the BitmapSource did the trick. I use a thread because this is not on the main thread of the application. I actually do run the software in debug mode but for some reason, VS2019 was not catching the exception - hence I assumed (always a mistake!) that something was being passed to the clipboard - just in an unfriendly format. – drG_Bristol Nov 22 '20 at 17:05
  • 1
    You should enable at least all CLR exceptions: press *Ctrl + Alt + E* and check the relevant boxes. By default VS won't break for all exceptions. – BionicCode Nov 22 '20 at 17:10
  • That has turned out to be really useful - Thank you – drG_Bristol Nov 23 '20 at 11:41