Come strutturare correttamente un progetto in winform?

23

Qualche tempo fa ho iniziato a creare un'applicazione Winform e in quel momento era piccola e non ho pensato a come strutturare il progetto.

Da allora ho aggiunto funzionalità aggiuntive come avevo bisogno e la cartella del progetto sta diventando sempre più grande e ora penso che sia il momento di strutturare il progetto in qualche modo, ma non sono sicuro di quale sia il modo corretto, quindi ho alcune domande.

Come ristrutturare correttamente la cartella del progetto?

Al momento sto pensando a qualcosa del genere:

  • Crea cartella per moduli
  • Crea cartella per le classi di utilità
  • Crea cartella per le classi che contengono solo dati

Qual è la convenzione di denominazione quando si aggiungono le classi?

Devo anche rinominare le classi in modo che la loro funzionalità possa essere identificata semplicemente guardando il loro nome? Ad esempio, rinominando tutte le classi di moduli, in modo che il loro nome termini con Form . O non è necessario se vengono create cartelle speciali per loro?

Che cosa fare, in modo che non tutto il codice per il modulo principale finisca in Form1.cs

Un altro problema che ho riscontrato è che la forma principale sta diventando sempre più massiccia con ogni funzionalità che aggiungo, il file di codice (Form1.cs) sta diventando molto grande. Ho ad esempio un TabControl e ogni scheda ha un mucchio di controlli e tutto il codice finito in Form1.cs. Come evitare questo?

Inoltre, conosci articoli o libri che trattano questi problemi?

    
posta user850010 14.04.2012 - 09:39
fonte

5 risposte

20

Sembra che tu ti sia imbattuto in alcuni dei problemi più comuni, ma non preoccuparti, possono essere risolti:)

Per prima cosa devi guardare la tua applicazione in modo leggermente diverso e iniziare a scomporla in blocchi. Possiamo dividere i pezzi in due direzioni. Per prima cosa possiamo separare la logica di controllo (le regole aziendali, il codice di accesso ai dati, il codice dei diritti degli utenti, tutto quel genere di cose) dal codice dell'interfaccia utente. Secondo, possiamo suddividere il codice dell'interfaccia utente in blocchi.

Quindi faremo l'ultima parte prima, rompendo l'interfaccia utente in blocchi. Il modo più semplice per farlo è quello di avere un singolo modulo host su cui componi la tua interfaccia utente con usercontrols. Ogni controllo utente sarà responsabile di una regione del modulo. Immagina quindi che la tua applicazione abbia un elenco di utenti e quando fai clic su un utente, una casella di testo sotto viene riempita con i loro dettagli. Potresti avere un controllo utente che gestisce la visualizzazione dell'elenco utenti e un secondo che gestisce la visualizzazione dei dettagli dell'utente.

Il vero trucco qui è come gestisci la comunicazione tra i controlli. Non vuoi che 30 controlli utente nel modulo tengano tutti i riferimenti a caso e chiamino metodi su di essi.

Quindi crei un'interfaccia per ogni controllo. L'interfaccia contiene le operazioni che il controllo accetterà e gli eventi che solleverà. Quando pensi a questa app, non ti importa se la lista di caselle di elenco cambia, sei interessato al fatto che un nuovo utente è cambiato.

Quindi, usando la nostra app di esempio, la prima interfaccia per il controllo che ospita la listbox di utenti includerebbe un evento chiamato UserChanged che passa un oggetto utente all'esterno.

Questo è fantastico perché ora se ti annoi della listbox e vuoi un controllo magico occhio zoomy 3D, basta codificarlo sulla stessa interfaccia e collegarlo:)

Ok, quindi parte seconda, separando la logica dell'interfaccia utente dalla logica del dominio. Bene, questo è un percorso logoro e ti consiglio di guardare lo schema MVP qui. È davvero semplice.

Ogni controllo ora è chiamato View (V in MVP) e abbiamo già coperto la maggior parte di ciò che è necessario sopra. In questo caso, il controllo e un'interfaccia per esso.

Tutto quello che stiamo aggiungendo è il modello e il presentatore.

Il modello contiene la logica che gestisce lo stato dell'applicazione. Conoscendo le cose, andrebbe nel database per ottenere gli utenti, scrivere nel database quando si aggiunge un utente e così via. L'idea è che puoi testare tutto questo in completo isolamento da tutto il resto.

Il presentatore è un po 'più complicato da spiegare. È una classe che si trova tra il modello e la vista. Viene creato dalla vista e la vista passa al presentatore tramite l'interfaccia che abbiamo discusso in precedenza.

Il presentatore non deve avere la propria interfaccia, ma mi piace crearne uno comunque. Rende ciò che vuoi che il presentatore faccia esplicito.

Quindi il relatore esporrà metodi come ListOfAllUsers che la vista userebbe per ottenere il suo elenco di utenti, in alternativa, potresti inserire un metodo AddUser nella vista e chiamarlo dal presentatore. Preferisco quest'ultimo. In questo modo il presentatore può aggiungere un utente alla listbox quando vuole.

