Dipendenza circolare fra classi

mercoledì 15 febbraio 2012 - 21.11
Tag Elenco Tags  VB.NET  |  .NET 4.0  |  Windows 7  |  Visual Studio 2010  |  Access (.mdb)

Lucchinet Profilo | Newbie

Ciao a tutti.

Come posso aggirare il problema delle dipendenze circolari fra classi?

Esempio

Form1, Form2, Form3

Dal Form1 chiamo il Form2 e il Form3
Dal Form2 chiamo il form3
Ho la necessità però di dover, dal Form3 aprire il Form2 come ulteriore istanza.

Ogni Form è una libreria di classi in un progetto differente, tutti collegato sotto un'unica soluzione.

Grazie per i suggerimenti.

Vinsent Profilo | Senior Member

Intendi qualcosa tipo:
Dim asd As New Form1 asd.Show()
???

Lucchinet Profilo | Newbie

No,

Dal Form1 chiamo:

dim Fr2 as new classe2.Form2
Fr2.show()

Dal form2 chiamo:

dim Fr3 as new classe3.Form3
Fr3.show()

Dal Form3 ho la necessità di visionare dei dati che visiono nel form2, ma con un'altra istanza diversa da quella che richiama Form3
dal Form3 chiamo:

dim Fr2 as new classe2.Form2
Fr2.show()

Ma non posso, in quanto nel progetto Form3 non posso importare la classe2 a causa della dipendenza circolare.

Per essere più esplicito:
Tralasciando il Form1, ho un form Fatture dove creo o modifico le fatture (form2), al suo interno ho collegato la classe Form3 che è l'elenco delle fatture, da dove però, volendo posso visualizzare o aprire delle fatture, indipendentemente da quello che stavo facendo nel Form2.

Adesso l'ho aggirato così:
Ho creando una classe che scrive un file XML con i comandi che devono essere eseguiti.
Ho creato una classe per leggere il file XML con i comandi e che richiama tutte le finestre che mi interssano (non generando nessun riferimento circolare).
L'unico problema di questo sistema è che non ho un controllo diretto fra il form chiamante e il form chiamato.

Qualcuno ha suggerimenti?

InsettoScoppiettato Profilo | Junior Member

Se postassi un po' di logica probabilmente sarebbe più semplice capire come funziona il sistema, ma basandomi su quanto hai agginto nell'ultimo post, tu stai cercando di aprire una finestra contenente un elenco di Fatture che appartengono ai dati del SW (probabilmente a livello di application), non al form2 tout court (che probailmente non farà nemmeno da wrapper per quei dati).
Io lascerei che sia il form1 (ovvero la scermata principale) ad aprire l'elenco, in ogni caso se lasci il Form2 e il Form3 come processi BackGround nati dal thread del Form1 non avrai grossi problemi di interazione (immagino che il Form3 sia ReadOnly ui dati dell'applicazione).
Se proprio non vuoi rinunciare alla possibilità di non dover cambiare il focus dal Form2 al Form1 per lanciare l'elenco contenuto nel Form3, puoi decidere di legare l'evento CLick del bottone the lancia il Form3 (bottone che appartiene al Form2) ad una routine di callback che richiama le logiche di ShowDIalog() del form3 inserite nella classe Form1).
IN pratica lasci che un evento del Form3 (quello che deve aprire il form3) lanci un evento del Form1 (o meglio la sua logica).
Ciao

Alessandro Parma
Programmazione multipla scoposta con prognosi ancora da definirsi

Lucchinet Profilo | Newbie

Grazie per la risposta.

Vagamente ho intuito cosa mi vuoi dire.
Ti rispiego meglio cosa devo fare così mi dici meglio come volevi fare.

Ho una soluzione in VB2010, al suo interno ho più librerie di classi (DLL).
Gestionale (Libreria di classi)
Fatture (Libreria di classi)
ElencoFatture (Libreria di classi)

