1

I've got a reporting application that runs server-side that reads a stored BMP from my database (as a byte[]), converts it back to an image, and then places it into an Excel spreadsheet that forms the base for this report (this report is ultimately delivered to the client for download.) To do this I'm trying to use the server-side clipboard to handle the 'pasting' of the image into a specific range in the worksheet. Here's the code snippet -

System.Drawing.Image image;
Bitmap bm;
Graphics g;
Excel.Range range;

MemoryStream ms = new MemoryStream(graphRecs.ElementAt(0).Graph, 0, 
   graphRecs.ElementAt(0).Graph.Length);
ms.Write(graphRecs.ElementAt(0).Graph, 0, graphRecs.ElementAt(0).Graph.Length);
image = System.Drawing.Image.FromStream(ms, true);

bm = new Bitmap(413, 130);
g = Graphics.FromImage(bm);
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.DrawImage(image, 0, 1, 413, 130);

Clipboard.SetDataObject(bm, false, 4, 250);
range = ws.get_Range(cBlkPtr[6, 2], cBlkPtr[6, 2]);
ws.Paste(range, bm);
Clipboard.Clear();

Running this in debug mode under VS2008 seems to work fine - the image is converted, added to the clipboard, and pasted into the specified range with no problems. After I publish the webapp to my IIS server, this fails on the 'Clipboard.SetDataObject' statement with the following exception -

Requested Clipboard operation did not succeed.
at System.Windows.Forms.Clipboard.ThrowIfFailed(Int32 hr)
at System.Windows.Forms.Clipboard.SetDataObject(Object data, Boolean copy, Int32 
       retryTimes, Int32 retryDelay) 
at ReportGenerate.buildPvsClinicSections(Worksheet ws, Object j, patientRecord p, String 
       patientStatus, String programType)

I am under the assumption that this error has to do with NOT being in a SingleThreadApartment. I've added the 'AspCompat=true' directive to my ASPX page with no change (didn't think it would help as AspCompat is more for ASP than ASPX). Since I can't add the [STAThread] to my 'main' (that would be IIS), I'm at a loss on how to proceed. I'm also open to changing the approach I'm using in adding the image to the spreadsheet, as long as I can explicitly specify (via the range) where to place it. Using Shape.AddPicture for instance doesn't allow me to do this.

Any ideas?

Thanks.

Update

I've updated the code snippet to start a second thread with the correct ApartmentState -

range = ws.get_Range(cBlkPtr[6, 2], cBlkPtr[6, 2]);

ClipboardModel cbm = new ClipboardModel(bm, range, ws);
System.Threading.Thread cbThread = new System.Threading.Thread(new
     System.Threading.ParameterizedThreadStart(DoClipboardStuff));
cbThread.SetApartmentState(System.Threading.ApartmentState.STA);
cbThread.Start(cbm);
cbThread.Join();

The 'DoClipboardStuff' method looks like this -

[STAThread]
protected void DoClipboardStuff(object o)
{
  try
  {
    ClipboardModel cbm = (ClipboardModel)o;

    Clipboard.SetDataObject(cbm.bm, false, 4, 250);
    cbm.ws.Paste(cbm.range, cbm.bm);
    Clipboard.Clear();
  }
  catch (Exception e)
  {
    StreamWriter sw = new StreamWriter(@"C:\Myopia\Log.txt");
    sw.WriteLine(e.Message);
    sw.WriteLine(e.StackTrace);
    sw.Flush();
    sw.Close();
    throw e;
  }
}

I'm now getting the exact same error as before, only now in this method. I'm beginning to suspect that it's not the ApartmentState, but rather the lack of a 'UI'. I don't know if the normal Win32 interface would be any better, but that's my next approach (unless someone else has a more, .NET'ish solution.)

Update #2

While I haven't been able to resolve the issue with IIS 6 and the clipboard, I've managed to work around this problem by writing the reconstructed BMP to a temp file, then using the Shapes.AddPicture to place it where I need it to be -

g.DrawImage(image, 0, 1, 400, 75);

bm.Save(@"c:\Myopia\temp.bmp");

Excel.Shape xlShape = ws.Shapes.Item("Rectangle 2");
float left = xlShape.Left;
float top = Convert.ToSingle(ws.get_Range("A1", cBlkPtr[5, 2]).Height);
float width = xlShape.Width;
float height = xlShape.Height;

xlShape = ws.Shapes.AddPicture(@"c:\Myopia\temp.bmp", 
   Microsoft.Office.Core.MsoTriState.msoFalse, Microsoft.Office.Core.MsoTriState.msoCTrue,
        left, top, width, height);

Not an ideal solution, but one that works for now. The only issue with this approach is that I seem to have a loss of resolution between reconstructing the BMP from the byte[], saving it to the temp.bmp file, and then adding it back in - the bmp looks 'fuzzy'. May have to look for a less 'lossy' format to use.

JJalenak
  • 123
  • 8

1 Answers1

0

If STA is indeed the problem then try performing the operation in a new thread that you set to STA before you start it as shown in the Skeet(tm)'s answer to this question:

in .NET, How do I set STAThread when I'm running a form in an additional thread?

Somewhere deep in the back of my mind though a little voice is suggesting that the clipboard might only be available to apps with a UI (the dev web server does for example)... Hope I'm wrong though!

Community
  • 1
  • 1
Andras Zoltan
  • 41,961
  • 13
  • 104
  • 160
  • Updated original question. Modified the code to fire a second thread with the correct ApartmentState, but succeeded in only moving the error down to my helper method. – JJalenak Nov 16 '10 at 21:29
  • JJanelak: In that case, then, I'm not entirely sure. I've just setup a new web application with a page that sets some text on the clipboard and then renders it off the clipboard. All I had to do was set aspcompat on the page as you have done. This works on both DevWebServer and IIS7.5 (with the app pool using the default ApplicationPoolIdentity). In this case, it could be the identity of the app pool on the live server, since access to the clipboard is a security privilege and is therefore controlled by the type of security context the user has. – Andras Zoltan Nov 16 '10 at 23:01
  • Andras - Unfortunately I'm still stuck with IIS 6, and frankly am not real knowledgable on IIS. From what I can tell, the DefaultAppPool is what I'm using, with a 'Predefined' Identity of the Network Service. The website application is set to use DefaultAppPool, and under the ASP.Net tab, Application, the Identity Settings are Local Impersonation with the admin username / password. What should I be checking for? THanks..... – JJalenak Nov 17 '10 at 14:45