Idalgo Cantelli

.NET programming

  Home :: Contact :: Syndication  :: Login
  12 Posts :: 0 Stories :: 0 Comments :: 403 Trackbacks

martedì 24 maggio 2005 #

Ho installato – un po’ in ritardo – la Beta 2 di Visual Studio 2005 e ho iniziato a esplorare le novità del mio namespace preferito: System.Drawing.

Chi lo ha già utilizzato per ottenere effetti grafici con le versioni precedenti del Framework sa che uno dei problemi principali è ridurre il flickering dei controlli su cui si compiono operazioni di disegno. Il modo classico per risolvere il problema è utilizzare il metodo SetStyle nel costruttore del nostro controllo Owner-draw:

 

    this.SetStyle(ControlStyles.UserPaint, true);

    this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);

    this.SetStyle(ControlStyles.DoubleBuffer, true);

    this.SetStyle(ControlStyles.ResizeRedraw, true);

 

 

La versione 2.0 del framework ha introdotto un oggetto che permette di gestire in maniera più flessibile le aree del controllo su cui vogliamo eseguire operazioni di disegno complesse.

Si tratta della classe BufferedGraphics. La classe non dispone di un costruttore pubblico, ma è possibile averne un’istanza chiamando il metodo Allocate della classe BufferedGraphicsContext.

Ogni Application Domain ha un suo BufferedGraphicsContext, a cui è possibile ottenere un riferimento tramite la proprietà statica BufferedGraphicsManager.Current:

 

 private BufferedGraphics bg = null;

 private BufferedGraphicsContext appDomainGraphicsContext;

 

 ///

 /// Costruttore.

 ///

 public Form1()

 {

     InitializeComponent();

     this.SetStyle(ControlStyles.ResizeRedraw, true);

           

appDomainGraphicsContext = BufferedGraphicsManager.Current;

 

bg = appDomainGraphicsContext.Allocate(this.CreateGraphics(),                                          this.ClientRectangle);

     bg.Render();

 

}

 

Come si vede dall’esempio, il metodo Allocate accetta come secondo parametro un oggetto Rectangle, il che significa che possiamo utilizzare il doppio buffer per una sottoarea specifica del nostro controllo (nell’esempio utilizziamo in realtà tutta l’area, con la proprietà ClientRectangle).

Una volta ottenuto l’oggetto BufferedGraphics, è possibile delegare ad esso operazioni di disegno che altrimenti genererebbero sfarfallìo (tipicamente succede quando la form viene ridimensionata, ma sarebbe il caso di usarlo anche qualora volessimo realizzare un CAD o delle animazioni).

Nell’esempio, si utilizza il metodo Paint per disegnare lo sfondo della Form sfumato e un’ellisse all’interno di essa. Se si ridimensiona il Form, l’immagine non presenta nessuno sfarfallìo.

 

 

private void Form1_Paint(object sender, PaintEventArgs e)

{

   using (LinearGradientBrush Linear = new LinearGradientBrush(this.ClientRectangle,

                        ProfessionalColors.ToolStripGradientBegin,

                        ProfessionalColors.ToolStripGradientEnd,

                        LinearGradientMode.Horizontal),

                       Linear2 = new LinearGradientBrush(this.ClientRectangle,

                       Color.Blue, Color.YellowGreen, LinearGradientMode.Vertical))

           

  {

    using (BufferedGraphics bg = appDomainGraphicsContext.Allocate (e.Graphics, this.ClientRectangle))

     {

        bg.Graphics.FillRectangle(Linear, this.ClientRectangle);

        bg.Graphics.DrawEllipse(new Pen(Linear2, 5), this.ClientRectangle);

        bg.Render();

     }

  }

}

?>

posted @ 23.24

domenica 22 maggio 2005 #

Reflector è uno dei miei decompilatori preferiti. Permette di vedere il meccanismo interno delle classi della Framework Common Library e di capire nei dettagli tanti aspetti del .NET Framework.

Reflector è disponibile gratuitamente all’indirizzo http://www.aisto.com/roeder/dotnet/ . Se lo scaricate usate di frequente il menu Check for Updates, dal momento che escono versioni aggiornate quasi ogni settimana.

 

E’ divertente decompilare i metodi dell’assembly Microsoft.VisualBasic.dll. Si scopre così che tante vecchie keyword del Visual Basic 6.0 che sono state “apparentemente” mantenute sono in realtà dei wrapper di metodi C#.

