Search code examples
xamluser-interfaceentity-framework-corewinui-3community-toolkit-mvvm

Data Entry + Clear Controls After Data Entry


I am trying to make a simple form that adds data to the database and if the data is valid clears/resets the textboxes and other controls. Currently I use a RelayCommand to send all the data from the controls to my View Model where it validates it and adds it to the database.

The issue I am having though is that once it's in the database I need to interact again with the page and clear the textboxes, but using my current method (no 2) I have no way of doing that.

Not to long ago I moved from Win Forms to WinUi 3 and since then have been trying to adapt to the MVVM way of doing things. When it comes to this problem, from my research it seems there are several ways of handling data entry in WinUi 3.

The first is like in WinForms with the button click and the code behind file, the second using RelayCommands is what I am currently doing and the third way I saw suggested in another question.

Would combining the second and third options to be the best way to go? Ie add separate fields for each control and then use the Button Command to call a RelayCommand to link it all together?

  1. OnClick
<Button Content="Add" Click="AddSupplier" />
  1. Via Command
 <Button x:Name="AddSupplierBtn" Content="Add Supplier" Grid.Row="4" Grid.Column="1"
         Command="{x:Bind ViewModel.AddSupplierCommand}"
         CommandParameter="{x:Bind ViewModel.CreateSupplier(NameTextBox.Text, AddressTextBox.Text, PhoneNumberTextBox.Text, WebsiteTextBox.Text), Mode=OneWay}"/>

  1. Bind controls To fields and do it all in the class Example taken from here: How do I clear my TextBox using MVVM after my Command fires
<TextBox x:Name="messageBox" Text="{Binding TextBoxInput, Mode=TwoWay}">

public string TextBoxInput
    {
        get { return _textBoxInput; }
        set
        {
            _textBoxInput = value;
            OnPropertyChanged(nameof(TextBoxInput));
        }
    }
    private string _textBoxInput;

My own code currently is as follows:

SupplierPage

<Grid Loaded="OnRootGridLoaded">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="auto" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
<TextBlock Text="Name" Grid.Row="0" Grid.Column="0" />
<TextBox x:Name="NameTextBox" Grid.Row="0" Grid.Column="1" />

<TextBlock Text="Address (Optional)" Grid.Row="1" Grid.Column="0" />
<TextBox x:Name="AddressTextBox" Grid.Row="1" Grid.Column="1"/>

<TextBlock Text="PhoneNumber (Optional)" Grid.Row="2" Grid.Column="0" />
<TextBox x:Name="PhoneNumberTextBox" Grid.Row="2" Grid.Column="1"/>

<TextBlock Text="Website" Grid.Row="3" Grid.Column="0" />
<TextBox x:Name="WebsiteTextBox" Grid.Row="3" Grid.Column="1"/>

<Button x:Name="AddSupplierBtn" Content="Add Supplier" Grid.Row="4" Grid.Column="1"
        Command="{x:Bind ViewModel.AddSupplierCommand}"
        CommandParameter="{x:Bind ViewModel.CreateSupplier(NameTextBox.Text, AddressTextBox.Text, PhoneNumberTextBox.Text, WebsiteTextBox.Text), Mode=OneWay}"/>
</Grid>

View Model

public partial class SupplierViewModel: ObservableObject
{
    RecipeDBContext context;

    [ObservableProperty]
    private ObservableCollection<Supplier> _suppliers;

    public SupplierViewModel()
    {
        context = new RecipeDBContext();
        _suppliers = new ObservableCollection<Supplier>();
        UpdateSuppliers(context.Suppliers.ToList());
        _suppliers.CollectionChanged += this.OnCollectionChanged;
    }

    public void UpdateSuppliers(List<Supplier> suppliers)
    {
        Suppliers.Clear();        
        foreach (Supplier supplier in suppliers)
        {            
            Suppliers.Add(supplier);
        }
    }

    

    public Supplier CreateSupplier(string name, string address, string phoneNumber, string website) => new(name, address, phoneNumber, website);

    [RelayCommand]
    private void AddSupplier(Supplier supplier)
    {        
        if (supplier != null)
        {
            Suppliers.Add(supplier);
        }
        else
        {

        }
    }

