sábado, 18 de abril de 2015

Trabajar con archivos Excel desde WindowsForms (SpreadSheetLight)

 

Hola a todos:

En este articulo les compartiré como de una forma sencilla podemos trabajar con archivos Excel desde un proyecto de Visual Studio sin necesidad de recurrir a los tan tediosos objetos Com de Microsoft Office que muchas veces provocan mas dolores de cabeza que soluciones.

Buscando por la red la manera de como operar con archivos Excel de una forma sencilla me encontré con una pequeña librería de uso libre, la cual ofrece diferentes funcionalidades para trabajar con estos archivos sin romperse tanto la cabeza y sin la necesidad de referencias ningún objeto COM, hablo de:

SpreadSheetLight

“SpreadsheetLight is an open source Open XML spreadsheet library for .NET Framework written in C#, and is released under the MIT License. You can create new Open XML spreadsheets, or work with existing Open XML spreadsheets that are compatible with Microsoft Excel 2007/2010/2013 and LibreOffice Calc.”

Esta pequeña librería explota la  característica de los Xml que conforman la mayoría de los archivos del paquete de Microsoft Office a través del uso de:

SDK de Open XML 2.5 para Office

Con el Open Xml tenemos todo el acceso disponible a los XMl de los archivos de Microsoft Office pudiendo explotar los datos y estilos contenidos en ellos de una manera muy sencilla, pero SpreadSheetLinght hace este camino aun mas fácil al exponer funcionalidad convertidos en metodos y funciones parametrizables y muy intuitivos y por debajo se encarga de explotar los XML’s por medio del Open Xml.

En este articulo les mostrare como:

1. Crear un archivo de Microsoft Office Excel desde Visual Studio en un proyecto WindowsForms y como poblarlo con datos desde un control DataGridView.

2. Como abrir, leer y cargar a un control DataGridView los datos de un archivo de Microsoft Office Excel

Como siempre…

Antes de entrar de lleno en el tema del manejo de archivos de Microsoft Office Excel recuerde que: “El objetivo no es otro mas que el de orientar a los Parvulos .Net sobre como usando una pequeña librería podemos trabajar con archivo de Microsoft Office Excel, todo lo escrito en este articulo no es ensayado y no es revisado por nadie mas por lo cual, podría contener errores gramaticales y sintácticos, el articulo y sus conceptos no pretenden ser la verdad absoluta del tema por lo tanto siéntase con la confianza de dejar sus comentarios y opiniones en la sección de comentarios al final del mismo y si lo considera prudente envíeme un correo electrónico por medio del formulario de contacto con sus ideas, opiniones y experiencias sobre el tema, y por ultimo si el articulo le es de utilidad por favor considere dejar un comentario de agradecimiento, apoyar al mismo recomendando los artículos y unirse como miembro del blog. Si se siente agradecido puede ver los anuncios que están en este articulo haciendo Click en ellos.”

Requisitos: Visual Studio 2013, Framework 4.0, descargar la librería SpreadSheetLigh pero sobre todo muchas ganas de aprender.

También como siempre recomiendo encarecidamente que antes de descargar los proyectos de ejemplo (que les pondré al final de articulo), traten de hacerlo ustedes mismos siguiendo paso a paso todo lo que se mencionara aquí, si tienen dudas en uno en especifico no duden en contactarme.

Creación de un Archivo EXCEL usando SpreadSheetLingth

1. Inicie Visual Studio 2013 y cree un proyecto del tipo WindowsForms

2. Agregue un nuevo elemento del tipo WindowsForms

3. Agregue controles TextBox, label, button y un control DataGridView y cree el siguiente diseño

1

Recuerde nombrar a los controles acorde los datos que representen, ejemplo: lblIdEmpleado, txtEmpleado, txtApellidoPaterno, btnCargar, etc,

Instalar SpreadSheetLigth

Para instalar SpreadSheetLigth en nuestro proyecto, tenemos dos opciones:

1. Usar el paquete de Nuget para descargar en línea la librería e instalarla en nuestro proyecto, para esto:

Localice el Explorador de soluciones –> Seleccione el proyecto que acaba de crear –> Click derecho –> Administrar paquetes Nuget.

2

Del panel izquierdo seleccione “En línea” –> en el panel izquierdo escriba SpreadsheetLight –> Presione la tecla “Enter” –> Del panel  Central ubique SpreadsheetLight –> Click sobre el botón Instalar

3

4

Después de instalado usted podrá ver la referencia de la librería dentro de las referencias del proyecto:

5

2. La segunda opción que tiene para instalar SpreadsheetLight es mediante la creación de la referencia a la librería directamente desde las referencias del proyecto, para ello:

  • Descargue la librería desde esta dirección: SpreadSheetLight
  • En el explorador de soluciones ubique el proyecto que acaba de crear
  • Posicionese en Reference –> Click derecho –> Agregar referencia

