Программно (С#) конвертировать 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