{"id":1365,"date":"2022-12-03T20:33:24","date_gmt":"2022-12-03T20:33:24","guid":{"rendered":"https:\/\/www.budus.it\/Blog\/?p=1365"},"modified":"2022-12-03T20:33:25","modified_gmt":"2022-12-03T20:33:25","slug":"c-winui-3-mvvm-toolkit-enter-like-tab-key-to-move-focus-on-next-element","status":"publish","type":"post","link":"https:\/\/www.budus.it\/Blog\/c-winui-3-mvvm-toolkit-enter-like-tab-key-to-move-focus-on-next-element\/","title":{"rendered":"C# + WinUI 3 + MVVM Toolkit (Enter like Tab Key to Move Focus on Next Element)"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Quelli che seguono sono degli esempi su come implementare quanto descritto nel titolo in pratica.<\/p>\n\n\n\n<!--more-->\n\n\n\n<p class=\"wp-block-paragraph\">In altri termini: un&#8217;App Windows che usa <a rel=\"noreferrer noopener\" href=\"https:\/\/learn.microsoft.com\/it-it\/windows\/apps\/winui\/winui3\/\" target=\"_blank\">WinUI 3<\/a> come interfaccia, scritta in <a rel=\"noreferrer noopener\" href=\"https:\/\/learn.microsoft.com\/it-it\/dotnet\/csharp\/\" target=\"_blank\">C#<\/a>, che sfrutta il <a rel=\"noreferrer noopener\" href=\"https:\/\/learn.microsoft.com\/en-us\/windows\/communitytoolkit\/mvvm\/introduction\" target=\"_blank\">MVVM Toolkit<\/a> .<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">WinUI 3<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\"><\/blockquote>\n<cite>Si tratta sostanzialmente della Libreria Interfaccia per Windows 10\/11<\/cite><\/blockquote>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">C#<\/p>\n<cite>\u00c8 uno dei linguaggi di programmazione (oltre a C++ e Visual Basic) che si possono usare in .NET .<\/cite><\/blockquote>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">MVVM Toolkit<\/p>\n<cite>\u00c8 una libreria che consente di usare il &#8220;<em><strong>pattern MVVM<\/strong><\/em>&#8221; usando dei &#8220;tag&#8221; che &#8220;scrivono il codice al posto tuo&#8221;, una roba &#8220;magica&#8221;, a capo della quale c&#8217;\u00e8 pure un italiano molto bravo.<\/cite><\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">Ebbene, mettendo tutto insieme, volevo fare in modo che si potesse passare da un controllo all&#8217;altro premendo il tasto INVIO e non solo con il TAB. Inoltre volevo che il <em>form<\/em> venisse validato a seguito dell&#8217;invio, nonch\u00e9 l&#8217;interfaccia si aggiornasse durante la digitazione (e tutto questo lo fa MVVM Toolkit, che scrive una <em>marea di codice sotto le quinte, in tempo reale, mentre uno scrive il proprio codice !<\/em>).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Dato che prevedo mi servir\u00e0 questa cosa in futuro, me la scrivo qui sinteticamente, ma che possa aiutare anche altri disgraziati come me.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La <em>soluzione <\/em>si chiama &#8220;<code>WinUITest<\/code>&#8221; e l&#8217;ho realizzata partendo da &#8220;Blank App Packaged (WinUI 3 in Desktop)&#8221; (io ho Visual Studio Community, configurato per usare la lingua inglese &#8230; mi agevola a trovare soluzione ad eventuali messaggi di errore).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Partiamo da <code>MainWindow.xaml<\/code>, che rappresenta l&#8217;interfaccia dell&#8217;App, molto semplice, due campi di immissione testo ed un tasto per inviare il form:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;Window\n    x:Class=\"WinUITest.MainWindow\"\n    xmlns=\"http:\/\/schemas.microsoft.com\/winfx\/2006\/xaml\/presentation\"\n    xmlns:x=\"http:\/\/schemas.microsoft.com\/winfx\/2006\/xaml\"\n    xmlns:d=\"http:\/\/schemas.microsoft.com\/expression\/blend\/2008\"\n    xmlns:local=\"using:WinUITest\"\n    xmlns:mc=\"http:\/\/schemas.openxmlformats.org\/markup-compatibility\/2006\"\n    mc:Ignorable=\"d\">\n\n    &lt;Grid>\n        &lt;Grid.ColumnDefinitions>\n            &lt;ColumnDefinition Width=\"*\" \/>\n        &lt;\/Grid.ColumnDefinitions>\n\n        &lt;StackPanel\n            Grid.Column=\"0\"\n            Margin=\"20\"\n            Spacing=\"20\">\n            &lt;TextBox\n                x:Name=\"NomeTextBox\"\n                KeyDown=\"EnterKeyEventManager\"\n                PlaceholderText=\"Nome\"\n                Text=\"{x:Bind ViewModel.FirstName, Mode=TwoWay}\" \/>\n            &lt;TextBox\n                x:Name=\"CognomeTextBox\"\n                KeyDown=\"EnterKeyEventManager\"\n                PlaceholderText=\"Cognome\"\n                Text=\"{x:Bind ViewModel.LastName, Mode=TwoWay}\" \/>\n            &lt;TextBlock Text=\"{x:Bind ViewModel.FullName, Mode=OneWay}\" \/>\n\n            &lt;Button Click=\"{x:Bind Submit}\" Content=\"Invia\" \/>\n        &lt;\/StackPanel>\n    &lt;\/Grid>\n\n&lt;\/Window>\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Quindi il relativo <code>MainWindow.xaml.cs<\/code>, che rappresenta il codice di controllo:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>using System.Diagnostics;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing Microsoft.UI.Xaml;\nusing Microsoft.UI.Xaml.Controls;\nusing Microsoft.UI.Xaml.Input;\nusing WinUITest.Services;\nusing WinUITest.ViewModel;\n\n\/\/ To learn more about WinUI, the WinUI project structure,\n\/\/ and more about our project templates, see: http:\/\/aka.ms\/winui-project-info.\n\nnamespace WinUITest;\n\n\/\/\/ &lt;summary>\n\/\/\/ An empty window that can be used on its own or navigated to within a Frame.\n\/\/\/ &lt;\/summary>\n&#91;ObservableObject]\npublic sealed partial class MainWindow : Window\n{\n\n    public ValidationWithServiceModel ViewModel\n    {\n        get; \n    }\n\n    public MainWindow()\n    {\n        InitializeComponent();\n\n        FancyService service = new();\n        ViewModel = new(service);\n    }\n\n    &#91;RelayCommand]\n    public void Submit()\n    {\n        ViewModel.Submit();\n\n        NomeTextBox.Focus(FocusState.Programmatic);\n    }\n\n    private void EnterKeyEventManager(object sender, KeyRoutedEventArgs e)\n    {\n        if (e.Key == Windows.System.VirtualKey.Enter)\n        {\n            try\n            {\n                FindNextElementOptions options = new()\n                {\n                    SearchRoot = (DependencyObject)((Control)sender).XamlRoot.Content\n                };\n                \n                Debug.WriteLine($\"{sender} = {FocusManager.TryMoveFocus(FocusNavigationDirection.Next, options)}\");\n            }\n            catch (System.Exception ex)\n            {\n                Debug.WriteLine($\"ERROR: {ex.Message}\");\n            }\n        }\n    }\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Infine abbiamo il &#8220;modello&#8221;, rappresentato da <code>ValidationWithServiceModel.cs<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>using System;\nusing System.ComponentModel.DataAnnotations;\nusing System.Diagnostics;\nusing System.Linq;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing WinUITest.Service;\n\nnamespace WinUITest.ViewModel;\n\npublic partial class ValidationWithServiceModel : ObservableValidator\n{\n    private readonly IFancyService service;\n\n    public ValidationWithServiceModel(IFancyService service)\n    {\n        this.service = service;\n    }\n\n    #region \"Validation\"\n    public static ValidationResult ValidateName(string name, ValidationContext context)\n    {\n        var isValid = ((ValidationWithServiceModel)context.ObjectInstance).service.Validate(name);\n\n        if (isValid)\n        {\n            return ValidationResult.Success;\n        }\n\n        return new ValidationResult(\"The name contains invalid characters\");\n    }\n    #endregion\n\n    #region \"Properties\"\n    &#91;ObservableProperty]\n    &#91;NotifyPropertyChangedFor(nameof(FullName))]\n    &#91;CustomValidation(typeof(ValidationWithServiceModel), nameof(ValidateName))]\n    private string firstName = string.Empty;\n\n    &#91;ObservableProperty]\n    &#91;NotifyPropertyChangedFor(nameof(FullName))]\n    private string lastName = string.Empty;\n\n    public string FullName => $\"{FirstName} {LastName}\";\n    #endregion\n\n    public void Submit()\n    {\n        ValidateAllProperties();\n\n        if (HasErrors)\n        {\n            Debug.WriteLine($\"&#91;E] ERROR: {string.Join(Environment.NewLine, GetErrors().Select(e => e.ErrorMessage))}\");\n        }\n        else\n        {\n            Debug.WriteLine($\"&#91;I] SUBMITTED: {FullName}\");\n\n            FirstName = string.Empty;\n            LastName = string.Empty;\n        }\n    }\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Per eseguire la <em>validazione del form<\/em> viene utilizzato un <em>servizio creato ad-hoc<\/em> e rappresentato dall&#8217;Interfaccia <code>IFancyService<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>namespace WinUITest.Service;\n\npublic interface IFancyService\n{\n    bool Validate(string name);\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Nel mio caso <em>custodita<\/em> nel file <code>Contracts\\Services\\IFancyService.cs<\/code> e dalla sua implementazione <code>Services\\FancyService.cs<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>using System.Text.RegularExpressions;\nusing WinUITest.Service;\n\nnamespace WinUITest.Services;\n\npublic class FancyService : IFancyService\n{\n    public bool Validate(string name)\n    {\n        return Regex.IsMatch(name, @\"^&#91;A-Za-z ]+$\");\n    }\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Sia chiaro: <strong>il codice di cui sopra \u00e8 frutto di <em>estenuanti ricerche<\/em>, video vari ed \u00e8 copiato a destra e manca da tutte queste fonti<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Purtroppo non ho &#8211; ancora, nel momento in cui scrivo &#8211; un tutoria decente da seguire su tutta la faccenda, lo sbattimento \u00e8 ancora grande, soprattutto perch\u00e9 <strong>non c&#8217;\u00e8 un&#8217;interfaccia per creare rapidamente la UI<\/strong>, bisogna scriversi tutto a mano e vedere i cambiamenti run-time, non il massimo.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Quelli che seguono sono degli esempi su come implementare quanto descritto nel titolo in pratica.<\/p>\n","protected":false},"author":1,"featured_media":1366,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[57,6],"tags":[277,278,98,279],"class_list":["post-1365","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-development","category-software","tag-net","tag-c-2","tag-windows","tag-winui"],"_links":{"self":[{"href":"https:\/\/www.budus.it\/Blog\/wp-json\/wp\/v2\/posts\/1365","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.budus.it\/Blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.budus.it\/Blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.budus.it\/Blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.budus.it\/Blog\/wp-json\/wp\/v2\/comments?post=1365"}],"version-history":[{"count":1,"href":"https:\/\/www.budus.it\/Blog\/wp-json\/wp\/v2\/posts\/1365\/revisions"}],"predecessor-version":[{"id":1367,"href":"https:\/\/www.budus.it\/Blog\/wp-json\/wp\/v2\/posts\/1365\/revisions\/1367"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.budus.it\/Blog\/wp-json\/wp\/v2\/media\/1366"}],"wp:attachment":[{"href":"https:\/\/www.budus.it\/Blog\/wp-json\/wp\/v2\/media?parent=1365"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.budus.it\/Blog\/wp-json\/wp\/v2\/categories?post=1365"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.budus.it\/Blog\/wp-json\/wp\/v2\/tags?post=1365"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}