Elenco Forms in un exe .Net

lunedì 10 marzo 2008 - 17.00

sacsacsac Profilo | Newbie

Secondo voi è possibile con .net caricarsi un eseguibile compilato in .net ed elencare tutti i forms contenuti in esso, anche magari tramite reflection?

Se si, qualcuno potrebbe postarmi una bozza di codice?


Grazie

aiedail92 Profilo | Expert

Certo che sì

Col codice seguente ottieni tutte le classi che derivano, direttamente o indirettamente, da Form; per l'esempio aggiunge le classi a una TreeView (ho commentato il codice nel caso non si capisca qualche passaggio):

//Questa variabile conterrà l'assebmbly //Caricato dal file System.Reflection.Assembly yourAssembly; try { //Prova a caricare l'assembly da un file yourAssembly = Assembly.LoadFile("Nome del file"); } catch (Exception ex) { //Se viene generato un errore mostra un messaggio d'avviso //Con nome dell'errore e messaggio e interrompe la ricerca MessageBox.Show(string.Format( "Si è verificato il seguente errore:\r\n{0}:\r\n{1}", //Il nome dell'eccezione e il messaggio ex.GetType().Name, ex.Message)); return; } //Cancello i nodi della TreeView tuaTreeView.Nodes.Clear(); //Con GetTypes si ottengono tutti i tipi //dichiarati nell'assembly foreach (Type t in yourAssembly.GetTypes()) { //Con IsSubclassOf si ottiene un valore booleano //Che indica se la classe deriva (direttamente o non) //Dal tipo specificato if (t.IsSubclassOf(typeof(Form))) { //Se sì aggiungo il nome della classe //all'elenco dei nodi tuaTreeView.Nodes.Add(t.Name); } }

Naturalmente poi il codice è adattabile per tutte le esigenze

Luca

sacsacsac Profilo | Newbie

Ottimo Luca, funziona alla grande!

... e se volessi anche elencare tutti i controlli contenuti in ogni form?

aiedail92 Profilo | Expert

Per trovare i controlli devi elencare tutti i campi dichiarati nella Form e quindi controllare se il tipo del campo deriva da Control. Estendo l'esempio precedente aggiungendo i controlli come sottonodi del nodo della Form:

//Questa variabile conterrà l'assebmbly caricato dal file System.Reflection.Assembly yourAssembly; try { //Prova a caricare l'assembly da un file yourAssembly = Assembly.LoadFile("Nome del file"); } catch (Exception ex) { //Se viene generato un errore mostra un messaggio d'avviso //Con nome dell'errore e messaggio e interrompe la ricerca MessageBox.Show(string.Format( "Si è verificato il seguente errore:\r\n{0}:\r\n{1}", //Il nome dell'eccezione e il messaggio ex.GetType().Name, ex.Message)); return; } //Cancello i nodi della TreeView tuaTreeView.Nodes.Clear(); //Con GetTypes si ottengono tutti i tipi dichiarati nell'assembly foreach (Type t in yourAssembly.GetTypes()) { //Con IsSubclassOf si ottiene un valore booleano //Che indica se la classe deriva (direttamente o non) //Dal tipo specificato if (t.IsSubclassOf(typeof(Form))) { //Se sì aggiungo il nome della classe all'elenco dei nodi //E memorizzo il nodo per poter aggiungere //eventuali sottonodi TreeNode addedForm = tuaTreeView.Nodes.Add(t.Name); //Faccio il controllo di tutti i campi della Form; //Con GetFields si ottengono i campi pubblici dichiarati foreach (FieldInfo field in t.GetFields()) { //Controllo che il campo sia di un tipo che derivi da Control if (field.FieldType.IsSubclassOf(typeof(Control))) { //Aggiungo il nodo come sottonodo del nodo Form: addedForm.Nodes.Add(field.Name); } } } }

Se invece che ottenere solo i controlli pubblici vuoi ottenere ad esempio anche quelli privati devi passare al metodo GetFields una combinazione di BindingFlags, in questo caso la seguente:

BindingFlags bf = BindingFlags.Public | //Include i membri pubblici BindingFlags.NonPublic | //Include i membri non pubblici BindingFlags.Instance; //Include i membri d'istanza

Luca

sacsacsac Profilo | Newbie

caspita mi sta dando questo errore: (considera che mi carico un eseguibile c:\temp\prova.exe)

in System.Reflection.Module.GetTypesInternal(StackCrawlMark& stackMark)
in System.Reflection.Assembly.GetTypes()
in WinFormCS.Form1.button1_Click(Object sender, EventArgs e) in C:\Documents and Settings\fabbry\Documenti\Visual Studio 2005\Projects\WinFormCS\WinFormCS\Form1.cs:riga 39



quando faccio la gettype, qui:

foreach (Type t in yourAssembly.GetTypes())


come mai?

sacsacsac Profilo | Newbie

ok!

ho invocato Assembly.LoadFrom anzichè Assembly.LoadFile e pare che funziona...

sacsacsac Profilo | Newbie

