Binding with Prism Navigation - xaml

I'm trying to bind string path in XAML using prism navigateto extension method from a button in ListView.
Apparently the BindingContext isn't recognized to be the same as in the ListView
Here's the sample code of what i'm trying to achieve.
<ListView
x:Name="MainMenu"
CachingStrategy="RetainElement"
HasUnevenRows="True"
ItemsSource="{Binding MenuItems}"
Margin="20,0,0,0"
SeparatorVisibility="Default">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<buttons:SfButton
Style="{StaticResource BaseButtonStyle}"
HeightRequest="70"
TextColor="{StaticResource MenuTextColor}"
Text="{Binding Title}"
HorizontalTextAlignment="Start"
VerticalOptions="CenterAndExpand"
HorizontalOptions="FillAndExpand"
ImageSource="{Binding MenuItemType, Converter={StaticResource MenuItemTypeConverter}}"
ShowIcon="True"
Command="{prism:NavigateTo Name={Binding Item.View}}"
/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Any ideas how to set the binding in this situations?
Regards.

Within any sort of ListView, CollectionView, etc where you are binding an ItemsSource and then have some sort of DataTemplate to display an individual item in that collection, the Binding Context within that DataTemplate is the individual item in the collection not the ViewModel that that provides the Binding Context of both your Page and the ListView.
There are technically a couple parts to this that you will want to understand. Let's say that your model looks like:
public class FooModel
{
public string Text { get; set; }
public string NavigationPath { get; set; }
}
And let's say that you have a collection of FooModel like:
public ObservableCollection<FooModel> FooItems { get; }
In XAML you might have something like:
<ListView ItemsSource="{Binding FooItems}">
However when you go to reference properties of FooItem you will just reference them like:
<ListView.DataTemplate>
<DataTemplate>
<ViewCell>
<Button Text="{Binding Text}"
Command="{prism:NavigateTo Name={Binding NavigationPath}" />
</ViewCell>
</DataTemplate>
</ListView.DataTemplate>
Now assuming that the issue isn't that you're just adding Item erroneously, let's look at some other possible issues/solutions. To start let's look at the start of our page.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="http://prismlibrary.com"
x:Name="page"
x:Class="HelloWorld.Views.ViewA">
The big thing to notice here is that I've added the x:Name attribute so that I can reference the page itself later. In general within the context of something like a ListView if I need to access say the FooCommand property within my ViewModel I might would change my XAML markup from:
<Button Command="{Binding FooCommand}" />
To instead look at the Page's BindingContext like this:
<Button Command="{Binding BindingContext.FooCommand, Source={x:Reference page}}" />
While this will help you in general within your ListView it still doesn't necessarily help you with the issue of using Prism's Navigation Extensions. For this you may need to pass in the SourcePage like the following:
<Button Command="{prism:NavigateTo 'Foo', SourcePage={x:Reference page}}" />
In the event this doesn't work for some reason then you may be possible that the BindingContext isn't getting set properly on the Navigation Extension itself. To work around this you would want to update your command as follows:
<Button x:Name="cellButton"
Command="{prism:NavigateTo Name={Binding View},
BindingContext={x:Reference cellButton}}" />
Note that if you need to reference the SourcePage or add the BindingContext to resolve your issue, please provide a sample that reproduces the issue and open an issue on GitHub.

Related

ListView doesn't display data in xamarin

I am new to xamarin and trying to display a listview. My attempt is as follows.
<ContentPage.Content>
<StackLayout>
<ListView x:Name="ComListView" RowHeight="80">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<Grid Margin="8">
<Label Grid.Column="0" Grid.Row="0" Grid.RowSpan="2" Grid.ColumnSpan="2" Text="hello world" FontAttributes="Bold" />
</Grid>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
But when I ran the app, even I navigate to correct page; nothing display. For any other views, it will display the right output. So where could I get wrong on the listview?
You have to put items to the ListView. Currently, you only specify the ItemTemplate, which determines how an item will look (in your case, they will all look the same (i.e. Labels with 'hello world' text)). But the ListView is empty and so there are no items to show.
You can set items using the ItemsSource property of the ListView.
Usually, you use Binding inside an ItemTemplate to show item content.
Let's say we have a class Person:
class Person
{
public string Name { get; set; }
}
Then you create a List of Person objects and add it to your ListView in code:
var personnel = new List<Person>
{
new Person { Name = "Booker" },
new Person { Name = "Elizabeth" }
};
ComListView.ItemsSource = personnel;
And in your XAML, you have to set the ItemTemplate so that it shows the Name of each Person:
<Label Text="{Binding Name, Mode=OneWay}" />
Note: You can also bind to ItemsSource from XAML if you have a ViewModel.
You are not binding the data to the listview.
use the ItemSource property to bind data, either from xaml, code behind or use a binding to populate from your ViewModel

