View Full Version : [C# 2.0] Refactor su UI
RaouL_BennetH
12-11-2009, 14:01
Ciao a tutti :)
Ho un oggetto di tipo ToolStrip che viene caricato a runtime sui vari forms
con il seguente codice:
ActionMenu m = new ActionMenu();
this.controls.Add(m);
Su questo toolstrip, ci sono i classici pulsanti di nuovo, salva, modifica, etc..
Ora, nel codice che riguarda il toolstrip, scrissi questi metodi:
public void MenuOnLoad()
{
menu.Items[1].Enabled = true;
menu.Items[3].Enabled = false;
menu.Items[7].Enabled = false;
menu.Items[13].Enabled = true;
}
public void MenuOnNewClicked()
{
menu.Items[1].Enabled = false;
menu.Items[3].Enabled = true;
menu.Items[7].Enabled = true;
menu.Items[13].Enabled = false;
}
Ed altri metodi del genere.... (non orribilate per favore :( )
In pratica, questi metodi fanno in modo che quando viene premuto ad esempio il pulsante 'Nuovo', quest'ultimo si disabilita e vengono abilitati i pulsanti di 'Salva' e 'Annulla' (sempre restando in tema di esempio).
Le cose sono simili per gli altri 'stati' del menu.
Per quanto rudimentale e magari stupida come cosa, riesco ad utilizzare il menu agevolmente.
La domanda che vi pongo sperando di trarne nuovi spunti di apprendimento è quindi del tipo 'come fareste voi' :) oppure se avete mai fatto qualcosa del genere che possa essermi di aiuto nel comprendere quali sono le implementazioni più efficaci da utilizzare.
Grazie mille :)
RaouL.
RaouL_BennetH
12-11-2009, 14:37
una prima bozza:
private void SetButtonState(ToolStripButton btn, bool enabled)
{
btn.Enabled = enabled;
}
giangian2
13-11-2009, 08:31
Ciao,
nei miei progetti se devo abilitare/disabilitare dei pulsanti del ToolStrip mi registro sull'evento Application.Idle (http://msdn.microsoft.com/it-it/library/system.windows.forms.application.idle.aspx). Poi nella funzione in base a variabili, stati, etc. attivo o disattivo i pulsanti.
:)
Prova a studiare il pattern
MVC oppure MVP oppure MVVM.
Troverai la riposta che viene data al tuo problema per la gestione logica delle GUI.
RaouL_BennetH
17-11-2009, 12:32
Prova a studiare il pattern
MVC oppure MVP oppure MVVM.
Troverai la riposta che viene data al tuo problema per la gestione logica delle GUI.
Ho iniziato a studiare tutto ciò che concerne questi patterns.
Devo dire però che, per quanto appreso fino ad ora, non sono semplici da
implementare per una persona sola (almeno per me).
In generale, io sono passato dal classico approccio di tipo "faccio tutto nel form", alla separazione (in parte) dei vari aspetti;
Ovvero: creo una libreria dove rappresento le varie entità nel modo più semplice possibile, tipo:
public class Articolo
{
private int articoloId;
private string descrizione;
public int ArticoloId { get; set; }
//etc...
}
Poi, trattandosi spesso di interazione con i db, mi creo dei 'gestori' :
namespace BusinessObject
{
public class BOArticolo : Entity.Articolo
{
private IList articoli;
public IList GetAll()
{
factory = new Factory();
articoli = factory.GetAll<Articolo>();
return articoli;
}
//seguono i vari GetById(), Add(), etc..
}
}
So che si potrebbe benissimo gestire il tutto da un'unica classe, ma, e parlo per me, più separo più mi trovo bene :p
Ora, tornando al discorso UI, sto facendo qualche prova di apprendimento con quei patterns, ma sono ancora lontano dal poterci cavar fuori qualcosa di produttivo.
Ho messo un attimo da parte il discorso del toolstrip, creandomi un progettino del tipo windows control library e, dopo qualche sforbiciata al codice, riesco ad utilizzarlo agevolmente.
Una delle domande che invece vorrei sollevare adesso (in ambito windows form) è:
in presenza di molti oggetti di input (textbox, combobox) etc.. immaginando due scenari:
1) Salvataggio dopo validazione
Oggetto o = new Oggetto();
o.Descrizione = txtDescrizione.Text;
o.Data = userData.Value;
o.Totale = int.Parse(txtTotale.Text);
//etc...
//o anche:
Oggetto o = new Oggetto();
o.GetValori(txtDescrizione.Text, userData.Value, int.Parse(txtTotale.Text), etc...)
factory.Add(o);
2) Dopo aver ottenuto l'oggetto che mi interessa, devo presentarlo a video:
Oggetto o = factory.GetById(1);
txtDescrizione.Text = o.Descrizione;
userData.Value = o.Data;
txtTotale.Text = o.Totale.ToString();
//etc..
Ora la domanda:
Ci sono altri modi per fare queste operazioni?
Se in un programma devo presentare 50 entità diverse, per ciascuna UI di riferimento dovrò fare un lavoro del genere?
Grazie mille :)
RaouL.
tomminno
17-11-2009, 14:38
Prova a studiare il pattern
MVC oppure MVP oppure MVVM.
Troverai la riposta che viene data al tuo problema per la gestione logica delle GUI.
Ecco questo però mi sembra un caso in cui tutta la logica rimane confinata nella GUI.
Con l'MVC un'azione su una interfaccia si ripercuote sul modello tramite il controller, ma qui è tutto puramente relativo alla GUI (o vediamo la GUI come il Modello e la View contemporaneamente).
Inoltre la sostanza non cambierebbe perchè la struttura del codice verrebbe semplicemente spostata dalla GUI al controller.
Alla fine si tratta di gestire una sequenza di stati ben determinati.
RaouL_BennetH
19-11-2009, 00:11
Una delle domande che invece vorrei sollevare adesso (in ambito windows form) è:
in presenza di molti oggetti di input (textbox, combobox) etc.. immaginando due scenari:
1) Salvataggio dopo validazione
Oggetto o = new Oggetto();
o.Descrizione = txtDescrizione.Text;
o.Data = userData.Value;
o.Totale = int.Parse(txtTotale.Text);
//etc...
//o anche:
Oggetto o = new Oggetto();
o.GetValori(txtDescrizione.Text, userData.Value, int.Parse(txtTotale.Text), etc...)
factory.Add(o);
2) Dopo aver ottenuto l'oggetto che mi interessa, devo presentarlo a video:
Oggetto o = factory.GetById(1);
txtDescrizione.Text = o.Descrizione;
userData.Value = o.Data;
txtTotale.Text = o.Totale.ToString();
//etc..
Ora la domanda:
Ci sono altri modi per fare queste operazioni?
Se in un programma devo presentare 50 entità diverse, per ciascuna UI di riferimento dovrò fare un lavoro del genere?
Grazie mille :)
RaouL.
uppettino....
In sostanza, mi/vi chiedevo se c'è modo di creare una sorta di persistenza quando il testo o un valore contenuto in uno dei controlli presenti (textbox, checkbox etc..) cambia .
E' sempre MVP op MVC o MVVM, ovvero il pattern piu' usato in questi casi.
E' testabile, divisibile tra persone diverse, facilmente serializzabile/deserializzabile, la cui logica e' facilmente riusabile anche se si decide di cambiare tutta la gui, che resta solo lo strato di presentazione.
tomminno
19-11-2009, 13:36
E' sempre MVP op MVC o MVVM, ovvero il pattern piu' usato in questi casi.
E' testabile, divisibile tra persone diverse, facilmente serializzabile/deserializzabile, la cui logica e' facilmente riusabile anche se si decide di cambiare tutta la gui, che resta solo lo strato di presentazione.
Scusa ma non capisco, il problema accennato è relativo esclusivamente alla GUI.
E poi anche con un semplice applicativo n-tier puoi cambiare tutta la GUI senza intaccare il resto del programma.
RaouL_BennetH
19-11-2009, 15:42
Allora, ho fatto un piccolo passetto in avanti in merito alla serializzazione.
Attualmente ho scelto la via dell'oggetto: XmlSerializer.
Sono riuscito quindi a serializzare il mio oggetto in un file Xml.
C'è ancora un passaggio che mi sfugge però:
spero di spiegarmi con un esempio:
Oggetto o = new Oggetto();
o.Valore = txt1.Text;
o.Data = datePicker.Value;
o.AltroValore = txtAltro.Text;
Serialize s = new Serialize();
s.Serializza(o);
Le mie domande in questo punto sono:
1) Quando il form viene caricato, ovviamente tutti gli oggetti presenti
hanno un valore nullo (riferendomi ai vari textbox, combo et similia) ; devo serializzarlo nel momento del load del form oppure
dopo aver popolato i vari oggetti per l'input?
2) normalmente, quando salvo i dati dell'input utente in un database, svuoto il form . Come funziona il meccanismo con il quale la serializzazione dovrebbe aiutarmi a gestire in modo automatico il fatto che se richiamo un oggetto, o ne creo uno nuovo, non debba starmi a scrivere chilometri di:
oggetto.x = textBox.Text etcetera ?
Grazie mille :)
RaouL.
RaouL_BennetH
19-11-2009, 16:11
un momento ... ... :wtf:
forse ho capito.
In pratica, una volta che ho serializzato l'oggetto con il relativo metodo, in sostanza io non andrò più ad intervenire settando di volta in volta i valori dei suoi campi: Cioè:
private void Serializza()
{
Oggetto bla = new Oggetto();
bla.BlaBla = txt1.Text;
bla.AsdAsd = txt2.Text;
serializza(bla);
}
Dopodichè, quando devo salvare o modificarne uno già presente, devo prendere quello che mi viene restituito dalla deserializzazione ?
Scusa ma non capisco, il problema accennato è relativo esclusivamente alla GUI.
E poi anche con un semplice applicativo n-tier puoi cambiare tutta la GUI senza intaccare il resto del programma.
Si parla di abilitazione/disabilitazione di elementi esegubili (le entri di un menu), un classico problema efficacemente risolvibile con il pattern Command applicato al pattern MVC.
E la modularita' offerta da MVC (e derivati) e' tale per cui si cambia la GUI, mettendonde un'altra, e anche al logica relativa alla abilitazione/disabilitazione resta salva.
E testabile, che e' un requisito di ordine ancora superiore alla riusabilita'.
tomminno
20-11-2009, 10:53
Si parla di abilitazione/disabilitazione di elementi esegubili (le entri di un menu), un classico problema efficacemente risolvibile con il pattern Command applicato al pattern MVC.
Quello che mi sto chiedendo è come il pattern MVC influsce su qualcosa che è solo parte di una View?
Mi potresti fare un esempio di utilizzo di MVC dove la View (intesa come GUI) è a sua volta un MVC?
E la modularita' offerta da MVC (e derivati) e' tale per cui si cambia la GUI, mettendonde un'altra, e anche al logica relativa alla abilitazione/disabilitazione resta salva.
Questo è impossibile.
La logica di attivazione/disattivazione dipende anche dagli oggetti grafici che utilizzi.
Se cambio gli oggetti grafici cambia completamente il codice e molto probabilmente anche la logica che riguarda la gestione della GUI.
Ad esempio potrei scambiare a livello grafico un click su un pulsante con segnalazione diretta sul Controller con la combinazione pulsante + visualizzazione di un dropdown prima non visibile + selezione elemento dalla dropdown appena visualizzata, sulla selezione eseguo quello che prima veniva eseguito sul solo click del pulsante.
Quindi se prima magari dopo il click su un pulsante dovevo disabilitare altri pulsanti ora magari devo disabilitare elementi della dropdown oppure non renderli visibili o magari la nuova organizzazione grafica mi consente proprio di non disabilitarli.
Cambia completamente la GUI, ma l'azione finale e i dati necessari alla sua esecuzione rimangono gli stessi.
Oppure modifichi il Controller in conseguenza di ogni modifica della View?
E' il Controller a dover dire cosa e come deve essere visualizzato sul View?
Direi proprio di no, altrimenti il Controller sarebbe tarato su una View perdendo di generalità, quindi cambiando la View saresti costretto a riscrivere anche il Controller.
Assurdo.
Ovvero nell'ottica del pattern MVC scrivo un'altra View, alternativa alla precedente, ma non vedo come il pattern possa aiutarmi a scrivere questa nuova View.
E testabile, che e' un requisito di ordine ancora superiore alla riusabilita'.
Anche i test della GUI devono essere completamente riscritti cambiando gli oggetti e le proprietà che devono andare a testare.
Se prima dovevo testare che un pulsante fosse effettivamente disabilitato, ora devo verificare che un valore specifico del checkbox non sia selezionato oppure che non sia proprio presente nella lista.
Cambia proprio la logica della GUI.
Come mi aiuta l'MVC?
Quello che mi sto chiedendo è come il pattern MVC influsce su qualcosa che è solo parte di una View?
Mi potresti fare un esempio di utilizzo di MVC dove la View (intesa come GUI) è a sua volta un MVC?
Non so se ho capito la domanda.
Si puo' modellare un controllo complesso con MVC, a sua volta utilizzato una o piu' volte all'interno di un'altra Vista.
Ma il concetto e' che hai sempre uno strato di uno o piu' controller da una parte, e uno strato di uno o piu' viste (anche i controlli complessi sono viste) dall'altra.
Questo è impossibile.
La logica di attivazione/disattivazione dipende anche dagli oggetti grafici che utilizzi.
La logica di attivazione deve dipendere solo dal modello(i) e dagli stati del controller.
Tutta e solo la logica deve stare nel controller. La vista e' solo la presentazione del controller.
Niente della vista deve essere la sorgente di informzione, ma tutto deve essere demandato (Binding e NotifyPropertyChanged) al controller.
Es: Una checkbox tipicamente sorgente di informazione bool, dovra' andare a scrivere/leggere il proprio contenuto in un opportuno bool del controller, mediante binding.
Nient'altro deve essere legato al bool esposto dalla checkbox.
Nessun legame/iterazione deve essere permesso nel codebehind della vista, tra i vari controlli della vista.
Tutti devono andare a leggere/scrivere i propri dati sul controlo, mediante binding.
Di fatto sull'evoluzione del MVC chiamata MVVM, il codebehind della vista in pratica non esiste. E' vuoto.
E i binding sono descritti in XAML, quindi di codice sulla vista proprio potrebbe non essercene.
L'utilita' sta nel fatto che Modello e Controller possono essere istanziati e funzionare senza bisogno della vista.
Che e' proprio lo scopo per poter raggiungere la testabilita' completa di tutta la logica degli Unit tests, che non possono istanziare finestre per vari motivi.
- Ad esempio perche' il processo di test non e' un STA thread, e quindi non puo' aprire finestre.
- Se crei un STA Thread apposito per creare finestre, allora avrai un'applicazione eseguibile agnostica della quale non potrai andare a leggere/scrivere gli stati del controller, e i test sono una pena (vedi test per GUI sotto)
- Anche in questo caso tutto potrebbe andare male, dato che tipicamente la macchina di test e' una macchina sulla quale girano solo servizi, eventualmente neppure loggata, con il risultato che i test di GUI falliscono perche' non possono essere aperte finestre da nessuno.
Se cambio gli oggetti grafici cambia completamente il codice e molto probabilmente anche la logica che riguarda la gestione della GUI.
Quando si dice "Salvo la logica se devo cambiare la vista", per "cambiare la vista" si intende se cambio il tipo di vista. Magari passo da una Winform a una WPF, oppure ad una WebApp, oppure ad un'applicazione a caratteri DOS.
Non se cambia la logica. Certo che se cambia la logica in MVC dovra' cambiare sia il controller che la vista...
Quello che cambia e' come viene presentato il contesto della vista, che e' parte del controller. Certo che serve uno stato in piu' perche' si e' aggiunto un pulsante in piu', significa che probabilmente o non si era fatta bene l'analisi oppure, poiche' l'abilitazione/disabilitazione ha un significato logico (l'utente puo'/non puo' fare un'operazione), e' proprio questo il caso in cui occorre mutare in primis il controller perche' c'e' uno stato in piu', e poi la presentazione di questo nuovo stato sulla vista. Ma il percorso logico non e' il viceversa. Non e' perche' cambio la vista che allora devo cambiare il controller.
Ad esempio potrei scambiare a livello grafico un click su un pulsante con segnalazione diretta sul Controller con la combinazione pulsante + visualizzazione di un dropdown prima non visibile + selezione elemento dalla dropdown appena visualizzata, sulla selezione eseguo quello che prima veniva eseguito sul solo click del pulsante.
Se hai bisogno di avere un'azione con selezione di un elemento da dropdown con un elemento, mentre prima l'azione era senza parametri, allora significa che stai cambiando la logica, perche' hai un parametro in piu' di prima a concorrere degli stati. Di conseguenza cambi il controller per modellare gli stati, e poi la vista per rappresentare gli input/output della logica. E non il viceversa. Nel senso che devi sentire il bisogno di cambiare la vista proprio perche' hai una parte di logica in piu' del controller da dover presentare.
Quindi se prima magari dopo il click su un pulsante dovevo disabilitare altri pulsanti ora magari devo disabilitare elementi della dropdown oppure non renderli visibili o magari la nuova organizzazione grafica mi consente proprio di non disabilitarli.
Come detto prima, tutti i parametri di abilitazione/disabilitazione, tipicamente mediate Command pattern oppure semplici bool se per elementi che non scatenano esecuzioni, saranno in "Binding" con opportuni bool o Command del controller.
Anche i test della GUI devono essere completamente riscritti etc.
Il pattern MVC serve per rendere testabile tutta e sola la logica, non la GUI.
Ma per testare la GUI occorrera' semplicemente testare i Bindings.
Questo lo si fa tipicamente mediante utility come QTP, che simulano processi tipo
"Apro la finestra, cerco il controllo di nome pippo, ci muovo il mouse sopra, clicco, asserto che succeda questo e quest'altro"
Ma poiche' non e' necessario testare la GUI ma solo i bindings, il compito di QTP (o simili) e' piu' semplice di quando lo si usa per testare un'applicazione intera.
Ed e' anche fattibile, dato che altrimenti ti ritroveresti con un misto di UnitTest + IntegrationTest difficilmente distrivcabile.
Nel senso che se testi un'applicazione completa vecchio stile, ti ritroverai l'applicazione con attaccati i servizi esterni, che magari non sono attivi o testabili. Insomma una pena inestricabile.
Se invece hai adottato MVC potrai testare la GUI con i soli binding, appositamente gettati su una Mock di input/output, che sara' da dove QTP andra' a scrivere o pescare i dati da confrontare per i test. Senza bisogno di scomodare la logica o addirittura i servizi.
Certo che parlarne cosi', senza avere un esempio sottomano, e' abbastanza difficile.
Ma per curiosita', l'hai mai adottato o anche solo studiato?
tomminno
21-11-2009, 11:41
La logica di attivazione deve dipendere solo dal modello(i) e dagli stati del controller.
Scusa ma to ho citato un esempio di come solo cambiando oggetti grafici cambia la logica della View e solo della View.
Metti che hai una serie di pulsanti, ne clicchi uno e ne disabiliti altri, poi cambi e metti un checkbox + dropdown, dove puoi decidere se togliere proprio gli elementi che prima disabilitavi.
Il controller comanderà le 2 View con lo stesso evento, ma le 2 View avranno il loro codice per la gestione di quello stato, che ovviamente dipende da cosa devono visualizzare e come lo devono visualizzare.
Tutta e solo la logica deve stare nel controller. La vista e' solo la presentazione del controller.
Scusa ma come fa il controller a dire allora il pulsante deve essere disabilitato, ma se la View in esame il pulsante non ce l'ha?
Il controller potrà ritornare alla View uno stato del Model, ma poi quali controlli abilitare visualizzare o altro è pura logica della View.
O no?
Niente della vista deve essere la sorgente di informzione, ma tutto deve essere demandato (Binding e NotifyPropertyChanged) al controller.
La vista è per forza sorgente di informazione, le informazioni dell'utente partono dalla View e poi si propagano fino al Model.
Sempre che non sia una view di semplice visualizzazione dati.
Es: Una checkbox tipicamente sorgente di informazione bool, dovra' andare a scrivere/leggere il proprio contenuto in un opportuno bool del controller, mediante binding.
E se una lista di checkbox va a sostituire una serie di pulsanti abilitati disabilitati?
Che fai cambi il controller per adattarsi alla modifica della View?
Nient'altro deve essere legato al bool esposto dalla checkbox.
Ma chi ti dice che io nella vista non decida che bool=false corrisponda a visible=false piuttosto che checked=false o enabled=false?
Deve essere il controller a specificarlo esattamente? E se il controller impone checked=false, ma l'oggetto grafico non ha la proprietà checked?
Il controller mi dirà bool=false, sarà la View a farci quello che deve.
Nessun legame/iterazione deve essere permesso nel codebehind della vista, tra i vari controlli della vista.
Tutti devono andare a leggere/scrivere i propri dati sul controlo, mediante binding.
Quindi modifichi il controller al variare della View?
Ovvero se modifichi la presentazione dei dati, da una visualizzazione statica (tutte le informazioni visibili con stati abilitato/disabilitato) ad informazioni visualizzate dinamicamente (controlli che vengono visualizzati o meno a seconda dello stato) devi cambiare il controller per adattarsi alla mutata View?
Di fatto sull'evoluzione del MVC chiamata MVVM, il codebehind della vista in pratica non esiste. E' vuoto.
E i binding sono descritti in XAML, quindi di codice sulla vista proprio potrebbe non essercene.
Quindi ogni vista ha il suo controller?
E poi parli di XAML, ma XAML è parte della View perchè non può essere sempre e comunque visto come codice? E' xml piuttosto che C# ma la sostanza non cambia.
Se sono in una WebApp me ne faccio di poco di XAML e scrivo il codice della View nella pagina aspx o ascx che sia.
E anche in Asp.Net MVC vado a modificare il codice inline nella pagina.
L'utilita' sta nel fatto che Modello e Controller possono essere istanziati e funzionare senza bisogno della vista.
Ma se mi dici che la vista non deve avere codice, significa che ogni cambiamento sulla vista comporta una modifica al controller, oggetti diversi possono comportare legami differenti con lo stesso dato. E se è il Controller a dover dire abilitato/disabilitato piuttosto che visibile/non visibile devo cambiare anche il Controller quando modifico la View.
Personalmente non avevo interpretato così l'MVC, avevo sempre inteso che la View dovesse comunque occuparsi della gestione della visualizzazione dei dati, comandando o essendo comandata dal controller per la modifica del model, ma mai che il controller dovesse conoscere cosa viene visualizzato e come.
Che e' proprio lo scopo per poter raggiungere la testabilita' completa di tutta la logica degli Unit tests, che non possono istanziare finestre per vari motivi.
- Ad esempio perche' il processo di test non e' un STA thread, e quindi non puo' aprire finestre.
- Se crei un STA Thread apposito per creare finestre, allora avrai un'applicazione eseguibile agnostica della quale non potrai andare a leggere/scrivere gli stati del controller, e i test sono una pena (vedi test per GUI sotto)
- Anche in questo caso tutto potrebbe andare male, dato che tipicamente la macchina di test e' una macchina sulla quale girano solo servizi, eventualmente neppure loggata, con il risultato che i test di GUI falliscono perche' non possono essere aperte finestre da nessuno.
Non necessariamente i test devono essere automatici fino a questo punto. Inoltre puoi sempre avere una macchina con un utente loggato (anche se con sessione bloccata) per l'esecuzione di test sulla GUI.
Altrimenti secondo questa visione sarebbe impossibile effettuare test su interfacce web, e invece si fanno eccome, lo stesso vale per le interfacce tradizionali.
Quando si dice "Salvo la logica se devo cambiare la vista", per "cambiare la vista" si intende se cambio il tipo di vista. Magari passo da una Winform a una WPF, oppure ad una WebApp, oppure ad un'applicazione a caratteri DOS.
Non se cambia la logica. Certo che se cambia la logica in MVC dovra' cambiare sia il controller che la vista...
Scusa ma la visualizzazione del Model, non è detto che sia differente solo a livello tecnico* io posso avere 2 Winform come View dello stesso Model che rispondono agli stessi comandi del Controller, ma che mi visualizzano e gestiscono la visualizzazione in maniera completamente differente, ad esempio una View potrebbe nascondere alcuni dati, che va riscritto il Controller?
*che poi cambiare tecnologia di visualizzazione comporta lo stravolgimento dell'interfaccia quindi secondo quello che dicevi prima devo cambiare il controller perchè è lui a dovermi esplicitare direttamente abilita/disabilita piuttosto che visualizza/non visualizzare.
Se passo da una webapp tradizionale ad una ajax è la view a stabilire cosa è in un ajaxpanel piuttosto che no, hai voglia al controller a dire cambia lo stato di un oggetto che non sta nell'ajax panel, lo stato di quell'oggetto non verrà visualizzato e magari questo è il comportamento voluto per la View.
Quello che cambia e' come viene presentato il contesto della vista, che e' parte del controller. Certo che serve uno stato in piu' perche' si e' aggiunto un pulsante in piu', significa che probabilmente o non si era fatta bene l'analisi oppure, poiche' l'abilitazione/disabilitazione ha un significato logico (l'utente puo'/non puo' fare un'operazione), e' proprio questo il caso in cui occorre mutare in primis il controller perche' c'e' uno stato in piu', e poi la presentazione di questo nuovo stato sulla vista.
Io però non sto aggiungendo logica di gestione, ma solo logica di visualizzazione. I dati e gli eventi su di essi rimangono gli stessi, cambio solo come questi vengono presentati.
Ma il percorso logico non e' il viceversa. Non e' perche' cambio la vista che allora devo cambiare il controller.
Mi hai detto che la Vista non deve contenere codice, se cambio il modo di presentare gli oggetti non potendolo fare nella vista devo farlo nel controller.
Quindi il controller viene ad essere tarato sulla specifica visualizzazione.
Se hai bisogno di avere un'azione con selezione di un elemento da dropdown con un elemento, mentre prima l'azione era senza parametri, allora significa che stai cambiando la logica, perche' hai un parametro in piu' di prima a concorrere degli stati. Di conseguenza cambi il controller per modellare gli stati, e poi la vista per rappresentare gli input/output della logica. E non il viceversa. Nel senso che devi sentire il bisogno di cambiare la vista proprio perche' hai una parte di logica in piu' del controller da dover presentare.
Io invece la vedo solo come una diversa presentazione degli stessi medesimi dati, per me è solo una riorganizzazione grafica, cambio gli stati di presentazione:
se in una pulsantiera ad un click disabilito alcuni pulsanti, poi ne clicco un'altro e cambio di conseguenza gli stati dei pulsanti, io potrei decidere che invece di far vedere tutti i pulsanti, magari li visualizzo come una specie di wizard, dove la prima selezione mi determina un cammino di visualizzazione specifico, in cui faccio apparire solo i comadi ammessi in altra forma rispetto ai predenti pulsanti.
Per me è solo un refactoring della GUI, sinceramente non vedo l'esigenza di cambiare il controller.
Il pattern MVC serve per rendere testabile tutta e sola la logica, non la GUI.
Mah la testabilità ce l'hai anche senza MVC, non è che se non implementi l'MVC un programma è intestabile.
Ma per testare la GUI occorrera' semplicemente testare i Bindings.
Esattemente come in un applicativo tradizionale testeresti gli effetti degli eventi usando un mock sul livello Business.
Nel senso che se testi un'applicazione completa vecchio stile, ti ritroverai l'applicazione con attaccati i servizi esterni, che magari non sono attivi o testabili.
Infatti per questo esistono i mock.
Ma i mock ti servono in ogni caso anche nell'MVC, dopo tutto il model potrà provenire da qualche fonte dati esterna no?
Come carichi il model? O con i test di integrazione o con i mock.
Mi è capitato più volte di dover inserire unit test su applicativi convenzionali, se l'applicativo segue un minimo di ordine è fattibilissimo.
Decisamente da rinunciarci per programmi con "funzioni" di migliaia di righe, con codice replicato decine di volte.
Spesso nasce l'esigenza anche di testare l'intero applicativo perchè magari la fonte dati viene modificata anche in altri contesti all'insaputa del programma, quindi è necessario sapere anche quando il programma smette di funzionare per influssi esterni, i semplici unit test in questo caso non aiutano, il programma risulterebbe sempre funzionante, quando nella realtà non lo è.
Se invece hai adottato MVC potrai testare la GUI con i soli binding, appositamente gettati su una Mock di input/output, che sara' da dove QTP andra' a scrivere o pescare i dati da confrontare per i test. Senza bisogno di scomodare la logica o addirittura i servizi.
Scusa ma questo lo puoi fare con o senza MVC. Non è assolutamente esclusiva di quel pattern. Anche un applicativo n-tier che non rispetta i canoni MVC è testabile esattamente allo stesso modo.
Certo che parlarne cosi', senza avere un esempio sottomano, e' abbastanza difficile.
Ma per curiosita', l'hai mai adottato o anche solo studiato?
Si l'ho adottato diverse volte e il più delle volte mi capita di dover cambiare View mantenendo inalterato Controller e Model.
Ovviamente se la logica è puramente di presentazione io la scrivo nella View, non mi sogno minimamente di modificare il Controller per gestire nel dettaglio gli stati della View, che comporterebbe avere un Controller per ogni View.
Potrei anche aver interpretato male il pattern, se così fosse preferirei il tradizionale n-tier dove quello che riguarda la visualizzazione rimane confinato nella GUI e tutta la logica nel business, che non deve preoccuparsi di come vengono visualizzati i dati.
SAi che non ho capito il tuo scopo in questa discussione?
Stai chiededno informazioni perche' vorresti capire meglio (Anche se dici che l'hai usato, da alcune domande o considerazioni che hai posto ho forti dubbi)
oppure vorresti dire il tuo punto di vista?
E' uno dei pattern piu' complessi, ed e' il piu' usato in ambito GUI.
Chi lo vuole usare lo usi, chi non lo vuole usare non lo usi.
Quando mi venono poste problematiche come quelle di partenza, per me e' naturale risolvere i problemi con il baglio di conoscenze che ho acquisito.
Modi di fare GUI ne ho fatti provati e usati decine. Da GUI fatte con i piedi di programmi stile anni 70 a GUI avanzatissime come quelle che sto usando proprio in questo periodo.
E da quanto ho appreso finora MVC (MVVM nello specifico) e' il piu' efficiente se si devono ottenere diversi requisiti. I soliti di riusabilita', modularita', divisione di compiti tra chi disegna la logica e chi disegna la GUI, etc.
Ma senza esempi pratici e' inutile andare avanti.
tomminno
21-11-2009, 13:29
SAi che non ho capito il tuo scopo in questa discussione?
Stai chiededno informazioni perche' vorresti capire meglio (Anche se dici che l'hai usato, da alcune domande o considerazioni che hai posto ho forti dubbi)
oppure vorresti dire il tuo punto di vista?
No, semplicemente non capisco il tuo.
Ovvero se è corretto il tuo punto di vista io non ho capito niente dell'MVC. Per questo sto cercando di capire.
Ovvero ogni funzionalità della View deve essere mappata 1:1 sul Controller?
Se è così ogni Controller è strettamente legato alla View, altrimenti la View può avere una sua logica di funzionamento che non è sotto diretto controllo del Controller (quello che sostengo io, visto che è quello che mi capita di continuo nel refactoring di GUI mantenendo intatto tutto il resto).
E' uno dei pattern piu' complessi, ed e' il piu' usato in ambito GUI.
Chi lo vuole usare lo usi, chi non lo vuole usare non lo usi.
Quando mi venono poste problematiche come quelle di partenza, per me e' naturale risolvere i problemi con il baglio di conoscenze che ho acquisito.
Quello che stavo cercando di capire era come il Controller potrebbe risolvere un problema che è esclusivamente logica della GUI. Tutto qui.
Modi di fare GUI ne ho fatti provati e usati decine. Da GUI fatte con i piedi di programmi stile anni 70 a GUI avanzatissime come quelle che sto usando proprio in questo periodo.
E da quanto ho appreso finora MVC (MVVM nello specifico) e' il piu' efficiente se si devono ottenere diversi requisiti. I soliti di riusabilita', modularita', divisione di compiti tra chi disegna la logica e chi disegna la GUI, etc.
Appunto quindi la View mantiene un certo grado di indipendenza dal Controller, quindi non capisco come questo possa risolvere un problema ad esclusivo carico della View.
Ma senza esempi pratici e' inutile andare avanti.
Un esempio pratico è anche quello che ha dato origine al topic, ci sono una serie di stati della View.
Come il controller comanda direttamente i singoli elementi grafici? Secondo me non è il suo compito, secondo te invece è il controller a stabilire esattamente le proprietà di ogni singolo elemento grafico.
No, semplicemente non capisco il tuo.
Il mio punto di vista e' quello di dare una possibile risposta a chi ha chiesto un consiglio, per una problematica che data in pasto a MVC "e' proprio la morte sua".
Comunque,
Il concetto principale del MVC e' quello di Binding, ovvero un "amenicolo" che crea un legame:
- ad una via tra controller e vista (Forward binding o one way)
- ad una via tra vista e controller (Backward Binding)
- a due vie (Two ways)
In presenza di un binding forward, ogni volta che cambia la proprieta' nel controller, allora cambiera' automaticamente la proprieta' reltaiva nella vista.
(Tipicamente per segnalare al mondo esterno uno stato o una informazione del controller)
in un binding backward, ogni volta che cambio la proprieta' del contrllo, allora cambiera' automaticamente anche la proprieta' relativa del controller (Tipicamente per dare gli input in pasto al controller)
Un binding a 2 vie fa invece ambo le cose (Tipicamente per quando c'e' una proprieta' che puo' essere precaricata con qualche valore da qualche servizio, ma che puo' pero' poi anche essere cambiata dall'utente)
Ad ogni vista e' associata, in ogni istante, un controller. Il controller e' di un certo tipo specifico, ma questo tipo non e' importante per la vista.
La vista sa che c'e' un "oggetto" che espone determinate caratteristiche.
A seconda che si scelga un MVC implementato mediante interfaccia oppure mediante WeakType, allora la vista si trovera' un'istanza di una interfaccia, oppure semplciemente un object.
MVVM e' WeakType. Per quanto riguarda la vista, il controller e' un semplice "object", che avra' alcune determinate proprieta', accedute internamente dai binding di WPF mediate reflection immagino.
Nel primo caso il binding con questo oggetto verra' fatto esattamente, nel secondo caso avverra' solo per nome, p.es. con una prorieta' del controller chiamata "CanChangeTown"
La vista non deve avere nessuna logica. Potra' avere all'interno di se' solitamente i binding e nient'altro. Compito dei binding e' quello di spostare gli input fatti sull vista, verso il contoller, e viceversa.
Certo, ci sono elementi grafici che fanno parte solo strato di presentazione, e quindi della sola vista,
ad esempio:
- Quando capita un errore, voglio segnalare sulla vista che c'e' questo errore. Il controller espone una stringa "Error". Non appena questa stringa e' diversa da null, allora la vista deve presentare quest'errore come meglio crede.
(E poi tipicamente la presenza di un valore in questa stringa pilota anche altre proprieta' esposte, quelle relativa agli stati, ma non necessariamente)
Potrai effettuare il binding di questa stringa direttamente sulla proprieta' Text di una label, perche' il grafico ha deciso che e' meglio cosi'. E qui non avrai bisogno di alcun codice aggiuntivo.
Oppure potrai presentare questa stringa nel titolo della finestra. E qui non avrai bisogno di alcun codice aggiuntivo, effettuerai il binding sulla proprieta' Text della vista
Oppure potrai presentare questa stringa su un controllone custom che accetta appunto una stringa, e che ciclera' le lettere una ad una presentandole su una porzione di schermo, renderizzando ciascun carattere a mano, diesgnandolo proprio con linee in grande (Drawline). Qui avremo quindi un timer interno, che si occupera' della presentazione, magari un pezzo di codice per disegnare il carattere, etc.
E qui avrai pacchi di codice aggiuntivo relativo alla vista, ma il concetto e' che tutto questo nuovo "Controllone" dovra' poter essere pilotato e "acceso" semplicemente con una stringa. Ovvero cio' che si vuole segnalare all'utente.
"Potresti" avere questo pacco di codice aggiuntivo. Ma tipicamente non l'avrai (vedi fine)
Qualunque cosa tu scelga comunque potresti avere del codice attivo nella vista, ma che non deve utilizzare come input nient'altro che questa stringa, ed eventualmente altre proprieta' che il controller espone.
Ma le abilitazioni/disabilitazioni di controlli di una vista sono tipicamente e quasi sempre tutte informazioni decise a priori, da colore che hanno studiato la logica.
Io ad esempio NON voglio a priori che l'utente possa cambiare il numero di soldi richiesti su un bancomat tra quando li ha digitati, il bancomat ha controllato e quando l'utente magari clicca OK.
Ci sara' un controllo relativo server-side, ma nel disegno della GUI io non voglio neppure permettere all'utente di farlo, e quest'informazione la presentero' a livello di controller.
Il controller avra' qundi una proprieta'
public bool CanChangeMoneyField
che verra' bindata con Forward Binding dalla vista verso la proprieta' che piu' le aggrada (Magari la IsEnabled di una textbox)
Il controller cambiera' il valore di questa proprieta' a suo piacimento, durante l'esecuzione.
In questo modo io potro' testare in 2 momenti diversi
Un primo momento sara' il test della logica.
Ovvero crereo' l'istanza del controller, con le mock dei servizi creo le istanze dei modelli, passo di volta in volta i modelli al controller, piloto le proprieta' del controller che sarebbero in realta' pilotate dai binding della vista (Ad usempio il PIN messo dall'utente del bancomat)
e vado a controllare che le propireta' output del controller siano transite negli stati corretti (ad esempio il CanChangeMoney vada a false quando ci si aspetta)
Non ho creato istanze di vista.
Poi testero' la vista.
Per come e' costruita devo testare solo i Binding.
Per testare i binding daro' in pasto alla vista una versione semplificata del controller, con stesse anolghe proprieta' del controller originale, ma Fake.
Simulero' ad esempio gli input nelle textbox, e andro' a vedere che la proprieta' relativa bindata sul controller abbia assunto io valore corretto.
Simulero' un click del mouse su un pulsante, e andro' a vedere che il comando relativo del controller finto sia stato eseguito.
Non ho usato il controller vero, e il tool di test grafico (come dicevo ad esempio QTP) avra' un compito quasi banale.
Scrivo un testo nella textbox, guardo l'output di 1 o proprieta' del fake controller.
Clicco un pulsante, e vado a vedere cosa e' capitato sul controller (Se si implementa il Command pattern e' addirittura standard e banale)
Niente mock, niente servizi, niente logica. Solo i binding.
Cambio il CanChangeMoneyField del controller Fake, e andro' a vedere se la textbox dei soldi si e' effettivamente disabilitata.
Nell'esempio di cui prima con quel controllone, se quel controllone e' un oggetto davvero complesso, si puo' pensare di modellare anche lui con MVC (Questo chiedevi all'inizio?)
Ovvero a sua volta esso avra' una vista di presentazione e un controller con appunto almeno una proprieta' stringa, magari chiamata "InputString".
Poi il controller di questo controllone esporra' altre proprieta' come "QualcosaDaPresentare", dove di volta in volta verra' piazzata ll'informazione sufficiente per renderizzare la singola lettera che di volta in volta capitera' a schermo, quinid magari una lista di punti-linee, colori, etc.
Ma di nuovo la vista relativa sara' stupida.
E potremo testare questo controllone di nuovo con i due momenti.
Cambio la vista da WPF a WebApp?
Cabmio il modo di rappresentare l'errore da "Mettilo sul titolo" a "Usa il nuovissimo controllone che disegna un carattere alla volta"?
Fa lo stesso. La vista cambia di sicuro, ma la logica del controller e' salva.
^TiGeRShArK^
21-11-2009, 15:25
Imho una buona spiegazione del paradigma M-V-VM è in questo video:
http://blog.lab49.com/archives/2650
Anche se è *lievemente* lunghetto.. :stordita:
cmq sostanzialmente concordo con gugoxx nella sua altrettanto lunga spiegazione. :asd:
tomminno
21-11-2009, 20:45
I
Qualunque cosa tu scelga comunque potresti avere del codice attivo nella vista, ma che non deve utilizzare come input nient'altro che questa stringa, ed eventualmente altre proprieta' che il controller espone.
Perfetto stiamo sostenendo la stessa cosa.
Ma le abilitazioni/disabilitazioni di controlli di una vista sono tipicamente e quasi sempre tutte informazioni decise a priori, da colore che hanno studiato la logica.
Io ad esempio NON voglio a priori che l'utente possa cambiare il numero di soldi richiesti su un bancomat tra quando li ha digitati, il bancomat ha controllato e quando l'utente magari clicca OK.
Metti che il bancomat ti faccia vedere le cifre che puoi prelevare, il controller di darà una lista di valori, poi però sulla View puoi decidere di visualizzarli come meglio credi, ad esempio potresti mostrare le opzioni sopra i 200 Euro dopo la pressione su un pulsante altri, è necessario che il controller gestisca questo evento? Forse si forse no, basta un piccolo refactoring sulla GUI che questo evento non ha più senso di esistere, ergo se cambio view devo cambiare anche il controller. Posso anche mostrarli tutti insieme, oppure paginati mostrandone 4 per pagina se sono molti: devo avere un corrispondete evento sul controller per leggere gli elementi ad ogni click? ecc...
Io dico di no.
Mi sembrano tutte logiche della View che non hanno influenza sul model o il controller.
In questo modo io potro' testare in 2 momenti diversi
Un primo momento sara' il test della logica.
Ovvero crereo' l'istanza del controller, con le mock dei servizi creo le istanze dei modelli, passo di volta in volta i modelli al controller, piloto le proprieta' del controller che sarebbero in realta' pilotate dai binding della vista (Ad usempio il PIN messo dall'utente del bancomat)
e vado a controllare che le propireta' output del controller siano transite negli stati corretti (ad esempio il CanChangeMoney vada a false quando ci si aspetta)
Non ho creato istanze di vista.
Tutti questi test li fai nella stessa identica maniera anche senza MVC.
Niente mock, niente servizi, niente logica.
Basta intendersi, per me quello che hai elencato è comunque un mock del vero controller. Hai sostituito l'oggetto reale con uno che ha un comportamento predeterminato.
Anche perchè devi anche testare cosa succede se il tuo Controller non risponde quello che ti aspetti. Se clicchi e ottieni una eccezione?
Solo i binding.
Testare solo i binding mi pare riduttivo. E la copertura dei test?
Cambio il CanChangeMoneyField del controller Fake, e andro' a vedere se la textbox dei soldi si e' effettivamente disabilitata.
Questo è esattamente un controller mock.
Nell'esempio di cui prima con quel controllone, se quel controllone e' un oggetto davvero complesso, si puo' pensare di modellare anche lui con MVC (Questo chiedevi all'inizio?)
No mi riferivo alla manipolazione diretta degli oggetti grafici, niente che riguardasse il controller.
Cambio la vista da WPF a WebApp?
Cabmio il modo di rappresentare l'errore da "Mettilo sul titolo" a "Usa il nuovissimo controllone che disegna un carattere alla volta"?
Fa lo stesso. La vista cambia di sicuro, ma la logica del controller e' salva.
Quindi la View dovrà avere una buona dose di logica! Passare da WPF a una WebApp comporta un sacco di modifiche che rimangono confinate alla View (eventuale gestione delle Sessioni o ViewState o la loro totale assenza la gestione delle sessioni in memoria su server o su db, caratteristiche del tutto assenti in WPF). O riscrivi il controller.
Puoi sempre mettere un altro pattern MVC sopra quello esistente in modo che il Model di questo nuovo componente sia il Controller precedente, ma il problema si sposta logicamente verso il livello superiore: deve il nuovo Controller essere a conoscenza di Sessioni o ViewState oppure la loro esistenza rimane confinata nella View? Se cambio dall'uno all'altro agisco solo sulla nuova View o mi modifico anche il nuovo Controller?
^TiGeRShArK^
21-11-2009, 21:19
Testare solo i binding mi pare riduttivo. E la copertura dei test?
Testare solo i binding di una View in un M-V-VM implementato bene è l'unica cosa da fare.
Se devi testare altre cose vuol dire che non hai rispettato le guideline di quel paradigma.
Infatti non utilizzando questo paradigma ti ritrovi del codice boiler-plate nel code behind, codice che deve essere testato, ma che ovviamente non può essere testato in maniera separata rispetta alla gui, dunque quando lo testerai non farai uno unit-testing, ma stai sconfinando nell'integration test.
Un'applicazione WPF scritta rispettando al 100% il paradigma M-V-VM è perfettamente funzionante anche con il code-behind totalmente vuoto....
E dire che ancora non lo so usare bene, lo sto imparando solo ora, ma cmq credo che si capisca piuttosto bene il meccanismo dal video che ho linkato o cmq dalle varie spiegazioni online.
Tipo un'altra che mi sembra fatta piuttosto bene che avevo iniziato a leggere è questa:
http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.