In questo post descriverò come utilizzare un componente commerciale (e assolutamente non free) per creare pdf.

Sottolineo che la mia è una recensione assolutamente indipendente, solo con l'intento di mostrare una possibile soluzione.

Segnalalo anche che per creare documenti pdf da codice Xamarin esistono anche alternative free, ma dovendo offrire questa funzionalità cross-platform, e non esistendo una librareria che offre queste caratteristiche, potrebbe essere preferibile usare un componente a pagamento: questo in particolare permette di avere un codice univoco per tutte le piattaforme e quindi risulta più facile da manuntenere.

Ovviamente è noto che esistono anche altri componenti commerciali: descrivo questo solo perchè io l'ho provato, e lo ha trovato soddifacente.

Eppoi il blog è mio..... e quindi scrivo di che voglio... no ?!

Il componente in parola è XFinium pdf (in linkografica il sito di riferimento). Per poche centinaia di euro (tale è il costo della licenza più economica per 1 dev) è possibile ottenere il componente in versione standard, che offre tutte le funzionalità richieste per la maggior parte degli utilizzi.

Preparatevi: la documentazione a corredo è realmente sconcertante. Pochi avrebbero saputo fare peggio: in contrasto i progetti sample in allegato sono curati e in gran numero. Quindi sono una buona base di partenza per qualsiasi cosa Vi occorre fare.

Il prodotto presenta un mare di opzioni, che all'inizio spiazzano e confondono, ma una volta capito il meccanismo poi dopo non sono così difficili da gestire.

E come detto l'help è "diversamente utilizzabile". Noi abbiamo usato il tutto per creare un pdf di un ordine da cliente: da codice condiviso PCL abbiamo usato il pattern DependencyService. Ecco l'interfaccia: il valore restituito è il path del pdf stesso.

public interface IOrder2PdfFile
{
  string Ordine2PdfFile(DateTime _DataDocumento,
  string _CondizioneDiPagamento,
  string _NomeCliente, 
  ObservableCollection< GEST_Ordini_Righe_DettaglioOrdine> _ListaArticoliInOrdine,
  decimal _TotaleOrdine);
}

Nel seguito l'implementazione in Android: per le altre piattaforme il codice è sostanzialmente uguale. In pratica si crea un file pdf con delle intestazioni e quindi delle righe prodotto utilizzando una tabella.

public class Order2PdfFile : IOrder2PdfFile
{
	public string  Ordine2PdfFile(DateTime _DataDocumento,
		string _CondizioneDiPagamento,
		string _NomeCliente, 
		ObservableCollection< GEST_Ordini_Righe_DettaglioOrdine> _ListaArticoliInOrdine,
		decimal _TotaleOrdine)
	{


         var output = Run(_DateTime _DataDocumento,
		string _CondizioneDiPagamento,
		string _NomeCliente, 
		ObservableCollection< GEST_Ordini_Righe_DettaglioOrdine> _ListaArticoliInOrdine,
		decimal _TotaleOrdine);

		string pdfPath = string.Empty;
		

		for (int i = 0; i < output.Length; i++)
		{
			
			pdfPath = Path.Combine(Forms.Context.ExternalCacheDir.ToString()
				, output[i].FileName);
			

			Stream pdfStream = File.Open(pdfPath, FileMode.Create);
			output[i].Document.Save(pdfStream);
			pdfStream.Flush();
			pdfStream.Close();
		}

		Java.IO.File pdffile = new Java.IO.File(pdfPath);
		pdffile.SetReadable(true, false);

		return pdfPath;
	}

	private static void  AggiungiRiga(PdfFlowDocument _Documento,string _Testo)
	{
		PdfFormattedContent _NuovaRiga = new PdfFormattedContent();
		_NuovaRiga.Paragraphs.Add(_Testo);
		
		PdfFlowContent NuovaRigaPdfFlowContent = new PdfFlowTextContent(_NuovaRiga);
		_Documento.AddContent(NuovaRigaPdfFlowContent);
	}