Tutti sappiamo che gli array nel .NET Framework hanno dimensione fissa. Ma allora come mai in VB.NET è presente l’istruzione ReDim Preserve? Era l’istruzione che in VB6 ridimensionava un array mantenendo intatto il suo contenuto.

Decompilando l’assembly Microsoft.VisualBasic.dll con Reflector si può cercare nella classe Strings il metodo privato e statico RedimPreserve
 private static Array RedimPreserve(Array SourceArray, int Length)
{
      Type type1 = SourceArray.GetType();
      Array array2 = Array.CreateInstance(SourceArray.GetType().GetElementType(), Length);
      Array.Copy(SourceArray, array2, Length);
      return array2;
}

Come si vede, il ReDim Preserve non modifica le dimensioni dell’array originale, ma lo copia in un nuovo array la cui lunghezza è passata come parametro a questo metodo privato. Perciò, se siamo seguaci di Visual Basic .NET ma non vogliamo travestire troppo il nostro codice da Visual Basic 6, consideriamo la possibilità di usare direttamente Array.Copy al posto di ReDim Preserve.

 

Chi è passato da Visual Basic .NET a C# lamenta l’assenza di alcune utili funzioni come IsDate. Ma... tutto quello che si può fare con VB.NET si può fare anche con C#. Nulla di più facile che decompilare il metodo IsDate() di VB.NET (nella classe Information) ed ecco che sappiamo come tradurlo in C#. Il giro è un po’ tortuoso perché il metodo ci rimanda alla classe sealed DateType del namespace Microsoft.VisualBasic.CompilerServices. Ed ecco che nel metodo FromString troviamo il cuore della funzione:

 

public static DateTime FromString(string Value, CultureInfo culture)

{

DateTime time1;

try

{

time1 = DateTime.Parse(StringType.ToHalfwidthNumbers(Value), culture,

DateTimeStyles.NoCurrentDateDefault | DateTimeStyles.AllowWhiteSpaces);

}

catch (Exception)

{

throw new InvalidCastException(Utils.GetResourceString("InvalidCast_FromStringTo",

Strings.Left(Value, 0x20), "Date"));

}

return time1;

}

 

Quindi per verificare se un oggetto è una data, la funzione IsDate chiama il metodo statico DateTime.Parse, intercetta un’eventuale eccezione e riscatena una InvalidCastException al chiamante.

Perciò, se passiamo a IsDate un oggetto che non è una data valida, il metodo scatenerà un’eccezione per restituirci false. A questo punto dobbiamo decidere se possiamo accettare il costo di un’eccezione (che come sappiamo non è piccolo) o se vogliamo usare metodi diversi da IsDate per validare le nostre date. Un’alternativa potrebbero essere le Regular Expressions.

 

In un precedente post ho ceduto anch’io alla tentazione e ho mostrato del codice C# che include il namespace Microsoft.VisualBasic: utilizzavo la funzione RGB per convertire un oggetto System.Drawing.Color a 32 bit in un oggetto Color di Excel, a 24 bit:

 

using Microsoft.VisualBasic;

using Utilities;

 

private void MyColorRange(object Rng, Color Col)

{

 

  using (new switchCultureInfo())

      ((Excel.Range)Rng).Interior.Color = Information.RGB(Col.R, Col.G, Col.B);

 

}

 

Ma non c’è più bisogno di dire che anche in questo snippet possiamo facilmente sbarazzarci di VB:

 

using Utilities;

 

private void MyColorRange(object Rng, Color Col)

{

 

  using (new switchCultureInfo())

      ((Excel.Range)Rng).Interior.Color = (((Col.B * 0x10000) + (Col.G * 0x100)) + Col.R);

 

}

 

 

Qualcuno potrebbe obiettare che tutto ciò è un’inutile pignoleria. Ma personalmente preferisco evitare di avere un assembly in più caricato in memoria piuttosto che cedere alle abitudini radicate dopo anni di sviluppo con VB. In fondo se vogliamo fare emettere un piccolo beep, costa davvero poco sforzo, anzichè scrivere una cosa del tipo

 

Microsoft.VisualBasic.Information.Beep();

 

Chiamare la funzione nativa della user32.dll:

 

[DllImport("user32", CharSet=CharSet.Unicode)]
internal static extern int MessageBeep(int uType);


 

