AIUTOOOO User control WPF come si fa?

mercoledì 27 febbraio 2013 - 23.09
Tag Elenco Tags  C#  |  .NET 4.0  |  Windows 7  |  Visual Studio 2010

cicndre Profilo | Newbie

Salve a tutti ragazzi!!
Avrei bisogno del vostro aiuto per creare la mia prima applicazione in WPF.
Navigando in rete ho scoperto Dev Express che aggiunge dei tool a Visual Studio come il componente Tile ( questo componente permette di creare delle applicazioni con interfaccia simile ad applicazioni METRO).
Quello che vorrei è, partendo dalla Form principale, cliccare su questo Tile e aprire un nuovo user control all'interno della form principale.

Con C# riesco ad ottenere questo semplicemente creando lo userControl e poi aggiungendolo al Form ma qui non riesco a farlo... quindi vi chiedo una mano!

Inoltre vorrei specificare che non è mia intenzione realizzare un'applicazione web quindi non vorrei utilizzare le NavigationBar

Spero di non aver incasianto troppo il tutto e vi ringrazio anticipatamente per l'aiuto!!


0v3rCl0ck Profilo | Guru

Ciao,

Quindi diciamo che vorresti avere una sorta di navigazione come quella di Windows 8 all'interno della tua applicazione, corretto? Quando faccio click sulla tile devo potere aprire il contenuto/dettaglio a tutto schermo o no e potere poi tornare indietro alla schermata principale, fammi capire se questa è tua intenzione
-------------------------------------------------------
Michael Denny
Lead Software Developer & Solutions Architect
http://blogs.dotnethell.it/Regulator/

cicndre Profilo | Newbie

Ciao!!
Si diciamo che ho intenzione di realizzare un'applicazione che sia del tutto simile ad una di windows 8 però, come detto prima ho un po di difficoltà perchè è la prima volta che mi avvicino al wpf, mentre in c# sono riuscito ad otenere un qualcosa di simile.

Grazie per la tua rispsta... a questo punto aspetto altre notizie

0v3rCl0ck Profilo | Guru

Che versione di visual studio utilizzi?

Hai la possibilità di utilizzare visual studio 2012 anche versione express (gratis)?


-------------------------------------------------------
Michael Denny
Lead Software Developer & Solutions Architect
http://blogs.dotnethell.it/Regulator/

cicndre Profilo | Newbie

Si sto utilizzando visual studio 2012 su windows 7 ed in piu le suite di tools di VS ho integrato quelli di DEV EXPRESS tra i quali anche il tile.

0v3rCl0ck Profilo | Guru

Scusa se ho ritardato un po', ma stavo finendo l'articolo che ho allegato alla risposta...

In teoria ci sono tanti modi per fare quello che dici, dal metodo più a basso livello fino a quello più sosfisticato ed elegante sfruttando le potenzialità di WPF e del suo modello fatto di DependencyProperty con le derivate AttachedProperty, Commanding e Binding, e quindi in pratica, ti direi fin da subito di sfruttare queste potenzialità.

Stringendo a basso livello potresti semplicemente crearti una MainWindow e un UserControl, esattamente come si faceva in windows form, e poi sulla MainWindow inserire un elemento placeholder, anche qui come faresti in windows form se ti serve sapere in che punto preciso iniettare l'usercontrol, oppure semplicemente, nel code behind della MainWindow, crearti l'instanza dell'UserControl e iniettarlo direttamente sulla MainWindow così:

public partial class MainView : Window { public MainView() { InitializeComponent(); var startScreenView = new StartScreenView(); this.AddChild(startScreenView); } }

dove MainView è una semplice Window (WPF) e StartScreenView è un User Control (WPF).