6

  • En el panel izquierdo elija “Examinar” –> presione el botón “Examinar” –> diríjase a la ubicación de descarga –> Seleccione la librería –> Click en “Agregar” –> Click en “Aceptar” –> Después usted podrá ver la referencia a la librería en la carpeta de referencias, tal cual como se muestra en la pantalla relacionado a Nuget.

Sea cual la opción que haya escogido para instalar SpreaSheetLigth, le comento que SpreadSheetLigth tiene una dependencia hacia el OpenXml v2.0 por lo que usted tiene que descargar y referenciar esta librería, por favor descargue la librería y haga la referencia.

Bien, teniendo ambas librerías referenciadas procedamos a crear algunas líneas de código…

Crear un documento Excel usando SpreadSheetLigth:

Entiendo que usted conoce la manera de poblar un control DataGridView, de no ser así por favor antes de continuar analice lo platicado en este articulo.

Llenar DataGridView con datos de TextBox’s usando Datasource o el método Add()

Crearemos un método que mediante la librería cree un objeto del tipo SLDocument el cual nos servirá para ir seteando valores a lo que dentro del archivo Excel serán las celdas:

        /// <summary>
///
Metodo encargado de crear un archivo Excel y poblarlos con los datos de un control DataGridView
/// </summary>
private void CreateExcelFile()
{
//Creamos un objeto SqlDocument
SLDocument sl = new SLDocument();
//usamos la función SetCellValue para poblar la celda
//sl.SetCellValue(indicedefila, indicedecolumna, "valor a establecer");
sl.SetCellValue(1, 1, "Id Empleado");
sl.SetCellValue(1, 2,
"Empleado");
sl.SetCellValue(1, 3,
"Apellido Paterno");
sl.SetCellValue(1, 4,
"Apellido Materno");

//definimos el indice de la fila donde comenzaremos a vaciar los datos del DataGridView
int rowIndex = 2;
//
//definimos el indice de la columna donde comenzaremos a vaciar los datos
int columnIndex = 1;
//
//Recorremos las filas del control
foreach (DataGridViewRow row in dgvDatos.Rows)
{
//Recorremos la lista de columnas del control
for(int i = 0; i < dgvDatos.Columns.Count; i++)
{
//comenzamos a enviar los valores al objeto SLDocument, usando el valor de la fila y columna
sl.SetCellValue(rowIndex, columnIndex, Convert.ToString(row.Cells[i].Value));
columnIndex += 1;
}
rowIndex += 1;
columnIndex = 1;
}
//
//Guardamos el documento
//como no definimos ningun directorio el archivo se creara automaticamente en la carpeta bin/debug
sl.SaveAs("TestExcel.xlsx");
}
 
