Capturing and updating an image source using MVVM in Xamarin - c#

I'm trying to capture photo and display the captured image in Xamarin but changing the image source binding just doesn't seem to work. This seems really simple so I'm not quite sure where I'm going wrong.
MainPageViewModel.cs
public class MainPageViewModel : ViewModelBase
{
private string _imageSource;
public string ImageSource
{
get { return _imageSource; }
set
{
_imageSource = value;
SetProperty(ref _imageSource, value);
}
}
public DelegateCommand TakePhotoCommand { get; private set; }
public MainPageViewModel(INavigationService navigationService, IPageDialogService pageDialogService)
: base(navigationService)
{
Title = "Main Page";
_dialogService = pageDialogService;
TakePhotoCommand = new DelegateCommand(TakePhoto);
}
async void TakePhoto()
{
await CrossMedia.Current.Initialize();
if (!CrossMedia.Current.IsCameraAvailable || !CrossMedia.Current.IsTakePhotoSupported)
{
await _dialogService.DisplayAlertAsync("No Camera", ":( No camera avaialble.", "OK");
return;
}
var file = await CrossMedia.Current.TakePhotoAsync(new Plugin.Media.Abstractions.StoreCameraMediaOptions
{
PhotoSize = Plugin.Media.Abstractions.PhotoSize.Medium,
Directory = "Sample",
Name = "test.jpg"
});
if (file == null)
return;
// This does get called ok
ImageSource = file.Path;
}
}
ViewModelBase.cs
public class ViewModelBase : BindableBase, INavigationAware, IDestructible
{
protected INavigationService NavigationService { get; private set; }
private string _title;
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
public ViewModelBase(INavigationService navigationService)
{
NavigationService = navigationService;
}
public virtual void OnNavigatedFrom(NavigationParameters parameters)
{
}
public virtual void OnNavigatedTo(NavigationParameters parameters)
{
}
public virtual void OnNavigatingTo(NavigationParameters parameters)
{
}
public virtual void Destroy()
{
}
}
MainPage.xaml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="PhotoTesting.Views.MainPage"
Title="{Binding Title}">
<StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
<Image Source="{Binding ImageSource}" WidthRequest="200" HeightRequest="200" Aspect="AspectFill" />
<Button x:Name="CameraButton" Text="Take Photo" Command="{Binding TakePhotoCommand}" />
</StackLayout>
</ContentPage>
I know the image capture bit is working ok, the problem just seems to be setting the image.source after the page has loaded.

You need to bind the Source property of Image to an ImageSource in MainPage.xaml
The ImageSource object can be obtained from the file stream. Here is the code:
public class MainPageViewModel : ViewModelBase
{
private ImageSource _imageSource;
public ImageSource ImageSource
{
get { return _imageSource; }
set
{
_imageSource = value;
SetProperty(ref _imageSource, value);
}
}
public DelegateCommand TakePhotoCommand { get; private set; }
public MainPageViewModel(INavigationService navigationService, IPageDialogService pageDialogService)
: base(navigationService)
{
Title = "Main Page";
_dialogService = pageDialogService;
TakePhotoCommand = new DelegateCommand(TakePhoto);
}
async void TakePhoto()
{
await CrossMedia.Current.Initialize();
if (!CrossMedia.Current.IsCameraAvailable || !CrossMedia.Current.IsTakePhotoSupported)
{
await _dialogService.DisplayAlertAsync("No Camera", ":( No camera avaialble.", "OK");
return;
}
var file = await CrossMedia.Current.TakePhotoAsync(new Plugin.Media.Abstractions.StoreCameraMediaOptions
{
PhotoSize = Plugin.Media.Abstractions.PhotoSize.Medium,
Directory = "Sample",
Name = "test.jpg"
});
if (file == null)
return;
// Here is the problem
//ImageSource = file.Path;
// This is the fix
ImageSource = ImageSource.FromStream(() => file.GetStream());
}
}

Related

Why does my Xamarin Forms ViewModel not accept a parameter passed in Xamarin Forms Shell?