ma.... non ti consiglio per niente questo approccio, non ha senso con le potenzialità offerte da WPF, e gli strumenti che direttamente e indirettamente ci offre per fare un eccezionale UI composition (http://blogs.ugidotnet.org/topics/archive/2009/03/27/ui-composition-thread.start.aspx) o per lo meno una gestione dinamica degli user control, senza avere troppo codice da manutenere, e sopratutto cercando di slegare ogni componente tra di loro, rendendoli autonomi, e facendoli comunicare tra di loro sfruttando un message broker, che semplicisticamente si può definire come un oggetto statico, che può essere utilizzato da qualsiasi parte nell'applicazione, e che permette di lanciare un messaggio all'interno della stessa, poi se c'è qualcuno che è in ascolto di quel messaggio gli verrà recapitato ed eseguirà quello che deve, in questo modo in pratica capisci come una main Window può comunicare con i suoi figli UserControl, senza necessariamente doverli conoscere, si scambiano messaggi attraverso un terzo attore, quindi nel tuo caso una MainView aprirà di default una sorta di StartScreenView, questa view avrà le tile, che quando sono premute, spareranno un messaggio al broker, dichiarando l'intenzione dell'utente nell'aprire un'altra view (app), la MainView già prima si era sottoscritta a quel tipo di messaggio sfruttando il message broker (NON alla view, quindi nessuna dipendenza!), quindi dicevamo la MainView riceve il messaggio, istanzia la vista richiesta dal messaggio (ovviamente la MainView conosce cosa fare con il messaggio!) e la inietta nel placeholder o direttamente come unico figlio della window. Stessa cosa per chiudere una vista e tornare allo StartScreen, ci sarà un messaggio GoToStartScreenMessage, che ogni vista che vuole dare la possibilità all'utente di tornare allo startscreen conosce, e che semplicemente lancierà al broker, alla ricezione del messaggio da parte della MainWindow, la stessa mostrerà di nuovo lo StartScreenView e successivamente metterà in background la precedente vista, esattamente come fa Windows 8, da qui si capisce quanto può essere potente WPF, che ti permette con davvero poche righe di codice, ma ben studiate, di ricreare una parte del sistema operativo di windows 8, dove puoi riuscire tranquillamente a riscriverti i principi di background, foreground e chiusura delle tue sotto viste, esattamente come se fosse applicazioni windows store (metro app), anche se sono dei semplici moduli della tua applicazione, ma vista nell'ottica di tante sotto applicazioni, capisci quanti vantaggi può darti, ti permette di lavorare chiuso in una funzionalità dell'applicazione senza dovere pensare a tutte le iterazioni con le altre, e un altro enorme vantaggio anche nella collaborazione di un team e nella manutenibilità del codice nel tempo, per non parlare della possibilità di fare un software multi-licence, dove regalare la shell (MainWindow), ma fare pagare ogni singolo modulo/app.

ok sono stato super logorroico, ma spero di averti aperto un piccolo mondo.

Ritornando all'approccio che ti consiglierei, lascia perdere il code behind delle window/usercontrol, cerca inizialmente di pensare come se non lo avessi a disposizione (*1), e cerca di studiararti il MVVM (INotifyPropertyChanged), Commanding (ICommand), Dependency property (DependencyProperty, AttacchedProperty) e bene come funziona lo XAML che ti aprirà un vero e proprio mondo, cerca in particolare le keyword: DataContext, Binding, ControlTemplate e DataTemplate (tantissimi concetti li trovi scritti in italiano e bene sul forum di Mauro Servienti http://milestone.topics.it che dopo tutta questa pubblicità almeno mi deve una birra!! e io a lui perchè mi fa comodo :) ). Nel frattempo per avere un applicazione pronta all'utilizzo potresti già pensare di utilizzare un framework MVVM completo come Radical (ce ne sono tanti altri, ma per progetti nuovi io consiglierei questo, non perchè sia più potente o cazzate del genere, semplicemente perchè è stato riscritto da zero prendendo spunto da tutti gli altri framework, cercando di crearne uno più "pulito", per il semplice fatto che è nuovo e non deve sottostare alla richiesta di retrocompatibilità), questi sono i punti per arrivare a quello che chiedevi, poi da li, tanto studio e in bocca al lupo:

- seguire le istruzioni del mio post su radical: http://blogs.dotnethell.it/Regulator/Tutorial-WPF-MVVM-in-un-Minuto-con-Radical__19072.aspx

- aggiungere altri 2 items, all'interno della cartella Presentation, per la shell: StartScreenView.xaml (UserControl) e StartScreenViewModel.cs (Class)

- aggiungere altri 2 items, all'interno della cartella Presentation, di prova per aggiungere un modulo/app: HelloWorldView.xaml (UserControl) e HelloWorldViewModel.cs (Class)

- nella MainView.xaml mettere il ContentPresenter per abilitare la Dynamic UI Composition, ovvero il placeholder della shell su cui iniettare i vari moduli:

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

- nello StartScreenView.xaml mettere una cosa così (puramente a scopo illustrativo):

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

- nello StartScreenViewModel.cs si gestisce semplicemente il comando di OpenView, chiedendo al broker di lanciare un messaggio di tipo OpenHelloWorldViewMessa():

using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Input; using Topics.Radical.ComponentModel.Messaging; using Topics.Radical.Windows.Input; using Topics.Radical.Windows.Presentation; using WpfApplication1.Messaging; namespace WpfApplication1.Presentation { public class StartScreenViewModel : AbstractViewModel { readonly IMessageBroker messageBroker; public ICommand OpenView { get; private set; } public StartScreenViewModel(IMessageBroker messageBroker) { this.messageBroker = messageBroker; this.OpenView = DelegateCommand.Create() .OnExecute(p => OpenViewHandler()); } public void OpenViewHandler() { Debug.WriteLine("OpenViewHandler()"); this.messageBroker.Broadcast(this, new OpenHelloWorldViewMessage()); } } }

- Nella vista HelloWorldView.xaml metti quello che vuoi più il pulsante per tornare allo start screen:

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

- Il view model HelloWorldViewModel.cs è praticamente identico all'altro, solo che cambia il tipo di messaggio lanciato in OpenStartScreenViewMessage():

using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Input; using Topics.Radical.ComponentModel.Messaging; using Topics.Radical.Windows.Input; using Topics.Radical.Windows.Presentation; using WpfApplication1.Messaging; namespace WpfApplication1.Presentation { public class HelloWorldViewModel : AbstractViewModel { readonly IMessageBroker messageBroker; public ICommand OpenStartScreen { get; private set; } public HelloWorldViewModel(IMessageBroker messageBroker) { this.messageBroker = messageBroker; this.OpenStartScreen = DelegateCommand.Create() .OnExecute(p => OpenStartScreenHandler()); } public void OpenStartScreenHandler() { Debug.WriteLine("OpenStartScreenHandler()"); this.messageBroker.Broadcast(this, new OpenStartScreenViewMessage()); } } }

- Aggiungi una cartella Messaging

- Aggiungi le classi per i due messaggi:

OpenHelloWorldViewMessage.cs
OpenStartScreenViewMessage.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using WpfApplication1.Presentation; namespace WpfApplication1.Messaging { public class OpenHelloWorldViewMessage { } } using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace WpfApplication1.Messaging { public class OpenStartScreenViewMessage { } }
in questo caso i messaggi non trasportano nessun'altra informazione con se, ma quasi sempre lo fanno.

- Creare una cartella Handlers all'interno della cartella Messaging (è importantissimo che la struttura sia esattamente come ti sto dicendo, perchè sono delle regole di default che utilizza Radical, che possono anche essere cambiate, ma ovviamente è tutto più veloce se le si segue, come funziona anche per ASP.NET MVC se hai dimestichezza con le regole di routing dei Controller)

- Crea sotto la cartella Handlers la gestione dei messaggi:

ApplicationBootCompletedHandler.cs
Il codice sorgente non è stato renderizzato qui
perchè non c'è sufficiente spazio.
Clicca qui per visualizzarlo in una nuova finestra
Questo è un messaggio di "sistema" che genera Radical non appena ha finito di eseguire il suo startup.

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

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

- Il tutto dovrebbe magicamente funzionare. Ovviamente non c'è niente di magico, ci sono tante righe di codice e strumenti utilizzati in Radical che facilitano il lavoro dello sviluppatore WPF, una cosa che salta subito all'occhio che non esistono punti dove si vedono istanziazioni degli oggeti viewResolver, regionService, e in generale tutte le dipendenze dei vari ViewModel, questo perchè Radical utilizza un framework (Castle) di IoC (inversion of control) per eseguire la DI (dependency injection), e con una serie di regole ben definite, per cui è sembre in grado di mantenere una relazione tra view e viewmodel, e inoltre in ogni punto dell'applicazione dove si necessita di un qualsiasi componente infrastrutturale di Radical, è sufficiente definirlo come dipendenza nella classe stessa, ovviamente se la stessa è attivata dal flusso di Radical-Castle (quindi utilizzando ad esempio il viewResolver).

Ti ho svalangato un sacco di nozioni, dato un esempio pratico, ora spetta a te approfondire, perchè ovviamente c'è troppo da dire per una risposta a un thread.

Probabilmente da questa risposta farò un esempio completo che pubblicherò sul mio blog, riguardante appunto la UI Composition con Radical, ma non ti so dire la data di pubblicazione, collegati all'RSS per rimanere in ascolto.

Ciao!!


*1 - Non voglio dire che il code behind non va mai usato, ma in pratica si utilizza principalmente se si sta creando un vero e proprio CustomControl, che comunque sconsiglio di pensare subito alla creazione di un CustomControl non appena pensiamo non ci sia il controllo che ci serve in quelli di WPF, perchè il 90% dei casi e forse di più, il controllo già esiste nella libreria di WPF, solo che non lo si riesce a vedere, nel senso che, bisogna pensare alla libreria di controlli wpf come solo un punto di partenza, non l'arrivo, l'arrivo che ci danno sono solo 2 schifosi controlli leggermente stilizzati in base al sistema operativo in cui siamo, però la realtà è che il grosso lavoro sta nello sviluppo dei comportamenti dei controlli, cioè un button, può diventare una tile in pochissimo tempo di "sviluppo", in realtà non è neanche sviluppo è styling in xaml, si prende il button, gli si crea un altro template grafico, ma si mantengono tutte le potenzialità dei comportamenti del button che non dobbiamo riscrivere, ad esempio il fatto che ci venga eseguito un Command nel momento in cui premiamo il pulsante! Quindi prima di fare un CustomControl, pensa se davvero hai bisogno di scrivere dei comportamenti che già esistono nei controlli standard, perchè un custom control ha del valore aggiunto se ha dei comportamenti (behaviour) nuovi, non nell'interfaccia grafica!
-------------------------------------------------------
Michael Denny
Lead Software Developer & Solutions Architect
http://blogs.dotnethell.it/Regulator/

cicndre Profilo | Newbie

Ciao 0v3rCl0ck,
ti ringrazio infinitamente per tutte queste informazioni e adesso mi metto a subito a provare il tutto.

Ti ringrazio infinitamente per la tua disponibilità!!!

Grazie ancora e a presto!!

0v3rCl0ck Profilo | Guru

Di niente, ho preso la palla al balzo per creare un paio di post, la community è sempre fonte di ispirazione.

Per favore accetta una delle mie risposte così si chiude il thread, se poi avrai altre domande specifiche apri un altra richiesta.

Ciao!
-------------------------------------------------------
Michael Denny
Lead Software Developer & Solutions Architect
http://blogs.dotnethell.it/Regulator/

0v3rCl0ck Profilo | Guru

Mi sono reso conto che nella risposta prima ho parlato di main view che si sottoscrive al messaggio di cambio vista, invece per non violare il paradigma del MVVM che detta la regola di non legare il view model alla view, la gestione dinamica della shell viene direttamente rilasciata ai message handler, oppure si poteva anche sottoscrivere al broker nel app.xaml.cs ma radical anche in questo caso cerca di aumentare il più possibile il disaccoppiamento e utilizzando parecchie convenzioni ti da la possibilità di creare classi per la gestione di piccoli lavori e che poi lui attiva automaticamente, in questo modo forza lo sviluppatore a concentrarsi sul business della propria applicazione e lo spinge ad avere un ottimo design del codice.
-------------------------------------------------------
Michael Denny
Lead Software Developer & Solutions Architect
http://blogs.dotnethell.it/Regulator/

cicndre Profilo | Newbie

Scusami se ti scoccio ancora ma nel riprodurre il tuo esempio mi sono trovato davanti ad alcuni errori che sono i seguenti:

-All'interno della classe ApplicationBootCompletedHandler : ho un errore sulla riga

Il codice sorgente non è stato renderizzato qui
perchè non c'è sufficiente spazio.
Clicca qui per visualizzarlo in una nuova finestra
ed in particolare ho un errore sul KnowRegion.ShellContentRegion dove mi indica che nel contesto corrente non esiste "KnowRegion".

-Gli altri errori si presentano nelle classi OpenStartScreenViewMessageHandler e OpenHelloWorldViewMessageHandler ma sono tutti relativi alla stessa riga ovvero:

this.regionService.GetKnownRegionManager<MainView>() .GetRegion<IContentRegion>("ShellContentRegion") .Content = this.viewResolver.GetView(StartScreenView);

1) La corrispondenza migliore del metodo di overload per 'Topics.Radical.Windows.Presentation.ComponentModel.IViewResolver.GetView(System.Type)' presenta alcuni argomenti non validi c:\users\andrea\documents\visual studio 2012\Projects\WPF_windows8_Radical\WPF_windows8_Radical\Messaging\Handlers\OpenStartScreenViewMessageHandler.cs

