Genera pagine WPF complete (XAML + ViewModel + code-behind) per il gestionale ATEC PM, rispettando il design system e i pattern architetturali del progetto. Usa questa skill ogni volta che l'utente chiede di creare una nuova pagina, vista, schermata, finestra o dialog WPF per il gestionale ATEC. Anche quando dice cose come "aggiungi la pagina X", "crea la schermata per Y", "mi serve una vista per gestire Z", o semplicemente nomina un layout (griglia, treeview, dashboard, form). Se l'utente sta lavorando sul progetto ATEC PM e menziona UI, pagine, viste, o schermate WPF, questa skill è quasi certamente quella giusta.
Questa skill genera pagine WPF complete e coerenti con il design system del gestionale ATEC PM. Produce tre file: XAML (vista), ViewModel (logica), code-behind (cablaggio eventi e caricamento dati).
Ogni volta che serve creare una nuova pagina, dialog o controllo per il client WPF del gestionale ATEC PM. I layout supportati sono:
Leggi il file references/templates.md nella directory di questa skill per avere i template XAML, ViewModel e code-behind pronti per ciascun layout. Usali come punto di partenza e adattali all'entità richiesta.
Questi valori sono fissi e vanno rispettati in ogni pagina generata.
#F7F8FA (grigio freddo chiaro)#FFFFFF con bordo #E4E7EC (1px)#2563EB (blu), hover #1D4ED8, pressed #1E40AF#1A1D26 con accent #4F6EF7#DC2626 — Success: #16A34A — Warning: #D97706#111827 — Secondario: #6B7280 — Disabilitato: #9CA3AFTutto Segoe UI:
#6B7280Non ridefinire questi stili, usali direttamente:
FlatBtn, PrimaryBtn, DangerBtn, SuccessBtn, GhostBtnModernDataGrid, ModernColumnHeader, ModernCell, ModernRowModernTextBox, HeaderSearchBoxTreeViewStyleSmoothExpanderViews/[NomePagina]Page.xaml + .xaml.csViews/[Feature]/[NomePagina]Page.xaml + sotto-cartella ViewModels/Views/[Entity]Dialog.xaml + .xaml.csViewModels/ se più di uno[Feature]Page.xaml (es. SuppliersPage.xaml)[Entity]Dialog.xaml (es. SupplierDialog.xaml)[Entity]VM.cs o [Entity]ViewModel.csbtnNew, txtSearch, dgItems, treeItemsOgni ViewModel implementa INotifyPropertyChanged con questo pattern:
public class NomeEntityVM : INotifyPropertyChanged
{
private string _campo = "";
public string Campo
{
get => _campo;
set { _campo = value; Notify(); }
}
// Per collezioni usa ObservableCollection<T>
public ObservableCollection<ItemVM> Items { get; set; } = new();
public event PropertyChangedEventHandler? PropertyChanged;
private void Notify([CallerMemberName] string? name = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
Punti chiave:
Notify() nel setter[CallerMemberName] per evitare stringhe magicheObservableCollection<T> per liste con bindingRecalcTotals() o simile per ricalcoli a cascataIl code-behind gestisce: inizializzazione, caricamento dati via API, eventi UI, filtri.
private List<EntityDto> _allItems = new();
private async Task Load()
{
txtStatus.Text = "Caricamento...";
try
{
string json = await ApiClient.GetAsync("/api/endpoint");
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var response = JsonSerializer.Deserialize<ApiResponse<List<EntityDto>>>(json, options);
if (response?.Success == true)
{
_allItems = response.Data ?? new();
ApplyFilter();
txtStatus.Text = $"{_allItems.Count} elementi";
}
}
catch (Exception ex) { txtStatus.Text = $"Errore: {ex.Message}"; }
}
private CancellationTokenSource? _filterCts;
private async void FilterChanged(object sender, TextChangedEventArgs e)
{
_filterCts?.Cancel();
_filterCts = new CancellationTokenSource();
try
{
await Task.Delay(300, _filterCts.Token);
ApplyFilter();
}
catch (TaskCanceledException) { }
}
private void ApplyFilter()
{
var filtered = _allItems.AsEnumerable();
// applica filtri da TextBox con Tag
foreach (var kvp in _filterBoxes)
{
string val = kvp.Value.Text.Trim().ToLower();
if (!string.IsNullOrEmpty(val))
{
string prop = kvp.Key;
filtered = filtered.Where(x =>
(x.GetType().GetProperty(prop)?.GetValue(x)?.ToString() ?? "")
.ToLower().Contains(val));
}
}
dgItems.ItemsSource = filtered.ToList();
}
private void BtnNew_Click(object sender, RoutedEventArgs e)
{
var dlg = new EntityDialog { Owner = Window.GetWindow(this) };
if (dlg.ShowDialog() == true) _ = Load();
}
private void BtnEdit_Click(object sender, RoutedEventArgs e)
{
if (dgItems.SelectedItem is EntityDto item)
{
var dlg = new EntityDialog(item) { Owner = Window.GetWindow(this) };
if (dlg.ShowDialog() == true) _ = Load();
}
}
public NomePage()
{
InitializeComponent();
Loaded += async (_, _) => await Load();
}
Quando l'utente chiede una nuova pagina:
references/templates.md per il template del layout sceltoMainWindow.xaml.csRicorda: il codice deve essere funzionante e collegato alle API REST del server (http://localhost:5100). Usa sempre ApiClient.GetAsync/PostAsync/PutAsync/DeleteAsync per le chiamate.