Il programma ha questa logica:
Per poter accedere all'elenco fatture, devo passare dal form fatture, dove posso decidere se creare una fattura (stesso form) o caricarla, scegliendola dall'elenco (ElencoFatture) tramite il valore di indice di ritorno dall'eleco stesso verso il form Fatture.

Ma se sto facendo quest'ultima operazione (cercando una fattura) e per visionarne una senza chiudere l'ElencoFatture, come posso fare a richiamare una nuova istanza della classe Fatture, senza cadere nella dipendenza circolare fra classi, e mantenendo un collegamento diretto fra il form chiamante e il form chiamato?

Riferimenti a classi

Gestionale <-- Fatture
Fatture <-- ElencoFatture
Ma se cerco di inserire Nell' ElencoFatture <-- Fatture, si crea una dipendenza circolare.

'Attualmente lo faccio (come ho spiegato nell'altro post) usando un file che viene scritto da una classe apposita e letto da un'altra classe apposita con all'interno tutti i comandi che mi necessitano, ma non ho un collegamento diretto.

Sicuramente mi sono spiegato male!!!

Vinsent Profilo | Senior Member

Da quello che ho potuto capire dai tuoi post e dalla risposta di InsettoScoppiettato l' unica soluzione possibile***, è quella di "rifare" l' intero progetto in modo che le Form servano solo per visualizzare dati e inviare "richieste" mentre l' elaborazione vera e propria la svolge una "classe madre" che si occupa appunto dell' elaborazione e della gestione delle stesse Form.
Ad esempio:
A = classe madre
B C = form
con A puoi aprire B e C e dargli le informazioni da visualizzare
se da B vuoi aprire C, B deve dire ad A di aprire C
se C vuole modificare un dato di B deve comunicarlo ad A che esegue il comando e comunica il cambiamento a B e C.
In pratica le form non devono "parlarsi" tra di loro ma essere coordinate dalla "classe madre".
Penso di essermi spiegato abbastanza male...

***secondo me e senza vedere il codice che hai scritto

Lucchinet Profilo | Newbie

Certo potrebbe essere essere una soluzione percorribile.

Concretamente però come posso strutturare questi lanci di comandi dai form verso il principale?
Durante l'inizializzazione del form secondario devo passare un riferimento del form chiamate, utilizzandolo poi per lanciare i comandi?

Es.

Dal Form principale (FormPR):
...
'crea l'oggetto passando il form principale come riferimento
dim A as new Form1(FormPR)
...
public Function apriForm2(byref Form as Form) as integer
dim B as new Form2(FormPR)
dim Valore = B.Cerca()
return Valore
end function
...

Dal Form A:
...
'tramite il riferimento lancia un comando
dim ValoreDiRitorno = FromPR.apriForm2(FormPR)
...

Una cosa del genere?

InsettoScoppiettato Profilo | Junior Member

