Reputation: 65
I'm learning MAUI by developing a simple application, in which user is asked to enter product characteristics such as name (string) and price (decimal), and then select an image via File Explorer (filepath is stored as a string). I then store this data in a custom Sneaker class (contains those three fields and nothing more) and display it to the user on a second page.
Basically, it works just fine, but my issue is that I want to display filepath of a user-picked file in a corresponding entry field in XAML page when user picks a file, but it remains empty, even though it saves filepath to a correct field and image gets displayed on second page.
Below I'll provide my code for ViewModel and XAML page with user input form, as well as an image of my application window. Did I made a mistake somewhere in my code?
UPDATE:
Solution by Jason: I changed my Sneaker class to make it partial, also adding [ObseervableProperty] annotation to my FilePath field (also changed property name to camelCase so the property wouldn't collide with field name):
public partial class Sneaker: ObservableObject
{
public string ProductName { get; set; }
public decimal Price { get; set; }
[ObservableProperty]
public string? filePath;
}
My XAML page for user input:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiTestApplication.Page1"
xmlns:viewmodel="clr-namespace:MauiTestApplication.ViewModels"
x:DataType="viewmodel:SneakerViewModel"
Title="Page1">
<VerticalStackLayout
Margin="30"
Padding="30,0"
Spacing="25">
<Label
Text="Welcome to .NET MAUI! This is product page!"
TextColor="{AppThemeBinding Dark=Black}"
Style="{StaticResource Headline}"
SemanticProperties.HeadingLevel="Level1"
HorizontalOptions="Center" />
<Entry Placeholder="Enter product name" Text="{Binding Sneaker.ProductName}" />
<Entry Placeholder="Price USD" Text="{Binding Sneaker.Price}" Keyboard="Numeric" />
<Grid ColumnSpacing="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Entry x:Name="FilePick" Grid.Column="0" Placeholder="File path" Text="{Binding Sneaker.FilePath}" />
<Button Grid.Column="1" Text="Select a picture..." Command="{Binding PickFileCommand}" />
</Grid>
<Button Grid.Column="0" Grid.Row="1" Text="Add position" Command="{Binding AddSneakerCommand}" />
</VerticalStackLayout>
</ContentPage>
My SneakerViewModel.cs:
public class SneakerViewModel: INotifyPropertyChanged
{
private Sneaker _sneaker;
public Sneaker Sneaker
{
get { return _sneaker; }
set
{
_sneaker = value;
OnPropertyChanged(nameof(Sneaker));
}
}
public List<Sneaker> Sneakers { get; } = new List<Sneaker>();
public ICommand AddSneakerCommand { get; }
public ICommand PickFileCommand { get; private set; }
private string _filePath;
public string FilePath
{
get { return _filePath; }
set
{
_filePath = value;
Sneaker.FilePath = value;
OnPropertyChanged(nameof(FilePath));
}
}
private async Task PickFile() // I use FilePicker to avoid having user to enter filepath by hand
{
var result = await FilePicker.Default.PickAsync();
if (result != null)
{
Sneaker.FilePath = result.FullPath;
OnPropertyChanged(nameof(FilePath));
}
}
public SneakerViewModel()
{
Sneaker = new Sneaker();
AddSneakerCommand = new Command(AddSneaker);
PickFileCommand = new Command(async () => await PickFile());
}
private void AddSneaker()
{
Sneakers.Add(Sneaker);
Sneaker = new Sneaker(); // Reset the Sneaker instance after adding to the list
OnPropertyChanged(nameof(Sneaker)); // Notify the UI to update the bindings
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
My code for Page1.xaml.cs:
public partial class Page1 : ContentPage
{
public Page1()
{
InitializeComponent();
BindingContext = ViewModelLocator.SneakerViewModel;
}
}
ViewModelLocator.cs:
public class ViewModelLocator
{
private static SneakerViewModel _sneakerViewModel;
public static SneakerViewModel SneakerViewModel
{
get
{
if (_sneakerViewModel == null)
{
_sneakerViewModel = new SneakerViewModel();
}
return _sneakerViewModel;
}
}
}
And finally, visuals of my input form page in app:
Upvotes: 0
Views: 570
Reputation: 89169
you are binding to Text="{Binding Sneaker.FilePath}"
so the FilePath
property of the Sneaker
class needs to be Observable
. This is what makes the UI refresh when the data changes
Upvotes: 2