Cargar un documento Excel usando SpreadSheetLigth:
        private void Import()
{
//usamos el objeto FileSteam para recuperar el archivo
FileStream fs = new FileStream("TestExcel.xlsx", FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
//
//Creamos el obejto SLDocument para cargar el archivo Excel
SLDocument sl = new SLDocument(fs);

//refinimos el indice de la fila de donde comenzaremos a leer
int rowIndex = 2;

//recorremos el objeto SLDocument mediante un ciclo While
//este ciclo recorre el documento miestras no se encuentre una celda vacia
while (!string.IsNullOrEmpty(sl.GetCellValueAsString(rowIndex, 1)))
{
//tomamos los valores de las celdas y lo pasamos a las respectivas columnas del control DataGridView
dgvDatos.Rows.Add(sl.GetCellValueAsString(rowIndex, 1), sl.GetCellValueAsString(rowIndex, 2),
sl.GetCellValueAsString(rowIndex, 3), sl.GetCellValueAsString(rowIndex, 4));

//incrementeamos una unidad al indice de la fila para continuar con el recorrido
rowIndex += 1;
}
}

Bien hasta aqui tenemos la codificacion necesaria para Crear y Leer un archivo de Microsoft Excel usando SpreadSheetligth, solo nos queda probarlo:


Primero les mostrare que dentro de mi directorio de trabajo no tengo ningún archivo Excel:


7


Ejecute la aplicación:


8


Presione el botón Generar Excel y diríjase a su directorio de trabajo para que observe que se acaba de crear un archivo Excel y que este tiene un peso en kb lo cual significa que no esta vacío.


9


Abra el archivo y observe que los datos tal cual como los cargamos al control se encuentran en el archivo Excel.


10


Realice algunos cambios en la información cargada en el archivo, esto a fin de comprobar que la carga del archivo se realiza correctamente y que no depende solo de los datos que se crearon con la librería.


11


Ahora, probemos la carga:


Para eso asegúrese que el formulario este totalmente limpio:


1


Presione el botón “Cargar Excel” y observe como los datos se cargan correctamente:


12


Existe demasiada funcionalidad que podemos explotar de esta librería, como por ejemplo:


Definir un estilo y formato a las celdas, colores, imágenes, filtros, formulas, comentarios, copiar estilo por celda, rangos, renombrar libros, crear libros, etc.


Para mas ejemplos por favor diríjase a: http://spreadsheetlight.com/sample-code/


Para mas información y documentación sobre Open Xml, por favor dirijase a: Welcome to the Open XML SDK 2.5 for Office


Escribir este articulo me llevo mas de 1 hora, dejar un comentario de agradecimiento le tomara 5 minutos.

Saludos desde Monterrey, Nuevo León México!
Ejemplo C#
Ejemplo Vb.Net

martes, 3 de marzo de 2015

ReportViewer y Rdlc, ejemplo facturación

Hola a todos:
El siguiente articulo pretende mostrar la manera de crear un reporte local usando ReportViewer y Local Report o Rdlc enlazando su origen datos (DataSource) a una fuente proveniente de una Lista Genérica de propiedades. Como se que esto puede resultar un tema demasiado complicado cuando no se tiene experiencia en el manejo de reportes tratare de que el articulo sea lo mas descriptivo posible y para ello me apoyare en la mayor cantidad de imágenes posibles, traten de realizar el ejemplo siguiendo el tutorial, si un paso no les queda claro siéntanse con toda confianza de hacer las consultas necesarias, al final del articulo tendrán el link de descarga del proyecto de ejemplo.

Los datos los enviaremos desde un formulario simulando ser los datos de una Factura comercial, que contiene Datos del cliente y los artículos adquiridos.

Bien, comencemos creando un Formulario y arrastrando los controles necesarios hasta obtener un formulario como este:
dgvReportViewer
Agregue un segundo formulario, llámelo FacturaRpt.cs este contendrá el control ReportViewer al cual enlazaremos el LocalReport, asi que agregue uno arrastrándolo desde el cuadro de herramientas.
l19
Agregue un LocalReport, deje el nombre default:
newItem
newReport
Ahora, crearemos las clases contenedoras de las entidades, recuerde que una entidad esta compuesta por campos y propiedades.
Inserte una nueva clase y nómbrela EArticulo, la cual contendrá todas las propiedades de la entidad Articulo, la clase deberá de quedar con esta estructura:

Código C#:

namespace ReportViewerInvoiceReport_CSharp
{
public class EArticulo
{
public int Numero { get; set; }
public string Upc { get; set; }
public string Descripcion { get; set; }
public decimal Piezas { get; set; }
public decimal Precio { get; set; }
public decimal Importe { get; set; }
}
}

Código Vb.Net:


Public Class EArticulo
Public Property Numero() As Integer
Public Property
Upc() As String
Public Property
Descripcion() As String
Public Property
Piezas() As Decimal
Public Property
Precio() As Decimal
Public Property
Importe() As Decimal
End Class

Después inserte una nueva clase y nómbrela EFactura, esta clase contendrá todas las propiedades del encabezado de la factura, la clase deberá de quedar con esta estructura:


Código C#:

using System;
using System.Collections.Generic;

namespace ReportViewerInvoiceReport_CSharp
{
public class EFactura
{
public int Numero { get; set; }
public string Nombre { get; set; }
public string Rfc { get; set; }
public string Direccion { get; set; }
public decimal Subtotal { get; set; }
public decimal Iva { get; set; }
public decimal Total { get; set; }
public DateTime FechaFacturacion { get; set; }

//Creamos una lista con una nueva Instancia de la clase Articulo
//esta lista contendra el detalle de la factura
public List<EArticulo> Detail = new List<EArticulo>();
}
}

Código Vb.Net:


Imports System.Collections.Generic
Public Class EFactura
Public Property Numero() As Integer
Public Property
Nombre() As String
Public Property
Rfc() As String
Public Property
Direccion() As String
Public Property
Subtotal() As Decimal
Public Property
Iva() As Decimal
Public Property
Total() As Decimal
Public Property
FechaFacturacion() As DateTime

'Creamos una lista con una nueva Instancia de la clase Articulo
'esta lista contendra el detalle de la factura
Public Detail As New List(Of EArticulo)()

End Class

Configurar el Reporte del informe “Report1.rdlc”

Para poder usar las clases de EFactura para llenar el Encabezado del reporte y EArticulo para el detalle del mismo, primero debemos de generar el proyecto, para ello localice el menú Generar –> Generar Solución

 Establecer la fuente de datos del Reporte

Active el cuadro Datos del informe, Menú Ver –>Datos del informe

dataSet

Establezca el Nombre del conjunto de datos y haga click sobre el botón Nuevo:

dataS1

Seleccione Objeto y haga Click sobre el botón Siguiente:

 obje

Seleccione la clase EFactura, recuerde que esta clase contiene las propiedades para el encabezado de la factura y haga Click sobre el botón Finalizar (Si no hubiera generado el proyecto estas clases no estarían visibles, así que si no las ve por favor cierre la ventana y genere la solución):

 sele

Observe que ya tiene una fuente de datos seleccionada, solo haga Click en el botón Aceptar:

l1

Bien, ya tiene una Fuente de datos configurada:

l2

Siga los mismos pasos para agregar la fuente de datos para el detalle del reporte solo que ahora seleccionara la clase EArticulo, al final tendrá un resultado como el siguiente:

l3

Configurar tamaño y encabezado del informe

Active la regla del informe para ello menú Informe –> Regla

Configure el tamaño del informe, menú Informe –> Propiedades del informe

l4

Un punto muy importante en este paso es tener presente el Ancho o alto del reporte dependiendo que tipo de orientación se haya elegido, en este caso el ancho es lo que interesa, considere que tiene un ancho máximo de 21.59 cm. a este espacio le restara el valor total de los márgenes que son 2.0 cm en total ya que configuro un margen de 0.5 cm, dando un espacio de trabajo real disponible de 19.59 cm., si en el diseño de nuestro reporte nos pasamos de este espacio disponible nuestro reporte en lugar de mostrarnos una hoja nos mostrar ara dos, normalmente la segunda hoja sin o con pocos datos.

Bien, continuemos…

Agregue un encabezado de reporte, menú Informe –> Agregar encabezado de pagina

Active el cuadro de herramientas, menú Ver –> Cuadro de herramientas:

l5

Arrastre tantos Cuadros de texto como se requieran, recuerde que nuestros datos provienen de una clase, entonces por cada campo-propiedad de la clase tiene que crear un control Cuadro de texto para poder visualizar el dato…esto de crear un control por cada propiedades de la clase origen es únicamente para este reporte, en realidad usted puede elegir que datos mostrar y cuales no.

Configurar control cuadro de texto:

Click derecho sobre el control cuadro de texto que deseemos configurar:

Recuerde que establecer un nombre a los controles que vayamos utilizando es de vital importancia porque será en base a este como los identificaremos, en proyectos pequeños tal vez no tenga problemas si deja los nombres asignados por default pero, en proyectos grandes esta mala practica nos provocara constantes dolores de cabeza, entonces, nunca olvide ponerle un nombre a los controles. Considere que el nombre debe de identificar fácilmente al control al que hace referencia y al dato que contendrá o al cual estará relacionado, en este caso txtFactura es muy descriptivo, ya que se entiende que es un control textBox o cuadro de texto y que contendrá el numero de factura.

Para definir el origen del dato que mostrara el control cuadro de texto despliegue el ComboBox Valor, vera que este estará cargado con las propiedades de las clases las cuales definimos como fuente de datos, no le será difícil identificar a que clase pertenece cada una ya que al final podra ver el nombre que le puso al origen de datos.

l10

Desactivar la casilla “Permitir aumentar el alto” puede resultar muy importante para conservar un buen diseño, tener activada esta casilla permite que el control cambie de tamaño en tiempo de ejecución adecuándose al tamaño del valor que contiene, modificando con esto el diseño hasta el punto de generar hojas innecesarias.

l6

l7

l8

En este ejemplo el campo Factura será del tipo numérico por lo cual para conservar un formato de numero correcto, tendrá que configurar el lenguaje que utilizara el campo, para mi ubicación (México) la configuración de lenguaje será “es-Mex”, para configurar esta propiedad:

  Seleccione el control cuadro de texto –> Presione F4:

l9

Si no hiciera esto, un formato numérico podría no mostrarse correctamente por ejemplo la cantidad Mil doscientos cincuenta y seis con cincuenta y seis decimales en México será 1,256.56 pero en España se escribiría de esta manera 1.256,56. Esta configuración debemos de hacerla aun si en la clase Main de nuestro proyecto hayamos definido un lenguaje de manera predeterminada.

Agregar parámetros al Reporte:

Para abarcar mas sobre el tema de ReportViewer y local report, subamos un poco mas el nivel de nuestro proyecto agregando parámetros al mismo, esto no siempre es necesario así que en este articulo solo lo haremos para fines ilustrativos, es decir, para cuando lleguemos a necesitarlo tendremos el conocimiento disponible de como usarlos.

En el Datos del informe –> Click derecho sobre Parametros –> Agregar parametro:

l11

De nuevo, recuerde que nombrar correctamente a cada control que agregamos es de vital importancia:

l12

Agregue un  nuevo parámetro y nómbrelo parametroEmpresa.

l13

Ahora, arrastre dos controles Cuadro de texto al informe, y vaya a las propiedades para configurar el valor a mostrar:

l14

Haga lo mismo para el segundo control agregado, veamos como va nuestro diseño del informe:

l15

Para el detalle del informe, agregue un Tabla arrastrándola desde el cuadro de herramientas, vaya a las propiedades de la tabla recién agregada, ubique la propiedad DataSetName par establecer el origen de datos y seleccione Detalle, recuerde que ese fue el nombre que establecimos al origen de datos:

l16

Agregue las columnas que falten para completar el cuerpo del informe, para ello haga Click derecho sobre la tabla, Insertar columna, Izquierda o derecha.

Después establezca el valor que mostrara cada columna, para ello tiene dos opciones, seleccionar el valor desde el icono que vera en la esquina superior derecha de cada celda o ir a propiedades del cuadro de texto tal cual como lo hizo anteriormente:

l17

Establezca el tamaño de fuente, borde, tipo de dato, lenguaje a utilizar (recuerde lo comentado anteriormente sobre el formato de numero/moneda)

¿Como va nuestro diseño de informe?

l18

Si tienen un resultado como este, pueden dar su tarea por terminada en cuanto al diseño del informe si aun no lo logran, todo es cuestión que revisen como configurar un control TextBox o cuadro de texto como gusten llamarlo y ponerse a ver Alineación, tipo de fuente, borde, relleno, etc…

Hasta aquí daremos por terminado nuestro diseño, ahora vayamos a:

Establecer una Lista Genérica como DataSource de un local report:

Ubique el Formulario contenedor del ReportViewer llamado FacturaRpt, seleccione el icono superior derecho del reportViewer –> Seleccione el local report que utilizaremos (el único que hemos diseñado)

l20

Una vez echo lo anterior, genere el evento Load del Formulario, para ello puede hacer doble click sobre la barra de titulo o seleccionar el form y presionar la tecla de función F4, esto abrirá la caja de propiedades, en la parte superior podrá ver un icono con forma de “rayo” de color amarillo, haga click sobre el para ver los eventos que implementa el control, después ubique el Evento que le interese implementar en este caso nos interesa el evento “Load”.

Copie y pegue el siguiente código:


Código C#:

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using Microsoft.Reporting.WinForms;

namespace ReportViewerInvoiceReport_CSharp
{
public partial class FacturaRpt : Form
{
//
//Cree dos listas una para el Encabezado y otra para el detalle
//
public List<EFactura> Invoice = new List<EFactura>();
public List<EArticulo> Detail = new List<EArticulo>();
//
//Cree las propiedades publicas Titulo y Empresa
//
public string Titulo { get; set; }
public string Empresa { get; set; }

public FacturaRpt()
{
InitializeComponent();
}

private void FacturaRpt_Load(object sender, EventArgs e)
{
//Limpiemos el DataSource del informe
reportViewer1.LocalReport.DataSources.Clear();
//
//Establezcamos los parámetros que enviaremos al reporte
//recuerde que son dos para el titulo del reporte y para el nombre de la empresa
//
ReportParameter[] parameters = new ReportParameter[2];
parameters[0] = new ReportParameter("parameterTitulo", Titulo);
parameters[1] = new ReportParameter("parameterEmpresa", Empresa);

//
//Establezcamos la lista como Datasource del informe
//
reportViewer1.LocalReport.DataSources.Add(new ReportDataSource("Encabezado", Invoice));
reportViewer1.LocalReport.DataSources.Add(new ReportDataSource("Detalle", Detail));
//
//Enviemos la lista de parametros
//
reportViewer1.LocalReport.SetParameters(parameters);
//
//Hagamos un refresh al reportViewer
//
reportViewer1.RefreshReport();
}
}
}

Código Vb.Net:

Imports Microsoft.Reporting.WinForms

Public Class FacturaRpt
'
'Cree dos listas una para el Encabezado y otra para el detalle
'
Public Invoice As New List(Of EFactura)()
Public Detail As New List(Of EArticulo)()
'
'Cree las propiedades publicas Titulo y Empresa
'
Public Property Titulo() As String
Public Property
Empresa() As String

Private Sub
FacturaRpt_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load

'Limpiemos el DataSource del informe
ReportViewer1.LocalReport.DataSources.Clear()
'
'Establezcamos los parametros que enviaremos al reporte
'recuerde que son dos para el titulo del reporte y para el nombre de la empresa
'
Dim parameters As ReportParameter() = New ReportParameter(1) {}
parameters(0) = New ReportParameter("parameterTitulo", Titulo)
parameters(1) = New ReportParameter("parameterEmpresa", Empresa)

'
'Establezcamos la lista como Datasource del informe
'
ReportViewer1.LocalReport.DataSources.Add(New ReportDataSource("Encabezado", Invoice))
ReportViewer1.LocalReport.DataSources.Add(New ReportDataSource("Detalle", Detail))
'
'Enviemos la lista de parametros
'
ReportViewer1.LocalReport.SetParameters(parameters)
'
'Hagamos un refresh al reportViewer
'
ReportViewer1.RefreshReport()
End Sub
End Class


Hagamos lo ultimo que falta, enviar los datos desde el formulario principal:

Genere el Evento load del Formulario principal,

VsEvent

Después de generado el evento trate de seguir estas líneas de código, escriba linea por linea si copia y pega será mas complicado que comprenda (suponiendo que su experiencia en desarrollo no es amplia):



Código C#:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;

namespace ReportViewerInvoiceReport_CSharp
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

/// <summary>
///
Funcion encargada de llenar el control DataGridView
/// <autor>José Luis García Bautista</autor>
/// </summary>
/// <returns>
Una lista generica de la clase artículo</returns>
private static List<EArticulo> FillDgv()
{
//
//Cree una lista generica de la entidad EArticulo
//
List<EArticulo> listaArticulos = new List<EArticulo>();

//
//Instancie la clase EArticulo para agregar datos a la lista
//
EArticulo item = new EArticulo
{
//Establezca valores a cada una de las propiedades
Numero = 1,
Upc = "7501020405680",
Descripcion = "Descripción del artículo 1",
Piezas = 6,
Precio = new decimal(12.50),
Importe = (decimal)(6 * 12.5),
};
//
//Agregamos el Item a la lista
//
listaArticulos.Add(item);

EArticulo item1 = new EArticulo
{
Numero = 2,
Upc = "7501040805610",
Descripcion = "Descripción del artículo 2",
Piezas = 3,
Precio = new decimal(22.50),
Importe = (decimal)(3 * 22.5),
};
listaArticulos.Add(item1);

EArticulo item2 = new EArticulo
{
Numero = 3,
Upc = "0412200805610",
Descripcion = "Descripción del artículo 3",
Piezas = 20,
Precio = new decimal(52.80),
Importe = (decimal)(20 * 52.80),
};
listaArticulos.Add(item2);

return listaArticulos;
}

private void Form1_Load(object sender, EventArgs e)
{
//
//La funcion GenerateNumber() se utiliza unicamente para generar un Número
//aleatorio que simulara ser el numerod e factura
txtnumero.Text = GenerateNumber().ToString();

//Establecemos la propiedad AutoGenerateColumns en False para evitar que se agreguen
//nuevas columnas a la derecha de las que creamos en tiempo de diseño.
//
dgvdetalle.AutoGenerateColumns = false;
//
//Establecemos el DataSource del control DataGridView
//
dgvdetalle.DataSource = FillDgv();
//
//Mapeamos las propiedades de la clase devuelta por la Funcion FillDgv()
//recuerde que esta funcion devuelve una lista del tipo EArticulo
//
dgvdetalle.Columns["columnNumero"].DataPropertyName = "Numero";
dgvdetalle.Columns["columnUpc"].DataPropertyName = "Upc";
dgvdetalle.Columns["columnDescripcion"].DataPropertyName = "Descripcion";
dgvdetalle.Columns["columnPiezas"].DataPropertyName = "Piezas";
dgvdetalle.Columns["columnPrecio"].DataPropertyName = "Precio";
dgvdetalle.Columns["columnImporte"].DataPropertyName = "Importe";

//
//Hacemos las sumatorias usando un método de extensión de Linq
//
decimal sum = FillDgv().Sum(x => x.Importe);
decimal iva = (Math.Round(((sum / 116) * 16), 2));
decimal subtotal = Math.Round(sum - iva, 2);

txttotal.Text = Convert.ToString(Math.Round(sum, 2));
txtiva.Text = Convert.ToString(iva);
txtsubtotal.Text = Convert.ToString(subtotal);
}

private static int GenerateNumber()
{
Random rdm = new Random();

return rdm.Next();
}


private void InvoiceGenerate()
{
//
//Hacemos una instancia de la clase EFactura para
//llenarla con los valores contenidos en los controles del Formulario
EFactura invoice = new EFactura();
invoice.Numero = Convert.ToInt32(txtnumero.Text);
invoice.Nombre = txtnombre.Text;
invoice.Rfc = txtrfc.Text;
invoice.Direccion = txtdireccion.Text;
invoice.FechaFacturacion = dtpfecha.Value.Date;
invoice.Subtotal = Convert.ToDecimal(txtsubtotal.Text);
invoice.Iva = Convert.ToDecimal(txtiva.Text);
invoice.Total = Convert.ToDecimal(txttotal.Text);

//Recorremos los Rows existentes actualmente en el control DataGridView
//para asignar los datos a las propiedades
foreach (DataGridViewRow row in dgvdetalle.Rows)
{
EArticulo article = new EArticulo();
//
//Vamos tomando los valores de las celdas del row que estamos
//recorriendo actualmente y asignamos su valor a la propiedad de la clase intanciada
//
article.Numero = Convert.ToInt32(row.Cells["columnNumero"].Value);
article.Upc = Convert.ToString(row.Cells["columnUpc"].Value);
article.Descripcion = Convert.ToString(row.Cells["columnDescripcion"].Value);
article.Piezas = Convert.ToDecimal(row.Cells["columnPiezas"].Value);
article.Precio = Convert.ToDecimal(row.Cells["columnPrecio"].Value);
article.Importe = Convert.ToDecimal(row.Cells["columnImporte"].Value);

//
//Vamos agregando el Item a la lista del detalle
//
invoice.Detail.Add(article);
}

//
//Creamos una instancia del Formulario que contiene nuestro
//ReportViewer
//
FacturaRpt frm = new FacturaRpt();
//
//Usamos las propiedades publicas del formulario, aqui es donde enviamos el valor
//que se mostrara en los parametros creados en el LocalReport, para este ejemplo
//estamos Seteando los valores directamente pero usted puede usar algun control
//
frm.Titulo = "Este es un ejemplo de Factura";
frm.Empresa = "Este es un ejemplo del Nombre de la Empresa";
//
//Recuerde que invoice es una Lista Generica declarada en el FacturaRtp, es una lista
//porque el origen de datos del LocalReport unicamente permite ser enlazado a objetos que
//implementen IEnumerable.
//
//Usamos el metod Add porque Invoice es una lista e invoice es una entidad simple
frm.Invoice.Add(invoice);
//
//Enviamos el detalle de la Factura, como Detail es una lista e invoide.Details tambien
//es un lista del tipo EArticulo bastara con igualarla
//
frm.Detail = invoice.Detail;
frm.Show();
}

private void btnImprimir_Click(object sender, EventArgs e)
{
InvoiceGenerate();
}
}
}
Código Vb.Net:
Public Class Form1