    void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {        
        if (e.NewItems != null)
        {            
            foreach (Supplier newItem in e.NewItems)
            {                                              
                context.Suppliers.Add(newItem);                
            }            
        }

        if (e.OldItems != null)
        {
            foreach (Supplier oldItem in e.OldItems)
            {                
                context.Suppliers.Remove(oldItem);
            }
        }
        context.SaveChanges();
    }
}

Model

public partial class Supplier: ObservableObject
{
    public int Id { get; set; }

    [ObservableProperty]
    private string _name;
    [ObservableProperty]
    private string _address;
    [ObservableProperty]
    private string _phoneNumber;
    [ObservableProperty]
    private string _website;

    public Supplier(string name, string address, string phoneNumber, string website) 
    {
        Name= name;
        Address= address;
        PhoneNumber= phoneNumber;
        Website= website;
    }
}

Solution

  • Xaml binding textbox to field

    <TextBox x:Name="AddressTextBox" Header="Address (Optional)" PlaceholderText="Enter Address" PlaceholderForeground="Gray" Text="{x:Bind Supplier.Address, Mode=TwoWay}" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2">
    

    Model

    public partial class Supplier: ObservableValidator
    {
        public int Id { get; set; }
        
        [ObservableProperty]
        [NotifyDataErrorInfo]
        [Required(ErrorMessage = "Name is Required")]    
        [MinLength(2, ErrorMessage = "Name should be longer than one character")]        
        private string _name = string.Empty;        
    
        [ObservableProperty]        
        private string _address = string.Empty;
    
        [ObservableProperty]
        [EmailAddress]
        private string _email = string.Empty;
    
        [ObservableProperty]
        [Phone]
        private string _phoneNumber = string.Empty;
    
        [ObservableProperty]
        [Url]
        private string _website = string.Empty;        
    
    
       
    
        public Supplier()
        {
            this.ErrorsChanged += Supplier_ErrorsChanged;
            this.PropertyChanged += Supplier_PropertyChanged;
    
            SetProperty(ref _name, _name, true, nameof(Name));        
        }
    
        ~Supplier()
        {
            ErrorsChanged -= Supplier_ErrorsChanged;
            PropertyChanged -= Supplier_PropertyChanged;
        }
        public Supplier(string name, string address, string email, string phoneNumber, string website) 
        {
            Name= name;
            Address= address;
            Email= email;
            PhoneNumber= phoneNumber;
            Website= website;
        }
    
        public string Errors => string.Join(Environment.NewLine, from ValidationResult e in GetErrors(null) select e.ErrorMessage);
    
        public void RemoveErrors()
        {
            ClearErrors();
        
        }
    
        private void Supplier_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName != nameof(HasErrors))
            {
                OnPropertyChanged(nameof(HasErrors)); // Update HasErrors on every change, so I can bind to it.
            }
        }
    
        private void Supplier_ErrorsChanged(object sender, System.ComponentModel.DataErrorsChangedEventArgs e)
        {                
            OnPropertyChanged(nameof(Errors)); // Update Errors on every Error change, so I can bind to it.
        }
    }
    
    

    ViewModel Code:

    public partial class SupplierViewModel: ObservableValidator
    {
        RecipeDBContext context;
    
        private Supplier _supplier = new Supplier();
        public Supplier Supplier => _supplier;
    
        [ObservableProperty]
        private ObservableCollection<Supplier> _suppliers;
    
     public SupplierViewModel()
     {
         context = new RecipeDBContext();
         _suppliers = new ObservableCollection<Supplier>();
         UpdateSuppliers(context.Suppliers.ToList());
         _suppliers.CollectionChanged += this.OnCollectionChanged;
     }
    
    [RelayCommand]
    private void AddSupplier(Supplier supplier)
    {
        if (supplier != null)
        {
            if (!supplier.HasErrors)
            {                                               
                Supplier s = new Supplier()
                {
                    Name= supplier.Name,
                    Address= supplier.Address,
                    PhoneNumber= supplier.PhoneNumber,
                    Website= supplier.Website,
                };
                Suppliers.Add(s);
                supplier.Name = string.Empty;
                supplier.RemoveErrors();
            }
            else
            {
                Debug.WriteLine("Errors\n" + supplier.Errors);
            }
        }
    }
    }