	private  static OutputInfo[] Run(DateTime _DataDocumento,
		string _CondizioneDiPagamento,
		string _NomeCliente, 
		ObservableCollection< GEST_Ordini_Righe_DettaglioOrdine> _ListaArticoliInOrdine,
		decimal _TotaleOrdine)
	{
		PdfFlowDocument document = new PdfFlowDocument();
		document.PageCreated += Document_PageCreated;
		PdfStandardFont FontGrosso = new PdfStandardFont();
		FontGrosso.Size = 27;
		
		PdfFormattedContent sellerInfo = new PdfFormattedContent();
		sellerInfo.Paragraphs.Add(new PdfFormattedTextBlock("Ordine", FontGrosso));

		PdfFlowTextContent sellerInfoText = new PdfFlowTextContent(sellerInfo);
		document.AddContent(sellerInfoText);

		AggiungiRiga(document, string.Empty);

		AggiungiRiga(document, "Data Documento: " + _DataDocumento.ToString("dd/MM/yyyy"));
		AggiungiRiga(document, "Cliente: " + _NomeCliente);
		AggiungiRiga(document, string.Empty);
		AggiungiRiga(document, "Condizione di Pagamento: " + _CondizioneDiPagamento);
		AggiungiRiga(document, string.Empty);
		
		PdfFlowContent invoiceItemsSection = BuildTblProdottiPerClasse(_ListaArticoli);
		document.AddContent(invoiceItemsSection);

		AggiungiRiga(document, string.Empty);

		PdfStandardFont FontGrassetto = new PdfStandardFont();
		FontGrassetto.Bold = true;
		

		PdfFormattedContent TotaleInfo = new PdfFormattedContent();
		TotaleInfo.Paragraphs.Add(new PdfFormattedTextBlock("Totale Ordine: € " + DecimalFormattato(_TotaleOrdine, false), FontGrassetto));

		PdfFlowTextContent InfoTotale = new PdfFlowTextContent(TotaleInfo);
		document.AddContent(InfoTotale);

		OutputInfo[] output = new OutputInfo[] { new OutputInfo(document, "Ordine.pdf") };
		return output;
	}

	private static void Document_PageCreated(object sender, PdfFlowPageCreatedEventArgs e)
	{
		e.PageDefaults.Margins.Left = 18;
		e.PageDefaults.Margins.Right = 18;
	}