''' <summary>
''' Funcion encargada de llenar el control DataGridView
'''
<autor>José Luis García Bautista</autor>
''' </summary>
''' <returns>Una lista generica de la clase artículo</returns>
Private Shared Function FillDgv() As List(Of EArticulo)
'
'Cree una lista generica de la entidad EArticulo
'
Dim listaArticulos As New List(Of EArticulo)()
'
'Instancie la clase EArticulo para agregar datos a la lista
'
'Establezca valores a cada una de las propiedades
Dim item As New EArticulo()
item.Numero = 1
item.Upc = "7501020405680"
item.Descripcion = "Descripción del artículo 1"
item.Piezas = 6
item.Precio = New Decimal(12.5)
item.Importe = CDec(6 * 12.5)
'
'Agregamos el Item a la lista
'
listaArticulos.Add(item)

Dim item1 As New EArticulo()
item1.Numero = 2
item1.Upc = "7501040805610"
item1.Descripcion = "Descripción del artículo 2"
item1.Piezas = 3
item1.Precio = New Decimal(22.5)
item1.Importe = CDec(3 * 22.5)

listaArticulos.Add(item1)

Dim item2 As New EArticulo()
item2.Numero = 3
item2.Upc = "0412200805610"
item2.Descripcion = "Descripción del artículo 3"
item2.Piezas = 20
item2.Precio = New Decimal(52.8)
item2.Importe = CDec(20 * 52.8)
listaArticulos.Add(item2)

Return listaArticulos
End Function


Private Sub
Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
'
'La funcion GenerateNumber() se utiliza unicamente para generar un Número
'aleatorio que simulara ser el numerod e factura
txtnumero.Text = GenerateNumber().ToString()

'Establecemos la propiedad AutoGenerateColumns en False para evitar que se agreguen
'nuevas columnas a la derecha de las que creamos en tiempo de diseño.
'
dgvdetalle.AutoGenerateColumns = False
'
'Establecemos el DataSource del control DataGridView
'
dgvdetalle.DataSource = FillDgv()
'
'Mapeamos las propiedades de la clase devuelta por la Funcion FillDgv()
'recuerde que esta funcion devuelve una lista del tipo EArticulo
'
dgvdetalle.Columns("columnNumero").DataPropertyName = "Numero"
dgvdetalle.Columns("columnUpc").DataPropertyName = "Upc"
dgvdetalle.Columns("columnDescripcion").DataPropertyName = "Descripcion"
dgvdetalle.Columns("columnPiezas").DataPropertyName = "Piezas"
dgvdetalle.Columns("columnPrecio").DataPropertyName = "Precio"
dgvdetalle.Columns("columnImporte").DataPropertyName = "Importe"
'
'Hacemos las sumatorias usando un método de extensión de Linq
'
Dim sum As Decimal = FillDgv().Sum(Function(x) x.Importe)
Dim iva As Decimal = (Math.Round(((sum / 116) * 16), 2))
Dim subtotal As Decimal = Math.Round(sum - iva, 2)

txttotal.Text = Convert.ToString(Math.Round(sum, 2))
txtiva.Text = Convert.ToString(iva)
txtsubtotal.Text = Convert.ToString(subtotal)
End Sub

Private Shared Function
GenerateNumber() As Integer
Dim
rdm As New Random()

Return rdm.[Next]()
End Function

Private Sub
InvoiceGenerate()
'
'Hacemos una instancia de la clase EFactura para
'llenarla con los valores contenidos en los controles del Formulario
Dim invoice As New EFactura()
invoice.Numero = Convert.ToInt32(txtnumero.Text)
invoice.Nombre = txtnombre.Text
invoice.Rfc = txtrfc.Text
invoice.Direccion = txtdireccion.Text
invoice.FechaFacturacion = dtpfecha.Value.[Date]
invoice.Subtotal = Convert.ToDecimal(txtsubtotal.Text)
invoice.Iva = Convert.ToDecimal(txtiva.Text)
invoice.Total = Convert.ToDecimal(txttotal.Text)

'Recorremos los Rows existentes actualmente en el control DataGridView
'para asignar los datos a las propiedades
For Each row As DataGridViewRow In dgvdetalle.Rows
Dim article As New EArticulo()
'
'Vamos tomando los valores de las celdas del row que estamos
'recorriendo actualmente y asignamos su valor a la propiedad de la clase intanciada
'
article.Numero = Convert.ToInt32(row.Cells("columnNumero").Value)
article.Upc = Convert.ToString(row.Cells("columnUpc").Value)
article.Descripcion = Convert.ToString(row.Cells("columnDescripcion").Value)
article.Piezas = Convert.ToDecimal(row.Cells("columnPiezas").Value)
article.Precio = Convert.ToDecimal(row.Cells("columnPrecio").Value)
article.Importe = Convert.ToDecimal(row.Cells("columnImporte").Value)

'
'Vamos agregando el Item a la lista del detalle
'
invoice.Detail.Add(article)
Next

'
'Creamos una instancia del Formulario que contiene nuestro
'ReportViewer
'
Dim frm As New FacturaRpt()
'
'Usamos las propiedades publicas del formulario, aqui es donde enviamos el valor
'que se mostrara en los parametros creados en el LocalReport, para este ejemplo
'estamos Seteando los valores directamente pero usted puede usar algun control
'
frm.Titulo = "Este es un ejemplo de Factura"
frm.Empresa = "Este es un ejemplo del Nombre de la Empresa"
'
'Recuerde que invoice es una Lista Generica declarada en FacturaRtp, es una lista
'porque el origen de datos del LocalReport unicamente permite ser enlazado a objetos que
'implementen IEnumerable.
'
'Usamos el metod Add porque Invoice es una lista e invoice es una entidad simple
frm.Invoice.Add(invoice)
'
'Enviamos el detalle de la Factura, como Detail es una lista e invoide.Details tambien
'es un lista del tipo EArticulo bastara con igualarla
'
frm.Detail = invoice.Detail
frm.Show()
End Sub

Private Sub
btnImprimir_Click(sender As System.Object, e As System.EventArgs) Handles btnImprimir.Click
InvoiceGenerate()
End Sub
End Class


Ahora solo queda probar nuestra aplicación:

Llenamos los datos:
l21

Presione el botón Imprimir, si siguió al pie de la letra el Articulo tendrá un reporte como este:

l22

Observe, en el reporte el Formato numérico y Módena, al igual que no tenemos hojas de mas sin datos…

Ahora, exporte el reporte a Pdf y observe que los márgenes están dentro del limite, sin generar hojas de mas, ni salirse de los márgenes:

l23

Bueno, hemos llegado al final de este tutorial, en un entrega posterior abordaremos las imágenes en Reportes Locales y utilizaremos una Bd como fuente de datos para nuestro reporte.

Saludos desde Monterrey, Nuevo León México!

Ejemplo C#
Ejemplo Vb.Net
Nota: El proyecto fue desarrollado en Vs2010 Ultímate usando Framework 4.0