2009-08-17 30 views
10

Quiero convertir un archivo de Excel en una imagen (cada formato está bien) programáticamente (C#). Actualmente estoy usando Microsoft Interop Libraries & Office 2007, pero no admite guardar en una imagen de forma predeterminada.Programmatically (C#) convertir Excel en una imagen

Así que mi solución alternativa actual es la siguiente:

  • Abrir archivo de Excel utilizando Microsoft interoperabilidad;
  • Encuentra el rango máximo (que contiene datos);
  • Use CopyPicture() en ese rango, que copiará los datos en el Portapapeles.

Ahora la parte difícil (y mis problemas):

Problema 1:

Utilización de la clase .NET portapapeles, yo no soy capaz de obtener los datos exactos copiados desde el portapapeles : los datos son los mismos, pero de alguna manera el formato está distorsionado (la fuente de todo el documento parece ser en negrita y un poco más ilegible mientras que no lo eran); Si pego desde el portapapeles con mspaint.exe, la imagen pegada es correcta (y justo como quiero que sea).

Desensamblé mspaint.exe y encontré una función que está usando (OleGetClipboard) para obtener datos del portapapeles, pero parece que no puedo hacer que funcione en C#/.NET.

Otras cosas que probé fueron los WINAPI de Portapapeles (OpenClipboard, GetClipboardData, CF_ENHMETAFILE), pero los resultados fueron los mismos que con las versiones de .NET.

Problema 2:

El uso de la gama y CopyPicture, si hay alguna imagen en la hoja de Excel, esas imágenes no se copian junto con los datos que rodean al portapapeles.

Parte del código fuente

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(); 
} 

Obtención de datos desde el portapapeles utilizando WINAPI tiene éxito, pero con mala calidad. Fuente:

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(); 
    } 
} 

Alguien tiene una solución? (Estoy usando .NET 2.0 por cierto)

+0

podría compartir su código fuente? ¿En qué formato quieres obtener los datos copiados? Como mapa de bits? –

Respuesta

3

lo hará.

Se puede ver nuestra ASP.NET (C# y VB) " gráfico de Excel y muestras Range Imaging" muestras here y descargar una versión de prueba gratuita here si quieres probarlo.

SpreadsheetGear también funciona con Windows Forms, aplicaciones de consola, etc. (no especificó qué tipo de aplicación está creando). También hay un control de Windows Forms para mostrar un libro en su aplicación si eso es lo que realmente desea.

exención de responsabilidad: Tengo SpreadsheetGear LLC

3

Por lo que entiendo por su pregunta, no puedo reproducir el problema.

He escogido un rango manualmente en Excel, eligieron copia como imagen con las opciones como se muestra en la pantalla y Bitmap seleccionado, a continuación, he utilizado el siguiente código para guardar los datos del portapapeles:

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(); 
    } 
} 

En cuanto a su segundo problema: hasta donde yo sé, no es posible seleccionar en Excel un rango de celda y una imagen al mismo tiempo. Si desea obtener ambos en una imagen al mismo tiempo, es posible que deba imprimir la hoja de Excel en un archivo de imagen/PDF/XPS.

+0

Gracias por actualizar. En mi caso, Clipboard.ContainsData (DataFormats.EnhancedMetafile) devuelve true, pero el Clipboard.GetData (DataFormats.EnhancedMetafile) siempre devuelve null. mspaint.exe (como siempre) puede pegar los datos que se copian en el portapapeles mediante CopyPicture() – Zurb

+0

@Zurb: ¿Obtiene 'null' de' Clipboard.GetData' también cuando ejecuta el código de ejemplo anterior ? Tenga en cuenta que está utilizando la clase del portapapeles WPF en 'System.Windows' y no la de los formularios de Windows. ¿Puedes verificar que ninguna otra aplicación esté bloqueando el portapapeles? –

+0

@divo: Olvidé mencionar que estoy usando .NET 2.0, por lo que no puedo ejecutar su código completamente tal como se publicó. Agregué parte de mi código fuente a la pregunta. – Zurb

1

Debido hilo asp.net no tiene el derecho a acceder ApartmentState portapapeles clase, por lo que debe escribir código para acceder Portapapeles en nuevo hilo. Por ejemplo:

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

en hilo principal:

.... 
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. 
.... 
+0

El código que publiqué se implementó para funcionar en una aplicación de formulario de Windows estándar de C# (que todavía no funciona por cierto), aunque planeaba usarlo en la Web. Gracias de todos modos. – Zurb

+0

Puedo confirmar que usar esto funciona para una solución web. – Dave

0

Es interesante que he estado haciendo esto en un compartimiento STA durante algún tiempo con éxito. Escribí una aplicación que se ejecuta semanalmente y envía informes de estado del proyecto, incluidos algunos gráficos generados mediante programación mediante Excel.

Anoche, esto falló todos los gráficos devueltos nulos. Estoy depurando hoy y no encuentro ninguna explicación solo de que el método Clipboard.GetImage() devuelve null de repente que no lo hizo. Al establecer un punto de interrupción en esta llamada, puedo demostrar de manera efectiva (presionando CTRL + V en MS-Word) que la imagen ES de hecho en el portapapeles. Por desgracia, continuar en Clipboard.GetImage() devuelve nulo (si estoy fisgoneando así o no).

Mi código se ejecuta como una aplicación de consola y el método Principal tiene el atributo [STAThread]. Lo depuro como una aplicación de formularios de Windows (todo mi código está en una biblioteca y simplemente tengo dos interfaces para eso).

Ambos devuelven nulo hoy.

Fuera de interés, giré la captura de gráficos en un subproceso como se indica (y tenga en cuenta que thread.ApartmentState está en desuso), y se ejecuta dulce, pero aún así, se devuelven nulos.

Bueno, me rendí e hice lo que cualquier geek de TI haría, reiniciado.

Voila ... todo estaba bien. Vaya a ver ... ¿es por eso que todos aborrecemos las computadoras, Microsoft Windows y Microsoft Office? Hmmmm ... ¡Hay algo, algo totalmente transitorio que puede sucederle a su PC que hace que Clipboard.GetImage() falle!

1

Este es un error con GDI + cuando se trata de convertir metarchivos a un formato de mapa de bits.
Ocurre en muchos campos electromagnéticos que muestra gráficos con textos. Para volver a crear, simplemente necesita crear un gráfico en Excel que muestre datos para sus ejes X e Y. Copie el gráfico como una imagen y péguelo en la palabra como un metarchivo. Abra el docx y verá un EMF en la carpeta multimedia. Si ahora abre ese EMF en cualquier programa de pintura basado en Windows que lo convierta en un mapa de bits, verá distorsiones, en particular, el texto y las líneas se harán más grandes y distorsionados. Desafortunadamente, es uno de esos problemas que es probable que Microsoft no admita ni haga nada al respecto. Esperemos que Google y Apple también se hagan cargo del mundo de la oficina/procesamiento de textos.

0

Si no le molesta Linux (estilo), puede usar OpenOffice (o LibreOffice) para convertir los xls primero a pdf, luego use ImageMagic para convertir el pdf a imagen. Una guía básica se puede encontrar en http://www.novell.com/communities/node/5744/c-linux-thumbnail-generation-pdfdocpptxlsimages.

Además, parece haber .Net API para los dos programas mencionados anteriormente. Ver: http://www.opendocument4all.com/download/OpenOffice.net.pdf y http://imagemagick.net/script/api.php#dot-net