	private static PdfFlowContent BuildTblProdottiPerClasse(	
			ObservableCollection _Lista)
	{

		PdfFont fontPiccolo = new PdfStandardFont();
		fontPiccolo.Size = 9;

		PdfFlowTableContent tblOrdine = new PdfFlowTableContent(8);
		tblOrdine.Border = new PdfPen(PdfRgbColor.Black, 0.5);

		tblOrdine.MinRowHeight = 25;
		tblOrdine.Columns[0].VerticalAlign = PdfGraphicAlign.Near; //Cod Art
		tblOrdine.Columns[0].Width = 45;
		tblOrdine.Columns[0].WidthIsRelativeToTable = false;
		tblOrdine.Columns[0].HorizontalAlign = PdfGraphicAlign.Near;

		tblOrdine.Columns[1].HorizontalAlign = PdfGraphicAlign.Near; // Descrizione
		tblOrdine.Columns[1].VerticalAlign = PdfGraphicAlign.Near;
		tblOrdine.Columns[1].Width = 200;
		tblOrdine.Columns[1].WidthIsRelativeToTable = false;

		tblOrdine.Columns[2].VerticalAlign = PdfGraphicAlign.Near; //Qta Ordinato
		tblOrdine.Columns[2].HorizontalAlign = PdfGraphicAlign.Far;
		tblOrdine.Columns[2].Width = 50;
		tblOrdine.Columns[2].WidthIsRelativeToTable = false;

		tblOrdine.Columns[4].HorizontalAlign = PdfGraphicAlign.Far; //Importo
		tblOrdine.Columns[4].VerticalAlign = PdfGraphicAlign.Near;
		tblOrdine.Columns[4].Width = 65;
		tblOrdine.Columns[4].WidthIsRelativeToTable = false;

		tblOrdine.Columns[5].HorizontalAlign = PdfGraphicAlign.Far; //Sc1
		tblOrdine.Columns[5].VerticalAlign = PdfGraphicAlign.Near;
		tblOrdine.Columns[5].Width = 30;
		tblOrdine.Columns[5].WidthIsRelativeToTable = false;

		tblOrdine.Columns[6].HorizontalAlign = PdfGraphicAlign.Far; //Sc2
		tblOrdine.Columns[6].VerticalAlign = PdfGraphicAlign.Near; 
		tblOrdine.Columns[6].Width = 30;
		tblOrdine.Columns[6].WidthIsRelativeToTable = false;
		
		tblOrdine.Columns[7].HorizontalAlign = PdfGraphicAlign.Far;
		tblOrdine.Columns[7].VerticalAlign = PdfGraphicAlign.Near; //Totale
		tblOrdine.Columns[7].Width = 55;
		tblOrdine.Columns[7].WidthIsRelativeToTable = false;

		(tblOrdine.DefaultCell as PdfFlowTableStringCell).Font = fontPiccolo;

		PdfFlowContentMargins margine = new PdfFlowContentMargins(3);
		(tblOrdine.DefaultCell as PdfFlowTableStringCell).InnerMargins = margine;

		tblOrdine.Rows.AddRowWithCells("Lista Articoli");
		tblOrdine.Rows[0].Cells[0].ColSpan = 8;
		tblOrdine.Rows[0].Cells[0].HorizontalAlign = PdfGraphicAlign.Center;
		tblOrdine.Rows[0].Cells[0].VerticalAlign = PdfGraphicAlign.Center;

		PdfFlowTableRow row = tblOrdine.Rows.AddRowWithCells("CodArt", "Descrizione",
					 "Qta",  "Euro", "Sc1", "Sc2", "Totale");
		for (int i = 0; i < row.Cells.Count; i++)
		{
			(row.Cells[i] as PdfFlowTableStringCell).HorizontalAlign = PdfGraphicAlign.Center;
			(row.Cells[i] as PdfFlowTableStringCell).VerticalAlign = PdfGraphicAlign.Near;
			(row.Cells[i] as PdfFlowTableStringCell).Multiline = true;

		}

		foreach (var RigaInClasse in _Lista)
		{
			
			row = tblOrdine.Rows.AddRowWithCells(RigaInClasse.CodArt, 
								RigaInClasse.Descrizione, 
								RigaInClasse.Qta, 
								"€ " + DecimalFormattato(RigaInClasse.ValUnit,false),
								RigaInClasse.Sc1,
								RigaInClasse.Sc2,
								 "€ " + DecimalFormattato(RigaInClasse.Imponibile,false));
			
			(row.Cells[1] as PdfFlowTableStringCell).Multiline = true;
			(row.Cells[1] as PdfFlowTableStringCell).Content = RigaInClasse.Descrizione.Replace("''","'");

		}

		return tblOrdine;
	}


	private static string DecimalFormattato(decimal _Importo, bool _SenzaDecimali)
	{
		if (_SenzaDecimali)
		{
			return _Importo.ToString("N0", CultureInfo.GetCultureInfo("it-IT"));
		}


		string stringadaritornare = string.Empty;

		if (_Importo == 0)
			return "0";
		else
		{
			if (_Importo < 1)
			{
				return _Importo.ToString("N2", CultureInfo.GetCultureInfo("it-IT"));
			}
		}
		return _Importo.ToString("N2", CultureInfo.GetCultureInfo("it-IT"));
	}
}

public class OutputInfo
{
	public OutputInfo(PdfDocument document, string fileName) : this(document, fileName, null)
	{
	}

	public OutputInfo(PdfDocument document, string fileName, PdfSecurityHandler securityHandler)
	{
		this.document = document;

		this.fileName = fileName;
		this.securityHandler = securityHandler;
	}

	private PdfDocument document;

	public PdfDocument Document
	{
		get { return document; }
		set { document = value; }
	}

	private string fileName;

	public string FileName
	{
		get { return fileName; }
		set { fileName = value; }
	}

	private PdfSecurityHandler securityHandler;

	public PdfSecurityHandler SecurityHandler
	{
		get { return securityHandler; }
		set { securityHandler = value; }
	}
}

