<ListView> of images - How open URL via TapGestureRecognizer? - xamarin

How can we open a URL (via TapGestureRecognizer) that is bound to a of images?
My ListView's ItemsSource is _partners (type of List).
The Partner class has two properties - WebUrl and ImageUrl.
<ListView x:Name="partnersListView">
<ListView.ItemTemplate>
<DataTemplate>
<!--<ImageCell ImageSource="{Binding ImageUrl}" Text="{Binding WebUrl}" />-->
<ViewCell>
<Image Source="{Binding ImageUrl}">
<Image.GestureRecognizers>
<TapGestureRecognizer Tapped="Image_TapGestureRecognizer_Tapped" NumberOfTapsRequired="1" CommandParameter="{Binding .}" />
</Image.GestureRecognizers>
</Image>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The list is showing images only, and I would like the app to navigate to a web site when the user taps the image. However, the following code - unsurprisingly - doesn't work.
private void Image_TapGestureRecognizer_Tapped(object sender, System.EventArgs e)
{
var tappedMenuItem = sender as MenuItem;
var partner = tappedMenuItem.CommandParameter as Partner;
Device.OpenUri(new Uri(partner.WebUrl));
}
Any suggestions, please?
Please note:
I want to keep the TapGestureRecognizer in XAML - not in the code-behind.
Thank you.

Best way to tackle this problem, is to bind the SelectedItem of the ListView to a property on your page.
So in your View
<ListView SelectedItem="{Binding SelectedPartner}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Image Source="{Binding ImageUrl}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
And in the Code Behind
public Partner SelectedPartner
{
set
{
if (value != null)
Device.OpenUri(new System.Uri(value.WebUrl));
}
}

The list is showing images only, and I would like the app to navigate to a web site when the user taps the image. However, the following code - unsurprisingly - doesn't work.
CommandParameter is just for Command, you can't get it from a tap event handler. To accomplish your requirement, you can either use TappedCallback together with TappedCallbackParameter or use Command together with CommandParameter:
Method 1(TapppedCallback and TappedCallbackParameter):
Modify your Partner class to hold an Action<View,object>:
public class Partner
{
public Partner(string weburl, string imageurl)
{
this.WebUrl = weburl;
this.ImageUrl = imageurl;
}
public Partner(string weburl, string imageurl, Action<View, object> callback) : this(weburl, imageurl)
{
this.CallBack = callback;
}
public Action<View, object> CallBack { get; set; }
public string WebUrl { get; set; }
public string ImageUrl { get; set; }
}
In your code-behind, define a TappedCallback function like below and initialize the items source with the this function:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
List<Partner> list = new List<Partner>
{
new Partner("http://www.url.com","timg.jpg",TappedCallback),
new Partner("http://www.url.com","timg.jpg",TappedCallback),
new Partner("http://www.url.com","timg.jpg",TappedCallback),
new Partner("http://www.url.com","timg.jpg",TappedCallback),
new Partner("http://www.url.com","timg.jpg",TappedCallback),
new Partner("http://www.url.com","timg.jpg",TappedCallback)
};
partnersListView.ItemsSource = list;
}
private void TappedCallback(View sender,object param)
{
var Partner = param as Partner;
Device.OpenUri(new Uri(Partner.WebUrl));
}
}
Use TappedCallback in Xaml:
<ListView x:Name="partnersListView">
<ListView.ItemTemplate>
<DataTemplate>
<!--<ImageCell ImageSource="{Binding ImageUrl}" Text="{Binding WebUrl}" />-->
<ViewCell>
<Image Source="{Binding ImageUrl}">
<Image.GestureRecognizers>
<TapGestureRecognizer TappedCallback="{Binding CallBack}" TappedCallbackParameter="{Binding .}" NumberOfTapsRequired="1" />
</Image.GestureRecognizers>
</Image>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Method 2(Command and CommandParameter):
Define an ICommand in your Partner class and constructor to accept ICommand:
public class Partner
{
public Partner(string weburl, string imageurl)
{
this.WebUrl = weburl;
this.ImageUrl = imageurl;
}
public Partner(string weburl, string imageurl, ICommand command) : this(weburl, imageurl)
{
this.TapCommand = command;
}
public string WebUrl { get; set; }
public string ImageUrl { get; set; }
public ICommand TapCommand { get; set; }
}
Create a MyCommand class to implement the ICommand interface :
public class MyCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
var Partner = parameter as Partner;
Device.OpenUri(new Uri(Partner.WebUrl));
}
}
In your code-behind initialize your items source with a new MyCommand object:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
List<Partner> list = new List<Partner>
{
new Partner("http://www.url.com","timg.jpg",new MyCommand()),
new Partner("http://www.url.com","timg.jpg",new MyCommand()),
new Partner("http://www.url.com","timg.jpg",new MyCommand()),
new Partner("http://www.url.com","timg.jpg",new MyCommand()),
new Partner("http://www.url.com","timg.jpg",new MyCommand()),
new Partner("http://www.url.com","timg.jpg",new MyCommand())
};
partnersListView.ItemsSource = list;
}
}
Use Command in Xaml:
<ListView x:Name="partnersListView">
<ListView.ItemTemplate>
<DataTemplate>
<!--<ImageCell ImageSource="{Binding ImageUrl}" Text="{Binding WebUrl}" />-->
<ViewCell>
<Image Source="{Binding ImageUrl}">
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding TapCommand}" CommandParameter="{Binding .}" NumberOfTapsRequired="1" />
</Image.GestureRecognizers>
</Image>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Related

