Dalla documentazione di RAVAL, la libreria MIT dei libri: la stessa che sta nel suo repository.
Grafica, conversioni, tabelle, alberi, stili — RAVAL 2.3
Le funzioni che quasi ogni programma Avalonia di misura o di progetto si riscrive: un grafico a curve con i marcatori, i numeri con i prefissi SI, i colori da mescolare, le figure da stampare a 300 dpi, una tabella che si ordina, un elenco di proprietà, uno stile pulito da strumento. Tutto in codice, senza XAML e senza lambda, nel namespace RAVAL.
Spiegazione con un esempio completo (la finestra di misura di un filtro): TUTORIAL.md, capitolo 15. Ogni membro, con la sua firma, è nel riferimento API.md.
Gli eventi si collegano a metodi con un nome: RChart.MarkersChanged (un Action), RTable.SelectionChanged (un Action<int> con la riga, −1 per nessuna), RPropertyList.Changed (un Action<string, string> con il nome e il valore nuovo, solo dopo Invio o l'uscita dalla casella, e solo se il testo è cambiato).
Conversioni — Graphics\RFormat.cs, Graphics\RColor.cs, Graphics\RCsv.cs
| Membro | Fa |
|---|---|
RFormat.Si(1.296e9, "Hz") | «1.296 GHz»: il prefisso che lascia da 1 a 999 prima del punto, al più digits cifre significative (4) |
RFormat.Parse("4k7") · Parse("10 MHz", "Hz") · Parse("5%") | rilegge i numeri scritti così, anche con il prefisso al posto della virgola e «u» per micro |
RFormat.Round(v, cifre) | arrotonda alle cifre significative |
RColor.Mix(a, b, t) · Lighten · Darken · WithOpacity | colori mescolati canale per canale |
RColor.Hex(c) · RColor.Luminance(c) | il testo «#1a7fb5»; la luminanza relativa di WCAG 2 (scritta chiara o scura sopra?) |
RColor.Palette · RColor.Series(i) | otto colori per le curve, leggibili sul bianco e sul quasi nero |
RCsv.Write(intestazioni, colonne...) · RCsv.Write("f", curve) · RCsv.Write(tabella) · RCsv.Read(testo) | il CSV con la virgola e il punto; i numeri nel formato «R», che si rileggono identici; le celle con virgole o virgolette fra virgolette (RFC 4180) |
Il grafico — Graphics\RChart.cs, RSeries.cs, RLimit.cs, RTicks.cs
var c = new RChart { TitleY = "dB", UnitX = "Hz" };
c.Series.Add(new RSeries("S21", f, s21));
c.Series.Add(new RSeries("misura", f, m, 1) { Dashed = true });
c.Limits.Add(new RLimit(1.27e9, 1.32e9, -1, RAva.Brush("#2b8a3e")) { Label = "S21 > −1" });
c.MarkersChanged += Leggi; // un metodo senza argomenti
| Membro | Fa |
|---|---|
Series, Limits, Markers | le curve, i limiti (segmenti orizzontali spessi: gli obiettivi di un ottimizzatore, una maschera) e le ascisse dei marcatori |
TitleY, TitleY2, UnitX, LogX, MinY, MaxY | titoli degli assi, unità delle ascisse (scritte con RFormat.Si), ascissa logaritmica, limiti fissi dell'asse sinistro |
RSeries.RightAxis | la curva usa l'asse destro, che compare da solo |
RSeries con il nome vuoto | si disegna ma resta fuori dalla legenda e dalle letture dei marcatori: le cento curve di una nuvola Monte Carlo |
FormatX | come scrivere le ascisse, se non con i prefissi SI (un gruppo di metodi) |
Culture | come scrivere i numeri di assi e marcatori: il punto (invariante) se non si sceglie altro, come RTable e RCsv; CultureInfo.CurrentCulture per la virgola del computer |
BackgroundKey, GridKey, InkKey | le risorse del tema dei colori («ChartBack», «ChartGrid», «ChartInk»), con quelli chiari o scuri di riserva |
PixelX, PixelY, ValueX, PlotArea | da valore a pixel e ritorno, dopo il primo disegno |
| il mouse | un clic mette il primo marcatore, Maiusc+clic ne aggiunge uno, il tasto destro li toglie; ogni marcatore scrive il valore di ogni curva (RSeries.At interpola) |
RSeries.Style (Line, Points, LineAndPoints) · PointSize · Sorted · With(x, y) | curve, punti o tutti e due; le ascisse in qualunque ordine (solo le curve Sorted si leggono con i marcatori, At per bisezione) |
MinX, MaxX · TimeAxis | limiti fissi dell'ascissa; le ascisse come tempi (RTime.X(data)), divisioni su secondi, minuti, ore, giorni tondi |
rotella, trascinamento, doppio clic · ZoomX, ZoomY, SetView, ResetView, Zoomed, ViewChanged, RangeX, RangeY, ValueY | lo zoom intorno al puntatore (Maiusc: l'ordinata), lo spostamento della vista, il ritorno a tutti i dati; il clic fermo resta il marcatore |
RLiveSeries(grafico, nome) · Add(x, y) · Add(data, y) · Window · MaxPoints · Drain · Clear | una curva che cresce mentre arrivano i dati, da qualunque thread, ridisegnata a lotti; con Window il grafico segue l'ultimo tratto |
RTime.X, Date, Step, Ticks, Label, Long | i tempi come ascisse: da data a secondi e ritorno, i passi tondi, le etichette |
RTicks.Step, Expand, Linear, Log | le divisioni «tonde» 1-2-5 di Heckbert, e 1-2-5 per decade sugli assi logaritmici |
Gli altri grafici — Graphics\RSmithChart.cs, RPolarChart.cs, RBarChart.cs, RHeatMap.cs
Spiegati nel capitolo 21 del tutorial (esempio ESEMPI_RAVAL\Esempi\GraficiRF.cs); prove in TEST_RAVAL\ProveGraficiRF.cs. Tutti prendono i colori da «ChartBack», «ChartGrid», «ChartInk» con la riserva chiara o scura (Graphics\RPlot.cs, interno), scrivono i numeri con Culture (il punto) e si salvano con SavePng(file, cm, dpi).
| Membro | Fa | ||
|---|---|---|---|
RComplexSeries(nome, punti, frequenze) · RComplexSeries.Pattern(nome, gradi, valori, dB) | una curva di complessi (Γ, S21) con le frequenze; un diagramma di radiazione | ||
RSmithChart: Z0, ShowImpedance, ShowAdmittance, MaxGamma | la carta, le griglie, la carta espansa oltre \ | Γ\ | = 1 |
Series, Circles (RSmithCircle con Dashed e Fill), Markers (RSmithMarker con Frequency e Draggable) | curve, cerchi, punti; ogni marcatore scrive frequenza, Z e VSWR (MarkerValues) | ||
MarkerMoved · Move(marcatore, Γ) · Under | il trascinamento per accordare; lo stesso da codice; il Γ sotto il puntatore, letto con Γ, Z, Y, VSWR, RL | ||
ZoomAt(punto, fattore) · ResetView() · Zoom · ViewCenter · PixelOf · GammaAt | la rotella, il trascinamento della vista, il doppio clic; da Γ a pixel e ritorno | ||
RSmithMath (RF\RSmithMath.cs, senza Avalonia) | Gamma, Impedance, Admittance, Vswr, ReturnLoss, Degrees, Polar; cerchi Resistance, Reactance, Conductance, Susceptance, Vswr, Q; OutputStability, InputStability, OutputStableInside, InputStableInside, Rollett; UnilateralGain, Noise | ||
RPolarChart: Decibels, Range, Max, ZeroAngle, Clockwise, AngleStep | modulo e fase, o dB con il centro Range dB sotto il bordo; zero in alto e verso orario per le antenne | ||
RBarChart: Categories, Series (RBarSeries), Stacked, ShowValues, Unit, BarRect | barre affiancate o impilate, i negativi verso il basso, il valore in cima | ||
RHeatMap: SetValues(m, x0, x1, y0, y1), Scale (RColorScale), Min, Max, Smooth, ValueAt, ColorOf | la matrice come immagine, con la barra della scala e il valore sotto il puntatore; NaN trasparente |
La carta di Smith e il grafico polare vengono da ASTRIP (MIT), tradotti in inglese e completati con zoom, letture dei marcatori, regioni colorate, dB e orientamento.
Le figure — Graphics\RImage.cs
RImage.SavePng(controllo, file, larghezza, altezza, dpi) disegna un controllo, anche senza finestra, a qualunque risoluzione, e scrive i dpi nel PNG (RImage.SetDpi per i PNG fatti altrove, RImage.Dpi per leggerli). RImage.Dip(8) è la larghezza in dip di una figura stampata larga 8 cm, che a 300 dpi diventa 945 pixel. È il modo di fare nitide le figure dei libri (la regola: almeno 300 dpi alla misura di stampa).
Tabelle ed elenchi — UI\RTable.cs, UI\RPropertyList.cs
var t = new RTable().Columns("Parte", "Valore:R", "Note");
t.AddRow("C1", "1.5p", "ceramico").AddRow("L1", "10n");
t.SelectionChanged += Mostra; // un metodo con un int
var p = new RPropertyList().Add("C", "1.5p").Add("ESR", "0.1");
p.Changed += Applica; // un metodo (string nome, string valore)
RTable: «:R» in fondo a un'intestazione allinea la colonna a destra, «:C» al centro. Un clic sull'intestazione ordina (per valore se ogni cella è un numero, anche con il prefisso SI; se no in ordine alfabetico), un secondo clic rovescia; un clic su una riga la sceglie. È unaGriddiTextBlock: va bene per decine o centinaia di righe.RPropertyList: nomi a sinistra, valori in caselle a destra;Changedarriva con Invio o quando la casella perde il fuoco, solo se il testo è cambiato davvero.Show(oggetto)elenca le proprietà pubbliche che stanno in una casella (testi, numeri, sì/no,enum) e scrive nell'oggetto i valori confermati, convertiti conRPropertyList.Convert(numeri comeRFormat, anche «1 MHz»). Un testo che non si converte è rifiutato:Rejectedcon il motivo.- Si leggono gli attributi
DisplayName,Description,ReadOnlyeBrowsable;Reload()rilegge l'oggetto;Targetè l'oggetto mostrato.
RVirtualTable(UI\RVirtualTable.cs): la tabella per diecimila o centomila righe, che costruisce solo le righe visibili (unListBoxvirtualizzato).LoadCsv(testo), con la prima riga come intestazioni e le colonne numeriche a destra;AddRoweAddRows.Sort(colonna, discendente), per valore o alfabetico;Filter, le righe con una cella che contiene il testo.Rows(tutte),Shown(quelle mostrate),SelectedIndexsempre inRows,SelectionChanged.SetWidth(colonna, dip);Text()dà le righe con le tabulazioni, per gli appunti.- Centomila righe si aprono in un terzo di secondo e si ordinano in due decimi.
RFormat.TryParse(testo, out valore):Parsesenza eccezioni, che scarta subito i testi che non cominciano come un numero.Parsesenza unità indicata ora legge anche «10 MHz» (1e7) e «5 V» (5).
L'albero — UI\RTree.cs, UI\RTreeNode.cs
Spiegato nel capitolo 17 del tutorial, con l'esempio ESEMPI_RAVAL\Esempi\Albero.cs; prove in TEST_RAVAL\ProveAlbero.cs.
RTree t = RTree.FromJson(testo); // i membri in cima; FromXml: la radice
t.Searchable = true; // la casella di ricerca
t.SelectionChanged += Scelto; // void Scelto(RTreeNode? n)
t.Opened += Apri; // doppio clic o Invio
t.Add(RTreeNode.FromFolder("C:\\")); // le cartelle si leggono quando si aprono
t.Select(t.Find("componenti[1].valore", "."));
| Membro | Fa |
|---|---|
new RTreeNode(testo, valore) · Add(figli...) · Remove | un nodo con i figli aggiunti; il valore si vede tenue accanto al testo |
new RTreeNode(testo, Caricatore) · Reload() | i figli letti dal caricatore la prima volta che servono, e di nuovo dopo Reload |
Key · Path(separatore) · Child(chiave) · Descendants() | il pezzo di percorso (il testo, se non cambiato); «server.port», «items[2].name»; il figlio con quella chiave; i nodi già letti, senza chiamare i caricatori |
Tag · Color · Item | l'oggetto del programma (l'RNode da cui è nato, il percorso del file); il colore del testo; la voce sullo schermo, se c'è già |
RTreeNode.FromJson · FromXml · FromNode · FromFolder | letti con RNode (scheda RDati.md): oggetti «{n}» e liste «[n]» con le foglie, elementi con gli attributi «@nome» e il testo; cartelle prima dei file, con la misura |
RTree.FromJson · FromXml · LoadJson · LoadXml · LoadNode | il controllo già pieno, o lo stesso controllo con un altro testo o un RNode |
Roots · Add · Remove · Clear | i nodi in cima |
Selected · Select(nodo) · SelectionChanged · Opened | la scelta: Select apre la strada e porta il nodo in vista |
Expand · Collapse · ExpandAll(livelli) · CollapseAll | aprire e chiudere; ExpandAll chiama i caricatori che incontra |
Find(percorso, separatore) | il nodo a un percorso scritto come lo scrive Path |
Filter · Found · Searchable · SearchBox | i nodi con le parole nel testo o nel valore, con quelli sopra (aperti) e sotto; quanti; la casella di ricerca e il suo testo segnaposto |
Le voci (TreeViewItem) si costruiscono solo quando il nodo sopra si apre: al loro posto c'è una voce finta che fa vedere la freccia. Un caricatore che lancia un'eccezione dà un figlio rosso con il messaggio e una riga Warning nel log. Il filtro non chiama i caricatori: cerca fra i figli già letti.
Stile — UI\RStyles.cs, UI\RAvaLook.cs
public override void Initialize() {
Styles.Add(new FluentTheme { DensityStyle = DensityStyle.Compact });
RStyles.Clean().Apply(this);
RStyles.Accent(this, Color.Parse("#1f5fa8"), Color.Parse("#5b9be0"));
}
| Membro | Fa |
|---|---|
new RStyles().For<Button>("toolbar").Set(prop, valore)...Apply(app) | stili globali scritti in codice: il selettore si costruisce con Selectors.OfType e Selectors.Class, senza la lambda di new Style(x => ...) |
RStyles.Clean() | l'insieme pronto: schede a 13 punti (Fluent le fa da 24), bottoni con gli angoli tondi, Button.toolbar piatto, Button.small per le tavolozze, TextBlock.caption per i titoli delle sezioni, TextBox.code a spaziatura fissa, alberi ed elenchi compatti |
stili.Apply(controllo) | gli stili su un solo controllo e su quello che contiene, non su tutta l'applicazione |
RStyles.Accent(app, chiaro, scuro) | il colore d'accento di Fluent nei due temi, con le tre tinte più chiare e più scure che Fluent usa per il passaggio del mouse e la pressione |
RAva.Caption, RAva.Card, RAva.Bar, RAva.ToolbarButton, RAva.CodeBox | i pezzi di un'interfaccia pulita: titolo di sezione, riquadro arrotondato, barra degli strumenti o di stato, bottone piatto, casella di codice |
Perché è fatta così
- I numeri si scrivono come li legge un tecnico. Su un asse «1.296 GHz» si legge, «1296000000» no; e chi scrive «4k7» in una cella deve poterlo rileggere.
RFormatè usato dal grafico e dalla tabella (che ordina per valore anche le celle con i prefissi). - Il grafico è disegnato a mano, come la carta di Smith del libro: nessuna dipendenza, ogni pixel si può spiegare in un capitolo, e senza schermo si prova con il mouse finto (
TEST_RAVAL\ProveGrafica.cs). - Le risorse dei colori hanno sempre una riserva. Un'applicazione che non definisce «ChartBack» vede comunque un grafico chiaro o scuro giusto.
Inciampi
- Una risorsa che manca toglie lo sfondo, e con lo sfondo il clic. Uno sfondo legato con
Resourcea una chiave che l'applicazione non ha resta nullo, e unBordersenza sfondo non riceve il mouse: le righe a strisce diRTablenon si potevano scegliere nell'applicazione di prova, che chiama i colori in italiano. Ora la striscia è solo dipinta e il clic va a unBordertrasparente sopra di lei. Bitmap.Save(percorso)è obsoleto in Avalonia 12: si passaPngBitmapEncoderOptions.Default, se no l'avviso (che nei progetti con gli avvisi come errori ferma la compilazione).- Il nome
BorderdentroRAvaè il metodo, non la classe: inRAvaLook.cssi scriveAvalonia.Controls.Border.BackgroundProperty(lo stesso inciampo diRAva.cs). - A 300 dpi le scritte dopo un ritaglio sparivano. Una
RenderTargetBitmapcreata a 300 dpi perdeva ogni testo disegnato dopo la chiusura di unPushClip: il grafico esportato per il libro non aveva né la legenda né i valori dei marcatori, mentre sullo schermo e a 96 dpi c'erano. Non dipende dal renderer di composizione (succede anche chiamandoRendera mano).RImagecrea la bitmap a 96 dpi con i pixel della risoluzione voluta e mette la scala come trasformazione: stessi pixel, tutto disegnato. Poi scrive nel PNG la risoluzione vera (il blocco pHYs, 11811 pixel al metro per 300 dpi), che i libri chiedono. La prova conta i pixel scritti nell'angolo della legenda (TEST_RAVAL/ProveGrafica.cs). - Nel disegno a mano l'opacità non la mette nessuno.
RImagepercorre l'albero visuale da sé, e fino alla 2.5.0 saltava solo gli elementi del tutto trasparenti: un testo conOpacity = 0.6usciva pieno. Ora ogni elemento con opacità minore di 1 si disegna dentro unPushOpacity; la prova controlla che un rettangolo nero al 50% su bianco esca grigio (TEST_RAVAL/ProveCorrezioni.cs).