Il Presenter avrebbe anche proprietà come CanEditUser, che restituiranno true se l'utente selezionato può essere modificato. La vista quindi interrogherà ogni volta che ha bisogno di sapere. Potresti volere quelli modificabili in nero e leggerne solo quelli in grigio. Tecnicamente questa è una decisione per la Vista, poiché è incentrata sull'interfaccia utente, indipendentemente dal fatto che l'utente sia modificabile, in primo luogo per il Presenter. Il presentatore sa perché parla con il Modello.

Quindi, in sintesi, usa MVP. Microsoft fornisce qualcosa chiamato SCSF (Smart Client Software Factory) che utilizza MVP nel modo in cui ho descritto. Fa anche molte altre cose. È piuttosto complesso e non mi piace il modo in cui fanno tutto, ma può essere d'aiuto.

    
risposta data 14.04.2012 - 12:08
fonte
8

Personalmente preferisco separare diverse aree di interesse tra diversi assembly invece di raggruppare tutto insieme in un singolo eseguibile.

In genere, preferisco mantenere una quantità minima di codice nel punto di ingresso dell'applicazione: nessuna logica aziendale, nessun codice GUI e nessun accesso ai dati (database / accesso ai file / connessioni di rete / ecc.); Solitamente limito il codice del punto di ingresso (ad esempio l'eseguibile) a qualcosa sulla falsariga di

  • Creazione e inizializzazione dei vari componenti dell'applicazione da tutti gli assiemi dipendenti
  • Configurazione di qualsiasi componente di terze parti da cui dipende l'intera applicazione (ad esempio Log4Net per l'output diagnostico)
  • anche io probabilmente includerò un bit di codice "catch all exceptions e record the stack trace" nella funzione principale che aiuterà a registrare le circostanze di eventuali errori critici / fatali imprevisti.