Inoltre, ricordiamo che l’includere Microsoft.VisualBasic può portare a pericolose ambiguità su alcuni metodi. Se non si ha l’occhio ben addestrato è possibile ad esempio confondere il metodo statico Format della classe System.String con il metodo statico Format della classe Microsoft.VisualBasic.Strings, e purtroppo i due metodi non fanno la stessa cosa.

E’ vero che la non collisione dei nomi è garantita solo a livello di namespace e non a livello di classe, ma se cerchiamo di non dare nemmeno uno spiraglio a possibili problemi, ne trarremo solo giovamento.

?>
posted @ 22.15

Il 1968 è stato l’anno della contestazione, ma per la storia dell’informatica è stato anche l’anno del leggendario articolo di Edsger Dijkstra “Go To Statement Considered Harmful”.

Questo è uno degli innumerevoli episodi nella storia delle teorie sull’architettura del codice citati da Steve McConnell nel suo libro Ingegneria del Codice –manuale pratico per la costruzione di software completo, appena tradotto da Mondadori Informatica.

E’ un libro scritto in uno stile molto amichevole e chiaro, che si incentra sulle metodologie di design ma soprattutto su quelle di scrittura e debugging del codice. Il grande pregio di questo testo è che attinge da tantissime fonti accademiche sulla progettazione del software, ma le espone in una maniera estremamente chiara. Sembra che McConnell riesca ad applicare al suo stile gli stessi principi di gestione della complessità che ritiene essere essenziali in un progetto software. Con grande naturalezza ci espone le migliori pratiche di design e una serie innumerevole di regole per scrivere codice robusto. E’ infatti uno dei pregi di questo libro quello di tradurre in esempi pratici e in regole applicabili nel lavoro di tutti i giorni le teorie dei guru della programmazione.

Gli esempi presentati sono in diversi linguaggi, da Visual Basic a Java a C#.

Così possiamo attingere a una quantità impressionante di suggerimenti che vanno dal disegno delle classi ai principi per minimizzare l’ambito di visibilità, dall’utilizzo degli array a quello dei puntatori, dalla gestione delle eccezioni all’uso della ricorsione, dai prefissi standardizzati alla misurazione delle prestazioni. In merito a quest’ultimo punto, vengono confrontati diversi tipi di approccio a elaborazioni particolarmente “pesanti” e vengono confrontati i tempi di esecuzione in diversi linguaggi. Si tratta di tabelle di confronto veramente utili. In molti casi McConnell sembra non prendere posizione – non solo rispetto ai linguaggi, ma anche rispetto a certe scelte metodologiche, e con grande rigore esamina i vantaggi e gli svantaggi di soluzioni alternative.

Vengono poi trattati argomenti importanti come il refactoring, il “pair programming”, lo unit test.

Non si tratta di banali tips, ma di indicazioni metodologiche che se applicate fanno la vera differenza tra dilettantismo e rigore in questo nostro strano mestiere.

Un’altra caratteristica che rende gradevole la lettura di questo libro sono le numerose citazioni che decorano i vari paragrafi. Ecco la mia preferita: Mentre lavoro a un problema non penso mai alla bellezza. Penso solo a come risolvere il problema. Ma quando ho finito, se la soluzione non è bella, so che è errata (R. Buckminster Fuller).

?>

posted @ 0.47

mercoledì 18 maggio 2005 #

Lavorare con i database è una pratica quotidiana. Un lavoro che deve affiancare la progettazione e lo sviluppo di database è la stesura di documentazione dettagliata sui database che creiamo. Per far ciò gli strumenti più comuni sono ErWin e Visio. E’ tuttavia possibile creare in modo rapido uno strumento di documentazione per SQL Server 2000.

Occorre utilizzare la libreria SQLDMO.dll (la si trova nella cartella Microsoft SQL Server\80\Tools\Binn\sqldmo.dll)

Si tratta di un oggetto COM che viene usato anche dall’Enterprise Manager per popolare il suo albero con la struttura dei database e delle tabelle.

Questa libreria fornisce accesso a tutti gli oggetti di Sql Server, dai database ai linked servers, dalle stored procedures ai file delle transazioni, dagli utenti ai job. Avrete insomma capito che utilizzandola ci si può creare un proprio Enterprise Manager senza dover conoscere nei più sottili dettagli le tabelle di sistema di SQL 2000.

Tempo fa avevo scritto una classe in VB.NET che elencava le stored procedures di un database.

