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);
Related
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();
});
}
}
}
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 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;
}
I'm trying to setup a gallery of images, that also features the ability to click on the image to show/hide a grid with image description, using the MVVM process.
However, i think my model, view model and view is not correctly setup because i am only able to display the images in the stacklayout if i use BindableLayout.ItemsSource="{Binding GalleryList.Gallery}" in the top stacklayout, but not able to access the other parts of the class, i.e. only the Gallery list.
So essentially, I cant access InfoGridVisible binding, since the it can't find/reach it? (i'm not sure to be honest).
Here is a snippet of my work so far:
View - testpage.xaml:
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="testproject.Pages.testpage"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:styles="clr-namespace:testproject.Styles"
xmlns:behaviours="clr-namespace:testproject.ViewModels"
mc:Ignorable="d"
xmlns:pancakeview="clr-namespace:Xamarin.Forms.PancakeView;assembly=Xamarin.Forms.PancakeView"
xmlns:controls="clr-namespace:ImageCircle.Forms.Plugin.Abstractions;assembly=ImageCircle.Forms.Plugin"
BackgroundColor="Orange"
x:Name="Root">
<ContentPage.Content>
<ScrollView>
<Grid
Grid.Row="3">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!--PROBLEM STARTS HERE -->
<StackLayout x:Name="GalleryStk" Grid.Row="1" Orientation="Vertical" BindableLayout.ItemsSource="{Binding GalleryList.Gallery}" Margin="10,0,10,0">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Grid RowSpacing="5" ColumnSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="AUTO"/>
<RowDefinition Height="AUTO"/>
</Grid.RowDefinitions>
<Frame x:Name="ImageFrame" CornerRadius="20" Padding="0" IsClippedToBounds="True" Grid.Row="0">
<Image Source="{Binding Image}" Aspect="AspectFill" Grid.RowSpan="2" HorizontalOptions="Center" VerticalOptions="Center">
<Image.GestureRecognizers>
<TapGestureRecognizer BindingContext="{Binding Source={x:Reference Root}, Path=BindingContext}" Command="{Binding TapCommand}"/>
</Image.GestureRecognizers>
</Image>
</Frame>
<!--<Image Source="ShadowOverlay" Grid.RowSpan="2" Aspect="Fill" VerticalOptions="End" HorizontalOptions="Fill" />-->
<Grid x:Name="InfoGrid" RowSpacing="10" ColumnSpacing="10" Grid.Row="0" IsEnabled="False" IsVisible="{Binding GalleryList.InfoGridVisible, Source={x:Reference InfoGrid}}">
<Grid.RowDefinitions>
<RowDefinition Height="AUTO"/>
<RowDefinition Height="25"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="AUTO"/>
</Grid.ColumnDefinitions>
<BoxView Color="Black" Opacity="0.5" CornerRadius="20" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" Grid.Row="0" Grid.RowSpan="5" Grid.ColumnSpan="3"/>
<Label Text="{Binding Title}" Padding="10,10,0,0" Grid.Row="0" Grid.ColumnSpan="3" Style="{StaticResource TitleLabel}" LineBreakMode="NoWrap"/>
<!--<controls:CircleImage Source="{Binding ProfileImage}" Aspect="AspectFill" Grid.Row="1" Grid.Column="0" WidthRequest="25" HeightRequest="25" />-->
<Label Text="{Binding Description}" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" Padding="10,0,10,0" Grid.Row="1" Grid.RowSpan="4" Grid.ColumnSpan="3" Style="{StaticResource HandleLabel}"/>
<!--<Button Text="See More" x:Name="ExpandContractButton" Clicked="ExpandContractButton_Clicked" Padding="10,0,10,0" Grid.Row="2" Grid.RowSpan="3" Grid.ColumnSpan="3"/>-->
<!--<StackLayout Orientation="Horizontal" VerticalOptions="Center" Grid.Column="2" Grid.Row="1" Spacing="5">
<Image Source="Eye"/>
<Label Text="{Binding ViewCount, StringFormat='{0:N0}'}" Style="{StaticResource HandleLabel}" />
<Label Text="views" Style="{StaticResource BodyLabel}"/>
</StackLayout>-->
</Grid>
</Grid>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</Grid>
</ScrollView>
</ContentPage.Content>
</ContentPage>
Code behind for view - testpage.xaml.cs
public partial class testpage : ContentPage
{
private Image _galleryImage;
public Grid GalleryInfoGrid;
private static testpage _instance;
public UserProfileViewModel vm { get; }
public static testpage Instance
{
get
{
if (_instance == null)
_instance = new testpage();
return _instance;
}
}
public testpage()
{
InitializeComponent();
vm = new UserProfileViewModel();
BindingContext = new UserProfileViewModel();
//var _galleryInfoGrid = (Grid)Root.FindByName("InfoGrid");
//_galleryInfoGrid.IsEnabled = true;
//_galleryInfoGrid.IsVisible = true;
}
Model:
{
public List<GalleryImage> Gallery { get; set; }
public string InfoGridVisible;
public string InfoGridEnabled;
}
public class GalleryImage
{
public string Title { get; set; }
public string Image { get; set; }
public string Description { get; set; }
}
Services (data):
public class ProfileService
{
private static ProfileService _instance;
public static ProfileService Instance
{
get
{
if (_instance == null)
_instance = new ProfileService();
return _instance;
}
}
public GalleryList GetGallery()
{
return new GalleryList
{
InfoGridEnabled = "False",
InfoGridVisible = "False",
Gallery = new List<GalleryImage>
{
new GalleryImage { Title="sevilla01.jpg", Image = "sevilla01.jpg", Description="Description1Description1Description1Description1 Description1Description1Description1 Description1Description1" },
new GalleryImage { Title="sevilla02.jpg", Image = "sevilla02.jpg", Description="Description2 Description1Description1Description1Description1Descript ion1Description1" },
new GalleryImage {Title="sevilla03.jpg", Image = "sevilla03.jpg", Description="Description3Description1Description1Description1" },
new GalleryImage {Title="sevilla04.jpg", Image = "sevilla04.jpg", Description="Description4Description1Description1" },
new GalleryImage {Title="sevilla05.jpg", Image = "sevilla05.jpg", Description="Description5Description1" },
new GalleryImage {Title="sevilla06.jpg", Image = "sevilla06.jpg", Description="Description6" },
new GalleryImage {Title="sevilla07.jpg", Image = "sevilla07.jpg", Description="Description7Description1Description1Description1Description1Description1Description1Description1Description1 Description1" }
}
};
}
}
View Model - UserProfileViewModel.cs:
public class UserProfileViewModel : BindableObject
{
//Models
private Profile _profile;
private GalleryList _galleryList;
int taps = 0;
ICommand tapCommand;
public UserProfileViewModel()
{
Profile = ProfileService.Instance.GetProfile();
GalleryList = ProfileService.Instance.GetGallery();
tapCommand = new Command(OnTapped);
}
public ICommand TapCommand
{
get { return tapCommand; }
}
void OnTapped(object s)
{
taps++;
//var info1Grid = testpage.Instance.GalleryInfoGrid;
//info1Grid.IsVisible = true;
//info1Grid.IsEnabled = true;
//GalleryList.InfoGridEnabled = "False";
//GalleryList.InfoGridVisible = "False";
Console.WriteLine("parameter: " + taps + " " + GalleryList.InfoGridVisible);
OnPropertyChanged();
}
public Profile Profile
{
get { return _profile; }
set
{
_profile = value;
OnPropertyChanged();
}
}
public GalleryList GalleryList
{
get { return _galleryList; }
set
{
_galleryList = value;
OnPropertyChanged();
}
}
}
Apologies for the code dump, any help would be appreciated as i have been stuck on this for a few days.
If there is a better method of implementing this, i would love to hear it.
What I want you try is something like this:
public class GalleryList : BindableObject {
private string _InfoGridVisible { get; set; }
private string _InfoGridEnabled { get; set; }
public List<GalleryImage> Gallery { get; set; }
public string InfoGridVisible
{
get { return _InfoGridVisible; }
set
{
_InfoGridVisible = value;
OnPropertyChanged();
}
}
public string InfoGridEnabled
{
get { return _InfoGridEnabled; }
set
{
_InfoGridEnabled = value;
OnPropertyChanged();
}
}
}
public class GalleryImage : BindableObject {
private string _Title { get; set; }
private string _Image { get; set; }
private string _Description { get; set; }
public string Title
{
get { return _Title; }
set
{
_Title = value;
OnPropertyChanged();
}
}
public string Image
{
get { return _Image; }
set
{
_Image = value;
OnPropertyChanged();
}
}
public string Description
{
get { return _Description; }
set
{
_Description = value;
OnPropertyChanged();
}
}
}
InfoGridVisible is not a property and hence binding when performs a lookup will never find it, the way to solve this is :
public class GalleryList
{
public List<GalleryImage> Gallery { get; set; }
public string InfoGridVisible { get; set; }
public string InfoGridEnabled { get; set; }
}
In the case of how to be able to get the Object within a Command you need to tell your TapGesture how to access to that command through this 2 options:
<TapGestureRecognizer Command ="{Binding Path=BindingContext.CommandToCall, Source={x:Reference Name=ParentPage}}" CommandParameter="{Binding .}"/>
Or:
<TapGestureRecognizer Command ="{Binding Source={RelativeSource AncestorType={x:Type vm:vmWhereCommandIs}}, Path=CommandToCall}" CommandParameter="{Binding .}"/>
Just in case you need more info here there are some resources:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/data-binding/relative-bindings
https://www.xamarinexpert.it/how-to-correctly-use-databinding-with-listview-and-collectionview/
All,
I am binding my Listview to a collection from a Viewmodel. CellView of the ListView includes an image. I would like to invoke a command in my viewmodel when I click the image in the list item.I am trying to avoid event handling in my model. Any idea ?
thanks !
Given below is the xaml and view model.
ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:jList="clr-namespace:JList;assembly=JList"
x:Class="JList.Pages.ItemDetailPage"
Title="Sub Items"
BindingContext="{Binding Source={StaticResource Locator}, Path=ItemDetailViewModel}"
>
<ContentPage.ToolbarItems >
<ToolbarItem Text="Add" Order="Primary" Priority="1" Command="{Binding AddItemCommand}"></ToolbarItem>
<ToolbarItem Text="Edit" Order="Primary" Priority="2" Command="{Binding EditItemCommand}"></ToolbarItem>
</ContentPage.ToolbarItems>
<StackLayout>
<SearchBar Placeholder="Search..." VerticalOptions="Fill" SearchCommand="{Binding SearchCommand}" Text="{Binding SearchString}" ></SearchBar>
<ListView RowHeight="200" ItemsSource="{Binding SubItemsCollection}" BackgroundColor="Gainsboro" SelectedItem="{Binding SubItemSelected, Mode=TwoWay}" x:Name="List" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell >
<StackLayout>
<Grid VerticalOptions="Fill" BackgroundColor="White" Padding="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image Source="{Binding ImagePath}" Aspect="AspectFit">
<Label Grid.Row="0" Grid.Column="0" Text="{Binding Name}" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" TextColor="Chocolate" Font="Bold,20" />
</Grid>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
<ContentPage.Behaviors>
<jList:CustomBehavior />
</ContentPage.Behaviors>
View Model
namespace JList.Core.ViewModels
{
public class ItemDetailViewModel : ViewModelBase, IViewModel
{
private IItemService _itemService;
private ICommandFactory _cmdFactory;
private INavigationService _navService;
private ItemListViewModel _parent;
private IAppInstanceData _appData;
public ItemDetailViewModel(IItemService itemService, ICommandFactory cmdFactory, INavigationService navService, IAppInstanceData appData, ItemListViewModel parent)
{
_itemService = itemService;
_cmdFactory = cmdFactory;
_navService = navService;
_parent = parent;
ParentItemSelected = _parent.ItemSelected.Id;
_appData = appData;
// FetchSubItemsAsync();
}
public int ParentItemSelected { get; set; }
private string _searchString;
public String SearchString
{
get { return _searchString; }
set
{
if (_searchString != value)
{
_searchString = value;
OnPropertyChanged();
}
}
}
private ObservableCollection<SubItem> _subItemsCollection;
public ObservableCollection<SubItem> SubItemsCollection
{
get { return _subItemsCollection; }
set
{
if (_subItemsCollection != null)
{
if (!_subItemsCollection.SequenceEqual(value))
{
_subItemsCollection = value;
OnPropertyChanged();
}
}
else
{
_subItemsCollection = value;
OnPropertyChanged();
}
}
}
private async void FetchSubItemsAsync()
{
ParentItemSelected = _parent.ItemSelected.Id;
var items = await _itemService.GetAllSubItemsAsync(_parent.ItemSelected.Id);
var coll = new ObservableCollection<SubItem>();
foreach (var it in items)
{
coll.Add(it);
}
SubItemsCollection = coll;
}
public void RefreshAsync()
{
FetchSubItemsAsync();
}
private SubItem _itemSelected;
public SubItem SubItemSelected
{
get => _itemSelected;
set
{
_itemSelected = value;
// _navService.PushView(typeof(EditSubItemViewModel).ToString());
}
}
#region FetchCommand
private ICommand _fetchItemsCommand;
public ICommand FetchItemsCommand
{
get
{
if (_fetchItemsCommand == null)
_fetchItemsCommand = _cmdFactory.CreateCommand(FetchSubItemsAsync, () => true);
return _fetchItemsCommand;
}
}
#endregion
#region AddItemCommand
private ICommand _addItemCommand;
public ICommand AddItemCommand
{
get
{
if (_addItemCommand == null)
_addItemCommand = _cmdFactory.CreateCommand(AddItem, () => true);
return _addItemCommand;
}
}
public void AddItem()
{
_appData.IsEditSubItem = false;
_navService.PushView(typeof(SubItemViewModel).ToString());
}
#endregion
#region EditItemCommand
private ICommand _editItemCommand;
public ICommand EditItemCommand
{
get
{
if (_editItemCommand == null)
_editItemCommand = _cmdFactory.CreateCommand(EditItem, () => true);
return _editItemCommand;
}
}
public void EditItem()
{
_appData.IsEditSubItem = true;
_navService.PushView(typeof(SubItemViewModel).ToString());
}
#endregion
#region SearchCommand
private ICommand _searchCommand;
public ICommand SearchCommand
{
get
{
if (_searchCommand == null)
_searchCommand = _cmdFactory.CreateCommand(SearchItemAsync, () => true);
return _searchCommand;
}
}
private async void SearchItemAsync()
{
var items = await _itemService.GetAllSubItemsAsync(_parent.ItemSelected.Id);
var sstring = SearchString.ToLower();
items = items.Where(i => i.Name.ToLower().Contains(sstring));
var coll = new ObservableCollection<SubItem>();
foreach (var it in items)
{
coll.Add(it);
}
SubItemsCollection = coll;
}
#endregion
}
}
You can add TapGestureRecognizer to the image and bind the command in your ViewModel. Also, you are binding the command inside ViewCell, so you need to set the source of BindingContext.
<ContentPage x:Name="ABCPage">
...
<Image Source="abc">
<Image.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Path=BindingContext.ImageCommand, Source={x:Reference Name=ABCPage}}"
CommandParameter="{Binding .}" />
</Image.GestureRecognizers>
</Image>
...
</ContentPage>