


















Studia grazie alle numerose risorse presenti su Docsity
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
Prepara i tuoi esami
Studia grazie alle numerose risorse presenti su Docsity
Prepara i tuoi esami con i documenti condivisi da studenti come te su Docsity
Trova i documenti specifici per gli esami della tua università
Preparati con lezioni e prove svolte basate sui programmi universitari!
Rispondi a reali domande d’esame e scopri la tua preparazione
Riassumi i tuoi documenti, fagli domande, convertili in quiz e mappe concettuali
Studia con prove svolte, tesine e consigli utili
Togliti ogni dubbio leggendo le risposte alle domande fatte da altri studenti come te
Esplora i documenti più scaricati per gli argomenti di studio più popolari
Ottieni i punti per scaricare
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
Appunti del corso del politecnico di Torino di programmazione di sistema
Tipologia: Appunti
1 / 26
Questa pagina non è visibile nell’anteprima
Non perderti parti importanti!



















Interfacce grafiche User Experience È il fattore chiave del successo di un prodotto, il modo in cui le persone entrano in contatto con il prodotto stesso: funzionalità, usabilità, versatilità, gradevolezza, robustezza, costo, margine di guadagno, e tanto altro. Ed anche il software necessita di queste caratteristiche, ma la natura immateriale lo lega significativamente all’interazione interfaccia gioco ruolo fondamentale. Deve essere efficace, efficiente, facile da imparare e possibilmente piacevole (di bell’aspetto). Affinchè si riesca a creare applicazioni che aiutano le persone e rendono la vita più facile, il progettista deve calarsi nei panni dell’utente: bisogna prendere coscienza di chi deve usare quel software, un operaio magari ha i guanti e non può usare uno schermo touch. Esistono problemi legati ad ambiente, tipologia utente, alle persone in sé: meccanismi psicologici comuni, capacità visive, ma ovviamente ogni utente è un universo a sé ; solo progettista non è l’utente tipico, perché deve calarsi nelle esigenze delle varie famiglie che possono accedere all’uso del prodotto. Bisogna saper parlare, ascoltare, osservare, fare domande alle persone, ma soprattutto bisogna prototipare. Veniamo al software; Interfaccia grafica costruisce parte visibile del software, è il suo abito. Ed è la fonte del pregiudizio sulla qualità e l’apprezzamento del prodotto complessivo: l’estetica e la piacevolezza e la semplicità di un sistema sono altrettanto importanti come le qualità ingegneristiche (che sono necessarie!). Interfacce devono essere comprensibili: ci devono essere affordance, suggerimenti naturali su come sia possibile interagire con esso, bisogna sia intuibile il funzionamento. Deve quindi esserci coerenza: suggerimento visuale e funzionalità devono coincidere, altrimenti si crea frustrazione e attrito.
È basato sula possibilità di disegnare sullo schermo di un insieme di componenti interattivi: interazione non è più guidata soltanto dal programma, utente può scegliere con quale componente interagire, e programma deve quindi codificare queste scelte. Una schermata applicativa è composta da un insieme di widget: widget aggeggio che serve ad uno scopo preciso :: bottoni->input, etc… I widget vivono molto bene con la programmazione ad oggetti, in quanto si presta ad essere modellata come una classe: mi serve bottone, creo classe button, che deve disegnare bottone, che deve avere un metodo che scatena qualcosa quando clicco, etc… È lecito anche pensare che tutti i widget vogliono essere disegnati su schermo, quindi ha senso credere ci sia una gerarchia, cosi da accomunare i comportamenti comuni, quindi avrò una classe comune da cui derivano tutti i widget. Quando progettiamo oggetti, mondo grafico mette a disposizione diversi pattern; i tre più semplici sono: composite, template e strategy. Poi ne vedremo anche altri 2. Nel programmare per interfaccia grafiche si deve cambiare paradigma, perché qt deve reagire ad aventi che avvengono in qualunque momento per farlo programma principale si appoggia ad una coda di messaggi, e andiamo a creare una struttura reattiva: main ogni volta che arriva un messaggio, dopo essersi messo in attesa e predisposto a riceverli, richiama una call_back. N.B. È il SO che inietta nella coda di messaggi i messaggi. Nel costruire questo approccio è conveniente immaginare che struttura interfaccia grafica sia fatta da widget, piccoli oggetti responsabili di una interazione elementare. I widget mappano molto bene sulle classi ogni widget è una classe, e ogni classe è parte di una gerarchia di eredità, dove nelle parti più in alto si trovano le cose in comune, come disegnarsi su schermo. Queste gerarchie sono costruite mettendo insieme alcuni tipici pattern.
pattern composite Io voglio creare classi in modo da poter permettere non solo gerarchia tra classi, ma anche tra oggetti: schermata vede annidati degli oggetti contenitore che contengono oggetti più semplici di tipo contenuto. Normalmente questo è realizzato pensando che nella catena di ereditarietà ho una classe base, component, che è quello sta dentro, e una classe composite, che è il contenitore.
A run time, il pattern composite crea un albero delle viste: posso avere come radice una window, radice della mia rappresentazione, all’interno di window posso ospitare del contenuto, e nel caso disegnato, abbiamo solo un group, un pannello. Dentro il group posso mettere un sottopezzo, dei widget specifici, una immagina, una label e un button. Questo albero può essere creato programmaticamente, perché creo window, poi group, poi image (group.addchild(image)), oppure posso descrivere che voglio ottenere un albero del genere , tipo attraverso xml, e mi viene generato codice adatto da un generatore di codice.
pattern template Buona parte degli algoritmi che sono usati nelle interfacce grafiche contengono dei blocchi fondamentali: per permettere di sfruttare ereditarietà , consentendo alle sottoclassi di customizzare comportamento algoritmi senza stravolgerli, viene usato questo pattern ESEMPIO: deve disegnare su schermo. Cosa vuol dire? Ho delle sottofasi: sicuramente devo riempiere il colore di background, poi all’interno di quello spazio devo disegnare me stesso, poi devo disegnare i miei figli, poi potrei dover disegnare un bordino. Per evitare che chi ha bisogno di modificare il modo in cui appare nello schermo, devi farsi carico delle varie fasi descritte, il metodo paint è diviso nelle varie fasi(passo 1, passo 2, passo 3, passo 4), metodi protected,
che possono essere riscritti dalla sottoclasse in questo modo sottoclasse può fare override della fase specifica per cui hanno bisogno di cambiare qualcosa.
pattern strategy Algoritmi complessi ed intercambiali, e si vuole permettere, a parità di istanza, di decidere di fare cose diverse. Caso tipico : disposizione figli se sono un panel, e ho 3 pezzi, come li piazzo? So soltanto che devono essere dentro il rettangolo, se il panel è un rettangolo. Per evitare di avere classi a gruppi che fanno di mettere su , in mezzo, etc… Si dice : io sono un gruppo ho un componente, il layout manager, che fa la disposizione nel panel dei figli. È un componente con molte sottoclassi possibili.
Ho quindi il vantaggio che con un unico flusso di elaborazione, un app single thread, gestisce roba spezzettata complessa che avviene in ordine qualunque. Le call_back sono eseguite in modo sincrono, ma ci sta un problema : se un call_back impiega più di 200 ms a completarsi, il programma ci appare bloccato perché nessuno svuota la coda. Quindi lo sforzo deve essere quello di scrivere un codice efficiente per le attività complesse o si spezzettano in fasi oppure si crea un thread secondario per gestirle. N.B. la maggior parte delle operazioni sono molto veloci. La struttura del main è la seguente:
COME GESTIAMO GLI EVENTI? La relazione tra evento e call_back è dinamica, ovvero è ottenuta da qualche tabella che mi dice quando si verifica ciò informo tizio o caio, dinamica significa che la modifico runtime, quando la mia applicazione cambia stato cambia significato di qualcosa. Ogni framework ha il suo modo per iniettare i messaggi nella coda, perché sebbene SO sia principale sorgente di informazione, posso essere interessato ad inserire cose particolari nella coda questi
meccanismi di solito hanno due punti di ingresso : in coda [ post – message , è il posto naturale] e raramente vogliamo iniettare un messaggio in cima, ma si deve fare molta attenzione nel farlo. Siccome alcuni algoritmi, quelli di disegno e posizionamento, sono onerosi, i framework tendono ad eseguirli in modo lazy (a non eseguirli proprio) a volte bisogna chiedere al framework di ridisegnare, altrimenti stato non cambia relazione tra ciò che vedo su schermo e ciò che mantengo in strutture dati è inizialmente corretta, ma se tocco delle cose , specialmente sulle customizzazione fatte da me, questo non comporta per forza un ridisegno, e ci sono dei metodi appositi per ridisegnare, che sono molto costosi e bisogna usarli con parsimonia. N.B. solitamente il ridisegno è automatico. La maggior parte degli eventi che si verificano, che è una quantità esorbitante, è gestita internamente dall’implementazione delle singole classi framework, e quindi non serve vengano trattati il nostro compito è impostare proprietà sui widget : voglio sfondo verde, bordo 3d e cose del genere. Diamo informazioni descrittive noi Ci sono però alcuni eventi che sono completamente lasciati al programmatore: che fa un bottone quando viene cliccato? Di default nulla, è un componente generico, noi decidiamo cosa scatena il premere il bottone. Come lo diciamo? registrano call_back. Se per un dato elemento non ho call_back , non succede nulla, viene ignorato e si passa avanti. In certe situazioni, molto frequenti, vorremmo che quando si verifica una certa cosa, ad esempio cambiamento di una variabile, scattasse qualche altra cosa ad esempio far sapere a chi è interessato al valore della variabile che questo è cambiato. Di solito si adotta il pattern OBSERVER per fare questo l’azione che scatta può essere lunga , e nel caso bisogna o dividerla in passi elementari o si attiva un thread secondario, che è fondamnetale non manipoli niente del visual tree, che appartiene solo al thread prinicipale ( visual tree non ha lock al suo interno, e si potrebbero creare interferenze). PLANT EVENT MANAGEMENT
questo è il pattenr MODEL-VIEW-CONTROLLER Logicamente succede questo: utente vede la vista, ci interagisce, scatenando call_back del controllore, che agiscono sul modello, che aggiorna suo stato interno e quindi dice a vista di ridisegnarsi. Come fa vista a cambiare tutte le volte che cambia il modello? Siccome modello non deve sapere della vista, per definizione, si usa il pattern observer. Idea di fondo è : ciò che può cambiare per i fatti suoi in questo pattern è chiamato subject( che sono i vari campi del modello) che incorpora dentro se un valore e una lista di oggetti interessati, che sono accomunati da implementare interfaccia observer , che contiene un solo metodo che gli dice: “ c’è stato un update”. Inizialmente lista è vuota, o ho una info di defualt. Subject interessati si iscrivono ad observer, e su lista si può operare con add e remove modello prende puntatore ad un observer, su cui può chiamare update se qualcuno modifica subject con un suo metodo, se il nuovo valore è diverso dal vecchio fa il giro su tutti gli iscritti chiamando update, e chi viene chiamato reagisce a questo update. Quando non interessa più sapere cambiamenti, si chiama unsubscribe per non comparire più nella sua lista per conoscere updates. Le singole viste responsabili delle varie parti si iscrivono ai diversi subject presenti nel modello per farci delle cose. (STESSA COSA MA SPIEGAZIONE DA SLIDE: Il modello mantiene una lista di viste "interessate" ai suoi cambiamenti ◦ All'atto della propria creazione, le viste si registrano presso il modello
◦ All'atto della propria distruzione cancellano la propria registrazione Ogni volta che il modello cambia (una parte del) proprio stato ◦ Notifica tutte le viste al momento registrate di tale cambiamento ◦ La notifica può contenere il dato aggiornato o la sola indicazione che è avvenuto un cambiamento La vista reagisce alla notifica aggiornando il proprio contenuto. FINE SLIDE.) In alcune situazioni registrazione non è direttamente tra vista e modello, ma passa da oggetto intermedio, l’adapter non sempre i cambiamenti del modello devono far scattare cose nella vista magari non serve ridisegnare limitazione a viste UTILI, per non sovraccaricare intero sottosistema grafico. QT Qt è un framework nato negli anni 90, nato dall’esigenza di non voler più impazzire per creare interfaccie grafiche e quindi creare un modo universale per implementarle, indipendente da piattaforma. Hanno aggiunto degli strati intermedi, definiendo un paio di macro e una gerarchia di classi con le macro marcano dei pezzi di codice, che vengono pacioccati da un tool esterno, chiamato MOC( metaobject compiler) noi scriviamo in C++ con all’interno alcune macro scritte nel modo in cui vengono dette. Il codice C+ che scriviamo è un pezzo solo, perché il MOC gli linka un altro pezzo di codice, rendendo il tutto funzionante. Qt introduce la reflection, ogni oggetto ha coscienza di quale sia sua classe, superclasse, metodi, attributi, cosi da potersi relazione dinamicamente con gli altri. Introduce un meccanismo observable, generalizzato basato su concetti di slot e signal. Introduce variant, any, che solo adesso esistono anche in C++. Qt è una framework multi-piattaforma scritto in C++, ma adesso si possono usare anche altri linguaggi. La versione più recente è la 5.12.3. Ha un certo numero di tool accessori, tra cui il MOC, il pezzo forte che permette di usare le classi Qt. Internamente è diviso in moduli, e si può decidere quali usare. I moduli si dividono in due gruppi:
Se non viene indicato nessun parent , rimane null e quell’oggetto non ha genitori, cioè è radice di un albero. Gi oggetti di tipo QObject vivono sullo heap, gli allochiamo con new, e non ci preoccupiamo di cancellarli, se non quello radice di tutto, perhcè è il parent a chiamare la delete quando necessario. Gli oggetti che non ereditano da QObject invece sono generalmente allocati sullo stack, e ne dobbiamo gestire eliminazione. Ci sono delle eccezioni: QFile, QApplication, QDialog derivano da QObject ma non c’è bisogno di dichiararli sullo heap. QObject sanno in quale thread sono stati creati ( si può risalire tramite metodo thread() ), e accetta di essere chiamato solo nel contesto di quel thread lì, perché vogliamo garantire albero sia gestito da un solo thread da nascita a morte ( di solito è il thread principale). È possibile dire che un QObject e tutti i suoi figli, sono spostati dal thread dove sono stati creati ad un altro ( metodo moveToThread(), ed è importante che venga spostato tutto l’albero). Idea che QObject possa formare gerarchie si presta a tante applicazioni, e una interessante è proprio la gerarchia delle viste. QObject ha come sottoclasse QWIdget, che rappresenta tutto ciò che può stare sullo schermo. QWIdget dà un po' più di semantica a relazione padre/figlio figlio occupa sullo schermo uno spazio che è incluso nello spazio del padre. Se padre viene nascosto, anche figlio è nascosto. Ciò vale anche per traslazione. Se manipoliamo albero viste, faccio sparire un bottone, non ho problemi : bottone si cancella dal padre e resto dell’albero resta al posto giusto. Ogni QObject può avere un nome, che è una QString, e si possono fare tutte le operazioni che si possono fare su std::string dello standard C++. MA NON SONO EQUIVALENTI: ci sono metodi per castarle tra loro. Ogni QObject ha un QString name. Avere un nome mi permette di chiamare i pezzi della mia interfaccia con un nome, e ciò mi permette di cercare per nome e non per posizione, ovvero mi facilita la vita. Internamente la gerarchia viene mantenuta tramite degli smart pointer, chiamati QPointer: è particolare il puntante va a registrare come listener dell’oggetto, che può essere solo un QObject o derivati, e dice : Se ti cancellano dimmelo che io cancello che io il riferimento a te. TRACING POINTER, e ciò semplifica programmazione : appena creo QPointer ad un oggetto, questo o punta per bene o punta a nulla. ASSOMIGLIANO A WEAK POINTER. Esiste QSharedPointer per puntare a qualsiasi tipo di dato, che mantiene internamente un conteggio dei riferimenti uguale a std::shared_pointer
QVariant È una classe che permette di gestire le union: ci sono volte che in un campo ho numeri, stringhe , o altre schifezze C++ mi offre la union: con gli stessi bit gestisce tipi di dati diversi. Ha due metodi : type() per conoscere tipo di dato e convert() per supportare conversione verso un altro tipo. QVariant permette di gestire union in Qt. Esiste un insieme di classi simile a std library di C++ : vettori,liste,etc… Semanticamente sovrapposzione 100%, cambia qualcosa nel nome dei metodi. {QList,QVector,QLinkedList,QStack,QQueue,QSet,QMap,QHash,QMultiMap,QMultiHAsh} Questi contenitori sono manipolabili con degli iteratori: Qt ne supporta di due tipi quelli simili a STL di C+
N.B. in questo esempio posso mettere bottone su stack perché non ho altri widget, se suoi figli ne lui è figlio. PRATICA CLion quando compila crea il CMakeList, un makefile che serve al compilatore. La compilazione di Qt è più complicata: prima di compilare il main dobbiamo farlo passare per il MOC, che si deve leggere il file del main e vedere se ci sono QObject poiché deve generare del codice aggiuntivo. Questa operazione deve essere specificata nel CMakeList file:
COME SI USA QMainWindow? ESEMPIO #include
Esiste un caso particolare nella gestione di questo tipo di situazioni: le finestre di dialogo, che bloccano finchè non le chiudo.
QDialog Sono un impiccio da programmare: quando la mostro il metodo resta piantato finchè non chiudo, e ciò contraddice idea che tutto quello che facciamo deve finire in fretta. Meta-Object System Un pezzo fondamentale di Qt, che ne permette il funzionamento e garantisce semplicità, è legato al fatto che gli oggetti Qt conoscono molte cose di se stessi ciò è possibile grazie al MOC. MOC abilità comunicazione tra widget diversi( la connect, che unisce slot e signal). Il MOC funziona soltanto sulle classi che derivano da QObject. Ma ci sta un problema: MOC non voleva fare parsing di tutto il C++, e quindi deve sapere quali classe derivano da QObject si usa una strategia accettabile: si utilizzano dei tool, si aggiungono delle macro in tutte le classi che derivano da QObject, sia direttamente che indirettamente, e la macro si chiama Q_OBJECT. Cosa fa il MOC? Legge classe e aggiunge le parti che mancano, ovvero: una parte per la type introspection: aggiunge un campo privato che si chiama meta object e un metodo pubblico che si chiama metaObject, che da la possibilità di conoscere cosa ci sia nel campo privato, e nel campo privato so come si chiama classe e quali metodi questa classe aggiunge, e una parte per la reflection. Inoltre guarda,sempre all’interno, e vede se sono presenti zone public slot o signals, metodi particolari che possono essere destinatari o sender di messaggi per comunicazione. I metodi slot sono fatti dal programmatore. I metodi signal sono vuoti e vengono implementati dal MOC; ma come fa MOC? Scopo singal è dire a tutti quelli che si sono iscritti all’oggetto “mi è successo questo”, e il MOC può farlo perché deve solo dire cosa è successo ad esempio Button ha la signal Press, che notifica se è stato premuto o meno.
Come facciamo a realizzare applicazioni interattive? Partiamo da un esempio: class Model: public QObject { Q_OBJECT int count, min, max; public: Model(int v, int min= 0 , int max= 100 ): count(v), min(min), max(max) { } public slots: void countUp() { if (count<max) { count++; emit changed(count); } } void countDown() { if (count>min) { count--; emit changed(count); } } signals: void changed(int newValue); }; é una classe model non grafica, che deriva da QObject, una classe C++, può far parte da una gerarchia, ha un meta object avendo la macro. Costrutttore si occupa di inizializzare count,min,max. Ha un paio di metodi che vivono in public slots: sono metodi normalissimi, di cui faccio implementazione, sia per countUp che per countDOwn. emit changed serve a far scattare un proprio segnale. Infatti più in basso abbiamo nei singlas il metodo changed viene chiamato da emit e ha dentro di se del codice generico, che dice che se qualcuno si è iscritto il segnale è scattato. Oggetti presenti nel nostro programma hanno possibilità di far sapere al resto mondo qualcosa che gli è successo tramite i signals: può essere un messaggio privo di azioni, oppure come quello visto in cui l’evento cambia il valore del count. Un oggetto lancia il proprio segnale grazie ad emit. Se un oggetto è interessato a sapere qualcosa che gli è successo attorno, implementa uno slot, che è un metodo destinato a ricevere indicazioni che è successo qualcosa. In QObject ho la funzione connect, che attaca un signal di un QObject allo slot di un altro QObject : è un collegamento dinamico, avviene runtime, e quindi posso dire che sta succedendo qualcosa.