Avalonia Code OnlyItaliano

From the documentation of RAVAL, the MIT library of the books: the same as in its repository.

This page is available in Italian only for now.

RAva — estensioni fluenti per Avalonia in codice

Sorgenti: RAVAL\UI\ · namespace RAVAL · classe statica RAva (divisa per gruppo in più file) e classe RThemes. Spiegazione completa con esempi: TUTORIAL.md, capitolo 12.

Ogni estensione assegna una proprietà o aggiunge qualcosa al controllo e restituisce il controllo stesso, del suo tipo vero (sono generiche, T Margin<T>(this T c, ...)): dopo new TextBox().Margin(4) si ha ancora una TextBox, e la catena continua.

RAva.VStack(8,
  new TextBlock().Text("Totale").FontSize(18).Bold().Foreground("#1a7fb5"),
  new TextBox { PlaceholderText = "Cerca…" }.OnTextChanged(Filtra).OnEnter(Cerca),
  RAva.HStack(8, new Button().Content("Annulla").OnClick(Chiudi),
                 new Button().Content("Salva").Classes("accent").OnClick(Salva)));

Disposizione, griglie, contenuti, clic, risorse — RAva.cs

EstensioneVale perEquivale a
Margin(tutti) · Margin(orizz, vert) · Margin(s, a, d, b)LayoutableMargin = new Thickness(...)
Align(o, v) · HAlign(o) · VAlign(v)LayoutableHorizontalAlignment, VerticalAlignment
Width(l) · Height(a)LayoutableWidth, Height
Column(c, span) · Row(r, span)ControlGrid.SetColumn/Row (e ...Span se maggiore di 1)
Dock(lato)ControlDockPanel.SetDock
Children(...)PanelChildren.AddRange
Text(s)TextBlockText
Content(o)ContentControlContent; un testo con il trattino di accesso dà anche il nome accessibile (vedi sotto)
Child(c)Decorator (Border...)Child
ToolTip(s)ControlToolTip.SetTip
OnClick(metodo)ButtonClick += un metodo senza argomenti
Resource(prop, chiave)ControlBind(prop, GetResourceObservable(chiave)): segue il tema
ThemeBrush(chiave, ripiego)Controlil pennello della risorsa nel tema attuale, per Render

Testo, cornici, aspetto — RAvaText.cs

EstensioneVale perEquivale a
FontSize(px)TextBlock, controlli con modello, altri ControlFontSize; su un pannello è ereditato dai figli
FontWeight(p) · Bold() · Italic()come sopraFontWeight, FontStyle
FontFamily(famiglia)come sopraFontFamily
Foreground(pennello) · Foreground("#rrggbb")come sopraForeground
Wrap() · TextAlign(a)TextBlockTextWrapping.Wrap, TextAlignment
Border(spessore, raggio) · CornerRadius(r)Border, controlli con modelloBorderThickness, CornerRadius
BorderBrush(...)Border, controlli con modelloBorderBrush
Padding(...) (1, 2 o 4 valori)Border/Decorator, TextBlock, controlli con modelloPadding
Background(...)Panel, Border, TextBlock, controlli con modelloBackground
Visible(b) · Enabled(b) · Opacity(o)ControlIsVisible, IsEnabled, Opacity
Classes(nomi...)ControlClasses.Add

«Controlli con modello» sono i TemplatedControl: Button, TextBox, CheckBox, ListBox, Window...

Pannelli pronti — RAvaPanels.cs

MetodoCrea o imposta
RAva.HStack(spazio, figli...)StackPanel orizzontale con Spacing
RAva.VStack(spazio, figli...)StackPanel verticale con Spacing
RAva.Grid(colonne, righe = "")Grid con ColumnDefinitions/RowDefinitions scritte come in Avalonia («Auto,,2,120»)
.Spacing(s)StackPanel.Spacing
.Spacing(colonne, righe)Grid.ColumnSpacing, Grid.RowSpacing

Eventi senza lambda — RAvaEvents.cs

