C'è qualcosa che ho dovuto imparare durante la progettazione di software di visualizzazione di immagini / dati medici in WPF / MVVM:
If your domain model deals with visualization, then your ViewModel has to contain visual information.
Elaborerò.
Facciamo un esempio di un sistema di contabilità. Qui è possibile ottenere tutte le funzionalità dell'applicazione nel modello, ma sarebbe strano per un essere umano gestire tutti i concetti contabili senza visualizzarli.
Inserisci la vista, puramente come aiuto (una vera e propria interfaccia grafica in un senso di interazione uomo-computer), in modo che l'operatore umano possa vedere lo stato delle informazioni sottostanti, che non ha una visuale stesso.
D'altro canto, prendiamo il tuo esempio estremo: un'applicazione il cui molto utile è quello di manipolare le immagini. Cioè, la classe del modello di dominio principale è l'immagine composita, con i suoi livelli e ognuno con i suoi effetti pixel e le maschere e così via.
Un concetto base di MVVM, Data Binding perde totalmente il suo senso qui: come si suppone di avere una proprietà nel ViewModel, che è legata a una proprietà nella View, se proprio la cosa stai rappresentando è già composto da pixel? Perlomeno, suppongo, il tuo code-behind dovrebbe essere molto ricco (invece di inesistente nel purista MVVM by-the-book).
Quello che sto facendo ultimamente (l'attuale applicazione che sto sviluppando ha un plotter in tempo reale personalizzato) è più o meno così:
-
Il mio livello di modello è un modello di dominio, contenente solo le classi concettuali relative al problema generale e le regole in base alle quali gli oggetti cooperano tra loro. Niente di correlato all'applicazione (ovvero, che potrebbe variare tra le diverse applicazioni che lavorano sullo stesso dominio) risiede in questo livello, che comunque non contiene l'infrastruttura WPF. I candidati di buona classe sarebbero CompositeCanvas
, CanvasLayer
, PixelEffect
, Mask
, Tool
, ecc.
-
Il livello ViewModel contiene la logica dell'applicazione, ovvero come l'applicazione corrente sceglie di manipolare gli oggetti del modello di dominio. Qui iniziamo a usare le classi relative a WPF. Ad esempio, molto probabilmente rappresenterei un CompositeCanvas
con un WriteableBitmap
, poiché può essere utilizzato direttamente come proprietà Source
di un controllo Image
tramite il classico binding di dati.
-
Il livello View sarebbe il più stupido possibile. A prescindere dai controlli di input della pelatura (pulsanti della barra degli strumenti, cursori del mouse personalizzati e manipolatori del manipolatore), l'immagine composita effettiva sarebbe composta da uno o più WriteableBitmaps impilati in un Canvas o Grid o ViewBox (o una combinazione di essi).
Una cosa importante da considerare è il modo in cui gli strati interagiscono tra loro:
- Se disponi di livelli indipendenti che non interagiscono tra loro, ovvero un'applicazione meno sofisticata di Photoshop, puoi comporli nella vista, impilando più
Image
i controlli l'uno sull'altro all'interno di una griglia.
- Ora se vuoi implementare le modalità di fusione , potresti preferire un singolo
Image
per rappresentare ogni livello e applica la composizione alla sua BitmapSource, che potrebbe essere un DrawingImage
con un DrawingGroup
contenente più ImageDrawing
, uno per ogni livello. Che trasferiscono il compositing al ViewModel.
- Ora se si desidera controllo completo , in modo da poter applicare sofisticati algoritmi di elaborazione delle immagini, rappresentare l'immagine come array (di doppio, sarebbe una buona cosa), cambiare le modalità colore e fai altre cose intelligenti, allora potresti perfettamente avere i tuoi livelli rappresentati nel modello, e avere qualche operazione per, per esempio, trasferire quell'array a un WriteableBitmap nel ViewModel, che quindi aggiorna automaticamente l'immagine della vista.
È interessante "visualizzare" (il gioco di parole) come, ad esempio, il disegno con una matita levigata e autolivellante funzionerebbe: con lo strumento selezionato, si fa clic sull'immagine e si trascina. È possibile implementare DataContext (ViewModel) come interfaccia, ad esempio ITool
e creare eventi del mouse come i metodi di chiamata MouseMove sul ViewModel. Dovresti quindi memorizzare le coordinate dello spazio di disegno (non lo spazio dello schermo) in una matrice, che potrebbe essere continuamente livellata da un algoritmo definito nel modello. Quindi, puoi rigenerare l'origine dell'immagine (cioè render nel modello), e nel ViewModel hai appena impostato il suo risultato sulla proprietà CanvasSource
, e l'evento modificato della proprietà si aggiornava automaticamente la vista. Questo è molto diverso dalla maggior parte delle demo di disegno del mouse trovate usando WPF.
Spero che questo aiuti!