Affinchè tutto funzioni occorre porre come reference del progetto la dll corretta e per la piattaforma. Il codice è sicuramente passibile di ogni tipo di miglioria, e ammettiamo che non è splendido ed è preso da un codice reale buttato già in fretta e furia per fornire la funzionalità, eppoi come spesso capita abbandonato lì. Però ha una caratteristica apprezzabile: funziona perfettamente !

Linkografia

XFinium Pdf

Qualche giorno fa stavo lavorando su un vecchio server 2003 quando ad un tratto ho perso la connettività rete dello stesso. Come al solito le cose peggiori capitano sempre alla vigilia del week end e dal cliente più esigente......

Lo so: windows 2003 è in end-of-life, ma sapete anche che qui a Genova è veramente difficile che qualcuno butti via qualcosa di ancora perfettamente funzionante.

Inoltre il server in questione è utilizzato in una network-sandbox apposita per ospitare un software legacy molto importante utilizzato solo per consultazione di vecchi archivi. Inoltre, detto tra noi, non provate anche voi un sottile piacere di fare qualcosa di diverso da quello che M$ vuole “consigliarci” ? (cioè in questo caso di buttare via un server per passare a una versione più recente) ?

Comunque dopo aver controllato i cavetti, lo switch, etc etc, mi accorgo che la cosa è proprio strana. Infatti non vanno i ping verso qualsiasi apparato di rete sia in rete LAN che WAN, e qualsiasi altro tentativo di connessione, ma per esempio arp -a mi restituisce una lista di associazioni aggiornata, e così anche se imposto sul server stesso un ip già usato in quel segmento di rete ottengo l’errore di indirizzo ip già usato.

Moooooolto strano.

Quindi alcuni stack di protocollo vanno (per esempio quello che si occupa della risoluzione arp), mentre non sembra andare per nulla lo stack IP.

Xamarin: Master/Details exampleNel seguito propongo un esempio pratico e semplicissimo di utilizzo di una pagina Master Detail, con tanto di menù di navigazione della nostra App. Il tutto ovviamente usando solo ed esclusivamente Xamarin.Forms (... e il che non è poco..).

La classe MasterDetailPage permette di costruire pagine costituite da due "panelli": uno superiore, il Master, e uno inferiore, chiamato Details.

Oss.: vedere la figura proposta: il menù è la pagina Master, altra è la Deatails. Più facile a vedersi che a spiegarsi.

Proprio per tale motivo tale classe propone, tra le altre, due proprietà: Master e Details (ma guardà un pò....).

Queste devono essere inzializzate a due pagine differenti. In fase di RunTime è possibile modificare queste proprietà: cliccare sulla voce di menù e cambiare il dettaglio. Come al solito un esempio fornirà subito chiarezza.

Nel nostro esempio il pannello superiore Master conterrà un menù, con alcune voci, cliccando le quali nel pannelo details verranno aperte le pagine relative. Partiamo dalle pagine che saranno visualizzate nel dettaglio.

Pizza Xamarin

Vi piace l'immagine che correda questo articolo ??? E' una mia creazione ! Per far parlare Xamarin anche un pò italiano.... In realtà stavo studiando Xamarin e avevo voglia di una pizza....... così ho pensato di unire le due cose !

Quando si lavora con il pattern MVVM come noto occorre sempre lavorare con il Binding, ed è molto importante centellinare e ottimizzare la sottoscrizione agli eventi degli oggetti grafici utilizzati: inoltre è tassativo farlo usando sempre proprietà bindable e "tutto il mondo" offerto da ICommand.

Questo argomento è oggettivamente complesso e articolato: sicuramente scriverò qualcosa nei prossimi post. Trovo infatti che la documentazione che si trova in giro sia estremamente noiosa e prolissa su questo argomento: io invece penso che tutta questa parte sia interessante e stimolante, una volta capiti i meccanismi di base.

Quindi in definitiva torniamo all'oggetto di questo post: ovviare al fatto che la ListView non propone di default una proprietà bindable per gestire l'evento della selezione di un item.

Infatti l'oggettopropone un semplice evento, ma come detto quando si lavora con il pattern MVVM non è buona cosa sottoscriverlo direttamente e gestirlo in tal modo.