Per quanto riguarda i componenti dell'applicazione stessi, di solito obiettivo almeno tre in una piccola applicazione

  • Livello di accesso ai dati (connessioni al database, accesso ai file, ecc.) - a seconda della complessità dei dati persistenti / memorizzati utilizzati dall'applicazione, potrebbero esserci molti di questi assembly: probabilmente creerò un assembly separato per gestione del database (Probabilmente anche più assembly se l'interazione con il database comportava qualcosa di complesso - ad esempio se si è stucchevoli con un database mal progettato, potrebbe essere necessario gestire le relazioni DB nel codice, quindi potrebbe avere senso scrivere più moduli per l'inserimento e recupero)

  • Livello logico - la "carne" principale contenente tutte le decisioni e gli algoritmi che fanno funzionare la tua applicazione. Queste decisioni non dovrebbero sapere assolutamente nulla della GUI (chi dice che c'è una GUI?), E non dovrebbero sapere assolutamente nulla sul database (Huh? C'è un database? Perché non un file?). Si spera che uno strato logico ben progettato venga "estratto" e inserito in un'altra applicazione senza dover essere ricompilato. In un'applicazione complicata, potrebbero esserci un sacco di questi assembly logici (perché potresti semplicemente voler strappare 'pezzi' senza trascinare il resto dell'applicazione)

  • Livello di presentazione (ovvero la GUI); In una piccola applicazione, potrebbe esserci un singolo "modulo principale" con un paio di finestre di dialogo che possono essere tutte raggruppate in un unico assembly: in un'applicazione più grande, potrebbero esserci assembly separati per parti funzionali dell'intera GUI. Le classi qui faranno poco più che far funzionare l'interazione dell'utente - sarà poco più di una shell con alcune convalide di input di base, gestendo qualsiasi animazione, ecc. Eventuali eventi / clic sui pulsanti che "fanno qualcosa" verranno inoltrati a il livello logico (quindi il mio livello di presentazione non conterrà rigorosamente alcuna logica applicativa, ma non imporrà nemmeno il codice GUI sul livello logico - quindi anche le barre di avanzamento o altre cose di fantasia siederanno nell'assemblaggio di presentazione / i)

La mia motivazione principale per dividere i livelli di presentazione, logica e dati per separare gli assiemi è questa: ritengo sia preferibile essere in grado di eseguire la logica dell'applicazione principale senza il database o la GUI.

Per dirla in altro modo; se voglio scrivere un altro eseguibile che si comporta esattamente nello stesso modo della tua applicazione, ma usa un'interfaccia a riga di comando o un'interfaccia web; e scambia la memoria del database per l'archiviazione dei file (o magari per un altro tipo di database), quindi posso farlo senza dover toccare la logica principale dell'applicazione - tutto quello che dovrei fare è scrivere un po 'uno strumento da riga di comando e un altro modello di dati, quindi "collega tutto insieme" e sono pronto per andare.

Potresti pensare "Beh, non ho intenzione di voglio per fare tutto ciò, quindi non importa se non posso scambiare queste cose" - il vero punto è che uno dei tratti distintivi di un'applicazione modulare è la capacità di estrarre "blocchi" (senza dover ricomporre nulla) e riutilizzare quei blocchi altrove. Per scrivere codice come questo, in genere ti costringe a pensare a lungo ea fondo sui principi di progettazione: dovrai pensare a scrivere molte più interfacce e riflettere attentamente sui compromessi dei vari principi SOLID (nella stessa come faresti per lo sviluppo comportamentale o TDD)

A volte raggiungere questa separazione da una massa monolitica di codice esistente è un po 'doloroso e richiede un sacco di refactoring accurato - va bene, dovresti essere in grado di farlo in modo incrementale - potresti persino raggiungere un punto in cui ci sono troppi assembly e decidi di tornare indietro nell'altro modo e iniziare a ricomporre le cose ancora insieme (andare troppo lontano nella direzione opposta può avere l'effetto di rendere quegli assemblaggi piuttosto inutili da soli)

    
risposta data 14.04.2012 - 13:09
fonte
3

Come per la struttura delle cartelle, ciò che hai suggerito è OK in generale. Potrebbe essere necessario aggiungere cartelle per le risorse (a volte le persone creano risorse in modo tale che ogni set di risorse sia raggruppato sotto un codice lingua ISO per supportare più lingue), immagini, script di database, preferenze utente (se non trattate come risorse), caratteri , dll esterni, dll locali, ecc.

What is the naming convention when adding classes?

Ovviamente, vuoi separare ogni classe al di fuori del modulo. Suggerirei anche un file per classe (sebbene MS non lo faccia nel codice generato per EF ad esempio).

Molte persone usano nomi brevi significativi al plurale (ad esempio, i clienti). Alcuni ptrfer il nome per essere vicino al nome singolare per la tabella del database corrispondente (se si utilizza il mapping 1-1 tra oggetti e tabelle).

Per le classi di denominazione ci sono molte fonti, per esempio dare un'occhiata a: . Convenzioni di denominazione e standard di programmazione netti - Best practice e / o Linee guida di codifica STOVF-C #

What to do, so that not all the code for main form ends up in Form1.cs

Il codice di supporto dovrebbe andare a una o più classi di helper. Altro codice molto comune ai controlli della GUI come l'applicazione delle preferenze dell'utente a una griglia, potrebbe essere rimosso dal modulo e aggiunto alle classi helper o sottoclassando il controllo in questione e creando lì i metodi necessari.

A causa della natura evento-azione di MS Windows Forms, non c'è nulla che io sappia che possa aiutarti a rimuovere il codice dal modulo principale senza aggiungere ambiguità e sforzo. Tuttavia, MVVM può essere una scelta (in progetti futuri), vedere ad esempio: MVVM per Windows forme .

    
risposta data 14.04.2012 - 11:02
fonte
1

La struttura di un progetto dipende totalmente dal Progetto e dalle sue dimensioni, tuttavia è possibile aggiungere alcune cartelle, ad es.

  • Comune (contenenti le classi, ad esempio le utilità)
  • DataAccess (classi relative all'accesso dei dati utilizzando sql o qualsiasi altro server di database che usi)
  • Stili (se hai qualche file CSS nel tuo progetto)
  • Risorse (ad esempio immagini, file di risorse)
  • WorkFlow (Classi correlate al flusso di lavoro se ne hai)

Non è necessario inserire moduli in alcun tipo di cartella, basta rinominare i moduli di conseguenza. Intendo il suo buon senso nessuno sa quale nome dovrebbe essere le tue forme meglio di te stesso.

La convenzione di denominazione è come se la classe stampasse un messaggio "Hello World", quindi il nome della classe dovrebbe essere qualcosa correlato all'attività e il nome appropriato della classe dovrebbe HelloWorld.cs.

puoi anche creare regioni, ad es.

#region Hello e poi out endregion alla fine.

Puoi creare classi per le schede, sono abbastanza sicuro che puoi, e un'ultima cosa è riutilizzare il tuo codice laddove possibile, la pratica migliore è quella di creare metodi e usarli di nuovo dove necessario.

Libri? ERM.

Non ci sono libri che ti dicono la struttura dei progetti poiché ogni progetto è diverso, impari questo tipo di cose per esperienza.

Spero che sia di aiuto!

    
risposta data 14.04.2012 - 10:43
fonte
1

Considera MVP come opzione perché ti aiuterà a organizzare la logica di presentazione, che è tutto nelle grandi applicazioni aziendali. Nella realtà, la logica delle app risiede principalmente nel database, quindi raramente hai la necessità di scrivere effettivamente un livello aziendale nel codice nativo, lasciandolo solo con la necessità di avere una funzionalità di presentazione ben strutturata.

La struttura simile a MVP si tradurrà in una serie di presentatori, o controller, se preferisci, che si coordineranno tra loro e non si mescoleranno con l'interfaccia utente o il codice di base. Potresti anche utilizzare diverse UI con lo stesso controller.

Infine, le semplici risorse organizzative, il disaccoppiamento dei componenti e la specificazione delle dipendenze con IoC e DI, insieme a un approccio MVP forniranno le chiavi per costruire un sistema che eviti errori e complessità comuni, sia consegnato in tempo e sia aperto alle modifiche .

    
risposta data 21.12.2016 - 19:29
fonte

Leggi altre domande sui tag