Программно (С#) конвертировать Excel в изображение

Я хочу преобразовать файл excel в изображение (каждый формат в порядке) программно (С#). В настоящее время я использую библиотеки Microsoft Interop и Office 2007, но по умолчанию он не поддерживает сохранение изображения.

Итак, моя текущая работа выглядит следующим образом:

  • Открыть файл Excel с помощью Microsoft Interop;
  • Узнайте максимальный диапазон (содержащий данные);
  • Используйте CopyPicture() в этом диапазоне, который скопирует данные в буфер обмена.

Теперь сложная часть (и мои проблемы):

Проблема 1:

Используя класс .NET Clipboard, я не могу получить EXACT скопированные данные из буфера обмена: данные одинаковые, но каким-то образом форматирование искажается (шрифт всего документа кажется полужирным, а немного нечитабельно, пока они не были); Если я вставлю из буфера обмена с помощью mspaint.exe, вставленное изображение будет правильным (и так же, как я хочу, чтобы оно было).

Я разобрал mspaint.exe и нашел функцию, которую он использует (OleGetClipboard), чтобы получить данные из буфера обмена, но я не могу заставить его работать в С#/.NET.

Другими вещами, которые я пробовал, были Clipboard WINAPI (OpenClipboard, GetClipboardData, CF_ENHMETAFILE), но результаты были такими же, как с использованием версий .NET.

Проблема 2:

Используя диапазон и CopyPicture, если на листе Excel есть какие-либо изображения, эти изображения не копируются вместе с окружающими данными в буфер обмена.

Некоторый исходный код

Excel.Application app = new Excel.Application();
app.Visible = app.ScreenUpdating = app.DisplayAlerts = false;
app.CopyObjectsWithCells = true;
app.CutCopyMode = Excel.XlCutCopyMode.xlCopy;
app.DisplayClipboardWindow = false;
try {
 Excel.Workbooks workbooks = null;
 Excel.Workbook book = null;
 Excel.Sheets sheets = null;
 try {
 workbooks = app.Workbooks;
 book = workbooks.Open(inputFile, false, false, Type.Missing, Type.Missing, Type.Missing, Type.Missing, 
 Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, 
 Type.Missing, Type.Missing);
 sheets = book.Worksheets;
 } catch {
 Cleanup(workbooks, book, sheets); //Cleanup function calls Marshal.ReleaseComObject for all passed objects
 throw;
 }
 for (int i = 0; i < sheets.Count; i++) {
 Excel.Worksheet sheet = (Excel.Worksheet)sheets.get_Item(i + 1);
 Excel.Range myrange = sheet.UsedRange;
 Excel.Range rowRange = myrange.Rows;
 Excel.Range colRange = myrange.Columns;
 int rows = rowRange.Count;
 int cols = colRange.Count;
 //Following is used to find range with data
 string startRange = "A1";
 string endRange = ExcelColumnFromNumber(cols) + rows.ToString();
 //Skip "empty" excel sheets
 if (startRange == endRange) {
 Excel.Range firstRange = sheet.get_Range(startRange, endRange);
 Excel.Range cellRange = firstRange.Cells;
 object text = cellRange.Text;
 string strText = text.ToString();
 string trimmed = strText.Trim();
 if (trimmed == "") {
 Cleanup(trimmed, strText, text, cellRange, firstRange, myrange, rowRange, colRange, sheet);
 continue;
 }
 Cleanup(trimmed, strText, text, cellRange, firstRange);
 }
 Excel.Range range = sheet.get_Range(startRange, endRange);
 try {
 range.CopyPicture(Excel.XlPictureAppearance.xlScreen, Excel.XlCopyPictureFormat.xlPicture);
 //Problem here <-------------
 //Every attempt to get data from Clipboard fails
 } finally {
 Cleanup(range);
 Cleanup(myrange, rowRange, colRange, sheet);
 }
 } //end for loop
 book.Close(false, Type.Missing, Type.Missing);
 workbooks.Close();
 Cleanup(book, sheets, workbooks);
} finally {
 app.Quit();
 Cleanup(app);
 GC.Collect();
}

Получение данных из буфера обмена с помощью WINAPI преуспевает, но с плохим качеством. Источник:

protected virtual void ClipboardToPNG(string filename) {
 if (OpenClipboard(IntPtr.Zero)) {
 if (IsClipboardFormatAvailable((int)CLIPFORMAT.CF_ENHMETAFILE)) {
 int hEmfClp = GetClipboardDataA((int)CLIPFORMAT.CF_ENHMETAFILE);
 if (hEmfClp != 0) {
 int hEmfCopy = ****************(hEmfClp, null);
 if (hEmfCopy != 0) {
 Metafile metafile = new Metafile(new IntPtr(hEmfCopy), true);
 metafile.Save(filename, ImageFormat.Png);
 }
 }
 }
 CloseClipboard();
 }
}

Кто-нибудь получил решение? (Я использую .NET 2.0 btw)

6 ответов

SpreadsheetGear для .NET будет делать это.

Вы можете увидеть наши образцы ASP.NET(С# и VB) " Примеры диаграмм и диаграмм диаграммы Excel" здесь и скачайте бесплатную пробную версию здесь, если вы хотите попробовать.

SpreadsheetGear также работает с Windows Forms, консольными приложениями и т.д. (вы не указали, какой тип приложения вы создаете). Существует также элемент управления Windows Forms для отображения рабочей книги в вашем приложении, если это то, что вы действительно после.

Отказ от ответственности: у меня есть SpreadsheetGear LLC


Из того, что я понимаю из вашего вопроса, я не могу воспроизвести проблему.

Я выбрал диапазон вручную в Excel, выбрал "Копировать как изображение" с параметрами, как показано на экране, и "Растровое изображение" выбрано, и я использовал следующий код для сохранения данных буфера обмена:

using System;
using System.IO;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Drawing.Imaging;
using Excel = Microsoft.Office.Interop.Excel;
public class Program
{
 [STAThread]
 static void Main(string[] args)
 {
 Excel.Application excel = new Excel.Application();
 Excel.Workbook wkb = excel.Workbooks.Add(Type.Missing);
 Excel.Worksheet sheet = wkb.Worksheets[1] as Excel.Worksheet;
 Excel.Range range = sheet.Cells[1, 1] as Excel.Range;
 range.Formula = "Hello World";
 // copy as seen when printed
 range.CopyPicture(Excel.XlPictureAppearance.xlPrinter, Excel.XlCopyPictureFormat.xlPicture);
 // uncomment to copy as seen on screen
 //range.CopyPicture(Excel.XlPictureAppearance.xlScreen, Excel.XlCopyPictureFormat.xlBitmap);
 Console.WriteLine("Please enter a full file name to save the image from the Clipboard:");
 string fileName = Console.ReadLine();
 using (FileStream fileStream = new FileStream(fileName, FileMode.Create))
 {
 if (Clipboard.ContainsData(System.Windows.DataFormats.EnhancedMetafile))
 {
 Metafile metafile = Clipboard.GetData(System.Windows.DataFormats.EnhancedMetafile) as Metafile;
 metafile.Save(fileName);
 }
 else if (Clipboard.ContainsData(System.Windows.DataFormats.Bitmap))
 {
 BitmapSource bitmapSource = Clipboard.GetData(System.Windows.DataFormats.Bitmap) as BitmapSource;
 JpegBitmapEncoder encoder = new JpegBitmapEncoder();
 encoder.Frames.Add(BitmapFrame.Create(bitmapSource));
 encoder.QualityLevel = 100;
 encoder.Save(fileStream);
 }
 }
 object objFalse = false;
 wkb.Close(objFalse, Type.Missing, Type.Missing);
 excel.Quit();
 }
}

Относительно вашей второй проблемы: насколько я знаю, в Excel невозможно использовать одновременно диапазон ячеек и изображение. Если вы хотите получить оба изображения одновременно, вам может потребоваться распечатать лист Excel в файле image/PDF/XPS.


Так как поток asp.net не имеет подходящего объекта ApartmentState для доступа к Clipboard Class, вы должны написать код для доступа к Clipboard в новом потоке. Например:

private void AccessClipboardThread()
{
 // access clipboard here normaly
}

в основном потоке:

....
Excel.Range range = sheet.get_Range(startRange, endRange); //Save range image to clipboard
Thread thread = new Thread(new ThreadStart(AccessClipboardThread));
thread.ApartmentState = ApartmentState.STA;
thread.Start();
thread.Join(); //main thread will wait until AccessClipboardThread finish.
....


Это ошибка с GDI +, когда дело доходит до преобразования метафайлов в формат битовой карты. Это происходит для многих EMF, которые отображают диаграммы с текстами. Чтобы воссоздать, вам просто нужно создать диаграмму в excel, которая отображает данные для его оси X и Y. Скопируйте диаграмму как изображение и вставьте слово в качестве метафайла. Откройте docx, и вы увидите EMF в папке с медиа. Если теперь вы откроете эту EMF в любой программе на основе окон, которая преобразует ее в растровое изображение, вы увидите искажения, в частности, текст и линии станут больше и искажены. К сожалению, это одна из тех проблем, которые Microsoft вряд ли признает или что-то предпримет. Пусть надеется, что Google и Apple вскоре перейдут в офис/мир обработки текстов.


Интересно, что я делал это в отделении STA некоторое время с успехом. Я написал приложение, которое работает на еженедельной основе и отправляет отчеты о статусе проекта, включая некоторые графики, которые я генерирую программно с помощью Excel.

Прошлой ночью это не привело к тому, что все графы вернули null. Я отлаживаю сегодня и не нахожу объяснений, что метод Clipboard.GetImage() возвращает null внезапно, чего не было. Установив точку останова при этом вызове, я могу эффективно продемонстрировать (нажав CTRL + V в MS-Word), что изображение действительно находится в буфере обмена. Увы, продолжение в Clipboard.GetImage() возвращает значение null (независимо от того, как я это просматриваю или нет).

Мой код работает как консольное приложение, а метод Main имеет атрибут [STAThread]. Я отлаживаю его как приложение Windows Form (весь мой код находится в библиотеке, и у меня просто есть два передних конца).

Оба возвращают null сегодня.

Из интереса я выделил сборщик диаграммы в поток, как указано (и обратите внимание, что thread.ApartmentState устарел), и он запускается сладко, но все же возвращаются значения null.

Хорошо, я сдался и сделал то, что сделал бы любой компьютерщик, перезагрузился.

Воила... все было хорошо. Идите по фигуре... вот почему мы все ненавидим компьютеры, Microsoft Windows и Microsoft Office? Хмммм... Есть что-то, что-то совершенно преходящее, что может случиться с вашим ПК, что делает Clipboard.GetImage() неудачным!


Если вы не против Linux (стиль), вы можете использовать OpenOffice (или LibreOffice), чтобы преобразовать xls сначала в pdf, а затем использовать ImageMagic для преобразования PDF в изображение. Основное руководство можно найти на http://www.novell.com/communities/node/5744/c-linux-thumbnail-generation-pdfdocpptxlsimages.

Кроме того, как представляется, API-интерфейсы .Net для обеих упомянутых выше программ. Видеть: http://www.opendocument4all.com/download/OpenOffice.net.pdf а также http://imagemagick.net/script/api.php#dot-net

licensed under cc by-sa 3.0 with attribution.