CollectionView with ObservableRangeCollection not updating data change

I want to change the data of one object in my ObservableRangeCollection, and that works but the corresponding 'CollectionView` does not update the changed data.
'CollectionView`
<RefreshView Grid.Row="1"
Grid.RowSpan="2"
Command="{Binding RefreshCommand}"
IsRefreshing="{Binding IsBusy, Mode=OneWay}">
<CollectionView x:Name="Collection"
ItemsSource="{Binding Locations}"
SelectionMode="Single"
BackgroundColor="Transparent"
ItemsLayout="VerticalList">
<CollectionView.EmptyView>
<StackLayout Padding="12">
<Label HorizontalOptions="Center" Text="Keine Daten vorhanden!" TextColor="White"/>
</StackLayout>
</CollectionView.EmptyView>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:MainModel">
<Frame HeightRequest="260">
<Grid>
<Image Source="{Binding Image}"
Aspect="AspectFill"/>
<Label Text="{Binding Name}"
FontSize="30"
TextColor="White"/>
</Grid>
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</RefreshView>
ViewModel
public class MainViewModel : VeiwModelBase
{
public ObservableRangeCollection<MainModel> Locations { get; set; } = new ObservableRangeCollection<MainModel>();
public ICommand RefreshCommand { get; }
public MainViewModel()
{
RefreshCommand = new AsyncCommand(Refresh);
}
public override void VModelActive(Page sender, EventArgs eventArgs)
{
base.VModelActive(sender, eventArgs);
var locs = new MainModel() { Image = "https://club-l1.de/wp-content/uploads/2019/11/dsc08645-1200x800.jpg", Name = "Test" };
Locations.Add(locs);
foreach (MainModel loc in Locations)
{
loc.Name = "Update";
}
}
private async Task Refresh()
{
IsBusy = true;
var locs = new MainModel() { Image = "https://club-l1.de/wp-content/uploads/2019/11/dsc08645-1200x800.jpg", Name = "Test"};
Locations.Add(locs);
foreach (MainModel loc in Locations)
{
loc.Name = "Update";
}
IsBusy = false;
}
}
The Project: https://github.com/Crey443/CollectionViewUpdate
MainModel needs to implement INotifyPropertyChanged, and any properties you want to bind (ie, Name) need to call PropertyChanged in their setters
public class MainModel
{
public string Name { get; set; }
public string Image { get; set; }
}