... visto che ci siamo ...

e se devo estrarre le proprietà dei controlli delle form, ad esempio una property Text di un Button ... ?


...ci sto provando ma non ci riesco a trovare la soluzione ...


Ciao e grazie infinite

aiedail92 Profilo | Expert

OK. Se ti è rimasta la curiosità di sapere a cosa fosse dovuto l'errore , prova a postare maggiori informazioni, come nome dell'eccezione ed eventualmente il messaggio.

Luca

sacsacsac Profilo | Newbie

ok... ti posto lo stacktrace dell'errore, ma non me lo da col LoadFrom:

col .LoadFile:

in System.Reflection.Module.GetTypesInternal(StackCrawlMark& stackMark)
in System.Reflection.Assembly.GetTypes()
in WinFormCS.Form1.button1_Click(Object sender, EventArgs e)


... leggersi gli attributi mi sa che non si puo vero?




aiedail92 Profilo | Expert

Per fare questo lavoro devi procedere in questo modo: dopo aver ottenuto il campo e aver controllato che derivi da Control, devi ottenere il tipo del campo (sempre mediante la proprietà FieldType); a questo punto devi utilizzare il metodo GetProperty sul tipo ottenuto e passare come argomento il nome della proprietà, nel nostro caso "Text". Questo restituisce un'istanza della classe PropertyInfo. Su questa (dopo aver controllato che non sia null) bisogna chiamare il metodo GetValue. Questo metodo richiede di passare come argomento un object: questo deve essere un'istanza valida di un controllo, cosicchè GetValue sappia qual è il controllo del quale ottenere la proprietà. Come secondo argomento bisogna passare invece un'array di object nel caso la proprietà sia indicizzata, ma poichè Text non lo è, bisogna passare null.

Ricapitolando:

//Ottengo la proprietà dal campo specificandone il nome PropertyInfo prop = field.FieldType.GetProperty("Text"); //Controllo che prop non sia null; se lo è //significa che la properietà non esiste (o non è visibile) if (prop != null) { prop.GetValue(/*Un'istanza di un controllo del tipo di field*/, null); }

Questo significa che per ottenere una proprietà devi prima conoscere quale sia il controllo da cui devi prelevarla, e se ciò lo devi fare su un'applicazione esterna (che deve pertanto essere già aperta), devi ottenere i vari controlli, suppongo, tramite le API.

Luca

aiedail92 Profilo | Expert

Tutto si può

Per leggere gli attributi usi la proprietà Attributes del campo, mentre per ottenere gli Attributi Personalizzati (ad esempio un DllImport o un Obsolete) devi utilizzare il metodo GetCustomAttributes, passando come argomenti o solo un valore booleano che indica se cercare gli attributi in tutta la gerarchia ereditaria, oppure anche un Type che indica di effettuare la ricerca solo degli attributi riconducibili a quel tipo.

Luca

sacsacsac Profilo | Newbie

Ciao Luca,
posto qui il codice cosi ti faccio vedere come ho proceduto e comunque mi dà errore nella riga in cui ci sono gli *****:

Il codice sorgente non è stato renderizzato qui
perchè non c'è sufficiente spazio.
Clicca qui per visualizzarlo in una nuova finestra



Questo è l'errore:

in System.Reflection.RuntimeMethodInfo.CheckConsistency(Object target)
in System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
in System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
in System.Reflection.RuntimePropertyInfo.GetValue(Object obj, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
in System.Reflection.RuntimePropertyInfo.GetValue(Object obj, Object[] index)


Ti viene in mente qualcosa?
Ciao e grazie infinite

aiedail92 Profilo | Expert

Ciao

Per analizzare un errore la cosa più semplice è vedere il tipo dell'eccezione e il messaggio. Nel tuo caso viene lanciata un'eccezione di tipo TargetException e il messaggio è "L'oggetto non corrisponde al tipo di destinazione." Questo avviene perchè tu dichiari ctl di tipo Control, mentre probabilmente quando cerchi di ottenere la proprietà da field, questo è di un altro tipo (ad esempio Button, TextBox ecc.) Poichè non esiste una conversione implicita da Control a Button (o TextBox ecc...), viene generata quest'eccezione.

Luca

sacsacsac Profilo | Newbie

ok! Ma mi sto per arrendere ... :)

Ammettiamo il codice sottostante, con l'estrazione di sole Label...
alla prima riga istanzio il tipo Label con lbl e con prop.GetValue tento di estrarre il valore della proprietà Text del controllo Label ... il valore è vuoto e l'ogetto lbl non è valorizzato ...
ma come faccio a trovare il contenuto Text di quella label?
Il codice sorgente non è stato renderizzato qui
perchè non c'è sufficiente spazio.
Clicca qui per visualizzarlo in una nuova finestra

Grazie infinite per la disponibilità ma soprattutto per la tua pazienza.

aiedail92 Profilo | Expert

Il codice dovrebbe essere giusto; per verificare fai così:

