Программно (С#) конвертировать 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 = CopyEnhMetaFileA(hEmfClp, null);
if (hEmfCopy != 0) {
Metafile metafile = new Metafile(new IntPtr(hEmfCopy), true);
metafile.Save(filename, ImageFormat.Png);
}
}
}
CloseClipboard();
}
}
Кто-нибудь получил решение? (Я использую .NET 2.0 btw)
Ответы
Ответ 1
SpreadsheetGear для .NET будет делать это.
Вы можете увидеть наши образцы ASP.NET(С# и VB) " Примеры диаграмм и диаграмм диаграммы Excel" здесь и скачайте бесплатную пробную версию здесь, если вы хотите попробовать.
SpreadsheetGear также работает с Windows Forms, консольными приложениями и т.д. (вы не указали, какой тип приложения вы создаете). Существует также элемент управления Windows Forms для отображения рабочей книги в вашем приложении, если это то, что вы действительно после.
Отказ от ответственности: у меня есть SpreadsheetGear LLC
Ответ 2
Из того, что я понимаю из вашего вопроса, я не могу воспроизвести проблему.
Я выбрал диапазон вручную в 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.
Ответ 3
Так как поток 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.
....
Ответ 4
Это ошибка с GDI +, когда дело доходит до преобразования метафайлов в формат битовой карты.
Это происходит для многих EMF, которые отображают диаграммы с текстами. Чтобы воссоздать, вам просто нужно создать диаграмму в excel, которая отображает данные для его оси X и Y. Скопируйте диаграмму как изображение и вставьте слово в качестве метафайла. Откройте docx, и вы увидите EMF в папке с медиа. Если теперь вы откроете эту EMF в любой программе на основе окон, которая преобразует ее в растровое изображение, вы увидите искажения, в частности, текст и линии станут больше и искажены. К сожалению, это одна из тех проблем, которые Microsoft вряд ли признает или что-то предпримет. Пусть надеется, что Google и Apple вскоре перейдут в офис/мир обработки текстов.
Ответ 5
Интересно, что я делал это в отделении 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() неудачным!
Ответ 6
Если вы не против 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