EstensioneVale perFirma del metodo
OnTextChanged(metodo)TextBoxvoid M(string testo), a ogni modifica
OnEnter(metodo)TextBoxvoid M(); Invio ascoltato in fase Tunnel, prima della casella (vale anche con AcceptsReturn), poi segnato gestito
OnChecked(metodo)ToggleButton (CheckBox, ToggleSwitch, RadioButton)void M(bool spuntato); l'indeterminato vale false
OnSelectionChanged(metodo)SelectingItemsControl (ListBox, ComboBox)void M(object? scelto)
OnThemeChanged(metodo)Controlvoid M(), quando cambia ActualThemeVariant
RAva.Every(intervallo, metodo)—void M(); restituisce il DispatcherTimer già avviato (fermarlo con Stop())

Voci dei menu — RAvaMenu.cs

EstensioneVale perCosa fa
OnClick(metodo)MenuItemClick += un metodo senza argomenti, come per i Button
HotKey("Ctrl+S")MenuItemla scorciatoia: InputGesture (la scrive a destra della voce) e HotKey (la fa funzionare)
var file = new MenuItem { Header = "_File" };
file.Items.Add(new MenuItem { Header = "_Salva" }.HotKey("Ctrl+S").OnClick(Salva));

OnClick del MenuItem non è generica: un secondo OnClick<T> con un vincolo diverso avrebbe la stessa firma di quello dei Button (errore CS0111). Così su un Button il compilatore sceglie la generica, su un MenuItem questa.

Accessibilità — RAvaAccess.cs

EstensioneVale perCosa fa
AutomationName(s)ControlAutomationProperties.SetName: il nome che il lettore di schermo (Assistente vocale, VoiceOver, Orca) annuncia
new Button().Content("_Prenota")              // si annuncia «Prenota»
new Button().Content("✕").AutomationName("Chiudi")      // si annuncia «Chiudi», non «✕»

Senza un nome, Avalonia annuncia il testo del contenuto così com'è: per Button, CheckBox e RadioButton anche il trattino di accesso («_Prenota»), mentre la Label lo toglie. Per questo Content, quando riceve un testo con _, mette anche il nome senza trattini, con la regola di AccessText: _x segna la lettera, __ è un trattino vero. Il nome dato con AutomationName vince sempre, prima o dopo Content; un secondo Content aggiorna solo il nome che aveva messo lui.

Colori e temi — RAvaColors.cs, RThemes.cs