Il codice sorgente non è stato renderizzato qui
perchè non c'è sufficiente spazio.
Clicca qui per visualizzarlo in una nuova finestra

Luca

sacsacsac Profilo | Newbie

... ok non sono stato chiaro su ciò che vorrei fare...

L'obiettivo sarebbe questo:
creare un programma che legge un file eseguibile su disco ed elencare in una treeview, tutti i form, tutti i controlli di ogni form e tutte le proprieta Text dei controlli.
Quindi se ho un eseguibile contenente una form1 con un button il cui valore Text è Ok, mi aspetto di avere nella treeview la form, il button ed il suo valore.

Treeview
|
+-->Form1
+------------>Button1 Name="btnOK" Valore="OK"

Grazie a te ho completato al 90% il contenuto della treeview, ma mi manca questa maledetta proprietà TEXT da leggere

... nell'esempio di codice che mi hai postato compare "Prova" perche è valorizzata la proprietà subito dopo l'istanza, ma io quella proprietà devo ricavarla dall'eseguibile ... !!!!

Si può fare?

Spero di essere stato chiaro.
E se mi rispondi di nuovo, sarai per me San Luca!!! :=)

aiedail92 Profilo | Expert

Ciao

Scusa l'attasa, dovevo creare un po' di suspense

Ancora una volta, mi dispiace deluderti, si può fare . In pratica devi prima inizializzare la Form, poi ottenere i valori che possiede ogni singolo controllo dichiarato in essa. Per fare questo lavoro, la Form la dichiariamo e la inizializziamo col metodo CreateInstance di yourAssembly, e poi utilizziamo su field il metodo GetValue, passando come argomento l'istanza della Form ottenuta prima; infine si ottiene il valore utilizzando il metodo GetValue della property passando come argomento il field. Amplio ancora il codice iniziale aggiungendo come sottonodo il Text del controllo:

Il codice sorgente non è stato renderizzato qui
perchè non c'è sufficiente spazio.
Clicca qui per visualizzarlo in una nuova finestra

L'unico problema è che questo metodo vale solo se i controlli del form sono inizializzati con tutti i valori nel costruttore; se invece fossero modificati ad esempio con l'evento Load o Shown, dovresti prima invocare l'evento e poi rieseguire tutto il controllo.

Luca

sacsacsac Profilo | Newbie

Ciao,
infatti aspettavo con ansia una tua risposta ... .... sempre impeccabile, grazie!

A questo punto piuttosto che incocciarmi in questa direzione, meglio spiegarti cosa devo fare, cosi potrai anche consigliarmi.

Penso che sia chiaro che devo sviluppare un pgm che in qualche modo estrae le informazioni ormai note ... questo "coso" deve possibilmente essere standard, nel senso che deve leggere tutti gli eseguibili (.net) e deve descrivere in un file XML quelle informazioni, perchè poi mi serviranno per altri scopi ( ti risparmio la pappardella) ...

Ecco la mia domanda:
se ci sono semplici applicazioni, si può istanziare la form senza problemi e procedere come hai indicato (anche se ci sarebbe anche un'altra soluzione più semplice senza spaccarsi con la reflection, quella di ciclare i controls della form istanziata e poi elaborare i controlli appartenente... etc etc) , ma se poco poco c'è un'applicazione più complessa perchè magari ci sono diverse form che ad esempio hanno come parametro nel costruttore una classe di database, un collegamento indispensabile esterno, magari permessi di accesso alla form, ... la cosa non la vedo fattibile o meglio le forms non le vedo tutte istanziabili facilmente ed in maniera standard.

La soluzione più percorribile in virtù di tutte le difficoltà pocanzi citate, potrebbe essere secondo te quella di leggersi ed elaborare tutti i files *designer.cs/vb ed estrarre forms, controlli e Text value degli stessi?

Attendo con molta ansia un tuo consiglio ... cmq ora è tardi quindi puoi rispondermi con calma, ti ho già stressato abbastanza!!!

Ciao ciao

aiedail92 Profilo | Expert

Dunque, se il Form richiede nel costruttore dei parametri, e possiedi i sorgenti, la strada più semplice potrebbe essere effettivamente quella che tu stesso hai indicato. A questo punto però avere i sorgenti diventa indispensabile, e l'applicazione non godrebbe più di quell' "universalità" che cercavi...

Luca

sacsacsac Profilo | Newbie

Infatti, mannaggia!...

Cmq ho deciso di adottare entrambi le soluzioni ... la prima per la costruzione del file XML e la seconda (quella di scorrersi i file designer*.cs/vb) a completamento delle properties mancanti.


Grazie infinite Luca
Ciao ciao
Partecipa anche tu! Registrati!
Hai bisogno di aiuto ?
Perchè non ti registri subito?

Dopo esserti registrato potrai chiedere
aiuto sul nostro Forum oppure aiutare gli altri

Consulta le Stanze disponibili.

Registrati ora !
Copyright © dotNetHell.it 2002-2024
Running on Windows Server 2008 R2 Standard, SQL Server 2012 & ASP.NET 3.5