我有一个在服务器端运行的报告应用程序,它从数据库中读取存储的 BMP(作为字节 []),将其转换回图像,然后将其放入构成此报告基础的 Excel 电子表格中(此报告最终交付给客户端供下载。)为此,我尝试使用服务器端剪贴板来处理将图像“粘贴”到工作表中的特定范围。这是代码片段 -
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();
在 VS2008 下以调试模式运行它似乎工作正常 - 图像被转换,添加到剪贴板,并粘贴到指定范围,没有任何问题。将 web 应用程序发布到 IIS 服务器后,“Clipboard.SetDataObject”语句失败,但出现以下异常 -
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)
我假设此错误与不在 SingleThreadApartment 中有关。我已将“AspCompat=true”指令添加到我的 ASPX 页面,没有任何更改(我认为这不会有帮助,因为 AspCompat 比 ASPX 更适合 ASP)。由于我无法将 [STAThread] 添加到我的“主”(即 IIS)中,因此我不知道如何继续。我也愿意改变将图像添加到电子表格中所使用的方法,只要我可以明确指定(通过范围)将其放置在哪里。例如使用 Shape.AddPicture 不允许我这样做。
有什么想法吗?
谢谢。
更新
我已经更新了代码片段,以使用正确的 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();
“DoClipboardStuff”方法如下所示 -
[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;
}
}
我现在得到与以前完全相同的错误,只是现在在此方法中。我开始怀疑这不是 ApartmentState,而是缺少“UI”。我不知道普通的 Win32 界面是否会更好,但这是我的下一个方法(除非其他人有更多的 .NET 风格的解决方案。)
更新#2
虽然我无法解决问题IIS 6 和剪贴板,我通过将重建的 BMP 写入临时文件,然后使用 Shapes.AddPicture 将其放置在我需要的位置,成功解决了这个问题 -
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);
不是一个理想的解决方案,但有效的解决方案目前。这种方法的唯一问题是,我在从 byte[] 重建 BMP、将其保存到 temp.bmp 文件,然后将其添加回来之间似乎失去了分辨率 - bmp 看起来“模糊”。可能需要寻找一种“损耗较小”的格式来使用。
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.
发布评论
评论(1)
如果 STA 确实是问题所在,那么请尝试在启动之前在设置为 STA 的新线程中执行该操作,如 Skeet(tm) 对此问题的回答所示:
在 .NET 中,当我正在另一个线程中运行表单?
在我内心深处的某个地方,尽管有一点声音暗示剪贴板可能仅适用于具有 UI 的应用程序(开发 Web 服务器确实< /em> 例如)...但希望我错了!
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!