di Francesco BalenaIn genere mi piace molto la possibilità di estendere all’infinito la potenza delle mie applicazioni semplicemente aggiungendo un reference a qualche assembly che contiene le funzioni o i controlli che mi servono. Mi piace molto meno il fatto di dover distribuire numerose DLL con i miei eseguibili. In questo articolo illustrerò una tecnica per comprimere tutte (o quasi) le DLL satelliti di una applicazione Windows Forms e “fonderle” con l’eseguibile principale.
Tutti i file che vi servono sono contenuti in questo ZIP, che contiene l’utility AsmZip.exe (che si lancia dalla riga di comando) e due file sorgenti, Unzipper.cs e Unzipper.vb. Copiate l’utility AsmZip in una directory presente nel path di sistema, per poterla richiamare facilmente.
I passi da seguire Ecco i passi necessari per implementare questa tecnica.
1) Aggiungere il file Unzipper.vb o Unzipper.cs al progetto principale della vostra applicazione, rispettivamente se lavorate in Visual Basic o Visual C#.
2) Nella procedura Main, aggiungete una istruzione che inizializza la classe AssemblyUnzipper (contenuta nel file sorgente aggiunto al punto precedente): ' (Visual Basic 2005) CodeArchitects.AssemblyUnzipper.Initialize() // Visual C# 2005 CodeArchitects.AssemblyUnzipper.Initialize(); È di fondamentale importanza che questo codice sia eseguito prima di ogni altra istruzione del programma, in particolare prima di mostrare un form che contiene al suo interno dei controlli di cui non volete distribuire le DLL. Se lavorate in VB e l’applicazione ha un form di partenza (e quindi non avete a disposizione la procedura Main), dovete inserire queste istruzioni nel costruttore statico del form di partenza: Shared Sub New() CodeArchitects.AssemblyUnzipper.Initialize() End Sub
3) compilate la soluzione, ovviamente in Release mode visto che utilizzerete questa tecnica poco prima di consegnare l’eseguibile al vostro cliente.
4) aprite una finestra con il prompt dei comandi nella directory di output del programma, e lanciate l’utility AsmZip in questo modo: AsmZip main.exe *.dll dove main.exe è il nome dell’eseguibile principale. Il comando precedente comprime *tutte* le DLL nella directory e mette i dati compressi in coda al file main.exe. Per comprimere solo alcune DLL tra quelle presenti nella directory dovete specificarne i nomi, come in questo esempio: AsmZip main.exe CodeArchitects*.dll Microsoft*.dll (Ci possono essere dei buoni motivi per non comprimere alcune DLL usate dalla applicazione, come spiego più avanti.)
5) a questo punto potete cancellare tutte le DLL che avete compresso, perchè l’applicazione – grazie alla classe AssemblyUnzipper - è in grado di trovarle da sola in coda al proprio eseguibile, di decomprimerle, e di caricarle in memoria.
I vantaggi Prima di procedere ad una spiegazione più dettagliata di come funziona questa tecnica, proverò a riassumere i suoi vantaggi:
a) deployment semplificato: dovete distribuite un minor numero di file (spesso il solo EXE principale) b) applicazioni più robuste: non vi è il rischio che il cliente renda il programma inutilizzabile cancellando inavvertitamente una DLL c) minore occupazione su disco (tutte le DLL sono compresse e accodate all'EXE principale) d) la possibilità di “nascondere” i vostri piccoli segretucci, ad esempio quali controlli di terze parti avete utilizzato nella applicazione. e) un maggior grado di protezione: il codice nelle DLL compresse non può essere decompilato, perlomeno non senza avere estratto e decompresso i singoli eseguibili.
È evidente che gli ultimi due punti non costituiscono una vera barriera per un programmatore anche solo un po’ esperto, se è davvero intenzionato a sapere quali DLL avete usato e a decompilarle. Per ottenere queste informazioni un hacker dovrà soltanto decompilare l’EXE principale, capire come funziona la classe AssemblyUnzipper, e scrivere un programmino che ne ripete il funzionamento ma salva su disco gli assembly compressi. In definitiva, questa tecnica permette di nascondere il vostro sorgente e le vostre DLL alla prima occhiata, ma non potete certo considerarla una tecnica di protezione dal reverse engineering.
L’utility AsmZip usa la classe GZipStream per comprimere le DLL originarie, quindi il grado di compressione che si può raggiungere con questa tecnica non è paragonabile a quello ottenibile con WinZip o WinRar, ma è comunque più che sufficiente, come mostra la figura.

Come funziona Questa tecnica si basa sull’evento AssemblyResolve dell’oggetto AppDomain, che scatenato quando il .NET Framework deve caricare un assembly richiesto dalla applicazione. Gestendo opportunamente questo evento potete mettere in pratica tante piccole “magie” che non sarebbero possibili in alcun altro modo. Ad esempio, potreste caricare gli assembly satelliti di una applicazione da uno share di rete oppure da un campo blob di un database.
La classe AssemblyUnzipper, invece, usa questo evento per cercare l’assembly richiesto da uno stream compresso che è stato accodato all’eseguibile del programma principale. // the handler for AssemblyResolve event static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs e) { // find the assembly with given name, cause error if not found AssemblyInfo info = null; if ( AsmInfos.TryGetValue(e.Name, out info) ) return ExtractAssembly(info); // signal error Debug.WriteLine("Failed to uncompress assembly " + info.Name); return null; } Ciascun oggetto AssemblyInfo tiene traccia di dove, nel file EXE, è memorizzato ciascuna DLL compressa. Il dictionary AsmInfos permette di trovare subito le informazioni associate ad una DLL con un certo nome; questo dictionary è creato nel metodo Initialize, alla partenza del programma, e viene poi usato ogni volta che l’applicazione richiede una DLL.
Non spiegherò nel dettaglio come funziona il codice, perchè il sorgente è commentato in modo adeguato, sia nella versione VB che C#.
I limiti Ho provato questa tecnica con numerose applicazioni Windows Form, senza alcun problema. Il limite principale deriva dal fatto che gli assembly caricati in questo modo hanno la proprietà Location nulla, ma se non utilizzate reflection per esplorare le proprietà degli assembly non ve ne accorgerete neanche. Ad esempio, se la vostra applicazione carica gli assembly dinamicamente da una certa directory, magari per esplorarne gli attributi, è evidente che quel codice non può funzionare se le DLL sono state compresse e cancellate. In tal caso dovrete evitare di processare queste DLL con AsmZip.
La classe AssemblyUnzipper funziona solo con applicazioni Windows Forms. Da quanto ne so, è possibile utilizzare l’evento AssemblyResolve anche con applicazioni ASP.NET, ma non è possibile usare la mia classe in quel contesto. In realtà, i problemi che questa tecnica risolve non sono molto sentiti in ambito ASP.NET, quindi non credo che abbia senso preparare una versione per applicazioni di quel tipo.
L'unico altro limite è che questa tecnica funziona con le DLL, ma non con l'EXE principale. Se avete un EXE di grosse dimensioni che richiamo poche piccole DLL, il vantaggio che ne potete ricavare è molto limitato. In tal caso, potete comunque raggiungere il massimo grado di compressione "complessiva" spostando i form dall’eseguibile principale in una DLL, e comprimendo poi tale DLL con AsmZip. In teoria, l’EXE di partenza dovrebbe contenere solo lo splash screen (se ne avete uno), e poi dovrebbe caricare il form principale dalla DLL che contiene l’applicazione vera e propria. In questo modo è spesso possibile ottenere un fattore di compressione complessivo superiore al 60 per cento.
Nota: nella prima implementazione di questa tecnica ero riuscito a comprimere anche l'EXE. In tal caso accodavo poi tutti i dati compressi a un piccolo eseguibile "stub" che aveva il solo compito di fare partire l'eseguibile vero e proprio. Dopo alcune prove, però, mi sono reso conto che i problemi che si venivano a creare erano superiori ai vantaggi, per cui ho deciso di utilizzare il metodo descritto in questo articolo.
di Francesco BalenaQualche tempo fa ho registrato per Microsoft Italia alcuni brevi webcast (20 minuti ciascuno) su .NET 2.0 e Visual Studio 2005. Si tratta di sessioni introduttive, ma "succose", su feature come generics, reflection, multitasking, e regular expression. C'è anche un mini-webcast su come creare applicazioni Windows Forms estendibili mediante plug ins. Finalmente tutti i webcast sono online, a questo URL: http://www.microsoft.com/italy/msdn/risorsemsdn/visualbasic/vs2005_pillole.mspx
di Marco BellinasoIn questo periodo sono occupato quasi settimanalmente con lo Starting Innovation Tour, un ciclo di conferenze gratuite organizzate da Microsoft nelle principali città italiane per mostrare a sviluppatori e partner le principali novità di Vista e Office 2007. Dopo Catania, Napoli, Bologna, Bari e Roma, questa settimana tocca a Padova, poi Milano, Firenze e Torino. A mio parere la conferenza è davvero un'ottima occasione per chi non ha ancora visto con i propri occhi (o toccato con mano, con preferite) le nuove major release dei due prodotti principali di MS. Io mi occuperò delle sessioni su Office, ovvero:
- Sviluppo con Office 2007 Client Side: come sfruttare il nuovo formato XML dei documenti per modificare o creare nuovi documenti senza avere Office installato - da applicazioni desktop o web - nonchè una panoramica sulla creazione di add-in, Action Pane e ribbon custom.
- Windows SharePoint Services 3.0: nuove funzionalità per le liste e document library (cestino, supporto per versioning esteso, supporto RSS,...), nuovi template (blog e wiki tanto per essere moderni, ma altro ancora), utilizzo masterpage a-là ASP.NET 2.0, webpart, utilizzo workflow built-in o utilizzo del nuovo SharePoint Designer per la definizione dei propri flussi.
- Office SharePoint Portal 2007: Excel Services (il motore di calcolo di Excel direttamente sul server + rendering via browser + esposizione del motore tramite web service!), Forms Services (praticamente le form InfoPath via browser!) e Business Data Catalog (indicizzazione e visualizzzione di record presi da un qualunque database o fonte di accesso ai dati esterna)
Insomma, mi sembra ci sia un bel po' di roba da vedere Per non parlare delle bellissime e impressionanti demo grafiche fatte con WPF per Vista! Dai, ci vediamo ad una delle prossime tappe allora...
di Francesco BalenaUno dei problemi che si devono risolvere quando si comincia a scrivere gerarchie a più livelli di form ereditate - in Windows Forms, intendo - è capire quando effettivamente il form si trova a design time, in modo ad esempio da evitare di eseguire certe azioni come il caricamento dei dati da file o database.
Basta usare la proprietà DesignMode che l'oggetto Form eredita da Component, dirà qualcuno di voi, ma evidentemente non è così semplice. Infatti, se il form MyForm deriva da FormBase, all'interno della classe FormBase quella proprietà restituisce False quando abbiamo caricato MyForm dentro il designer di Visual Studio. Allora ho tentato di determinare indirettamente se il form si trova a design time, ad esempio vendendo se la collection Application.OpenForms contiene qualche elemento, ma ho scoperto che quella collezione funziona anche a design-time. Lo stesso vale per la maggior parte degli eventi dei form, che in FormBase si scatenano sia quando l'applicazione è realmente in esecuzione sia quando MyForm è in design-mode.
Alla fine ho trovato una soluzione che sembra funzionare bene:
bool IsDesignMode = ( this.Parent != null && this.Parent.ToString().StartsWith("System.Windows.Forms.Design") );
Quando il form è ospitato in Visual Studio, esso è figlio di un altro form managed, la cui classe è appunto System.Windows.Forms.Design.DesignerFrame+OverlayControl, e il codice di cui sopra testa proprio questa condizione. La soluzione non mi piace affatto, perchè mi sembra uno sporco trucco che per giunta fallisce se sviluppate con ambienti di sviluppo differenti da Microsoft Visual Studio, però per il momento mi dovrò accontare.
Mi chiedo perchè il .NET Framework ci debba costringere a questi giri. Oppure forse esiste una soluzione più semplice e io non la conosco?
di Francesco BalenaIn Visual Basic 6 esiste una comodissima proprietà dell'oggetto App che permette di determinare se vi sono altre istanze in esecuzione della stessa applicazione. Manco a dirlo, questa proprietà non è mai stata migrata in VB.NET anche se, per fortuna, una applicazione scritta in VB 2005 può almeno usare l'evento StartupNextInstance:
' to display this code, open the Application page of the My Project designer and click the Application Events button
Namespace My Partial Friend Class MyApplication Private Sub MyApplication_StartupNextInstance( ByVal sender As Object, ByVal e As Microsoft.VisualBasic.ApplicationServices.StartupNextInstanceEventArgs) Handles Me.StartupNextInstance ' another instance of this application has been launched End Sub End Class End Namespace
Il problema di questo approccio è che l'evento viene sollevato nella prima istanza della applicazione, non nella nuova istanza, quindi alla partenza una applicazione VB.NET non può sapere con certezza se è l'unica istanza o meno. In altre parole, può solo sapere se e quando un'altra applicazione parte, non se l'applicazione corrente è la prima e unica istanza in esecuzione.
In .NET 2.0 è stato introdotto il nuovo tipo System.Threading.Semaphore che permette di risolvere il problema in modo davvero elegante. Un semaforo è un oggetto che può essere incrementato e decrementato. La cosa interessante è che se il semaforo ha un nome, allora esso è condiviso da tutte le applicazioni .NET in esecuzione che richiedono un riferimento al semaforo con quel particolare nome. Basta quindi che l'applicazione crei alla partenza un semaforo con un nome univoco (ad es. un nome che include il percorso dell'eseguibile, che è quasi certamente univoco) e più applicazioni possono condividere il contatore interno al semaforo. L'unico problema che resta da risolvere è assicurarsi che il valore del semaforo sia correttamente ripristinato quanto l'applicazione termina, ma anche questo è facile da ottenere usando un metodo Finalize.
In aggiunta alla proprietà PrevInstance - che restituisce False se l'applicazione era l'unica istanza al momento della partenza - la seguente classe VB6App espone anche la proprietà InstanceCount, che restituisce il numero totale di istanze in esecuzione in quel momento, incluso quindi l'applicazione corrente. Ecco il codice della classe VB2005:
Class VB6App ' the default instance Private Shared DefValue As New VB6App
' the system-wide semaphore Private semaphore As System.Threading.Semaphore ' initial count for the semaphore (very high value) Private Const MAXCOUNT As Integer = 10000
Private Sub New() Dim ownership As Boolean = False ' create a unique name, but strip invalid characters Dim name As String = "VB6App_" & System.Reflection.Assembly.GetExecutingAssembly().Location.Replace(":", "").Replace("\", "") semaphore = New System.Threading.Semaphore(MAXCOUNT, MAXCOUNT, name, ownership) ' decrement its value semaphore.WaitOne() ' if we got ownership, this app has no previous instances m_PrevInstance = Not ownership End Sub
' the PrevInstance property returns True if there was a previous instance running ' when the default instance was created Private Shared m_PrevInstance As Boolean
Public Shared ReadOnly Property PrevInstance() As Boolean Get Return m_PrevInstance End Get End Property
' return the total number of instances of the same application that are currently running Public Shared ReadOnly Property InstanceCount() As Integer Get ' release the semaphore and grab the previous count Dim prevCount As Integer = DefValue.semaphore.Release() ' acquire the semaphore again DefValue.semaphore.WaitOne() ' eval the number of other instances that are currently running Return MAXCOUNT - prevCount End Get End Property
Protected Overrides Sub Finalize() ' increment the semaphore when the application terminates semaphore.Release() End Sub
End Class
Notate che questa classe contiene un metodo Finalize senza implementare IDisposable. Si tratta di uno dei casi speciali in cui è giusto violare il pattern Dispose-Finalize.
Per comprende come funziona la classe, basta ricordare che il metodo Release dell'oggetto Semaphore incrementa il valore interno, mentre WaitOne lo decrementa. L'unica accortezza da usare con questo codice è di testare la proprietà VB6App.PrevInstance il prima possibile, ad esempio nel metodo Sub Main o nell'evento Load del form principale, per dare la possibilità alla classe di conservare il suo valore alla partenza del programma. Lo stesso form potrebbe poi testare il valore di InstanceCount in uscita, ad esempio se è necessario eseguire un codice di cleanup quando l'ultima istanza della applicazione termina:
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load If Not VB6App.PrevInstance Then ' open the common log file ' ... End If End Sub
Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing If VB6App.InstanceCount = 1 Then ' close the common log file. ' ... End If End Sub
Visto che l'utilità di questa classe, l'ho anche tradotta in C#:
public class VB6App { // the default instance private static VB6App DefValue = new VB6App(); // the system-wide semaphore private System.Threading.Semaphore semaphore; // initial count for the semaphore (very high value) private const int MAXCOUNT = 10000;
private VB6App() { // create a named (system-wide semaphore) bool ownership = false; // create the semaphore or get a reference to an existing semaphore
string name = "VB6App_" + System.Reflection.Assembly.GetExecutingAssembly().Location.Replace(":", "").Replace("\", ""); semaphore = new System.Threading.Semaphore( MAXCOUNT, MAXCOUNT, name, ref ownership); // decrement its value semaphore.WaitOne(); // if we got ownership, this app has no previous instances m_PrevInstance = !ownership; }
// the PrevInstance property returns True if there was a previous instance running // when the default instance was created
private static bool m_PrevInstance ;
public static bool PrevInstance { get { return m_PrevInstance ; } }
// return the total number of instances of the same application that are currently running
public static int InstanceCount { get { // release the semaphore and grab the previous count int prevCount = DefValue.semaphore.Release(); // acquire the semaphore again DefValue.semaphore.WaitOne(); // eval the number of other instances that are currently running return MAXCOUNT - prevCount; } }
~VB6App() { // increment the semaphore when the application terminates semaphore.Release(); } }
di Francesco BalenaIn pochi giorni mi sono arrivate due mail da altrettanti lettori, che chiedono come sia possibile - in una finestra Windows Forms che sfrutta l'evento Validating dei controlli per validare l'input - saltare la validazione se l'utente chiude il form. Se non si salta la validazione, infatti, l'effetto è che l'utente potrebbe non riuscire a chiudere il form. La risposta è molto semplice, ma evidentemente non è proprio ovvia, quindi la ripeto qui a beneficio di tutti.
Se l'utente chiude un form cliccando sul pulsante "X" o scegliendo "Close" dal menu di sistema, scatta prima l'evento Validating del controllo che ha il focus poi, anche se il primo evento ha decretato che la validazione è fallita, scatta l'evento FormClosing. La cosa interessante è che, se l'evento Validating ha impostato e.Cancel = True, nell'evento FormClosing è possibile resettare questo valore a False. In definitiva, quindi, per evitare che il codice nell'evento Validating impedisca all'utente di chiedere il form sono sufficienti queste istruzioni:
Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As FormClosingEventArgs) Handles Me.FormClosing If e.CloseReason = CloseReason.UserClosing OrElse e.CloseReason = CloseReason.WindowsShutDown Then e.Cancel = False End If End Sub
Un modo più radicale per evitare il problema è di nascondere il pulsante "X" oppure il comando "Close" del menu di sistema della finestra, impostando la proprietà ControlBox a False. Oppure potete disattivarla (magari anche solo temporaneamente) con il seguente codice:
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load SetCloseCommandState(Me.Handle, False) End Sub
Private Declare Function GetSystemMenu Lib "user32" (ByVal hWnd As IntPtr, ByVal bRevert As Integer) As IntPtr Private Declare Function EnableMenuItem Lib "user32" (ByVal hMenu As IntPtr, _ ByVal wIDEnableItem As Integer, ByVal wEnable As Integer) As Integer Const MF_GRAYED As Integer = &H1& Const MF_BYCOMMAND As Integer = &H0& Const SC_CLOSE As Integer = &HF060&
Sub SetCloseCommandState(ByVal hWnd As IntPtr, ByVal newState As Boolean) Dim hMenu As IntPtr = GetSystemMenu(hWnd, 0) Dim wFlags As Integer = MF_BYCOMMAND If newState Then wFlags = wFlags And Not MF_GRAYED Else wFlags = wFlags Or MF_GRAYED End If EnableMenuItem(hMenu, SC_CLOSE, wFlags) End Sub
di Francesco BalenaIeri mi scrive un mio lettore, Claudio Fontana, con una richiesta apparentemente semplice: come fare per evitare sfarfallii strani quando si devono aggiornare la maggior parte dei controlli sul form? Il problema si fa sentire soprattutto quando occorre aggiungere migliaia di elementi a listbox e treeview.
In VB6 questo problema si risolveva molto semplicemente impostando la proprietà Visible (o Enabled) a False durante l'aggiornamento, e re-impostandola a True alla fine delle operazioni: la cosa interessante è che, a meno di non inserire una istruzione Refresh esplicita, il controllo non veniva effettivamente reso invisibile o "grayed", ma il suo nuovo contenuto appariva di botto, senza alcun effetto di flickering. Non solo: se il controllo veniva reso invisibile l'aggiornamento veniva anche eseguito molto più velocemente, tipicamente nella metà del tempo solitamente necessario. Sfortunatamente, in .NET il trucco di rendere i controlli temporaneamente invisibili non funziona più, nel senso che il refresh avviene effettivamente e quindi il controllo scompare effettivamente per tutta la durata dell'aggiornamento. Occorre pensare a qualcos'altro.
Alcuni controlli Windows Forms espongono i metodi BeginUpdate e EndUpdate, che di fatto ottengono non solo di "congelare" un controllo durante le operazioni di aggiornamento ma anche di sveltire notevolmente le operazioni stesse, che diventano anche 2.5 volte più veloci. Però solo quattro controlli espongono questa proprietà: ListBox, ComboBox, TreeView e ListView. Se il vostro form contiene molti controlli di altro tipo è necessario pensare a qualcos'altro, e questo è il problema che mi ha sottoposto Claudio, dopo aver googlato a destra e a manca sulla Rete alla ricerca di una soluzione, senza alcun esito.
Poichè il problema era intrigante, ho deciso di dedicarci qualche minuto, e sono arrivato ad una soluzione abbastanza interessante e (se Claudio ha googlato bene) anche inedita. L'idea è davvero molto semplice: (1) si "fotografa" l'aspetto corrente del form, copiandolo pixel per pixel in una bitmap, (2) si crea un controllo PictureBox grande quanto il form e si carica la bitmap nella PictureBox, (3) si aggiunge la PictureBox alla collection Controls e la si porta in primo piano, in modo da coprire tutti gli altri controlli, (4) mentre l'utente vede l'immagine congelata del form, si aggiornano tutti i controlli, usando i metodi BeginUpdate/EndUpdate se possibile per velocizzare le operazioni, (5) quando l'update è completato, si elimina la PictureBox in modo che l'utente torni a vedere il vero contenuto del form.
Tutto questo algoritmo è in realtà molto semplice, e si riduce a una decina di righe. Però ho pensato di creare una classe ad-hoc, in modo che il codice sia facilmente riutilizzabile, rilasci correttamente le risorse, e si protegga da utilizzi errati:
Public Class FormFreezer Implements IDisposable
' The form being frozen Dim Form As Form ' the auxiliary PictureBox that will cover the form Dim PictureBox As PictureBox ' the number of times the Freeze method has been called Dim FreezeCount As Integer = 0
' create an instance associated with a given form ' and optionally freeze the form right away Public Sub New(ByVal form As Form, Optional ByVal freezeIt As Boolean = False) Me.Form = form If freezeIt Then Me.Freeze() End Sub
' freeze the form Public Sub Freeze() ' Remember we have frozen the form once more FreezeCount += 1 ' Do nothing if it was already frozen If FreezeCount > 1 Then Exit Sub
' Create a PictureBox that resizes with its contents PictureBox = New PictureBox() PictureBox.SizeMode = PictureBoxSizeMode.AutoSize ' create a bitmap as large as the form's client area and with same color depth Dim frmGraphics As Graphics = Form.CreateGraphics() Dim rect As Rectangle = Form.ClientRectangle PictureBox.Image = New Bitmap(rect.Width, rect.Height, frmGraphics) frmGraphics.Dispose()
' copy the screen contents, from the form's client area to the hidden bitmap Dim picGraphics As Graphics = Graphics.FromImage(PictureBox.Image) picGraphics.CopyFromScreen(Form.PointToScreen(New Point(rect.Left, rect.Top)), New Point(0, 0), New Size(rect.Width, rect.Height)) picGraphics.Dispose()
' Display the bitmap in the picture box, and show the picture box in front of all other controls Form.Controls.Add(PictureBox) PictureBox.BringToFront() End Sub
' unfreeze the form ' Note: calls to Freeze and Unfreeze must be balanced, unless force=true Public Sub Unfreeze(Optional ByVal force As Boolean = False) ' exit if nothing to unfreeze If FreezeCount = 0 Then Exit Sub ' remember we've unfrozen the form, but exit if it is still frozen FreezeCount -= 1 ' force the unfreeze if so required If force Then FreezeCount = 0 If FreezeCount > 0 Then Exit Sub
' remove the picture box control and clean up Form.Controls.Remove(PictureBox) PictureBox.Dispose() PictureBox = Nothing End Sub
' return true if the form is currently frozen Public ReadOnly Property IsFrozen() As Boolean Get Return FreezeCount > 0 End Get End Property
' ensure that resources are cleaned up correctly Public Overridable Sub Dispose() Implements IDisposable.Dispose Me.Unfreeze(True) End Sub End Class
Questo è invece la versione C#, tradotta da Claudio:
public class FormFreezer: IDisposable {
// The form being frozen
Form form;
// the auxiliary PictureBox that will cover the form
PictureBox pictureBox;
// the number of times the Freeze method has been called
int FreezeCount = 0;
// create an instance associated with a given form
// and freeze the form in base of flag freezeIt
public FormFreezer(Form form, bool freezeIt) {
this.form = form;
if (freezeIt) this.Freeze();
}
// freeze the form
public void Freeze() {
// Remember we have frozen the form once more
// Do nothing if it was already frozen
if (++FreezeCount > 1) return;
// Create a PictureBox that resizes with its contents
pictureBox = new PictureBox();
pictureBox.SizeMode = PictureBoxSizeMode.AutoSize;
// create a bitmap as large as the form's client area and with same color depth
Graphics frmGraphics = form.CreateGraphics();
Rectangle rect = form.ClientRectangle;
pictureBox.Image = new Bitmap(rect.Width, rect.Height, frmGraphics);
frmGraphics.Dispose();
// copy the screen contents, from the form's client area to the hidden bitmap
Graphics picGraphics = Graphics.FromImage(pictureBox.Image);
picGraphics.CopyFromScreen(form.PointToScreen(new Point(rect.Left, rect.Top)), new Point(0, 0), new Size(rect.Width, rect.Height));
picGraphics.Dispose();
// Display the bitmap in the picture box, and show the picture box in front of all other controls
form.Controls.Add(pictureBox);
pictureBox.BringToFront();
}
// unfreeze the form
// Note: calls to Freeze and Unfreeze must be balanced, unless force=true
public void Unfreeze(bool force) {
// exit if nothing to unfreeze
if ( FreezeCount == 0 ) return ;
// remember we've unfrozen the form, but exit if it is still frozen
FreezeCount -= 1;
// force the unfreeze if so required
if (force) FreezeCount = 0;
if (FreezeCount > 0) return;
// remove the picture box control and clean up
pictureBox.Controls.Remove(pictureBox);
pictureBox.Dispose();
pictureBox = null;
}
// return true if the form is currently frozen
public bool IsFrozen {
get { return (FreezeCount > 0); }
}
void IDisposable.Dispose()
{
this.Unfreeze(true);
}
}
Usare la classe FormFreezer è semplicissimo. Ecco un esempio di codice (che deve trovarsi all'interno di una classe Form, in modo che Me punti al form corrente):
Dim ff As New FormFreezer(Me, True) ' update controls here ' ... ff.Unfreeze()
Poichè la classe implementa IDisposable, è possibile racchiudere il codice di aggiornamento in un blocco Using, sia in C# che in VB 2005, ed evitare la chiamata esplicita a Unfreeze:
Using New FormFreezer(Me, True) ' Update controls here ' ... End Using
Notate che le chiamate a Freeze e Unfreeze devono essere bilanciate, ad esempio se si eseguono due chiamate a Freeze occorreranno poi due chiamate a Unfreeze per "scongelare" effettivamente il form. Questo comportamento è simile a quello esposto dalle API di sistema che nascondono e mostrano il cursore del mouse, e funziona correttamente il form anche se il codice chiama Freeze e passa poi il controllo a un altro metodo che chiama anch'esso Freeze (a condizione pero' di usare la stessa istanza di FormFreezer).
di Francesco BalenaMolti, se non la maggior parte, degli esempi di controlli Windows Forms che si trovano in giro per la rete contengono chiamate a codice unmanaged nelle DLL di Windows, in particolare uno o più metodi SendMessage per ovviare alle (pochissime) mancanze controlli del .NET Framework. Il problema è che un controllo del genere crea dei problemi quando l'applicazione viene eseguita in modalità ClickOnce, poichè richiede di modificare la CAS associata alla particolare applicazione.
Anche se questo problema non ha soluzione in generale, quando si intende inviare un messaggio al controllo che si sta subclassando è possibile fare a meno di una chiamata esplicita a SendMessage e usare invece il metodo protetto DefWndProc che viene ereditato dalla classe Control. Supponiamo ad esempioi di voler scrivere una ComboBox "enhanced", che espone la proprietà TopIndex, che imposta o restituisce l'indice del primo elemento visibile nella porzione di lista del controllo. Queste due operazioni si possono implementare inviando rispettivamente i messaggi CB_SETTOPINDEX e CB_GETTOPINDEX al controllo. Ecco come fare, usando il metodo DefWndProc invece di SendMessage:
Public Class ComboBoxEx Inherits System.Windows.Forms.ComboBox
Public Property TopIndex() As Integer Get Const CB_GETTOPINDEX As Int32 = &H15B Dim m As New Message() m.HWnd = Me.Handle m.Msg = CB_GETTOPINDEX Me.DefWndProc(m) Return m.Result.ToInt32() End Get Set(ByVal value As Integer) Const CB_SETTOPINDEX As Int32 = &H15C Dim m As New Message() m.HWnd = Me.Handle m.Msg = CB_SETTOPINDEX m.WParam = New IntPtr(value) Me.DefWndProc(m) End Set End Property
End Class
Incidentalmente, questa ComboBox enhanced vi può venire utile se state migrando applicazioni VB6. Infatti, la ComboBox e la ListBox di VB6 supportano la proprietà TopIndex, mentre in .NET solo la ListBox espone questa proprietà. Se avete del codice VB6 che utilizza la proprietà TopIndex di una ComboBox, l'approccio più semplice è sostituire il controllo con una ComboBoxEx.
di Giuseppe Dimauro

Ciao a tutti, vi riporto di seguito il calendario ufficiale dei prossimi webcast architetturali tenuti da Maurizio, Pierre, me ed altri. Il primo appuntamento e' domani. Illustrero' le novita' della rinnovata versione dell'Enterprise Library di Patterns & Practices per poi proseguire fino a giugno con una nutrita serie di argomenti e approfondimenti su tecnologie reali.
Calendario dei prossimi Architect Webcast
§Febbraio
•7/2: Pattern architetturali per la realizzazione di applicazioni e servizi - Parte I
•14/2: Pattern architetturali per la realizzazione di applicazioni e servizi - Parte II
•21/2: Introduzione alla metodologia agile MSF 4.0 con Visual Studio 2005 Team System
•28/2: Progettare il Web Testing nel mondo Enterprise con Visual Studio 2005 Team Test
§Marzo
•07/3: BizTalk Server 2006: uno strumento per tutta l'azienda
•14/3: BizTalk Server 2006: mille e uno usi di uno strumento versatile
•21/3: BizTalk Server 2006 e lo sviluppo di applicazioni orientate ai servizi
•28/3: WinFX: Windows Workflow Foundation - Parte I
§Aprile
•04/4: WinFX: Windows Workflow Foundation - Parte II
•11/4: Realizzare servizi distribuiti con Windows Communication Foundation - Parte I
•19/4: Realizzare servizi distribuiti con Windows Communication Foundation - Parte II
•27/4: Architettura SOA. Perché non se ne può fare a meno?
§Maggio
•09/5: Le applicazioni client negli scenari d'integrazione - Parte I
•16/5: Le applicazioni client negli scenari d'integrazione - Parte II
•23/5: Interoperabilità e migrazione tra .NET e COM
§Giugno
•06/6: Smart Client. Unire il meglio di idee e tecnologie diverse
•13/6: Il dato al centro dell'informazione aziendale. Come gestirlo
•20/6: Snellire i processi aziendali gestendo il flusso di informazioni con Office
•27/6: Smart Document: la nuova faccia del documento

di Francesco BalenaHo già parlato in questo blog di CodeWall.NET, un tool per la protezione degli eseguibili .NET 2.0 dalla decompilazione, a cui io e Vito Plantamura stiamo lavorando da un paio di mesi. Finalmente abbiamo una beta robusta e funzionante, e non vediamo l'ora di metterla online.
Date le finalità del prodotto, non è un mistero il fatto che abbiamo passato la maggior parte del tempo per implementare tutte le tecniche "anti-sprotezione" di nostra conoscenza, incluso alcune davvero esoteriche. E' un vero peccato non poterle descrivere nei dettagli, per motivi che dovrebbero essere evidenti. Comunque, qualche cosa la posso comunque raccontare.
Uno dei punti deboli di praticamente tutti i meccanismi di protezione di questo tipo è che, se un "cracker" trova il modo per sproteggere UN SOLO eseguibile protetto, egli è in grado di scrivere uno script o un tool in grado di sproteggere TUTTI gli eseguibili protetti, la qual cosa sarebbe ovviamente un vero disastro per noi e per i nostri clienti. Per ovviare a questo serissimo problema, ogni copia di CodeWall.NET è unica. Non solo utilizza una chiave differente, ma contiene anche un algoritmo di decifratura leggermente differente. Questo accorginmento rende l'attacco mediante script virtualmente impossibile.
Ecco un'altra tecnica che abbiamo adottato per rendere la protezione più robusta. Le applicazioni protette con CodeWall.NET possono contenere delle stringhe criptate, che saranno riportate in chiaro solo in fase di esecuzione, e solo se l'assembly "gira" sotto CodeWall.NET. Questo significa che se anche qualcuno riuscisse a estrarre un assembly dall'eseguibile protetto, dovrebbe capire come funziona il decrypt delle stringhe e poi decrittare le stringhe una a una. Attenzione, perchè non solo è possibile criptare le stringhe in questo modo: è anche possibile criptare nomi di classi e di metodi, quindi se queste stringhe non sono decrittate correttamente il programma non funzionerebbe affatto.
Ovviamente, il loader di CodeWall.NET è scritto in codice unmanaged, in modo da NON essere decompilabile con Reflector, Anakrino, o ILDASM. Un malintenzionato particolarmente motivato potrebbe usare un disassembler "classico", ma poi gli toccherebbe decifrare migliaia di opcode assembly "nativo", il che non è proprio alla portata di tutti. Purtroppo, pero', l'uso del codice unamanged limita l'utilizzo di CodeWall.NET ai soli eseguibili Win32 (Windows Forms e Console) che girano in modalità full-trust. Non possiamo quindi supportare appliczioni ASP.NET e neanche applicazioni ClickOnce, a meno che questi ultimi non girino appunto in full-trust mode.
Non abbiamo ancora stabilito il prezzo di listino di CodeWall.NET, ma prevediamo che si aggirerà sui 250 dollari (circa 200 euro) e quasi certamente ci sarà uno sconto per il periodo del lancio. A breve speriamo di mettere una versione "trial" disponibile sul sito. Nel frattempo, se avete domande lasciate pure un commento.
di Francesco BalenaQualche settimana fa scrivevo un post a proposito della covarianza e controvarianza dei delegate in C# 2.0, stupendomi del fatto che in giro per la rete in tanti parlano di queste nuove feature ma senza spiegare a cosa possono servire realmente.
Poichè tutti gli eventi .NET sono gestiti mediante delegate, grazie alla controvarianza è possibile che un unico metodo possa gestire più eventi del tipo (sender, e). anche se la loro signature è leggermente differente. Supponiamo di avere questo metodo:
Sub GestoreEvento(ByVal sender As Object, ByVal e As EventArgs) .. End Sub
Un metodo di questo tipo è in grado di gestire tutti gli eventi del tipo (sender, e), anche se il secondo argomento non è un EventArgs ma un tipo che eredita da EventArgs. Quindi un metodo di questo tipo è in grado di gestire virtualmente tutti gli eventi definiti nel .NET Framework, con pochissime eccezioni. (L'evento AppDomain.AssemblyResolve è una di queste eccezioni, perchè non è un metodo Sub ma una Function che restituisce un oggetto Assembly.)
Anche se la controvarianza permette in teoria di far gestire tutti gli eventi di un oggetto da un unico metodo, nella pratica le cose sono un po' più complicate, perchè il metodo GestoreEvento ha modo di sapere quale oggetto sta scatenando l'evento (tramite l'argomento sender) ma non quale evento è stato scatenato. Per ottenere questa informazione occorre scrivere del codice un po' più complesso.
Insomma, alla fine ho scritto un componente EventInterceptor che potete sistemare sulla superficie di un Windows Forms e che vi permette di catturare tutti (o alcuni) eventi di un controllo sul form (o al limite di tutti i controlli sul form) per mezzo di un unico gestore. Trovate l'articolo e il codice sorgente a corredo a questo link.
di Francesco BalenaUn quiz di Adrian Florea mi ha ricordato una tecnica che uso talvolta per evitare chiamate ricorsive a un metodo. La tecnica "classica" per evitare la ricorsione consiste nel definire un campo booleano a livello di classe e testarlo all'entrata del metodo. Questa tecnica è spesso usata negli eventi di tipo TextChanged che modificano a loro volta la proprietà Text di un controllo, e che quindi farebbero scattare una ricorsione infinita:
Dim insideTextChanged As Boolean
Private Sub TextBox1_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles TextBox1.TextChanged ' Exit if this is a recursive call. If insideTextChanged Then Exit Sub ' Forbid recursive calls from now on. insideTextChanged = True ' ... TextBox1.Text = TextBox1.Text & " " ' Permit recursive calls. insideTextChanged = False End Sub
Questo approccio funziona, ma è decisamente scomodo perchè richiede un bel po' di codice. Se poi esiste anche la minima possibilità che possa avvenire una eccezione, allora occorre che tutto il corpo del metodo sia avvolto in un blocco Try, che imposta insideTextChanged a False nella sezione Finally. Perchè non usare allora una funzione che permette di testare se siamo all'interno di una chiamata ricorsiva? Penso ad esempio a qualcosa di questo tipo:
Private Sub TextBox1_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles TextBox1.TextChanged ' Exit if this is a recursive call. If IsRecursive() Then Exit Sub ' ... TextBox1.Text = TextBox1.Text & " " End Sub
Ecco come implementare la funzione IsRecursive:
<System.Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
Public Shared Function IsRecursive() As Boolean Dim st As New StackTrace ' Check whether any method in the call stack is the same as the immediate caller. For n As Integer = 2 To st.FrameCount - 1 If st.GetFrame(1).GetMethod() Is st.GetFrame(n).GetMethod() Then Return True Next Return False End Function
Ecco la versione C#:
[System.Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)] public static bool IsRecursive() { StackTrace st = new StackTrace(); // Check whether any method in the call stack is the same as the immediate caller. for ( int n= 2; n < st.FrameCount; n++ ) { if ( st.GetFrame(1).GetMethod() == st.GetFrame(n).GetMethod() ) return true; } return false; }
Il metodo controlla il metodo del chiamante immediato - ossia st.GetFrame(1).GetMethod() - con il metodo di tutti gli altri chiamanti sullo stack, e restitusce True se trova un match. E' essenziale che il metodo IsRecursive sia marcato con l'attributo MethodImpl in modo da evitare che il JIT-compiler esegua l'inline del metodo e faccia saltare il meccanismo di controllo. Nella versione attuale del JIT compiler questo problema non dovrebbe mai accadere, perchè il JIT compiler non esegue l'inlining dei metodi che contengono un loop, ma per il futuro non si puo' mai dire, e questo attributo ci mette al riparo da sorprese. Per lo stesso motivo, non si dovrebbe chiamare IsRecursive da un metodo che potrebbe essere ottimizzato mediante inlining.
Per chi non lo sapesse, l'inlining è una tecnica di ottimizzazione grazie alla quale il JIT-compiler riesce a spostare il codice di un metodo direttamente nel codice del metodo chiamante, in modo da evitare una istruzione di chiamata. Solo metodi molto piccoli, 32 byte di IL o meno, che non hanno cicli o loop, e che non accettano dei value type come argomenti possono essere ottimizzati in questo modo, nella versione attuale del JIT compiler, quindi nella pratica l'inlining avviene in pochi casi, tipicamente per le proprietà che wrappano una variabile e per i metodi con al massimo 2-3 righe di codice.
di Giuseppe Dimauro
J
Problema: docking di controlli .NET direttamente nella client area di Outlook con VSTO per Visual Studio 2005
Il file contenente il codice sorgente: HtmlViewModified.zip (256.37 KB)
Impegnato per conto di un cliente che sta ricrivendo una serie di applicazioni direttamente dentro Office System con .NET 2.0 e VS2005 – e Outlook 2003 in particolare - mi sono imbattuto in un “piccolissimo” problema non di poco conto. Benche’ con gli altri prodotti di Office sia particolarmente semplice mostrare e usare form di ogni genere preparate in .NET, con Outlook 2003 ci siamo subito imbattuti in una serie di limiti che inizialmente sembravano insuperabili e decisamente antipatici. Per la precisione, a prima vista sembra che sia impossibile “dockare” le proprie form .NET all’interno dell’area client principale di lavoro di Outlook (l’area della inbox per intenderci) per scrivere applicazioni di produttivita’ utilizzando .NET, direttamente dentro Outlook. Pero’ ... con un po’ di fortuna e di intuito posso ora dire che l’empasse e’ durata relativamente poco. Infatti scaricato il preziosissimo esempio HtmlView disponibile sul sito di MSDN, che mostra come fare un po’ di report con del HTML plasmato via .NET, l’idea di ospitare le form (controlli windows form) .NET all’interno del HTML viewer (e’ il controllo del browser IE embedded dentro outlook) si e’ fatta subito strada. Devo ammettere che la mia bassissima conoscenza di HTML (che proprio non mi entra in testa - che ci volete fare) e’ stata, alla fine, la parte piu’ complessa del tutto. Per il resto il lavoro di inserimento delle form (controlli .NET) dentro Outlook e’ stato tutto in discesa e senza troppi problemi o intoppi. Descrivo brevemente per punti una serie di considerazioni e consigli da seguire per studiare il materiale allegato che mostra la soluzione del problema:
- aggiornate la connection string nel .config che fa una piccola query sul db northwind per mostrare un diagramma a torte dentro un report embedded nella form schiacciando uno dei due pulsanti che troverete in testa alla vista dentro Outlook dopo aver lanciato l’applicazione e selezionata la cartella HtmlView figlia della cartella Inbox.
- la classe BaseActiveXContainer (che ho copiato da qualche parte e modificato – l’autore mi perdoni per questo) contiene del pratico codice per registrare i controlli .net come componenti COM
- Generate tutte le volte un nuovo GUID per ogni nuovo controllo. Non usate la tecnica CCP (Cut Copy e Paste J - Copyright del mio amico Pierpaolo R.) con gli occhi bendati. Brutte sorprese vi aspettano.
- Nel file AssemblyInfo.cs (aggiunto a manina perche’ in disuso con vs2005 – decideremo una best practice spero tra pochissimo ...) ho inserito degli attributi assembly wide per evitare problemi di security per caricare dll che non sopportano di essere chiamate da assembly “partially trusted”.Senza, molte LoadAssembly dinamiche rischiano di dare parecchi problemi.
[assembly: AllowPartiallyTrustedCallers]
[assembly: FileIOPermission(SecurityAction.RequestMinimum, Unrestricted = true)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, Execution = true)]
[assembly: RegistryPermission(SecurityAction.RequestMinimum, Unrestricted = true)]
[assembly: UIPermission(SecurityAction.RequestMinimum, Unrestricted = true)]
- In testa alla classe HtmlViewContainer trovate una serie di attributi che vi permetteranno di riesporre attravers COM i vostri controlli .NET da “agganciare” dentro Office. La stessa classe contiene una serie di proprieta’ statiche che espongono globalmente nella applicazione i puntatori alle istanze di alcuni oggetti importanti di Outlook per interoperare con lo stesso con interfacce duali COM per supportare Automation.
- Il file “.htm” del progetto allegato mostra come invocare metodi dell’oggetto ospitato dal HTML, che ospita il controllo .NET nella pagina, via jscript e automation.
- Preparatevi mentalmente ad usare caspol o l'utility presente negli administrative tools per dare full trust ad eventuali DLL esterne bisognose di essere trattate in tal modo. Sappiate che il vostro assembly dentro la sandbox di Outlook e partially trusted. Vi consiglio di firmare digitalmente le vostre DLL e fare full trust su machine con le strong name degli assembly o direttamente un certificato buono.
- altre ed eventuali ... prossimamente :) stay tuned
Una piccola schermata che mostra quello che si puo' fare dentro Outlook con .NET e VSTO per VS2005 completato in treno mentre tornavo a casa :). Nel codice trovate tutto quello che serve per far parlare il vostro controllo con Outlook dall'interno dell'explorer html di Outlook.

Enjoy !
Sto lavorando su dei proof of concept per Microsoft per Office Word ed Excel avanzatissimi. Spero di condividere al piu' presto con voi il materiale che sto producendo.
Giuseppe
di Francesco BalenaSto correggendo il capitolo dedicato al namespace My, e sto aggiungendo alcuni dettagli che avevo tralasciato nella prima stesura, come la creazione di custom setting provider (per salvare i settings su altri medium oltre al classico file di configurazione, ad esempio un database) e la possibilità di eseguire il binding delle proprietà di un controllo con le impostazioni utente. I custom setting provider sono abbastanza complessi e interessano un numero relativamente ristretto di programmatori, mentre il binding dei setting è un argomento decisamente molto più semplice e che non mancherà di suscitare interesse di chiunque lavori con i Windows Forms. Insomma, l'argomento ideale per un post sul blog di dotnet2themax.
Ho trovato molti articoli e post sull'oggetto My.Settings di Visual Basic 2005 (nonchè dell'omologo Settings di C#), ma quasi tutti omettono di sottolineare il fatto che è possibile eseguire il binding di uno user setting con una proprietà di un form, incluso le proprietà Size e Location. Questa feature permette - tra le tante cose - di ritrovare ciascun form della applicazione nella posizione e nella forma in cui l'utente lo aveva lasciato la volta precedente (nella stessa sessione di lavoro o in una sessione precedente). L'infrastruttura di .NET 2.0 si prende carico di assegnare la proprietà quando il form viene caricato e di salvarla nel file di configurazione quando la proprietà è modificata.

Figura 1: Il designer di Visual Studio 2005 permette di definire settings a livello utente e applicazione.
|

Figura 2: Eseguire il binding di una proprietà con una impostazione utente.
|
Il bello è che non dovete scrivere neanche mezza riga di codice. Infatti, è sufficiente definire le proprietà in questione nella pagina Settings del designer My Project (Visual Basic) oppure Properties (C#), ad esempio MainFormLocation e MainFormSize (Figura 1); è essenziale che lo scope di queste impostazioni sia User, poichè le impostazioni di tipo Application non sono modificabili. A questo punto potete selezionare il form, passare alla finestra Properties, aprire la sezione (Application Settings), cliccare sulla freccia accanto al nome delle proprietà ClientSize e Location, e selezionare lo user setting da mettere in binding. Se non avete ancora create il setting in questione, basta cliccare sull'elemento New. (Figure 2)
Come ho già accennato, il dettaglio notevole è che il valore di queste impostazioni viene aggiornato automaticamente quando spostate e ridimensionate il form. Ovviamente si possono mettere in binding altre proprietà, ad esempio Text, BackColor, ecc. Facendo la stessa cosa per tutti i form della applicazione abbiamo a disposizione un meccanismo di persistenza delle impostazioni dei form semplice e potente, ancora una volta senza scrivere codice!
Ovviamente è possibile fare lo stesso con le proprietà dei singoli controlli. Non tutte le proprietà sono in grado però di notificare a .NET il fatto che sono state modificate. Perchè ciò avvenga il controllo deve implementare l'interfaccia IBindableComponent e deve implementare un evento XxxxChanged per la singola proprietà, oppure implementare la nuova interface INotifyPropertyChanged. La maggior parte dei controlli Windows Forms implementano queste interfacce, ma non tutti. Ad esempio, il controllo ToolStripItem non la implementa. In questo caso la proprietà sarà impostata correttamente quando il form viene mostrato, ma il valore del setting deve essere aggiornato via codice. |
di Giuseppe DimauroAlcuni sample recuperabili via Internet per provare e studiare VSTO (Visual Studio Tools for Office - pronunciato VISTO) per Outlook potrebbero dare problemi di compilazione. Io ho scaricato ad esempio questi sample da msdn.microsoft.com e ho dovuto apportare le seguenti modifiche nell'evento di startup utilizzando il metodo protetto GetPrimaryControl per sistemare il tutto:
Prima della modifica:
private void ThisApplication_Startup(object sender, System.EventArgs e) { // Initialize the event tracker object. _eventTracker = new EventTracker(this); // Create custom menu items. CreateMenu(); }
Dopo la modifica, utilizzando il metodo GetPrimaryControl:
private void ThisApplication_Startup(object sender, System.EventArgs e) { // Initialize the event tracker object. _eventTracker = new EventTracker(this.GetPrimaryControl()); // Create custom menu items. CreateMenu(); }
Vi consiglio vivamente di dare una occhiata a questa tecnologia perchè i risultati sono veramente notevoli. Ovviamente le stesse modifiche valgono anche per gli esempi in VB.
di Fabio CarucciEro partito, tempo fa, con un post in cui parlavo di grafi navigazionali inseriti in un file xml e dati "in pasto" a UIPAB. La mia intenzione era di far partire una possibile discussione sulla base della mia esperienza (e leggere la vostra) in fatto di disaccoppiamento della UI dal resto dell'applicazione, poi ho pensato che sarebbe stato meglio scrivere qualcosa di più sostanzioso per dare una base più consistente di riflessione. L'articolo è online e lo trovate al seguente link
Link all'articolo "UIPAB2 e l'arte di disaccoppiare le GUI"
mi piacerebbe sapere quanti di voi utilizzano questo application block, se qualcuno lo ha customizzato aggiungendo delle features utili e tutto quello che vi viene in mente rispetto all'argomento. Ritengo che non sia affatto banale progettare uno strato di presentation "realmente" disaccoppiato dagli altri strati quindi mi interessa conoscere le problematiche incontrate (eventualmente superate) e dare, se possibile, qualche suggerimento in proposito, oltre naturalmente a ciò che ho scritto nell'articolo sperando che vi sia utile.
Buona lettura!
di Francesco BalenaDiversi mesi fa mandai a Visual Studio Magazine un articolo su come usare custom attribute, form inheritance, e reflection per creare una infrastruttura per creare applicazioni WinForm estendibili mediante plug-in. Solo ora, con qualche mese di ritardo, mi accorgo che l'articolo è stato pubblicato online sul sito della rivista. Per leggere il testo completo occorre registrarsi, ma la registrazione è gratuita.
Il progetto di esempio include la libreria di estensibilità, una applicazione di esempio, e un piccolo plug-in di esempio. Per scrivere una applicazione estendibile basta ereditare i propri form dalla classe ExtensibileFormBase e, opzionalmente, visualizzare i form con il metodo statico FormExtenderManager.CreateForm. Il framework fornito a corredo permette di scrivere DLL che modificano a piacere i form usati dalla applicazionei principale (usando direttamente il designer di VS.NET) o addirittura di sostituire i form presenti della applicazione principale con form completamente differenti. E' un esempio piccolo ma completo (e abbastanza convincente) di quello che si può ottenere con i custom attribute.
di Marco BellinasoO almeno questo è il titolo di questa pagina su MSDN In realtà per ora io ne ho contate 47 di applicazioni di esempio, ma forse ne ne sono perse 3 perchè in fondo alla pagina c'è scritto "Coming soon - The remaining 51 samples are coming soon. Stay tuned." 
Ma non andiamo a pignolare, anche quello che c'è già è decisamente benvenuto! Le applicazioni sono fornite in C# o VB.NET e sono divise nelle seguenti categorie:
- Base Class Libraries: come cambiare le ACL di un file, implementare animazioni su console, scaricare file da FTP, comprimere e decomprimere file, usare le generic collections, ricavare informazioni sui drive, usare la classe Stopwatch, eseguire il PING e qualche altra operazione di networking.
- Data Access: query asincrone, uso e confronto di Datareader con DataSet, update batch, paginazione, bulk update, salvataggio e recupero di immagini dal DB, classi factory, uso di MARS, Notification Services, e UDT di SQL Server 2005.
- Web Development: master pages, API e controlli di membership, profili, controlli menu e TreeView, web part, data binding con i vari componenti xxxDataSource.
- Windows Forms: task asincroni, componenti BindingNavigator e BindingSource, estensioni alla DataGridView standard, i nuovi controlli MaskedTextBox, WebBrowser, StatusStrip, ToolStrip, SplitContainer e LayoutPanel.
Insomma, se ancora non avete dato un'occhiata a .NET 2.0 e VS.NET 2005, questi esempi pronti per essere eseguiti sono un'ottima occasione per vedere velocemente alcune delle novità introdotte. Complimenti per l'iniziativa, e come dice la pagina stessa: tenete d'occhio la pagina per gli ulteriori 51 (o 54?) esempi che hanno già promesso.
di Giuseppe DimauroUn'altra versione aggiornata a .NET 2.0 della straordinaria tecnologia User Interface Application Block e' stata rilasciata. In passato era più che altro un buon esempio ed un ottimo punto di partenza per organizzare un po' più seriosamente i propri progetti e applicazioni di bassa e media complessità avvalendosi di tecniche di organizzazione di grafi navigazionali esterni alla propria applicazione (per qualsiasi approfondimento "in lingua" leggete gli articoli pubblicati su dotnet2themax da Fabio Carucci). Questa volta, a quanto pare, la soluzione proposta sembra decisamente più impegnativa ed elaborata. Infatti il suo nome, in questa rinnovata versione, è Composite UI Application Block ed è indirizzata principalmente a problematiche di tipo Windows Based (la precedente prevedeva anche il web) di una certa complessità. I requisiti minimi sono VB.NET o C#, .NET Framework 2.0 e ovviamente Windows Forms. L'architettura e gli oggetti disponibili sono aumentati arricchendosi di una serie di building block pronti per essere utilizzati in applicazioni reali tanto che esistono già dei prodotti commerciali che ne fanno largamente uso (http://www.microsoft.com/serviceproviders/solutions/ccf.mspx).

Sono sicuro che su questo stesso sito vedrete apparire presto articoli in lingua italiana. Come Code Architects siamo già da tempo attivi su questo fronte includendo spesso questa tecnologia nei nostri piani (abbiamo prodotto anche applicazioni webform over http con questa tecnologia), prototipi, consulenze e quant'altro. Per approfondimenti, nel frattempo, vi consiglio di seguire il seguente link per la Community Technical Preview ufficiale MSDN: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpag2/html/CABCTP.asp
Enjoy :) Giuseppe Dimauro
|
|
Feed di Blog2theMax
Cerca nel blog
Archivio
| | Sun | Mon | Tue | Wed | Thu | Fri | Sat | | 28 | 1 | 2 | 3 | 4 | 5 | 6 | | 7 | 8 | 9 | 10 | 11 | 12 | 13 | | 14 | 15 | 16 | 17 | 18 | 19 | 20 | | 21 | 22 | 23 | 24 | 25 | 26 | 27 | | 28 | 29 | 30 | 31 | 1 | 2 | 3 | | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
Categorie
Powered by: newtelligence dasBlog 1.7.5016.2
|