SelectedItem binding is not updating the ViewModel

I feel like I'm missing something obvious because this is so simple. Thanks in advance for the help. I'm struggling with binding the SelectedItem of a simple ListView in a Xamarin application. I'm testing on UWP and am using Prism's MVVM BindableBase base class. Here's what I'm experiencing:
The page loads and nothing in the list is selected.
I select an item in the list.
The setter of SelectedGrade is called with a value of null.
After that, selecting items does not cause the SelectedGrade setter to be called.
Here's the relevant XAML:
<ListView BackgroundColor="#7F7F7F"
CachingStrategy="RecycleElement"
IsPullToRefreshEnabled="True"
IsRefreshing="{Binding IsBusy, Mode=OneWay}"
ItemsSource="{Binding Grades, Mode=OneWay}"
SelectedItem="{Binding SelectedGrade, Mode=TwoWay}"
RefreshCommand="{Binding RefreshCommand}"
RowHeight="50">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout HorizontalOptions="FillAndExpand"
VerticalOptions="CenterAndExpand"
Orientation="Horizontal"
Padding="10">
<Label HorizontalOptions="FillAndExpand"
Text="{Binding Title}"
TextColor="#272832"/>
<Label HorizontalOptions="FillAndExpand"
Text="{Binding Score}"
TextColor="Aquamarine" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Here's the ViewModel:
Grade _selectedGrade;
public Grade SelectedGrade
{
get { return _selectedGrade; }
set { SetProperty(ref _selectedGrade, value); } // this gets set to NULL the first time i select an item then never gets set again
}
Edit: I've also added an ItemSelected event listener in the code-behind and it is not being fired after the initial item is selected. The one time it is fired, the SelectedItemChangedEventArgs reveal that the ListView's SelectedItem is null. I can also see at this time that the ListView's ItemsSource has a collection of three items in it, as I would expect. I'm quite confused as to why the ListView thinks the SelectedItem is null and why it is not broadcasting when the selected item changes.
It looks like this was a bug in Xamarin. I upgraded from Xamarin.Forms 3.0.0.550146 to 3.1.0.637273 and now it works.
I have faced with the similar issue when List does not bind to view model with SelectedItem and Xamarin forms 5.0.0.2244.
It was resolved with setting default value of List property to new List() object instead null value in the view model before setting binding context . After that binding has became working.

how to add multiple command buttons inside of xamarin listview using MVVM?

I am trying to create a listview where users can select the list item to view the store and also be able to click on the image to take them to the product detail.
How can I add a command in the ViewCell?
<ListView ItemsSource="{Binding myData}" x:Name="myListView" SelectedItem="{Binding SelectedStore}" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<Label Text="{Binding Title}" />
<Image Source="product.png" >
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding ItemTapCommand}" CommandParameter="Id" />
</Image.GestureRecognizers>
</Image>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
public ICommand ItemTapCommand => new Command(ShowSelectedProductAsync());
private async Task ShowSelectedProductAsync()
{
//take them to view the item detail
}
The problem is that you are attempting to bind to a command that is not located on the individual item within the ListView; the BindingContext on your Image is the individual items inside your myData collection.
With other frameworks, for example, Wpf, you would simply use the RelativeSource part of binding to tell the control to look for your command on the BindingContext of another control, however this isn't possible in Xamarin Forms (RelativeSource is not supported).
What you can do is the following, but it requires your UserControl to have an x:Name:
<TapGestureRecognizer Command="{Bindind Path=BindingContext.ItemTapCommand,
Source={x:Reference MyUserControl}}"
CommandParameter="{Binding Id}" />
What this says is to use the UserControl as the source for the binding, and the property is a nested property on that UserControl composed of BindingContext.ItemTapCommand.
As a bonus, I updated the code because I felt that you probably meant to bind the value of the Id property of each record as the command parameter, and not merely to send the string "Id" as the command parameter.

