I am facing a strange problem and i don't know why this happens. I have created an AccountPage showing the Username, Name and a Description which i bound from the viewmodel. The App checks whether a token exists and pushes a LoginPage if there is no token.
If i now push the create Account Page from the Loginpage and create an account i get popped back to the Account page. Although the OnAppearing Method on the AccountPage gets called and the Bindings have the correct values in the VM there is nothing shown in the view. When i close the app and restart it, however, i can see the values of the Bindings on the View. How does this happen and how can i fix this?
the AccoountPage:
ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewmodel="clr-namespace:DoggoApp.ViewModels"
x:DataType="viewmodel:AccountPageViewModel"
BindingContext="AccountPageViewModel"
Title="{Binding Username}"
BackgroundColor="#FBFDFB"
x:Class="DoggoApp.Views.AccountPage">
<ContentPage.Content>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="0.27*"/>
<RowDefinition Height="0.1*"/>
<RowDefinition Height="0.3*"/>
<RowDefinition Height="0.7*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackLayout Orientation="Horizontal" Grid.Row="0">
<Frame CornerRadius="100" Padding="0" Margin="25" IsClippedToBounds="True">
<Image Source="default_user.jpg" Aspect="AspectFill" Scale="0.44" Margin="-60"/>
</Frame>
<StackLayout Margin="10,40">
<Label Text=""/>
<Label Text="Beiträge"
Style="{StaticResource AccountPage}"/>
</StackLayout>
<StackLayout Margin="10,40">
<Label Text=""/>
<Label Text="Follower"
Style="{StaticResource AccountPage}"/>
</StackLayout>
<StackLayout Margin="10,40">
<Label Text=""/>
<Label Text="Following"
Style="{StaticResource AccountPage}"/>
</StackLayout>
</StackLayout>
<StackLayout Grid.Row="1" Margin="15,-20">
<Label Text="{Binding Name}" FontAttributes="Bold" Style="{StaticResource AccountPage}"/>
<Label Text="{Binding Description}" Style="{StaticResource AccountPage}"/>
<Frame Padding="0,15">
<Button Text="Edit Profile" Margin="-20" Clicked="EditAccount_Clicked"/>
</Frame>
</StackLayout>
code behind:
namespace DoggoApp.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class AccountPage : ContentPage
{
private static AccountPageViewModel accountPageViewmodel;
public AccountPage()
{
InitializeComponent();
BindingContext = accountPageViewmodel = new AccountPageViewModel();
}
protected override async void OnAppearing()
{
accountPageViewmodel.NotRefreshTokenAction = (async (obj) =>
{
await DisplayAlert("DoggoApp", "Please log in again", "Ok");
});
base.OnAppearing();
bool LoggedIn = await accountPageViewmodel.Login();
if (!LoggedIn)
{
await Navigation.PushAsync(new LoginPage());
return;
}
}
the viewmodel:
public partial class AccountPageViewModel : ObservableObject, INotifyPropertyChanged
{
// == constants ==
private static readonly IAccountService accountService = DependencyService.Get<IAccountService>();
private static readonly ISaveService saveService = DependencyService.Get<ISaveService>();
// == observable Properties ==
private string username;
[ObservableProperty]
private string name;
[ObservableProperty]
private string description;
public string Username
{
get { return username; }
set
{
username = value;
OnPropertyChanged(nameof(Username));
}
}
public Action<bool> NotRefreshTokenAction { get; set; }
// == Relay Commands ==
[RelayCommand]
public async Task<bool> Login()
{
try
{
bool refreshed = await accountService.RefreshToken();
if(refreshed)
{
AccountDto dto = await accountService.GetDisplayAccount();
Username = dto.username;
Name = dto.name;
Description = dto.description;
return true;
}
throw new Exception("refreshed was false");
}
catch(Exception ex)
{
string message = ex.Message;
Console.WriteLine(message);
return false;
}
}
Create Account VM:
namespace DoggoApp.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class CreateAccountPage : ContentPage
{
private static CreateAccountViewModel createAccountViewModel;
public CreateAccountPage()
{
InitializeComponent();
BindingContext = createAccountViewModel = new CreateAccountViewModel();
createAccountViewModel.AccountCreatedAction = (async (obj) =>
{
await DisplayAlert("Doggoapp", "Registration Successfull", "Ok");
await Navigation.PopToRootAsync();
});
}
}
}
Related
I have 2 ViewModels (MVVM). I let show 2 as shown, it only shows data 1 ViewModel (the one below).
I put 1 and it shows up as normal.
This is how I display the data
<RefreshView x:DataType="locals:SliderViewModel"
Command="{Binding LoadSliderCommand}"
IsRefreshing="{Binding IsBusy, Mode=OneWay}">
<StackLayout Padding="8,0,8,4"
BindableLayout.ItemsSource="{Binding SliderShowInfos}"
Orientation="Horizontal"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand">
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout x:DataType="model:SliderShowInfo">
<Frame Padding="4"
HasShadow="False"
IsClippedToBounds="True"
BackgroundColor="Transparent">
<StackLayout Orientation="Horizontal">
<Frame Padding="0"
HasShadow="False"
CornerRadius="7"
IsClippedToBounds="True">
<Image Source="{Binding ImagesSlider}">
</Image>
</Frame>
</StackLayout>
</Frame>
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</RefreshView>
<RefreshView x:DataType="locals:ProductViewModel"
Command="{Binding LoadProductCommand}"
IsRefreshing="{Binding IsBusy, Mode=OneWay}">
<StackLayout Padding="8"
Orientation="Horizontal"
BindableLayout.ItemsSource="{Binding ProductInfos}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Frame Padding="5,0"
HasShadow="False"
IsClippedToBounds="True"
BackgroundColor="#fff">
<StackLayout x:DataType="model:ProductInfo">
</StackLayout>
</Frame>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</RefreshView>
This is how I display the data. I'm trying to display product listing and photo listing data. Please give me a solution that can combine 2 ViewModel
Update
SliderViewModel.cs
public class SliderViewModel:BaseSliderViewModel
{
ISliderShowRepository sliderShowRepository = new SliderShowService();
public Command LoadSliderCommand { get; }
public ObservableCollection<SliderShowInfo> SliderShowInfos { get; }
public SliderViewModel()
{
LoadSliderCommand = new Command(async () => await ExecuteLoadSliderCommand());
SliderShowInfos = new ObservableCollection<SliderShowInfo>();
}
public void OnAppearing()
{
IsBusy = true;
}
async Task ExecuteLoadSliderCommand()
{
}
}
ProductViewModel.cs
public class ProductViewModel : BaseProductViewModel
{
IProductRepository productRepository = new ProductService();
public Command LoadProductCommand { get; }
public ObservableCollection<ProductInfo> ProductInfos { get; }
public Command ProductTappedView { get; }
public ProductViewModel(INavigation _navigation)
{
LoadProductCommand = new Command(async () => await ExecuteLoadProductCommand());
ProductInfos = new ObservableCollection<ProductInfo>();
ProductTappedView = new Command<ProductInfo>(OnViewDetailProduct);
Navigation = _navigation;
}
private async void OnViewDetailProduct(ProductInfo prod)
{
await Navigation.PushAsync(new DetailProduct(prod));
}
public void OnAppearing()
{
IsBusy = true;
}
async Task ExecuteLoadProductCommand()
{
IsBusy = true;
try
{
ProductInfos.Clear();
var prodList = await productRepository.GetProductsAsync();
foreach (var prod in prodList)
{
ProductInfos.Add(prod);
}
}
catch(Exception)
{
throw;
}
finally
{
IsBusy = false;
}
}
}
DashboardViewModel.cs
public class DashboardViewModel
{
public SliderViewModel SliderShowVM { get; set; }
public ProductViewModel ProductVM { get; set; }
}
Dashboard.xaml.cs
public Dashboard()
{
InitializeComponent();
NavigationPage.SetHasNavigationBar(this, true);
BindingContext = new DashboardViewModel();
}
protected override void OnAppearing()
{
base.OnAppearing();
}
You cant just have 2 ViewModels for a single page. Instead, you can create another ViewModel, which will contains those 2 ViewModels that you need.
public class DashboardViewModel
{
public SliderShowViewModel SliderShowVM { get; set; }
public ProductViewModel ProductVM { get; set; }
}
And in dashboard constructor, assign the BindingContext
BindingContext = new DashboardViewModel();
And in your view, bind data like this:
...
<Image Source="{Binding SliderShowVM.ImagesSlider}">
...
BindableLayout.ItemsSource="{Binding ProductVM.ProductInfos}"
...
Also, if you don't need all data from SliderShowViewModel or ProductViewModel on your Dashboard, then you can define the properties that you really need for the dashboard inside DashboardViewModel, and to inject SliderShowViewModel and ProductViewModel instances via constructor (Might not be the best solution, but this way you will keep you view cleaner)
public class DashboardViewModel
{
public string RelevantProperty1 { get; set; }
public int RelevantProperty2 { get; set; }
...
public DashboardViewModel(SliderShowViewModel sliderVm, ProductViewModel productVm)
{
RelevantProperty1 = sliderVm.Something;
RelevantProperty2 = productVm.SomethingElse;
...
}
}
More explicit
DashboardViewModel class: It is a class with two properties of type SliderShowViewModel and ProductViewModel. These properties will store some instances (aka objects) of SliderShowViewModel and ProductViewModel respectively.
Create a DashboardViewModel instance and pass it as BindingContext for your Dashboard View:
public Dashboard()
{
InitializeComponent();
NavigationPage.SetHasNavigationBar(this, true);
var viewModel = new DashboardViewModel();
// Now we have the viewModel, but there is a problem.
// Remember those two viewModels (SliderShowViewModel and ProductViewModel)?
// Well, those properties are null, we have to provide values for them
// before setting the binding context.
viewModel.SliderViewModel = new SliderViewModel();
viewModel.ProductViewModel = new ProductViewModel();
BindingContext = viewModel;
}
Bind values from DashboardViewModel on our View:
<RefreshView Command="{Binding SliderViewModel.LoadSliderCommand}"
IsRefreshing="{Binding SliderViewModel.IsBusy, Mode=OneWay}">
<StackLayout Padding="8,0,8,4"
BindableLayout.ItemsSource="{Binding SliderViewModel.SliderShowInfos}"
Orientation="Horizontal"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand">
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout>
<Frame Padding="4"
HasShadow="False"
IsClippedToBounds="True"
BackgroundColor="Transparent">
<StackLayout Orientation="Horizontal">
<Frame Padding="0"
HasShadow="False"
CornerRadius="7"
IsClippedToBounds="True">
<Image Source="{Binding SliderViewModel.ImagesSlider}">
</Image>
</Frame>
</StackLayout>
</Frame>
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</RefreshView>
<RefreshView Command="{Binding ProductViewModel.LoadProductCommand}"
IsRefreshing="{Binding ProductViewModel.IsBusy, Mode=OneWay}">
<StackLayout Padding="8"
Orientation="Horizontal"
BindableLayout.ItemsSource="{Binding ProductViewModel.ProductInfos}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Frame Padding="5,0"
HasShadow="False"
IsClippedToBounds="True"
BackgroundColor="#fff">
</Frame>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</RefreshView>
Give it a try and let me know if it works.
I am displaying results and I added the ability to edit the displayed result. That works I get pop up with the text I want to edit. However I need to go up and down to display the edited text. I have tried to add IsRefreshong property but that has the same result. Do you have any suggestions? I need to display the edited text after I click on "ok " in my pop up not have to scroll down or up and then see the updated property
here is my xaml
<ListView BackgroundColor="{DynamicResource PageBackgroundColor}" x:Name="list"
HasUnevenRows="True" IsRefreshing="{Binding IsRefreshing}"
HorizontalOptions="CenterAndExpand"
VerticalOptions="FillAndExpand"
VerticalScrollBarVisibility="Never"
CachingStrategy="RecycleElement"
ItemsSource="{Binding Results, Mode=TwoWay}"
SeparatorVisibility="Default"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid BackgroundColor="{DynamicResource PageBackgroundColor}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4*"/>
<ColumnDefinition Width="12*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="12*"/>
<ColumnDefinition Width="4*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="1" Padding="0,3,3,0" Text ="{Binding FieldDescriptor}" Style="{StaticResource SubLabelBlackStyle}" HorizontalOptions="Start" BackgroundColor="{DynamicResource PageBackgroundColor}" HorizontalTextAlignment="Start" />
<Label Grid.Column="3" Padding="0,3,3,0" Text="{Binding FieldValue}" Style="{StaticResource SubLabelBlackStyle}" HorizontalOptions="Start" BackgroundColor="{DynamicResource PageBackgroundColor}" HorizontalTextAlignment="Start" >
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding BindingContext.EditTextCommand, Source={x:Reference Name=list}}" CommandParameter="{Binding .}"/>
</Label.GestureRecognizers>
</Label>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
here is the back of my page
public DisplayResult(RemoteCallResult<IEnumerable<DocumentData>> data)
{
InitializeComponent();
BindingContext = new ResultPageViewModel(data);
}
ViewModel property isRefreshing
public bool IsRefreshing
{
get => _isRefreshing;
private set
{
_isRefreshing = value;
NotifyPropertyChanged("IsRefreshing");
}
}
here is the edit method
public async Task EditTextAsync(DocumentData param)
{
PromptResult pResult = await UserDialogs.Instance.PromptAsync(new PromptConfig
{
InputType = InputType.Default,
Text = param.FieldValue,
Title = param.FieldValue,
});
if(pResult != null)
{
_isRefreshing = true;
param.FieldValue = pResult.Text;
Thread.Sleep(5);
_isRefreshing = false;
}
}
MYmodel
public string FieldValue { get; set; }
public string FieldDescriptor { get; set; }
Text="{Binding FieldValue,Mode=TwoWay}"
Add a two-way binding mode and you have not posted the DocumentData Model, Raise this FieldValue property in the model as well. It should do it.
You do not need to use is IsRefreshing tag for changing the value in the MVVM.
Here is running gif.
Please change the viewModel like following format. Achieve the INotifyPropertyChanged interface for all of your properties.
using System.ComponentModel;
using System.Text;
namespace PanCakeView
{
public class MyModel: INotifyPropertyChanged
{
string fieldValue;
public string FieldValue
{
set
{
if (fieldValue != value)
{
fieldValue = value;
OnPropertyChanged("FieldValue");
}
}
get
{
return fieldValue;
}
}
string fieldDescriptor;
public string FieldDescriptor
{
set
{
if (fieldDescriptor != value)
{
fieldDescriptor = value;
OnPropertyChanged("FieldDescriptor");
}
}
get
{
return fieldDescriptor;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Here is my viewModel. I used ObservableCollection for testing.
public class ResultPageViewModel
{
public ObservableCollection<MyModel> Results { get; set; }
public ICommand EditTextCommand { protected set; get; }
public ResultPageViewModel(ObservableCollection<MyModel> myModels)
{
Results = new ObservableCollection<MyModel>();
foreach (var item in myModels)
{
Results.Add(item);
}
EditTextCommand = new Command<MyModel>(async (key) =>
{
PromptResult pResult = await UserDialogs.Instance.PromptAsync(new PromptConfig
{
InputType = InputType.Default,
Text = key.FieldValue,
Title = "change value",
});
if (pResult!=null)
{
key.FieldValue = pResult.Text;
}
});
}
}
I do not know which style you used. I just use style that I setted.
<ListView BackgroundColor="AliceBlue" x:Name="list"
HasUnevenRows="True"
HorizontalOptions="CenterAndExpand"
VerticalOptions="FillAndExpand"
VerticalScrollBarVisibility="Never"
CachingStrategy="RecycleElement"
ItemsSource="{Binding Results, Mode=TwoWay}"
SeparatorVisibility="Default"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid BackgroundColor="Gray">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4*"/>
<ColumnDefinition Width="12*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="12*"/>
<ColumnDefinition Width="4*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="1" Padding="0,3,3,0" Text ="{Binding FieldDescriptor}" HorizontalOptions="Start" HorizontalTextAlignment="Start" />
<Label Grid.Column="3" Padding="0,3,3,0" Text="{Binding FieldValue}" HorizontalOptions="Start" HorizontalTextAlignment="Start" >
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding BindingContext.EditTextCommand, Source={x:Reference Name=list}}" CommandParameter="{Binding .}"/>
</Label.GestureRecognizers>
</Label>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Here is layout's background code.
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
ObservableCollection<MyModel> data = new ObservableCollection<MyModel>();
data.Add(new MyModel() { FieldDescriptor= "this is a Descriptor", FieldValue="1" });
data.Add(new MyModel() { FieldDescriptor = "this is a Descriptor", FieldValue = "2" });
data.Add(new MyModel() { FieldDescriptor = "this is a Descriptor", FieldValue = "3" });
data.Add(new MyModel() { FieldDescriptor = "this is a Descriptor", FieldValue = "4" });
data.Add(new MyModel() { FieldDescriptor = "this is a Descriptor", FieldValue = "5" });
data.Add(new MyModel() { FieldDescriptor = "this is a Descriptor", FieldValue = "6" });
this.BindingContext = new ResultPageViewModel(data);
}
}
==============Update=====================
If you use PopUp page,Here is running gif.
Here is my popup page code.
<?xml version="1.0" encoding="utf-8" ?>
<pages:PopupPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:Rg.Plugins.Popup.Pages;assembly=Rg.Plugins.Popup"
x:Class="PanCakeView.MyPopUpPage">
<Frame
VerticalOptions="Center"
HorizontalOptions="Center"
Padding="20, 20, 20, 20">
<StackLayout>
<Entry x:Name="entryCardName"
FontSize="Small"
Placeholder="{Binding FieldValue}"
Text="{Binding FieldValue}"
TextColor="Black"
ReturnType="Next">
</Entry>
<Button Text="Ok" Command="{Binding ConfirmPopUpCommand}" CommandParameter="{Binding }"/>
</StackLayout>
</Frame>
</pages:PopupPage>
PopUp page's background code.
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MyPopUpPage : Rg.Plugins.Popup.Pages.PopupPage
{
//public string FieldValue { get; set; }
private string _fieldValue;
public Command ConfirmPopUpCommand { get; }
public string FieldValue
{
set
{
_fieldValue = value;
}
get { return _fieldValue; }
}
public MyPopUpPage(MyModel myModel)
{
InitializeComponent();
_fieldValue = myModel.FieldValue;
ConfirmPopUpCommand = new Command(async (key) => {
myModel.FieldValue = FieldValue;
await PopupNavigation.Instance.PopAsync(true);
});
this.BindingContext = this;
}
}
}
Here is ResultPageViewModel.cs new code.
public class ResultPageViewModel
{
private ObservableCollection<MyModel> data;
private INavigation navigation;
public ObservableCollection<MyModel> Results { get; set; }
public ICommand EditTextCommand { protected set; get; }
public ResultPageViewModel(ObservableCollection<MyModel> myModels, INavigation navigation)
{
this.navigation = navigation;
Results = new ObservableCollection<MyModel>();
foreach (var item in myModels)
{
Results.Add(item);
}
EditTextCommand = new Command<MyModel>(async (key) =>
{
//PromptResult pResult = await UserDialogs.Instance.PromptAsync(new PromptConfig
//{
// InputType = InputType.Default,
// Text = key.FieldValue,
// Title = "change value",
//});
//if (pResult!=null)
//{
// key.FieldValue = pResult.Text;
//}
await navigation.PushPopupAsync(new MyPopUpPage(key));
});
}
}
}
You need add Navigation attribute in DisplayResult page. this.BindingContext = new ResultPageViewModel(data, Navigation);
I am displaying my properties in list. I used to use Acr.UserDialogs to display popup however I have decided to switch to RG for better UI.
Now when I clock on one field and edit it when I go back to the original page the value stay the same as it was before update. I have tried to add RefreshPull but that doesn't help
Back of my page
public DisplayResult(RemoteCallResult<IEnumerable<DocumentData>> data)
{
InitializeComponent();
BindingContext = _resultPageViewModel = new ResultPageViewModel(data);
}
my Xaml
<ListView BackgroundColor="{DynamicResource PageBackgroundColor}" x:Name="list" IsPullToRefreshEnabled="{Binding Update}"
ItemsSource="{Binding Results, Mode=TwoWay}"
SeparatorVisibility="Default"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid BackgroundColor="{DynamicResource PageBackgroundColor}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4*"/>
<ColumnDefinition Width="12*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="12*"/>
<ColumnDefinition Width="4*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="1" Padding="0,3,3,0" Text ="{Binding FieldDescriptor}" Style="{StaticResource SubLabelBlackStyle}" HorizontalOptions="Start" BackgroundColor="{DynamicResource PageBackgroundColor}" HorizontalTextAlignment="Start" />
<Label Grid.Column="3" Padding="0,3,3,0" Text="{Binding FieldValue, Mode=TwoWay}" Style="{StaticResource SubLabelBlackStyle}" HorizontalOptions="Start" BackgroundColor="{DynamicResource PageBackgroundColor}" HorizontalTextAlignment="Start" >
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding BindingContext.EditTextCommand, Source={x:Reference Name=list}}" CommandParameter="{Binding .}"/>
</Label.GestureRecognizers>
</Label>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
VIEWMODEL
public string FieldValue
{
get => _fieldValue;
set => SetValue(ref _fieldValue, value);
}
public ResultPageViewModel()
{
}
public ResultPageViewModel(RemoteCallResult<IEnumerable<DocumentData>> data)
{
EditTextCommand = new Command<DocumentData>(async (key) => await EditTextAsync(key));
Update = new Command<string>(async (key) => await UpdateValue(key));
LoadText(data); }
public async Task EditTextAsync(DocumentData param)
{
await Navigation.PushPopupAsync(new EditPopUp(param));
}
public async Task UpdateValue(string value)
{
_fieldValue = value;
}
ViewModel POPUP
private readonly ResultPageViewModel _resultPageViewModel;
private string _fieldValue;
public Command ConfirmPopUpCommand { get; }
public string FieldValue
{
set
{
_fieldValue = value;
NotifyPropertyChanged(nameof(FieldValue));
}
get { return _fieldValue; }
}
public EditPopUpViewModel(DocumentData param)
{
_resultPageViewModel = new ResultPageViewModel();
_fieldValue = param.FieldValue;
ConfirmPopUpCommand = new Command(async () => await ExecuteConfirmPopUpCommand());
}
private async Task ExecuteConfirmPopUpCommand()
{
await _resultPageViewModel.UpdateValue(_fieldValue);
await PopupNavigation.Instance.PopAsync(true);
}
POP UP
<Entry x:Name="entryCardName"
FontSize="Small"
Placeholder="{Binding FieldValue}"
Text="{Binding FieldValue}"
TextColor="Black"
ReturnType="Next">
</Entry>
When you show the pop up, I think you should also pass the ViewModel to the popupPage:
public async Task EditTextAsync(DocumentData param)
{
await Navigation.PushPopupAsync(new EditPopUp(param), this);
}
Then in the POPUP, you does not need to create a new one:
public MainPage(DocumentData param, ResultPageViewModel model)
{
_fieldValue = param.FieldValue;
Command ConfirmPopUpCommand = new Command(async () => await ExecuteConfirmPopUpCommand(model));
}
private async Task ExecuteConfirmPopUpCommand(ResultPageViewModel model)
{
await model.UpdateValue(_fieldValue);
await PopupNavigation.Instance.PopAsync(true);
}
this is updating the private internal field, bypassing the setter and NotifyPropertyChanged
public async Task UpdateValue(string value)
{
_fieldValue = value;
}
you want to update the public property that will fire the setter and NotifyPropertyChanged
public async Task UpdateValue(string value)
{
FieldValue = value;
}
As the title indicates I have problems with navigation.
I have a page called EvaluationPage, It contains four ContentPage inside has a Grid.
On this level I have a button (FAB) and CollectionView.
The CollectionView has Itemsource with ObservableCollection
The FAB open ReferenciaLaboralPage in mode Modal. If the form that has this page is filled out and the save button is clicked, add an item to the observable collection of the itemsource. It is done by Singleton.
The collectionview updates the new item, the item has a tap gesture that executes a command to open the page that was previously opened in modal mode and this is where it is not working. The command does run but the NavigateAsync does not display the page.
XAML EvaluacionPage
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<CollectionView ItemsSource="{Binding ReferenciasPersonales}" EmptyView="No existen referencias" VerticalOptions="FillAndExpand" Grid.Row="0">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical"
ItemSpacing="0" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Grid>
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding SelectedCommand}" NumberOfTapsRequired="1" />
</Grid.GestureRecognizers>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="80" />
</Grid.ColumnDefinitions>
<pancake:PancakeView Grid.Column="0">
<Label Text="{Binding Letter}" TextColor="White" />
</pancake:PancakeView>
<StackLayout Grid.Column="1" >
<Label Text="{Binding Nombre}" Style="{StaticResource RobotoBold}" FontSize="16" />
<Label Text="{Binding TipoReferencia}" Style="{StaticResource RobotoRegular}" />
<Label Text="{Binding Telefono}" Style="{StaticResource RobotoRegular}" />
<Label Text="{Binding TiempoConocerlo}" Style="{StaticResource RobotoRegular}" />
</StackLayout>
<Label Grid.Column="2" Text="{Binding FechaRegistro, StringFormat='{0:dd/MM/yyyy}'}" />
</Grid>
<BoxView HorizontalOptions="FillAndExpand" BackgroundColor="LightGray" HeightRequest="1" />
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<pancake:PancakeView Elevation="10" VerticalOptions="End" HorizontalOptions="End" Margin="0,0,20,22" HeightRequest="50" WidthRequest="50" CornerRadius="25" Grid.Row="0">
<pancake:PancakeView.GestureRecognizers>
<TapGestureRecognizer Command="{Binding OpenReferenciaCommand}" NumberOfTapsRequired="1" />
</pancake:PancakeView.GestureRecognizers>
<Label Text="" TextColor="White" Style="{StaticResource IconStyleSolid}" VerticalOptions="Center" HorizontalOptions="Center" />
</pancake:PancakeView>
</Grid>
CODE EvaluacionPageViewModel
public DelegateCommand OpenReferenciaCommand => _openReferenciaCommand ?? (_openReferenciaCommand = new DelegateCommand(ShowReferenciaPersonal));
async void ShowReferenciaPersonal()
{
await _navigationService.NavigateAsync("ReferenciaPersonalPage", useModalNavigation: true);
}
// Add item to datasource
public void AddReferenciaPersonal(ReferenciaPersonalItemViewModel item)
{
if (item.IdReferencia == 0)
{
ReferenciasPersonales.Add(item);
}
else
{
ReferenciasPersonales.Remove(ReferenciasPersonales.Where(w=>w.IdReferencia == item.IdReferencia).Single());
ReferenciasPersonales.Add(item);
ReferenciasPersonales = ReferenciasPersonales;
}
}
CODE ReferenciaPersonalPageViewModel
public class ReferenciaPersonalPageViewModel : ViewModelBase
{
private readonly INavigationService _navigationService;
private readonly IApiService _apiService;
private readonly IDialogService _dialogService;
private DelegateCommand _closeCommand;
private DelegateCommand _saveCommand;
private string _nombre;
private ReferenciaPersonalItemViewModel _item;
private bool _isRunning;
private bool _isEnable;
public ReferenciaPersonalPageViewModel(INavigationService navigationService, IApiService apiService, IDialogService dialogService) : base(navigationService)
{
_navigationService = navigationService;
_apiService = apiService;
_dialogService = dialogService;
_item = new ReferenciaPersonalItemViewModel(navigationService);
}
public string Nombre
{
get => _nombre;
set => SetProperty(ref _nombre, value);
}
public bool IsRunning
{
get => _isRunning;
set => SetProperty(ref _isRunning, value);
}
public bool IsEnable
{
get => _isEnable;
set => SetProperty(ref _isEnable, value);
}
#endregion
#region Commands
public DelegateCommand CloseCommand => _closeCommand ?? (_closeCommand = new DelegateCommand(ExecuteCloseCommand));
public DelegateCommand SaveCommand => _saveCommand ?? (_saveCommand = new DelegateCommand(ExecuteSaveCommand));
#endregion
#region Metodos
async void ExecuteCloseCommand()
{
await _navigationService.GoBackAsync();
}
private async void ExecuteSaveCommand()
{
IsRunning = true;
IsEnable = false;
_item.Direccion = Direccion;
_item.Nombre = Nombre;
_item.Telefono = Telefono;
_item.TiempoConocerlo = TiempoConocerlo;
_item.TipoReferencia = TipoReferencia;
_item.FechaRegistro = _item.IdReferencia > 0 ? _item.FechaRegistro : DateTime.Now.ToLocalTime();
EvaluacionPageViewModel.GetInstance().AddReferenciaPersonal(_item);
IsRunning = false;
IsEnable = true;
await _navigationService.GoBackAsync();
}
#endregion
public override void OnNavigatedTo(INavigationParameters parameters)
{
base.OnNavigatedTo(parameters);
if (parameters.ContainsKey("item"))
{
_item = parameters.GetValue<ReferenciaPersonalItemViewModel>("item");
Nombre = _item.Nombre;
Direccion = _item.Direccion;
Telefono = _item.Telefono;
TiempoConocerlo = _item.TiempoConocerlo;
TipoReferencia = _item.TipoReferencia;
}
}
}
CODE ReferenciaPersonalItemViewModel
public class ReferenciaPersonalItemViewModel : Referencia
{
private readonly INavigationService _navigationService;
private DelegateCommand _selectedCommand;
public ReferenciaPersonalItemViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
}
public DelegateCommand SelectedCommand => _selectedCommand ?? (_selectedCommand = new DelegateCommand(ShowItem));
private async void ShowItem()
{
await _navigationService.NavigateAsync("ReferenciaPersonalPage", useModalNavigation: true);
}
}
When I click on the (fab) button to open the modal the
NavigationUriPath before the NavigateAsync is:
/MainMasterDetailPage/NavigationPage/HomePage/ItineraryPage/EvaluationPage
When I click on the collectionview item to open modal the
NavigationUriPath before the NavigateAsync is:
/MainMasterDetailPage/NavigationPage/HomePage/ItinerarioPage/EvaluacionPage/ReferenciaPersonalPage?useModalNavigation=true
If I put the absolute path if it shows the page but shows it with a
navigation Bar
I'm trying to create login page in xamarin forms using mvvm design pattern
I got and expectation in my code but i don't know the reason
I Created ViewModel page it's code
public class LoginViewModel:ContentPage,INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public User user { get; set; }
private string username;
public string Username
{
get { return username; }
set
{
username = value;
PropertyChanged(this, new PropertyChangedEventArgs("Username"));
}
}
private string password;
public string Password
{
get { return password; }
set { password = value;
PropertyChanged(this, new PropertyChangedEventArgs("Password"));
}
}
public ICommand SubmitCommand { set; get; }
public LoginViewModel()
{
user = new User();
SubmitCommand = new Command(OnSubmit);
}
private async Task<bool> CheckLogin()
{
user = await Services.LoginService.GetUserByUsername(username);
if (user != null)
{
if (user.Password.Trim().Equals(password))
{
return true;
}
else
{
await LoginView.loginView.DisplayAlert("Error", "Invalid Login, try again", "OK");
return false;
}
}
return false;
}
private async void OnSubmit()
{
await CheckLogin();
}
}
}
and MyLoginView.xaml
<ContentPage
xmlns:vm="clr-namespace:PickNgo.ViewModel"
x:Class="PickNgo.View.LoginView"
BackgroundColor="White">
<ContentPage.Content>
<StackLayout>
<StackLayout BackgroundColor="#F27D0C">
<Image HorizontalOptions="Center" WidthRequest="300" Source="logo.png"/>
</StackLayout>
<StackLayout Padding="100,10,100,0">
<Frame CornerRadius="10" BackgroundColor="White" Padding="0" Margin="0">
<Entry x:Name="Email" Text="{Binding Username}" Placeholder="Enter your username"
PlaceholderColor="Black"
Keyboard="Email"
TextColor="Black"/>
</Frame>
<BoxView HeightRequest="10"/>
<Frame CornerRadius="10" BackgroundColor="White" Padding="0" Margin="0">
<Entry x:Name="Password" Text="{Binding Password}" Placeholder="Enter your password"
PlaceholderColor="Black" HeightRequest="40" WidthRequest="40"
IsPassword="True"
TextColor="Black"/>
</Frame>
</StackLayout>
<StackLayout Padding="100,30,100,0">
<Frame CornerRadius="20" BackgroundColor="#FF6600" Padding="0" Margin="0">
<Button Command="{Binding SubmitCommand}" Text="Login" Margin="0" TextColor="White" BackgroundColor="#FF6600"
FontAttributes="Bold" FontSize="Large"/>
</Frame>
</StackLayout>
</StackLayout>
</ContentPage.Content>
</ContentPage>
and my login.cs
public partial class LoginView : ContentPage
{
public Action DisplayInvalidLoginPrompt;
public static LoginView loginView;
public LoginView ()
{
InitializeComponent();
}
}
It throw objectSystem.NullReferenceException: Object reference not set to an instance of an object.
in this line
await LoginView.loginView.Navigation.PushModalAsync(new WelcomeView());
when i debug i found loginview is set to null but i don't know why ... any help ?
Its because you haven't initialized LoginView's loginView variable.
To be honest, you really don't need the public static LoginView loginView variable (the class itself is already of type LoginView). If you want to display an alert, simply use PickNgo.App.Current.MainPage.DisplayAlert("", "", "OK"); (assuming PickNgo is the name of your project)