Data binding for listview in xamarin

I am trying to populate listview with database table in xamarin forms app
I am getting null pointer exception
Below is XAML for listview
<ListView x:Name="_listView"
ItemsSource="{Binding itemsInList}"
Grid.Column="0"
Grid.Row="0"
SelectedItem="{Binding SelectedItem}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Label Text="{Binding Name}" Grid.Column="0" Grid.Row="0" />
</Grid>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Below is xaml.cs(code behind)
public List <ServiceProvider> itemlist;
public List <ServiceProvider> itemsInList
{
get {return itemlist;}
}
protected override void OnAppearing()
{
base.OnAppearing();
ExpensesDatabase dbcon = new ExpensesDatabase(completePath);
itemlist = dbcon.GetItems(completePath);
// _listView.ItemsSource = itemlist;
}
Below is db file
public class ExpensesDatabase
{
readonly SQLiteConnection database;
public ExpensesDatabase(string dbPath)
{
database = new SQLiteConnection(dbPath);
database.CreateTable < ServiceProvider > ();
}
public List < ServiceProvider > GetItems(string dbPath)
{
return database.Table < ServiceProvider > ().ToList();
}
}
Data is not displayed in listview
If you want the ListView to automatically update as items are added, removed and changed in the underlying list, you'll need to use an ObservableCollection. ObservableCollection is defined in System.Collections.ObjectModel and is just like List, except that it can notify ListView of any changes:
public ObservableCollection<ServiceProvider> itemsInList { get; set; }
Then make sure you have set the right bindingContext and initialized the ObservableCollection:
public MainPage()
{
InitializeComponent();
itemsInList = new ObservableCollection<ServiceProvider>();
BindingContext = this;
}
I write a sample to test and it works on my side, you can have a look at the full code:
public partial class MainPage : ContentPage
{
public ObservableCollection<ServiceProvider> itemsInList { get; set; }
public MainPage()
{
InitializeComponent();
itemsInList = new ObservableCollection<ServiceProvider>();
BindingContext = this;
}
protected override void OnAppearing()
{
base.OnAppearing();
itemsInList.Add(new ServiceProvider() { Name= "a"});
}
}
public class ServiceProvider : INotifyPropertyChanged
{
string name;
public event PropertyChangedEventHandler PropertyChanged;
public ServiceProvider()
{
}
public String Name
{
set
{
if (name != value)
{
name = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
}
get
{
return name;
}
}
}
Feel free to ask me any question if you still can't solve it.

Binding ViewModel field outside of Item Source

Hi I have an ItemSource which binds a list of contact
<ListView x:Name="contactsListView" ItemsSource="{Binding contacts}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Image Source="{Binding Should be to my View Model instead of Contacts}"></Image>
<Label Text="{Binding FullName}"></Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
the Full Name binding works fine. My problem is the Image Source is not included in the Contact model so I need to retrieve that from my view Model How can I do it?
VIEW MODEL
public class ContactsViewModel : INotifyPropertyChanged
{
public ObservableCollection<Contact> contacts { get; set; }
private string _contactImage;
public string ContactImage
{
get => _contactImage; set
{
_contactImage = value;
OnPropertyChanged("ContactImage");
}
}
public ContactsViewModel(List<Contact> _contacts)
{
contacts = new ObservableCollection<Contact>(_contacts);
ContactImage = "arrow.png";
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
CODE BEHIND VIEW
public partial class ContactListPage : TabbedPage
{
public ContactsViewModel vm;
public ContactListPage (List<Contact> _contacts)
{
vm = new ContactsViewModel(_contacts);
BindingContext = vm;
InitializeComponent();
}
}
This is because the scope within a ListView item is different than a level higher. To overcome this, create a reference to a parent control. I see you already named your ListView, so we can use that.
Do it like this: <Image Source="{Binding Path=BindingContext.ContactImage, Source={x:Reference contactsListView}}"></Image>

Xamarin rest api

Binding does not work in my code. what's wrong in this code?
HttpClient client = new HttpClient();
var response = await client.GetAsync(string.Format("uri link"));
string jsonstring = await response.Content.ReadAsStringAsync();
RootObject item = JsonConvert.DeserializeObject<RootObject>(jsonstring);
titles.ItemsSource =item.ToString();
XAML code
<ListView x:Name="titles" HasUnevenRows="False" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding note}"/>
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
class object:
public class Result
{
public string note { get; set; }
}
public class Response
{
public List<Result> results { get; set; }
}
public class RootObject
{
public Response response { get; set; }
}
you bind the lable to the note, but you set the titles.ItemsSource to the RootObject. the RootObject class doesn't have note. note is in Result class.
and you can't set the itemsource like that.
I suggest you to do this
var listItem = JsonConvert.DeserializeObject<List<Result>>(jsonstring);
titles.ItemsSource = l;
According to me are upto:
RootObject item = JsonConvert.DeserializeObject<RootObject>(jsonstring);
and can you try this code also after the above line:
titles.ItemsSource =item.Responce. results;

Xamrin Button is not working for mmvm-light Relay Command

In my Xamrin forms code i have configured MVVM light
RelayCommand is not hitting even though I have set the property in xaml page for the command.
XAML
<?xml version="1.0" encoding="utf-8"?><ContentPage BackgroundColor="White" xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ContractorActionSolution.CSA.CSAContentPages.Demo">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
<OnPlatform.iOS>0,20,0,0</OnPlatform.iOS>
</OnPlatform>
</ContentPage.Padding>
<ContentPage.Content>
<StackLayout Padding="10" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
<Image x:Name="imgLogo" Source="sisystems_logo.jpg" HorizontalOptions="FillAndExpand" VerticalOptions="Start"/>
<StackLayout VerticalOptions="CenterAndExpand" Padding="20" HorizontalOptions="FillAndExpand">
<Label x:Name="lblmsg" TextColor="#F25B63" Text="Error Message"/>
<Entry x:Name="txtUserName" Placeholder="Email"/>
<Entry x:Name="txtPassword" Placeholder="Password" IsPassword="True"/>
<Button Command="{Binding IncrementCommand}" Text="Continue"/>
<Label Text="Can't Login ?" TextColor="#F25B63" HorizontalOptions="Center"/>
</StackLayout>
</StackLayout>
</ContentPage.Content>
Code
public partial class Demo : ContentPage
{
DemoViewModel _myViewModel;
public Demo ()
{
InitializeComponent ();
}
protected override void OnAppearing()
{
base.OnAppearing();
BindingContext = App.Locator.DemoVM;
_myViewModel = BindingContext as DemoViewModel;
}
protected override void OnDisappearing()
{
base.OnDisappearing();
_myViewModel.CleanUP();
}
}
Model
public class DemoViewModel : BaseViewModel
{
private string _name;
public string Name
{
get { return _name; }
set { Set(() => Name, ref _name, value); }
}
private RelayCommand _incrementCommand;
/// <summary>
/// Gets the IncrementCommand.
/// </summary>
public RelayCommand IncrementCommand
{
get
{
return _incrementCommand ?? (_incrementCommand = new RelayCommand(
() =>
{
}));
}
}
public void CleanUP()
{
Name = string.Empty;
}
}
I have also tried using RelayCommand and RelayAsyncCommand both are not working.
Property bindings are working fine,
I can add binding for entry with a string property, but not able to add RelayCommand with a button.
The problem might be the fact that the handler is empty and optimization actually omits it after compilation. Try to add anything inside, like Debug.WriteLine("Test"); to see if the breakpoint is hit then.

Resources