I am trying to pass a parameter int SxCaseId in Xamarin forms shell to another viewmodel. I am able to see the SxCaseId value on the passing viewmodel, but it does not pass correctly to the receiving viewmodel. I see a blank page when navigating to the receiving page. Any help would be much appreciated.
Passing ViewModel
namespace QiX.ViewModels
{
public class SxCaseDetailsViewModel : ViewModelBase
{
private ISxCaseDataService _sxCaseDataService;
private SxCase _sxCase;
public Command StaffCommand { get; }
private int sxCaseId;
private bool isEditing;
public SxCaseDetailsViewModel(IConnectionService connectionService, IDialogService dialogService,
ISxCaseDataService sxCaseDataService) : base(connectionService, dialogService)
{
_sxCaseDataService = sxCaseDataService;
Title = _sxCase?.Facesheet;
StaffCommand = new Command(() => LoadStaff(SxCase));
SxCases = new ObservableCollection<SxCase>();
}
async void LoadStaff(SxCase sxCase)
{
await Task.Delay(500);
await Shell.Current.GoToAsync(
$"{nameof(StaffView)}?{nameof(StaffViewModel.SxCaseId)}={sxCase?.SxCaseId}");
}
public ObservableCollection<SxCase> SxCases { get; set; }
public int SxCaseId
{
get
{
return sxCaseId;
}
set
{
sxCaseId = value;
Task.Delay(500);
LoadSxCase(value);
}
}
private async Task LoadSxCase(int sxCaseId)
{
IsBusy = true;
await Task.Delay(500);
SxCase = await _sxCaseDataService.GetSxCasesDetail(sxCaseId);
sxCaseId = _sxCase.SxCaseId;
await Task.Delay(500);
IsBusy = false;
}
public SxCase SxCase
{
get => _sxCase;
set
{
SetProperty(ref _sxCase, value);
}
}
}
}
Receiving ViewModel
namespace QiX.ViewModels
{
[QueryProperty(nameof(SxCaseId), nameof(SxCaseId))]
public class StaffViewModel : ViewModelBase
{
private ISxCaseDataService _sxCaseDataService;
private IStaffDataService _staffDataService;
private SxCaseStaffJoin _selectedSxCaseStaffJoin;
private SxCaseStaffJoin _sxCaseStaffJoin;
public ObservableCollection<SxCaseStaffJoin> SxCaseStaffJoins { get; set; }
public Command LoadSxCaseStaffJoinCommand { get; }
public Command RefreshSxCaseStaffJoinCommand { get; }
public string sxCaseId;
public StaffViewModel(IConnectionService connectionService,
IDialogService dialogService, ISxCaseDataService sxCaseDataService,
IStaffDataService staffDataService) : base(connectionService, dialogService)
{
_sxCaseDataService = sxCaseDataService;
_staffDataService = staffDataService;
SxCaseStaffJoin = _sxCaseStaffJoin;
SxCaseStaffJoins = new ObservableCollection<SxCaseStaffJoin>();
Title = "Staff";
SxCaseStaffJoins = new ObservableCollection<SxCaseStaffJoin>();
LoadSxCaseStaffJoinCommand = new Command(async () => await ExecuteLoadSxCaseStaffJoinsCommand(Convert.ToInt32(sxCaseId)));
RefreshSxCaseStaffJoinCommand = new Command(async () => await LoadSxCaseStaffJoins(Convert.ToInt32(sxCaseId)));
}
public SxCaseStaffJoin SxCaseStaffJoin
{
get => _sxCaseStaffJoin;
set
{
SetProperty(ref _sxCaseStaffJoin, value);
}
}
public string SxCaseId
{
get
{
return sxCaseId;
}
set
{
sxCaseId = value;
Task.Delay(500);
}
}
private async Task ExecuteLoadSxCaseStaffJoinsCommand(int sxCaseId)
{
IsBusy = true;
try
{
SxCaseStaffJoins.Clear();
var sxCaseStaffJoins = await _staffDataService.GetSxCaseStaffJoinForSxCase(sxCaseId);
foreach (var sxCaseStaffJoin in sxCaseStaffJoins)
{
SxCaseStaffJoins.Add(sxCaseStaffJoin);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}
private async Task LoadSxCaseStaffJoins(int sxCaseId)
{
IsBusy = true;
await Task.Delay(500);
SxCaseStaffJoins = (ObservableCollection<SxCaseStaffJoin>)await _staffDataService.GetSxCaseStaffJoinForSxCase(sxCaseId);
IsBusy = false;
}
}
}
Page
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:utility="clr-namespace:QiX.Utility;assembly=QiX"
xmlns:iOsSpecific="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
xmlns:viewModels="clr-namespace:QiX.ViewModels;assembly=QiX"
x:Class="QiX.Views.StaffView"
x:DataType="viewModels:StaffViewModel"
Title="{Binding Title}"
utility:ViewModelLocator.AutoWireViewModel="True"
iOsSpecific:Page.UseSafeArea="True"
x:Name="StaffPage">
<ContentPage.Content>
<RefreshView x:DataType="viewModels:StaffViewModel" IsRefreshing="{Binding RefreshSxCaseStaffJoinCommand,IsBusy, Mode=TwoWay}">
<ScrollView>
<CollectionView ItemsSource="{Binding SxCaseStaffJoins}">
<CollectionView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding SxCaseId}"/>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ScrollView>
</RefreshView>
</ContentPage.Content>
</ContentPage>

How to add methods in an ObservableCollection?

I have an ObservableCollection that generates a button with the data of a Model in which I pass an image, a color and a link. The problem is that I want to put a method in it and when I press the button, call the method that I put into the ObservableCollection.
[Edited]
I am making a FloatingButton like this:
This floating button is made with a custom control. Each small button has a background color, an image and a link which are in Items.cs. In the viewmodel, I create an ObservableCollections with the Items.cs data that every time I add a list, a new button is added. What I want to do is to be able to add in addition to an image, a link and a color, a method that depending on the button you press, does what I want.
If I press the first small button that will be the first index in the list, that when I press it, it does one thing, and if I press the fourth button it will call another method that I want.
Example:
ItemList.Add(new Items { Website = "https://google.es/", Image = "web.png", ColorButton = "#B52D50", Method = "DoSomething" });
ItemList.Add(new Items { Website = "https://facebook.com/", Image = "facebook.png", ColorButton = "#B52D50", Method = "OpenFacebookApp" });
public void DoSomething()
{
//Do Something
}
public void OpenFacebookApp()
{
//Open Facebook App
}
This is my code:
ViewModel.cs:
public class ViewModel : INotifyPropertyChanged
{
//==============================================================
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
//==============================================================
string imageprimarybutton;
public string FirstImage
{
get => imageprimarybutton; set
{
imageprimarybutton = value;
OnPropertyChanged();
}
}
//=============
string firstButtonColor;
public string FirstButtonColor
{
get => firstButtonColor; set
{
firstButtonColor = value;
OnPropertyChanged();
}
}
//=============
private bool isVisible;
public bool IsVisible
{
get => isVisible;
set
{
isVisible = value;
OnPropertyChanged();
}
}
//=============
public ICommand LaunchWeb { get; private set; }
public ICommand OpenFloating { get; private set; }
public ObservableCollection<Items> ItemList { get; set; }
//=============
public ViewModel()
{
IsVisible = false;
FirstImage = "dots.png";
FirstButtonColor = "#B52D50";
OpenFloating = new Command(openFloatingButton);
LaunchWeb = new Command(async (url) =>
{
string AppLink = (string)url;
await Launcher.TryOpenAsync(AppLink);
});
ItemList = new ObservableCollection<Items>();
ItemList.Add(new Items { Website = "https://facebook.com/", Image = "facebook.png", ColorButton = "#B52D50" /*What I want: Method=OpenApp*/});
ItemList.Add(new Items { Website = "https://twitter.com/", Image = "twitter.png", ColorButton = "#B52D50" });
ItemList.Add(new Items { Website = "https://www.instagram.com/", Image = "insta.png", ColorButton = "#B52D50" });
ItemList.Add(new Items { Website = "https://google.com/", Image = "web.png", ColorButton = "#B52D50" });
}
/* And here the method I call in ItemList
public void OpenApp() {
Do something
}
*/
bool firstStart = true;
bool nextClick = true;
public void openFloatingButton()
{
if (firstStart)
{
FirstImage = "cross.png";
FirstButtonColor = "#6F1B31";
IsVisible = true;
firstStart = false;
}
else
{
if (nextClick)
{
FirstImage = "dots.png";
FirstButtonColor = "#B52D50";
IsVisible = false;
nextClick = false;
}
else
{
FirstImage = "cross.png";
FirstButtonColor = "#6F1B31";
IsVisible = true;
nextClick = true;
}
}
}
}
Items.cs:
public class Items : INotifyPropertyChanged
{
string url, image, color;
public string Website
{
get { return url; }
set {
url = value;
OnPropertyChanged("url");
}
}
public string Image
{
get {
return image;
}
set{
image = value;
OnPropertyChanged("image");
}
}
public string ColorButton{
get {
return color;
}
set{
color = value;
OnPropertyChanged("color");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I wrote a simple example for you and hope you can understand how the binding works.
First I add a Name property in the Items model which is use for distinguish different button:
public class Items : INotifyPropertyChanged
{
string url { get; set; }
string image { get; set; }
string color { get; set; }
string name { get; set; }
public string Website
{
get { return url; }
set
{
url = value;
OnPropertyChanged("Website");
}
}
public string Image
{
get
{
return image;
}
set
{
image = value;
OnPropertyChanged("Image");
}
}
public string ColorButton
{
get
{
return color;
}
set
{
color = value;
OnPropertyChanged("ColorButton");
}
}
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Then in the View Model, there is a MethodCommand which you can bind to the buttons:
public class viewModel {
public ObservableCollection<Items> ItemList { get; set; }
public ICommand MethodCommand { get; set; }
public viewModel()
{
ItemList = new ObservableCollection<Items>();
ItemList.Add(new Items { Website = "https://facebook.com/", Image = "facebook.png", ColorButton = "#B52D50", Name = "facebook"/*What I want: Method=OpenApp*/});
ItemList.Add(new Items { Website = "https://twitter.com/", Image = "twitter.png", ColorButton = "#B52D50", Name = "twitter" });
ItemList.Add(new Items { Website = "https://www.instagram.com/", Image = "insta.png", ColorButton = "#B52D50", Name = "insta" });
ItemList.Add(new Items { Website = "https://google.com/", Image = "web.png", ColorButton = "#B52D50", Name = "web" });
MethodCommand = new Command(test);
}
private void test(object obj)
{
string itemName= obj as string;
Console.WriteLine(itemName);
if (itemName == "facebook")
{
//perform your method with facebook
}
else if (itemName == "twitter")
{
//perform your method with twitter
}
else if (itemName == "insta")
{
//...
}
else
{
//...
}
}
}
Here is the code in Xaml, bind the MethodCommand to the Command and bind Name to the CommandParameter:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Name="myPage"
x:Class="App416.MainPage">
<CollectionView ItemsSource="{Binding ItemList}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10" >
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button Grid.Column="1"
Text="{Binding Name}"
Command="{Binding Source={x:Reference myPage}, Path=BindingContext.MethodCommand}"
CommandParameter="{Binding Name}"
/>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ContentPage>
And at last, set the bindingContext in MainPage:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
this.BindingContext = new viewModel();
}
}
Please feel free to ask me any question if you have:).

WPF c# How can I switch to dashboard view after successfull login attemp? Here's m code

Here is my xaml code. I want to update the current view after the successfull login attempt.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button Content="Login Page"
Command="{Binding UpdateCurrentViewModelCommand}"
CommandParameter="LoginView"/>
<Button Content="Register Page"
Command="{Binding UpdateCurrentViewModelCommand}"
CommandParameter="RegisterView"
Grid.Row="1"/>
<ContentControl Content="{Binding CurrentView}" Grid.Row="2"/>
</Grid>
public class MainViewModel : BaseViewModel
{
private BaseViewModel _currentView = new LoginViewModel();
public BaseViewModel CurrentView
{
get
{
return _currentView;
}
set
{
_currentView = value;
OnPropertyChanged(nameof(CurrentView));
}
}
public ICommand UpdateCurrentViewModelCommand { get; set; }
public MainViewModel()
{
UpdateCurrentViewModelCommand = new RelayCommand(UpdateCurrentView);
}
private void UpdateCurrentView(object obj)
{
if (obj.ToString() == "LoginView")
{
CurrentView = new LoginViewModel();
}
else if (obj.ToString() == "RegisterView")
{
CurrentView = new RegisterViewModel();
}
else if (obj.ToString() == "DashboardView")
{
CurrentView = new DashboardViewModel();
}
}
}
Here when user logs in it should update the current view, it is executing the command also I am getting the value in command parameter and it also updating the property CurrentView in MainViewModel but the problem is, it is not updating the UI the view is not displaying...
public class LoginViewModel : BaseViewModel
{
private string _email;
public string Email
{
get
{
return _email;
}
set
{
_email = value;
OnPropertyChanged(nameof(Email));
}
}
private string _password;
public string Password
{
get
{
return _password;
}
set
{
_password = value;
OnPropertyChanged(nameof(Password));
}
}
public ICommand LoginCommand { get; set; }
private StringBuilder ErrorMessages { get; set; } = new StringBuilder();
public LoginViewModel()
{
LoginCommand = new RelayCommandAsync(async (para) => await LoginUser(para));
}
private async Task LoginUser(object para)
{
SqlConnector sqlConnector = new SqlConnector();
if (ValidateForm() == false)
{
MessageBox.Show(ErrorMessages.ToString());
return;
}
User user = await sqlConnector.FindUserByEmail(Email);
if (user == null)
{
MessageBox.Show("Incorrect username or password");
return;
}
IPasswordHasher passwordHasher = new PasswordHasher();
var passwordResult = passwordHasher.VerifyHashedPassword(user.PasswordHash, Password);
if (passwordResult == PasswordVerificationResult.Success)
{
MessageBox.Show("Login success.");
//here is the problem...I am telling my MainViewModel's CurrentView property to update
but it's not listening to me.
//new MainViewModel().UpdateCurrentViewModelCommand.Execute("DashboardView");
new MainViewModel().CurrentView = new DashboardViewModel();
}
else
{
MessageBox.Show("Incorrect username or password");
}
ClearProperties();
}
private bool ValidateForm()
{
ErrorMessages.Clear();
bool isValid = true;
if (string.IsNullOrWhiteSpace(Email))
{
isValid = false;
ErrorMessages.Append("Email cannot be empty\n");
}
if (string.IsNullOrWhiteSpace(Password))
{
isValid = false;
ErrorMessages.Append("Password cannot be empty\n");
}
return isValid;
}
private void ClearProperties()
{
Email = Password = null;
}
This is not working as you are creating a new instance of MainViewModel after the successful login. This is not the instance that is the DataContext of your view.
You could pass a reference to the MainViewModel instance e.g., via the constructor to the LoginViewModel. But since the LoginViewModel doesn't really depend on the MainViewModel I wouldn't do that.
Instead I suggest one of the two following solutions. Generally your page view models shouldn't care about the content navigation. This should be only responsibility of the parent navigation view model, that already knows navigation details like current page or next page etc. Both examples follow this idea.
Also note that since you are creating new page view models every time the user navigates, you will lose all the content. Coming back to a previous page would show a blank initial page. Also the switch is very bad in terms of extensibility. Adding new pages is not very nice and would blow your UpdateCurrentView method.
I refactored your code to show an easy way to keep the page navigation simple and extensible. This are only small changes: Add an enum to replace strings in order to enable Intellisense and compiletime type checking support and add a Dictionary to replace the switch:
// Enum used to identify the requested page e.g as CommandParameter
public enum PageId
{
None = 0, LoginView, RegisterView, DashboardView
}
// Use a Dictionary to navigate to content based on the PageId enum
public class MainViewModel : BaseViewModel
{
private Dictionary<PageId, BaseViewModel> Pages { get; set; }
public MainViewModel()
{
this.Pages = new Dictionary<PageId, BaseViewModel>
{
{ PageId.LoginView, new LoginViewModel() },
{ PageId.RegisterView, new RegisterViewModel() },
{ PageId.DashboardView, new DashboardViewModel() }
};
}
private void UpdateCurrentView(object commandParameter)
{
if (commandParameter is PageId pageId
&& this.Pages.TryGetValue(pageId, out BaseViewModel nextPage))
{
this.CurrentView = nextPage;
}
}
}
<!-- Modified view to use the enum -->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button Content="Login Page"
Command="{Binding UpdateCurrentViewModelCommand}"
CommandParameter="{x:Static PageId.LoginView}"/>
<Button Content="Register Page"
Command="{Binding UpdateCurrentViewModelCommand}"
CommandParameter="{x:Static PageId.RegisterView}"
Grid.Row="1"/>
<ContentControl Content="{Binding CurrentView}" Grid.Row="2"/>
</Grid>
Solution 1: LoginSuccessful Event
You can expose a LoginSuccessful event, that the navigation view model can listen to:
MainViewModel.cs
public class MainViewModel : BaseViewModel
{
private Dictionary<PageId, BaseViewModel> Pages { get; set; }
public MainViewModel()
{
this.Pages = new Dictionary<PageId, BaseViewModel>
{
{ PageId.RegisterView, new RegisterViewModel() },
{ PageId.DashboardView, new DashboardViewModel() }
};
var loginView = new LoginViewModel();
loginView.LoginSuccessful += OnLoginSuccessful;
this.Pages.Add(PageId.LoginView, loginView);
}
private void UpdateCurrentView(object commandParameter)
{
if (commandParameter is PageId pageId
&& this.Pages.TryGetValue(pageId, out BaseViewModel nextPage))
{
this.CurrentView = nextPage;
}
}
private void OnLoginSuccessful(object sender, EventArgs e)
{
var loginViewModel = sender as LoginViewModel;
loginViewModel.LoginSuccessful -= OnLoginSuccessful;
UpdateCurrentView(PageId.LoginView);
}
}
LoginViewModel.cs
public class LoginViewModel : BaseViewModel
{
public event EventHandler LoginSuccessful;
private void OnLoginSuccessful() => this.LoginSuccessful?.Invoke(this, EventArgs.Empty);
private async Task LoginUser(object para)
{
SqlConnector sqlConnector = new SqlConnector();
if (ValidateForm() == false)
{
MessageBox.Show(ErrorMessages.ToString());
return;
}
User user = await sqlConnector.FindUserByEmail(Email);
if (user == null)
{
MessageBox.Show("Incorrect username or password");
return;
}
IPasswordHasher passwordHasher = new PasswordHasher();
var passwordResult = passwordHasher.VerifyHashedPassword(user.PasswordHash, Password);
if (passwordResult == PasswordVerificationResult.Success)
{
MessageBox.Show("Login success.");
OnLoginSuccessful();
}
else
{
MessageBox.Show("Incorrect username or password");
}
ClearProperties();
}
}
Solution 2: Continuation Callback
Or force the navigation view mode to provide a continuation callback via the constructor:
MainViewModel.cs
public class MainViewModel : BaseViewModel
{
private Dictionary<PageId, BaseViewModel> Pages { get; set; }
public MainViewModel()
{
this.Pages = new Dictionary<PageId, BaseViewModel>
{
{ PageId.LoginView, new LoginViewModel(() => UpdateCurrentView(PageId.LoginView)) },
{ PageId.RegisterView, new RegisterViewModel() },
{ PageId.DashboardView, new DashboardViewModel() }
};
}
private void UpdateCurrentView(object commandParameter)
{
if (commandParameter is PageId pageId
&& this.Pages.TryGetValue(pageId, out BaseViewModel nextPage))
{
this.CurrentView = nextPage;
}
}
}
LoginViewModel.cs
public class LoginViewModel : BaseViewModel
{
public Action LoginSuccessfulContinuation { get; set; }
// Constructor
public LoginViewModel(Action loginSuccessfulContinuation) => this.LoginSuccessfulContinuation = loginSuccessfulContinuation;
private async Task LoginUser(object para)
{
SqlConnector sqlConnector = new SqlConnector();
if (ValidateForm() == false)
{
MessageBox.Show(ErrorMessages.ToString());
return;
}
User user = await sqlConnector.FindUserByEmail(Email);
if (user == null)
{
MessageBox.Show("Incorrect username or password");
return;
}
IPasswordHasher passwordHasher = new PasswordHasher();
var passwordResult = passwordHasher.VerifyHashedPassword(user.PasswordHash, Password);
if (passwordResult == PasswordVerificationResult.Success)
{
MessageBox.Show("Login success.");
this.LoginSuccessfulContinuation?.Invoke();
}
else
{
MessageBox.Show("Incorrect username or password");
}
ClearProperties();
}
}

Pass Parameter for Dynamic Buttons - MVVM Light

The following code successfully creates two buttons dynamically, what I can not figure out is how to make the buttons open a different files when clicked.
What am I missing?
XAML:
<ItemsControl ItemsSource="{Binding DataButtons}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding ButtonName}"
Command="{Binding ButtonCommand}"
CommandParameter="{Binding FilePath}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
ViewModel:
namespace DynamicControlsMvvmLight.ViewModel
{
public class MainViewModel : ViewModelBase
{
private readonly ObservableCollection<ButtonModel> _dataButtons = new ObservableCollection<ButtonModel>();
public ObservableCollection<ButtonModel> DataButtons { get { return _dataButtons; } }
private ICommand _buttonCommand;
public ICommand ButtonCommand
{
get {
if (_buttonCommand == null) {
_buttonCommand = new RelayCommand<object>(CommandExecute, CanCommandExecute);
}
return _buttonCommand;
}
}
public MainViewModel()
{
ButtonModel data1 = new ButtonModel("Button 1", ButtonCommand, "c:/Folder/File1.PDF");
ButtonModel data2 = new ButtonModel("Button 2", ButtonCommand, "c:/Folder/File2.PDF");
DataButtons.Add(data1);
DataButtons.Add(data2);
}
private void CommandExecute(object FilePath)
{
ButtonModel button = FilePath as ButtonModel;
System.Diagnostics.Process.Start(button.FilePath);
}
private bool CanCommandExecute(object FilePath)
{
Console.WriteLine("CanCommandExecute Method...");
return true;
}
}
}
Model:
namespace DynamicControlsMvvmLight.Model
{
public class ButtonModel
{
public string ButtonName { get; set; }
public ICommand ButtonCommand { get; set; }
public string FilePath { get; set; }
public ButtonModel(string buttonName, ICommand buttonCommand, string filePath)
{
ButtonName = buttonName;
ButtonCommand = buttonCommand;
FilePath = filePath;
}
}
}
ERROR
I get the following error when I click any of the buttons.
RelayCommand expects to receive CommandParameter which is a string in this case.
The code must look like:
public ICommand ButtonCommand
{
get
{
if (_buttonCommand == null)
{
_buttonCommand = new RelayCommand<string>(CommandExecute, CanCommandExecute);
}
return _buttonCommand;
}
}
and
private void CommandExecute(string filePath)
{
System.Diagnostics.Process.Start(filePath);
}

ListView ItemSelected open detail page MVVM

Working in a personal project learning xamarin, got stucked in opening a detailed page about a selected item which I got from an API. The API, it returns an array like this :
{
"Restaurant": [
{
"Address": "Route Bounes Aires",
"RestaurantID": "1",
"RestaurantName": "John Doe",
"City": "Lorem Ipsum"
}
{
"Address": "Route Bounes Aires",
"RestaurantID": "2",
"RestaurantName": "John Doe",
"City": "Lorem Ipsum"
}]
I managed to bind these informations in a list view using the MVVM pattern. Now I can't seem to open a detailed page for the selcted item.
This is what I have so far:
restaurantviewmodel.cs
public class RestaurantViewModel : INotifyPropertyChanged
{
Service _services = new Service();
List<Restaurant> _restaurant;
public List<Restaurant> Restaurant
{
get { return _restaurant; }
set
{
if (value == _restaurant) return;
_branches = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public ICommand ReastaurantCommand
{
get
{
return new Command(async () =>
{
Reastaurant = await _apiServices.GetReastaurant();
await Application.Current.MainPage.Navigation.PushAsync(new ReastaurantPage(_restaurant));
});
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The service.cs
public async Task<List<Reastaurant>> GetReastaurant()
{
ListReastaurant restaurant = null;
try {
var client = new HttpClient();
client.DefaultRequestHeaders.Add("xxx", "xxx");
client.DefaultRequestHeaders.Add("xxx", xxx);
HttpContent content = new StringContent("");
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await client.PostAsync("https:www.here/goes/the/call/to/the/api", content);
response.EnsureSuccessStatusCode();
string json = await response.Content.ReadAsStringAsync();
restaurant = JsonConvert.DeserializeObject<ListReataurantDetails>(json);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message.ToString());
}
return restaurant.Restaurant;
}
The model restaurant.cs
public class Restaurant
{
public string Address { get; set; }
public string restaurantID { get; set; }
public string RestaurantName { get; set; }
public string City { get; set; }
}
The page restaurant.xaml :
<ListView x:Name="restaurantlistview"
HasUnevenRows="True" ItemSelected="restaurantlistview_ItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Padding="20, 10">
<Label Text="{Binding RestaurantName}"
FontSize="20"
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
The code behind restaurant.xaml.cs
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Restaurant : ContentPage
{
public Restaurant(List<Models.Restaurant> restaurant)
{
InitializeComponent();
NavigationPage.SetTitleIcon(this, "icon.png");
restaurantlistview.ItemsSource = restaurant;
}
private async void restaurantlistview_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
await Navigation.PushAsync(new RestaurantSinglePageDetails());
}
}
How can I approach to this problem?
I want to use the details of one restaurant to another page so I can show the address and the city and use these information to do different things. I think it's pretty easy I just didn't grasp well the concept of the mvvm pattern.
To clarify I'm not trying to pass all the data to another page, but just trying to access the information about a single item(restaurant).
I would really need some help. Thanks guys!
===edit===
public partial class RestaurantSinglePageDetails: ContentPage
{
public RestaurantSinglePageDetails(Models.Restaurant res)
{
InitializeComponent();
NavigationPage.SetTitleIcon(this, "logosmall.png");
BindingContext = new RestaurantDetailsViewModel(res);
//and here I'm supposed to have access to my selected restaurant.
}
}
restaurantdetailsdviewmodel.cs
public class RestaurantDetailsViewModel : INotifyPropertyChanged
{
// ==edit==
Restaurant restaurant;
public RestaurantDetailsViewModel(Restaurant restaurant)
{
this.restaurant = restaurant; // now we can use it in ViewModel
}
Service _services = new Service();
List<Info> _info;
public List<Info> Info
{
get { return _info; }
set
{
if (value == _info) return;
_info = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public ICommand GetInfoCommand
{
get
{
return new Command(async () =>
{
InfoData = await _apiServices.GetInfoData();
await Application.Current.MainPage.Navigation.PushAsync(new SingleDetailsAboutPrice(InfoData, restaurant));
});
}
}
}
I would like to use the RestaurantID here :
SingleDetailsAboutPrice.xaml.cs:
Restaurant restaurant;
public SingleDetailsAboutPrice(List<Models.InfoData> data, Restaurant restaurant)
{
InitializeComponent();
this.restaurant = restaurant;
//can't use the restaurantid here;
//some code goes here;
}
The error
The given key was not present in the dictionary
In your contentPage Restaurant class you should
InitializeComponent();
in the constructor class
XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Restaurant : ContentPage
{
public Restaurant(List<Models.Restaurant> restaurant)
{
InitializeComponent();
NavigationPage.SetTitleIcon(this, "icon.png");
restaurantlistview.ItemsSource = restaurant;
}
private async void restaurantlistview_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
//edit
var restaurant = (Restaurant)sender;
await Navigation.PushAsync(new RestaurantSinglePageDetails(restaurant));
}
}
to get item in what you selected:
var restaurant = (Restaurant)sender;
and next you have to create new Page
public partial class RestaurantSinglePageDetails: ContentPage
{
Restaurant res;
public RestaurantSinglePageDetails(Restaurant res)
{
InitializeComponent();
this.res = res;
//and here you have access to your selected restaurant.
}
}
To the res you have access from all class. So you can put this res when you move to another page.
===EDIT===
If I mean correctly, you want to pass RestaurantID to SingleDetailsAboutPrice so you have to pass it to RestaurantDetailsViewModeland then if you click on button put it to SingleDetailsAboutPrice(RestaurantId).
public partial class RestaurantSinglePageDetails: ContentPage
{
Restaurant res;
public RestaurantSinglePageDetails(Restaurant res)
{
InitializeComponent();
BindingContext = new RestaurantDetailsViewModel(item); //now you have access to restaurant in your viewModel. In this way you don't need use BindingContext in XAML
this.res = res;
//and here you have access to your selected restaurant.
}
}
And now in the RestaurantDetailsViewModel we need to create the constructor with Restaurant
public class RestaurantDetailsViewModel : INotifyPropertyChanged
{
Service _services = new Service();
Restaurant restaurant;
public RestaurantDetailsViewModel(Restaurant restaurant)
{
this.restaurant = restaurant; // now we can use it in ViewModel
}
List<Info> _info;
public List<Info> Info
{
get { return _info; }
set
{
if (value == _info) return;
_info = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public ICommand GetInfoCommand
{
get
{
return new Command(async () =>
{
InfoData = await _apiServices.GetInfoData();
await Application.Current.MainPage.Navigation.PushAsync(new SingleDetailsAboutPrice(restaurant)); // or if you want u can pass only restaurant.restaurantID.
});
}
}
}
And in SingleDetailsAboutPrice we create constructor with Restaurant or only RestaurantId
public partial class Restaurant : ContentPage
{
Restaurant restaurant;
public SingleDetailsAboutPrice(Restaurant restaurant)
{
InitializeComponent();
this.restaurant = restaurant;
}
String restaurantID;
// if you want only restaurantID
public SingleDetailsAboutPrice(String restaurantID)
{
InitializeComponent();
this.restaurantID = restaurantID;
}
}

Categories