MasterDetail ListView and editable ContentPresenter: what is wrong?

I'm based on the official Microsoft sample to create a MasterDetail ListView:
MasterDetail ListView UWP sample
I have adapted it to my case, as I want that users can edit directly selected items from the ListView. But I meet a strange comportement:
when I add a new item to the ListView, the changes of the current item, done in the details container, are well saved
but when I select an existing item in the ListView, the changes of the current item, done in the details container, are not saved
Here is a screenshot of my app:
The XAML of my ListView is like this:
<!-- Master : List of Feedbacks -->
<ListView
x:Name="MasterListViewFeedbacks"
Grid.Row="1"
ItemContainerTransitions="{x:Null}"
ItemTemplate="{StaticResource MasterListViewFeedbacksItemTemplate}"
IsItemClickEnabled="True"
ItemsSource="{Binding CarForm.feedback_comments}"
SelectedItem="{Binding SelectedFeedback, Mode=TwoWay}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.FooterTemplate>
<DataTemplate>
<CommandBar Background="White">
<CommandBar.Content>
<StackPanel Orientation="Horizontal">
<AppBarButton Icon="Add" Label="Add Feedback"
Command="{Binding AddItemFeedbacksCommand}" />
<AppBarButton Icon="Delete" Label="Delete Feedback"
Command="{Binding RemoveItemFeedbacksCommand}" />
</StackPanel>
</CommandBar.Content>
</CommandBar>
</DataTemplate>
</ListView.FooterTemplate>
</ListView>
The XAML of the ListView's ItemTemplate is:
<DataTemplate x:Key="MasterListViewFeedbacksItemTemplate" x:DataType="models:Feedback_Comments">
<StackPanel Margin="0,11,0,13"
Orientation="Horizontal">
<TextBlock Text="{x:Bind creator }"
Style="{ThemeResource BaseTextBlockStyle}" />
<TextBlock Text=" - " />
<TextBlock Text="{x:Bind comment_date }"
Margin="12,1,0,0" />
</StackPanel>
</DataTemplate>
The XAML of the Details container is like this:
<!-- Detail : Selected Feedback -->
<ContentPresenter
x:Name="DetailFeedbackContentPresenter"
Grid.Column="1"
Grid.RowSpan="2"
BorderThickness="1,0,0,0"
Padding="24,0"
BorderBrush="{ThemeResource SystemControlForegroundBaseLowBrush}"
Content="{x:Bind MasterListViewFeedbacks.SelectedItem, Mode=OneWay}">
<ContentPresenter.ContentTemplate>
<DataTemplate x:DataType="models:Feedback_Comments">
<StackPanel Visibility="{Binding FeedbacksCnt, Converter={StaticResource CountToVisibilityConverter}}">
<TextBox Text="{Binding creator, Mode=TwoWay}" />
<DatePicker Date="{Binding comment_date, Converter={StaticResource DateTimeToDateTimeOffsetConverter}, Mode=TwoWay}"/>
<TextBox TextWrapping="Wrap" AcceptsReturn="True" IsSpellCheckEnabled="True"
Text="{Binding comment, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</ContentPresenter.ContentTemplate>
<ContentPresenter.ContentTransitions>
<!-- Empty by default. See MasterListView_ItemClick -->
<TransitionCollection />
</ContentPresenter.ContentTransitions>
</ContentPresenter>
The "CarForm" is the main object of my ViewModel. Each CarForm contains a List of "Feedback_Comments".
So in my ViewModel, I do this when I add a new comment:
private void AddItemFeedbacks()
{
FeedbacksCnt++;
CarForm.feedback_comments.Add(new Feedback_Comments()
{
sequence = FeedbacksCnt,
creator_id = user_id,
_creator = username,
comment_date = DateTime.Now
});
SelectedFeedback = CarForm.feedback_comments[CarForm.feedback_comments.Count - 1];
}
=> the changes done in the Feedback_Comment that was edited before the add are well preserved
I don't do anything when the user select an existing Feedback_Comment: this is managed by the XAML directly.
=> the changes done in the Feedback_Comment that was edited before to select anoter one are not preserved
=> Would you have any explanation?
The TwoWay binding for the Text property is updated only when the TextBox loses focus. However, when you select a different item in the list, the contents of the TextBox are no longer bound to the original item and so are not updated.
To trigger the update each time the Text contents change, so that the changes are reflected immediately, set the UpdateSourceTrigger set to PropertyChanged:
<TextBox Text="{Binding comment, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Triggering changes everywhere
To ensure your changes are relflected everywhere including the list, you will need to do two things.
First, your feedback_comments is of type ObservableCollection<Feedback_Comments>. This ensures that the added and removed items are added and removed from the ListView.
Second, the Feedback_Comments class must implement the INotifyPropertyChanged interface. This interface is required to let the user interface know about changes in the data-bound object properties.
Implementing this interface is fairly straightforward and is described for example on MSDN.
The quick solution looks like this:
public class Feedback_Comments : INotifyPropertyChanged
{
// your code
//INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged( [ CallerMemberName ]string propertyName = "" )
{
PropertyChanged?.Invoke( this, new PropertyChangedEventArgs( propertyName ) );
}
}
Now from each of your property setters call OnPropertyChanged(); after setting the value:
private string _comment = "";
public string Comment
{
get
{
return _comment;
}
set
{
_comment = value;
OnPropertyChanged();
}
}
Note, that the [CallerMemberName] attribute tells the compiler to replace the parameter by the name of the caller - in this case the name of the property, which is exactly what you need.
Also note, that you can't use simple auto-properties in this case (because you need to call the OnPropertyChanged method.
Bonus
Finally as a small recommendation, I see you are using C++-like naming conventions, which does not fit too well into the C# world. Take a look at the recommended C# naming conventions to improve the code readability :-) .