Con pochissimo sforzo l’ho incapsulata in un Add-in per Microsoft Word. Non mi sono spinto molto oltre ma il risultato è gradevole. I metodi scrivono sul documento Word i database presenti sul server locale, e per ciascun database stampano l’elenco delle tabelle e delle stored Procedures.

Allego il codice della classe con i metodi che elencano gli oggetti. Ricordate che per usare la libreria SQLDMO.dll occorre avere SQL Server 2000 o MSDE installato sulla macchina di sviluppo. Poi occorre includere il riferimento al progetto .NET selezionando Add Reference -> COM -> Microsoft SQLDMO Object Library.

Siccome uno strumento del genere permette di elencare anche oggetti di amministrazione come i job e gli alerts, nonché di reperire informazioni sui file fisici e sui log delle transazioni, può essere appetibile anche per i Database Administrators oltre che per i progettisti. Il metodo getDatabaseFiles è un esempio di questo uso “amministrativo” della libreria.

Ecco come si presenta la classe:

 

 

 

using System;

using System.Diagnostics;

using SQLDMO;

using Microsoft.Office.Interop.Word;

using ApplicationClass = SQLDMO.ApplicationClass;

 

namespace WordAddin

{

        public class SQLDocumenter

      {

 

         #region Member variables

 

           internal SQLDMO.SQLServer server;

           internal SQLDMO.Database db;

           internal string serverName;

           internal string databaseName;

           internal string username;

           internal string password;

 

         #endregion

           

 

            /// Costruttore

            public SQLDocumenter()

            {

               server = new SQLDMO.SQLServer();

            }

 

 

 

 

 

            public void Connect()

            {

               try

               {

                  server.Connect(serverName, username, password);

               }

               catch (Exception ex)

               {

                  Trace.Fail(ex.Message);

               }

            }

 

 

 

            /// Elenca i Server registrati su un PC.

            public void getServers(Microsoft.Office.Interop.Word.Range rng)

            {

 

                  try

                  {

                     ApplicationClass APP = new ApplicationClass();

 

                     foreach (ServerGroup Sg in APP.ServerGroups)

                         foreach (RegisteredServer srv in Sg.RegisteredServers)

                                rng.InsertAfter(srv.Name + Environment.NewLine);

                  }

                  catch (Exception ex)

                  {

                     Trace.Fail(ex.Message);

                  }

            }

 

 

 

 

            /// Elenca i database di un server

            /// e scrive la lista su un oggetto Range di Microsoft Word.

            public void getDatabases(Microsoft.Office.Interop.Word.Range rng)

            {

                  rng.InsertAfter(string.Format("Database del server {0}{1}{1}",

server.Name, Environment.NewLine));

 

                  foreach (SQLDMO.Database db in server.Databases)

                  {

                        rng.InsertAfter(db.Name.ToUpper() + Environment.NewLine);

                        rng.InsertAfter("Database files: " + Environment.NewLine);

                                    getDatabaseFiles(db, rng);

                        rng.InsertAfter("TABELLE: "+ Environment.NewLine);

                                    myGetTables(db, rng);

                        rng.InsertAfter("STORED PROCEDURES:" + Environment.NewLine);

                                    myGetStoredProcedures(db, rng);

                        rng.InsertAfter("------------------------" +

                                        Environment.NewLine);

                  }

 

            }

 

 

 

 

            /// Enumera i nomi di file fisici del database (con il percorso completo), 

            /// le loro dimensioni in Megabyte

            /// e le dimensioni massime del file.

            /// -1 significa che il file non ha una dimensione massima.

            private void getDatabaseFiles(SQLDMO.Database db, 

                                          Microsoft.Office.Interop.Word.Range rng)

            {

                foreach (SQLDMO.FileGroup fg in db.FileGroups)

                {

                   foreach (SQLDMO.DBFile dbf in fg.DBFiles)

                       rng.InsertAfter(string.Format("Nome file: {0}; " +

"Size {1}MB; MaximumSize : {2}{3}" ,

                                  dbf.PhysicalName, dbf.Size,

 dbf.MaximumSize, Environment.NewLine));

                }

            }

 

 

 

            /// Elenca le tabelle di un database

            /// e scrive la lista su un oggetto Range di Word.

            /// Vengono escluse le tabelle di sistema.

            private void myGetTables(SQLDMO.Database db,

                              Microsoft.Office.Interop.Word.Range rng)