2) 'WPF_windows8_Radical.Presentation.StartScreenView' è 'tipo' ma è usato come 'variabile' c:\users\andrea\documents\visual studio 2012\Projects\WPF_windows8_Radical\WPF_windows8_Radical\Messaging\Handlers\OpenStartScreenViewMessageHandler.cs

3) Argomento 1: impossibile convertire da 'WPF_windows8_Radical.Presentation.StartScreenView' a 'System.Type' c:\users\andrea\documents\visual studio 2012\Projects\WPF_windows8_Radical\WPF_windows8_Radical\Messaging\Handlers\OpenStartScreenViewMessageHandler.cs


Ti chiedo altre informazioni per riuscire a risolvere questi problemini...

grazie ancora
saluti Andrea

0v3rCl0ck Profilo | Guru

niente perchè nel progetto che stavo facendo per il post, ci sono altre classi di supporto, ma non sono necessarie, semplicemente sostituisci questo:

KnowRegion.ShellContentRegion

con la stringa

"ShellContentRegion"

e dovrebbe funzionare
-------------------------------------------------------
Michael Denny
Lead Software Developer & Solutions Architect
http://blogs.dotnethell.it/Regulator/

0v3rCl0ck Profilo | Guru

mi sono accorto che anche questa riga:

this.viewResolver.GetView(HelloWorldView);

va sostituita con:

this.viewResolver.GetView<HelloWorldView>();

e il MainViewModel, deve ereditare dall'AbstractViewModel

ma forse te ne sarai già accorto :)
-------------------------------------------------------
Michael Denny
Lead Software Developer & Solutions Architect
http://blogs.dotnethell.it/Regulator/
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