Scusami non avevo capito che il codice era VB e io non sono abbastanza esperto.
In ogni caso mi sembra che quanto stai cercando di scrivere in codice VB sia la sequenza logica di quanto vorresti vedere a video, ma credo non sia la strada migliore.
Posta un po' di codice per dare un'idea di quanto stai facendo, altrimenti immaginiamo tutti cose diverse dalla realtà. Ad esempio io mi aspetterei quanto segue (scrivo in C# per comodità ma credo sia comunque abbastanza intellegibile):

Punto 1: i dati vanno segregati in classi che li gestiscono, sia per le letture che per le scritture. As esempio le fatture immagino che siano istanze di una classe del tipo:

using System; using System.Collections.Generic; public class InvoiceItem { string description; float qty; float unitPrice; float totalprice { get { return unitPrice * qty; } } } public class Fattura { //MEMBRI string address; ClientList cliente; List<InvoiceItem> itemList; static int maxID = 0; int invoiceID; public InvoiceValue { get { ..un foreach sui totalPrice di ogni elemento.... e lo ritorni } } public Fattura() { ... quanto serve qui per inizializzare una fattura... invoiceID = maxID++; } } class Program { public static List<Fattura> ListaFatture; ... varie proprietà.... ... il main che richiama il form principale... }



Tali dati andrebbero sencondo me dichiarati come statici nel wrapper del software (che in C# sarebbe la classe Program) in modo che siano raggiungibili da ogni form che tu possa generare.

Punto 2: ogni Form che crei deve contenere solo membri per gestire le funzioni essenziali dell'esperienza utente, non i dati a cui gli algoritmi si applicano. In linea di principio nemmeno gli algoritmi dovrebbero far parte delle classi che derivano da Form.

Punto 3: Ogni form deve poter accedere al database statico, in modo da poter sincronizzare gli accessi e non corrompere i dati. Quando scrivi che per poter accedere all'elenco fatture devi accedere al form3 secondo me fai un errore. I dati devono essere scorporati dal form, anzi devono essere segregati in qualcosa che possa essere gestito indipendentemente dalla vita del Form. (Domanda stupida: se il form non esiste in runtime non esiste nemmeno l'elenco fatture?, mi sembra assurdo...devo aver imaginato male)

Punto 4: Ogni fattura dovrebbe essere indirizzabile in lista tramite un campo identificativo univoco, come per esempio quello che io ho chiamato invoiceID, che è gestito da un contatore statico che sicuramente ti permette di generare id unici, e che viene incrementato direttamente nel costruttore di classe come ti ho indicato sopra. a questo punto qualsiasi form che acccede all'ipotetico metodo

public static Fattura Program.ListaFatture.GetFatturabByID(int requestedID) { foreach (Fattura ft in ListaFatture) { if (ft.InvoiceID == requestedID) return ft; } return null; }

può usare la fattura in lettura, scrittura, elencazione.


Un'ultima nota, che comqune ti sconsiglio: se proprio non voui modificare quanto hai già fatto, allora puoi aggiungere un referrer ad ogni Form seocndario richiamato dal primo form, del tipo

(dentro Form 2 e 3) private Form referrer; .... nella classe program al punto di definizione delle variabili globali metti public static Form linkerToForm1; nel costruttore di FOrm1 metti: Program.linkerToForm1 = this; e nel costruttore di Form2 o di Form3 metti referrer = Program.linkerToForm1

in questa mainera se dichiari i metodi pubblici di form1, potrai richiamarli da dovunque, generando qualcosa is simile ad un callback artigianale.

Ora però ti consiglio di postare un po' di codice davvero, altrimenti fai faticare chi tenta di aiutarti senza probabilmente darti vero valore aggiunto.
Spero di aveti comunque aiutato in qualche modo.

[terza modifica ma di sicuro l'ultima: leggiti questo post in VB per il passaggio variaibili tra form :: http://www.dotnethell.it/forum/messages.aspx?ThreadID=39991.]

Ciao
Alessandro
Alessandro Parma
Programmazione multipla scoposta con prognosi ancora da definirsi

Lucchinet Profilo | Newbie

Forse non stiamo parlando esattamente della stessa cosa.

Tralasciando "classi" o altro accennato fin'ora.

Ho una soluzione con all'interno 42 progetti "Librerie di classi".
Dentro ogni "Libreria di classi", ho uno o più Form che mi eseguono specifiche operazioni sui dati con ADO.NET.

Fino a che in ogni "Libreria di classi" aggiungo dei riferimenti ad altre "Librerie di classi", per poterle chiamare, va tutto bene.

Quando però, ho la necessità di richiamare dei form all'interno di "Librerie di classi", da delle "Librerie di classi" già collegate, il compilatore mi dice giustamente che non posso in quanto si creerebbe un riferimento circolare fra "librerie" e quindi me lo impedisce.

Quindi, come si può aggirare l'ostacolo?

Provo a strutturavi lo schema del progetto.

Soluzione
|--Progetto1 "Libreria di classi" (riferimenti al progetto2 e Progetto3)
| |--FormCLS1
|--Progetto2 "Libreria di classi" (riferimenti al progetto3)
| |--FormCLS2
|--Progetto3 "Libreria di classi" (non posso inserire riferimenti al progetto2 in quanto si creerebbe dipendenza circolare)
| |--FormCLS3
etc.

Nello svolgimento di azioni da parte dell'utente, l'ordine di caricamento dei progetti è:
Progetto1 (principale) --> Progetto2(Fatture) --> Progetto3(Elenco Fatture che ritorna un valore ID per poter aprire il fatture la fattura selezionata nell'elenco ). Mi sembra normale e logico.
Ma se l'utente per qualsiasi motivo, metre visiona l'elenco delle fatture, vuole aprire più fatture (indipendentemente dal form delle Fatture che ha richiamato l'elenco), non può in quanto non si può richiamare Progetto2 dal Progetto3.

Riferendomi ad un post precedente, se io mi portassi "dietro", un diferimento al progetto1 attraverso il progetto2 e il progetto3, dal progetto3, richiamando una funzione, potrebbe crearmi una nuova istanza del Progetto2, caricandomi la fattura che mi interessa giusto?
Problema, ho provato con un progettino vuoto.
Non è possibile creare un oggetto (in modo esplicito) nel progetto3, del progetto1 in quanto non c'è un suo riferimento esplicito tipo:

class FormCLS3 dim FormPrincipale as Progetto1.FormCLS1 end class

e scrivendo invece in modo inplicito

class FormCLS3 dim FormPrincipale as windows.form.form private sub ApriFatturaRemota() FormPrincipale.ApriFattura(ID) End sub End class

il compilatore mi dice che la funzione "ApriFattura() in FormPrincipale non esiste, quindi come potrei utilizzare questo metodo?

Spero di essermi spiegato meglio.

InsettoScoppiettato Profilo | Junior Member

Scusami, ho capito solo ora. TI ripeto, non sono sufficientemente esperto di VB per postarti codice risolutivo, ma ti lascio comunque un paio di soluzioni che proverei io, spero di esserti utile:
1) invece di definire il referrer nel form3 di tipo Form, in modo da caricargli dentro il link al form2, io definirei tale referrer di tipo IApriFattura:

class FormCLS3
dim FormPrincipale as IApriFattura

private sub ApriFatturaRemota()
FormPrincipale.ApriFattura(ID)
End sub
End class



Dove IApriFattura è un'interfaccia che implementa solo una routine pubblica chiamata ApriFattura (poi dovrai solo fare in modo che FOrm2 Implements IApriFattura). Questo funzionerebbe in C# ma non so come vengono implementate le interfacce in VB.... qualcosa del tipo
Interface IApriFattura Method Aprifattura(int _ID) End Interface


2) invece di definire un referrer nel form3 al form2 puoi definire un referrer direttamente alla funzione ApriFattura (un delegato in pratica), e magari a livello globale. Tale referrer verrà caricato con il metodo Aprifattura nel costruttore iniziale del SW o alla prima esecuzione del costruttore del form2, prendendolo dalla libreria CLS2 che deve però esportarlo.

3) condensare insieme i progetti CLS2 e CLS3 in un unico progetto, che mi sembra la soluzione più semplice.

Saluti
Alessandro Parma
Programmazione multipla scoposta con prognosi ancora da definirsi

Lucchinet Profilo | Newbie

Purtroppo e doverosamente, ho dovuto optare per raggruppare tutti i form della soluzione sotto un'unico progetto.

Mi ci è voluto tutto il giorno, ma c'è l'ho fatta!

Adesso fa quello che voglio io!!!

Grazie a tutti per l'interessamento e per l'aiuto.
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-2017
Running on Windows Server 2008 R2 Standard, SQL Server 2012 & ASP.NET 3.5