Define a collection inside XAML

I want to create a Binding to a collection of strings defined inside XAML.
In WPF I could create an ArrayList as a resource with a key, ready to be used as the source of a Binding (using a StaticResource).
Is this possible in Xamarin Forms?
EDIT: I've tried with this XAML with the solution proposed by #Stephane Delcroix, but I'm getting an Unhandled Exception:
<?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:sys="clr-namespace:System;assembly=mscorlib"
x:Class="ReferenceApp.Views.GamesPage"
Title="Games">
<ContentPage.Resources>
<x:Array Type="{x:Type sys:String}" x:Key="array">
<x:String>Hello</x:String>
<x:String>World</x:String>
</x:Array>
</ContentPage.Resources>
<Grid />
</ContentPage>
However, the exception is not thrown if I remove the <x:Array >... </x:Array>
What am I doing wrong?
I see that you are using the XF standard markup extensions. Your mistake seems to be in Type="{x:Type sys:String}", instead of sys:String you should write x:String which appears in the common xmlns:x
In this sample I fill a listview with strings
<ListView Margin="10">
<ListView.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>Hello</x:String>
<x:String>World</x:String>
</x:Array>
</ListView.ItemsSource>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
You can use the built-in x:Array
<x:Array Type="{x:Type sys:String}" x:Key="array">
<x:String>Hello</x:String>
<x:String>World</x:String>
</x:Array>
with sys defined as xmlns:sys="clr-namespace:System;assembly=mscorlib"
or any collection you like, e.g. List
<scg:List x:TypeArguments="{x:Type sys:String}" x:Key="genericList">
<x:String>Hello</x:String>
<x:String>World</x:String>
</scg:List>
with sys defined as before, and scg being xmlns:scg="clr-namespace:System.Collections.Generic;assembly=mscorlib"
This is an answer to the updated question.
<x:string> only supports constant values. You can address this issue with a Markup Extension
Its functionality is trivial: it returns its parameter.
using System;
using Xamarin.Forms.Xaml;
namespace YOURAPP.Extensions
{
public class StringExtension : IMarkupExtension
{
public string Value { get; set; }
public object ProvideValue(IServiceProvider serviceProvider)
{
return Value;
}
}
}
Use it like this in a view:
Add xmlns:ext="clr-namespace:YOURAPP.Extensions" to the root element
<ext:StringExtension Value="{anything here}" /> where you would otherwise want <x:string>
Note that this causes an Add to be called to the IEnumerable. For custom controls you would need initialization (to avoid a NullReferenceException) and and ObservableCollection to make sure that the view is updated on adding.