            {

                  foreach (SQLDMO.Table tb in db.Tables)

                  {

                        if (!tb.SystemObject)

                              rng.InsertAfter(string.Format("\t{0}{1}",

                              tb.Name, Environment.NewLine));

                  }

            }

 

 

 

 

 

            public void GetStoredProcedures(Microsoft.Office.Interop.Word.Range rng,

                                         string databaseName)

            {

 

                  foreach (SQLDMO.Database db in server.Databases)

                  {

                        if (string.Compare(db.Name, databaseName, true) == 0)

                        {                      

                              rng.InsertAfter(string.Format("Stored procedures " +

                                                            "del database {0}{1}{1}",

                                                            databaseName,

                                                            Environment.NewLine));

                              myGetStoredProcedures(db,rng);

                        }

                  }

            }

 

 

           

            /// Elenca le stored procedures di un database,

       /// e le scrive su un oggetto Range di Word.

            /// Sono escluse le stored procedures di sistema

            private void myGetStoredProcedures(SQLDMO.Database db,

                                               Microsoft.Office.Interop.Word.Range rng)

            {

               foreach (SQLDMO.StoredProcedure sp in db.StoredProcedures)

                  if (!sp.SystemObject)

                    rng.InsertAfter(string.Format("\t{0}{1}",

                                    sp.Name, Environment.NewLine));

            }

      }

}

?>

posted @ 23.05

lunedì 16 maggio 2005 #

Quando si manipolano gli oggetti di Excel in un assembly .NET capita a volte che venga scatenata un’eccezione di tipo COMException il cui messaggio è 'Old Format or Invalid Type Library'. Succede se si utilizza una versione di Excel in lingua inglese ma le impostazioni del proprio PC sono in una lingua diversa.

Un articolo della Knowledge Base (http://support.microsoft.com/default.aspx?scid=kb;en-us;320369 ) suggerisce un workaround a questo problema.

Occorre impostare la CurrentCulture a “en-US” prima dell’operazione critica, poi è possibile ripristinare la CultureInfo precedente (che avremo memorizzato in una variabile d’appoggio).

Sono in tutto tre righe di codice, ma come al solito è utile delegare questa piccola operazione a una classe riutilizzabile.

L’istruzione using e l’interfaccia IDisposable che ho descritto in un precedente post possono tornare utili anche in questo caso.

Ecco come si può implementare questa classe in C#:

 

using System;

using System.Globalization;

using System.Threading;

using System.Diagnostics;

 

namespace Utilities

{

      ///

      /// switchCultureInfo.

      /// Implementa

      /// e bufferizza in una variabile membro la

      /// corrente.

      /// Sul costruttore imposta la CurrentCulture su "en-US".

      ///

      ///

      /// Da usarsi per uno switch temporaneo alla CultureInfo di tipo "en-US"

      /// nei casi in cui l'utilizzo di alcuni oggetti di Office produce l'eccezione

      /// 'Old Format or Invalid Type Library'

      /// di tipo ,

      /// documentata nell'articolo 320369 della Knowledge Base.

      ///

      [DebuggerStepThrough()]

      public class switchCultureInfo: IDisposable

      {

 

            private CultureInfo originalCultureInfo = null;

 

      ///

      /// Costruttore. Memorizza nella variabile membro

      /// la CultureInfo corrente,

      /// per poterla ripristinare nel metodo .

      ///

      public switchCultureInfo()

      {

            originalCultureInfo = Thread.CurrentThread.CurrentCulture;

            Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");

      }

 

 

      public void Dispose()

      {

            Thread.CurrentThread.CurrentCulture = originalCultureInfo;

      }    

   }

}

 

 

Ed ecco come si può usarla per proteggere un’operazione che altrimenti genererebbe la COMException:

 

 

using Microsoft.VisualBasic;

using Utilities;

 

private void MyColorRange(object Rng, Color Col)

{

 

  using (new switchCultureInfo())

      ((Excel.Range)Rng).Interior.Color = Information.RGB(Col.R, Col.G, Col.B);

}

 

Nell’esempio si imposta il colore di una cella di Excel a partire da un oggetto System.Drawing.Color del Framework. Da notare che per convertire la structure System.Drawing.Color (che è a 24 bit) nell’oggetto Color di Excel il modo migliore è usare il metodo RGB della classe Microsoft.VisualBasic.Information.

Questo è uno dei rarissimi casi in cui può essere consigliabile includere il namespace Microsoft.VisualBasic in un progetto C#!!!

?>

posted @ 11.28