MembroFa
RAva.Brush("#1a7fb5")SolidColorBrush da un colore come in HTML («#rgb», «#rrggbb», «#aarrggbb», nomi come «Red»)
RAva.Brush(0xFF1A7FB5)SolidColorBrush da un numero ARGB
new RThemes().Color(chiave, chiaro, scuro)un colore nelle due versioni (stringhe o numeri)
RThemes.Apply(app) · RThemes.Apply(finestra)mette i colori in Resources.ThemeDictionaries[Light/Dark], senza togliere quelli che ci sono
RThemes.Countquanti colori sono stati dati

Perché è fatta così

  • Nomi inglesi, gli stessi delle proprietà. RAVAL è in inglese per tutti, come Avalonia, anche nel libro italiano (gli esempi del libro restano in italiano: i loro nomi sono del lettore). L'estensione che imposta Margin si chiama Margin: in una chiamata con le parentesi il compilatore scarta la proprietà, che non si può chiamare, e trova l'estensione (misurato con Avalonia 12.1.4). Eccezioni: AutomationName (Name in Avalonia è un'altra cosa), gli eventi con On..., e i pannelli pronti HStack/VStack, perché Row e Column sono già le estensioni della Grid.
  • Dentro RAva i tipi omonimi si scrivono per intero. Il metodo Border nasconde la classe Border in tutta la classe RAva (errore CS0118): lì si scrive Avalonia.Controls.Border, e così per Grid, Dock, ToolTip, FontWeight, FontFamily, CornerRadius.
  • Niente lambda. Ogni evento ha dietro una piccola classe privata (Click, TextChange, Enter, Check, Selection, Tick) che ricorda il controllo e il metodo e fa da gestore. Il metodo del programma riceve già il valore utile, non mittente ed EventArgs.
  • Un'estensione per tutti, con il tipo guardato a runtime. FontSize, Padding, Background... stanno su classi diverse (TextBlock, TemplatedControl, Border, Panel) e un vincolo generico non può dire «uno di questi». Le estensioni valgono per ogni Control e scelgono la proprietà giusta in base al tipo vero.
  • Errore chiaro invece di niente. Se il controllo non ha la proprietà (un Padding su uno StackPanel) l'estensione lancia InvalidOperationException con scritto per chi vale: un'impostazione ignorata in silenzio si scoprirebbe solo guardando la finestra. Per testo e carattere invece un controllo qualsiasi va bene: si imposta la proprietà ereditata TextElement.*, che i figli prendono.
  • Resource invece di un colore fisso. Il colore resta legato alla chiave del dizionario del tema, così cambia da solo quando si passa dal tema chiaro allo scuro; RThemes serve solo a scrivere i due dizionari a coppie.
  • ThemeBrush e OnThemeChanged per chi disegna a mano. In Render non c'è binding: si legge il pennello del tema attuale al momento del disegno, e si chiama InvalidateVisual quando il tema cambia.

Inciampi

  • Senza schermo i DispatcherTimer scattano solo nel ciclo vero. In una prova con Avalonia.Headless, Dispatcher.UIThread.RunJobs() esegue i lavori in coda ma non fa scattare i timer: per provare RAva.Every bisogna far girare il ciclo con Dispatcher.UIThread.PushFrame e fermarlo da un altro thread (vedi la classe Attesa in TEST_RAVAL\Prove.cs).
  • Il colore di Resource cambia dopo un giro del dispatcher. Dopo aver cambiato RequestedThemeVariant, la proprietà legata si aggiorna solo dopo RunJobs().
  • Invio e la TextBox. Una TextBox di una riga lascia passare Invio, anche nella fase Bubble; una con AcceptsReturn = true lo usa per andare a capo e lo segna gestito, e un KeyDown normale non lo vede. OnEnter ascolta nella fase Tunnel, prima della casella, quindi funziona in tutti e due i casi, e poi segna Invio gestito: la casella non va a capo.
  • Ordine dei gestori. Sullo stesso controllo i gestori della stessa fase sono chiamati dall'ultimo iscritto al primo: un gestore Tunnel iscritto prima di OnEnter non riceve Invio (a meno di handledEventsToo: true). Misurato con Avalonia 12.1.4 in TEST_RAVAL\ProveInvio.cs, che preme Invio con la tastiera finta di Avalonia.Headless (KeyPress) e registra chi lo riceve.
  • InputGesture scrive il tasto, non lo fa funzionare. Su un MenuItem serve anche la proprietà HotKey (la imposta l'estensione HotKey): allora Ctrl+S sceglie la voce anche a menu chiuso e con il fuoco in una TextBox. Non va usato per Ctrl+C, Ctrl+X, Ctrl+V e Ctrl+A, che la TextBox gestisce da sé.
  • Con ItemsSource la scorciatoia di un sottomenu chiuso non funziona. Le voci date con ItemsSource diventano controlli solo quando il sottomenu si apre: prima non sono nell'albero e HotKey non viene registrato. Con Items.Add la voce è subito figlia del menu e il tasto funziona. Misurato con Avalonia 12.1.4 in TEST_RAVAL\ProveMenu.cs.
  • Il lettore di schermo sente il trattino di accesso. In Avalonia 12.1.4 un Button con Content = "_Prenota" si annuncia «_Prenota»: il trattino lo toglie solo la Label. Misurato leggendo l'albero dell'automazione (ControlAutomationPeer.CreatePeerForElement(c).GetName()) in TEST_RAVAL\ProveAccesso.cs; Content lo corregge da sé.