Load an image in a listview from URI xamarin forms - c#

ok, so I have this listView which is supposed to show a bunch of items, each of which containing at least one photo.
The idea is to show the main photo in the listCell, and when an item is selected, its details are shown in a different Forms Page, and there its supposed to be able to access all its photos.
When the item doesn't have a photo, it will show a placeholder one from resources.
Problem: can't load the image from URI either binding the image source to a list property (from the viewModel) that contains the specific URI obj, or by binding it to the same property containing now strings, or by means of
<Image.Source>
<UriImageSource Uri="{Binding MainPhotoSource}" />
</Image.Source>
no matter. none of these seems to work.
already asked the Xamarin Team for help, and their answer was to come here, or go to the forums (which I already did, been waiting for almost two months, now, and the work needs to be delivered)...
any help, please?
EDIT:
Here's a piece of the ViewModel code.
In this first method, for each item I receive from the WCF, I add an equivalence in the format of this ItemDto obj to this ObservableCollection List.
// Sets this List observable collection to a new ItemDto obj,
// with certain properties from the Item.Find result[].
ObservableCollection<ItemDto> SetList(Item.Find[] result)
{
ObservableCollection<ItemDto> collection = new ObservableCollection<ItemDto>();
foreach (Item.Find item in result)
{
collection.Add(GetDto(item));
}
return collection;
}
// Gets a new ItemDto obj with the required fields from Item.Find obj.
ItemDto GetDto(Item.Find item)
{
return new ItemDto()
{
ID = item.ID,
UserID = item.User_ID,
MainPhotoSource = new Uri(_serverInbox + item.MediaItems[0].RelativeUrl),
Title = item.Title,
Description = item.Description,
Category = item.Category_Name,
DateCreated = GetFormatedDateCreated(item.DateCreated)
};
}

Uri property of UriImageSource requires an Uri rather than a string. But you can use a URI Bindable property in your View Model and bind to it:
Check this code
View Model
public string ProductNo
{
get { return _productNo}
set
{
if (_productNo != value)
{
_productNo = value;
RaisePropertyChanged();
RaisePropertyChanged(() => ThumbnailImageUri);
}
}
}
public Uri ThumbnailImageUri
{
get
{
if (_thumbnailImageUri == null)
{
_thumbnailImageUri = new Uri(String.Format("http://www.YOURWEBSITE.com/{0}.jpg", _productNo));
}
return _thumbnailImageUri;
}
}
View
<StackLayout BindingContext="{Binding SelectedProduct}">
<StackLayout Orientation="Horizontal">
<Image HorizontalOptions="EndAndExpand"
VerticalOptions="Center">
<Image.Source>
<UriImageSource Uri="{Binding ThumbnailImageUri}"/>
</Image.Source>
</Image>
<Label Text="{Binding ProductNo}"
Font="Bold, Large"
HorizontalOptions="StartAndExpand"
VerticalOptions="Center"/>
</StackLayout>
</StackLayout>

Here is, what works for me - hope this helps you
First my BindingContext:
public class ItemContainer
{
public ItemContainer()
{
Collection = SetList(new[] { "1", "2", "3", "4" });
}
ObservableCollection<ItemDto> SetList(string[] result)
{
ObservableCollection<ItemDto> collection = new ObservableCollection<ItemDto>();
foreach (string item in result)
{
collection.Add(GetDto(item));
}
return collection;
}
public ObservableCollection<ItemDto> Collection { get; set; }
ItemDto GetDto(string item)
{
return new ItemDto() { MainPhotoSource = new Uri(_serverInbox + item) };
}
}
My Page1.xaml looks like this:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="App1gcm.Page1">
<ListView ItemsSource="{Binding Collection}" VerticalOptions="Center" HorizontalOptions="Center" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Image Source="{Binding MainPhotoSource}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
And I merge them on creating as MainPage in App.cs:
public App()
{
// The root page of your application
MainPage = new Page1
{
BindingContext = new ItemContainer()
};
}

Related

Xamarin UWP seems to bind to the wrong view model

I have a Xamarin project for Android and UWP. This issue seems to only happen on UWP.
In my Xamarin project I have ContentPage with a view model bound as context. In this ViewModel there's an ObservableCollection with another kind of view model. When I create a new instance of this underlying ViewModel and add to my ObservableCollection, sometimes the ContentPage works as expected, showing an item in my ListView. But sometimes there's an empty element added, that I can see when hovering over the list. When this happens I get a bunch of warnings in the Output tab.
My DownloadsPage:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Downloader.Converters"
mc:Ignorable="d"
x:Class="Downloader.Views.DownloadsPage">
<ContentPage.Resources>
<ResourceDictionary>
<local:DownloadStatusToColorConverter x:Key="downloadStatusToColor" />
</ResourceDictionary>
</ContentPage.Resources>
<RefreshView IsRefreshing="{Binding IsBusy, Mode=TwoWay}" Command="{Binding LoadItemsCommand}">
<ListView x:Name="DownloadsListView" SelectionMode="None" ItemsSource="{Binding Downloads}" RowHeight="70">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Padding="10" BackgroundColor="{Binding DownloadStatus, Converter={StaticResource downloadStatusToColor}}">
<Label Text="{Binding Name}"
d:Text="{Binding .}"
LineBreakMode="NoWrap"
Style="{DynamicResource ListItemTextStyle}"
FontSize="16" />
<Grid Grid.Row="0" Grid.Column="0" Padding="10,0,10,0">
<ProgressBar BackgroundColor="Transparent" Progress="{Binding PercentDownloaded}" HorizontalOptions="FillAndExpand" HeightRequest="20">
</ProgressBar>
<Label Text="{Binding PercentString}" HorizontalTextAlignment="Center"></Label>
</Grid>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</RefreshView>
</ContentPage>
DownloadsViewModel is set as context in the code-behind like this:
public partial class DownloadsPage : ContentPage
{
private readonly DownloadsViewModel _viewModel;
public DownloadsPage()
{
InitializeComponent();
BindingContext = _viewModel = new DownloadsViewModel();
Device.StartTimer(TimeSpan.FromSeconds(1), () =>
{
Device.BeginInvokeOnMainThread(() => _viewModel.RefreshDownloads());
return true;
});
}
}
The bound DownloadsViewModel:
public class DownloadsViewModel : BaseViewModel
{
public ObservableCollection<DownloadViewModel> Downloads { get; set; } = new ObservableCollection<DownloadViewModel>();
public Command LoadItemsCommand { get; set; }
public DownloadsViewModel()
{
Title = "Downloads";
LoadItemsCommand = new Command(() => {
IsBusy = true;
Downloads.Clear();
RefreshDownloads();
IsBusy = false;
});
}
public void RefreshDownloads()
{
foreach (var download in DownloadManager.GetDownloads())
{
var existingDownload = Downloads.FirstOrDefault(d => d.Id == download.Id);
if (existingDownload != null)
{
existingDownload.UpdateValues(download);
}
else
{
Downloads.Add(new DownloadViewModel(download));
}
}
}
}
And the ObservableCollection contains DownloadViewModel that looks like this:
public class DownloadViewModel : BaseViewModel
{
private IDownload _download;
public DownloadViewModel(IDownload download)
{
UpdateValues(download);
}
private string _id;
public string Id
{
get { return _id; }
set { SetProperty(ref _id, value); }
}
private string _name;
public string Name
{
get { return _name; }
set { SetProperty(ref _name, value); }
}
private DownloadStatus _status;
public DownloadStatus DownloadStatus
{
get { return _status; }
set { SetProperty(ref _status, value); }
}
public double PercentDownloaded
{
get
{
return _download.DownloadedBytes == -1
? 0f
: (double)_download.DownloadedBytes / _download.TotalBytes;
}
}
public string PercentString { get => $"{(int)(PercentDownloaded * 100)} %"; }
public void UpdateValues(IDownload download)
{
_download = download;
Id = _download.Id;
Name = _download.Name;
DownloadStatus = _download.Status;
}
}
The error I sometimes get which causes items in my ListView to be empty:
Binding: 'DownloadStatus' property not found on 'Downloader.ViewModels.DownloadsViewModel', target property: 'Xamarin.Forms.StackLayout.BackgroundColor'
Binding: 'Name' property not found on 'Downloader.ViewModels.DownloadsViewModel', target property: 'Xamarin.Forms.Label.Text'
Binding: 'PercentDownloaded' property not found on 'Downloader.ViewModels.DownloadsViewModel', target property: 'Xamarin.Forms.ProgressBar.Progress'
Binding: 'PercentString' property not found on 'Downloader.ViewModels.DownloadsViewModel', target property: 'Xamarin.Forms.Label.Text'
When debugging I've confirmed that the item is added to my ObservableCollection as expcted.
How come sometimes it's looking for DownloadStatus, Name, PercentDownloaded and PercentString on DownloadsViewModel instead of DownloadViewModel?
Xamarin UWP seems to bind to the wrong view model
I checked your code sample and it works as expect. But I found the progress value does not update automatically that cause the listview item can't display, I have update the IDownload interface add PercentDownloaded property. For the testing it could works in uwp platform.
The problem was that the ViewModels did not have setters with INotifyPropertyChanged implemented for all properties. The source code is available on Github, and the commit that fixes the issue is this one.

Xamarin Forms Image not showing in listview

I want to display a /!\ icon in my listview. Unfortunately, it's not showing up anywhere.
I am 100% positive it's in the right place in both the iOS and Android app. I have double checked that it is a Android resource (as per screenshot below)
The code used to initialize the image: alarmIcon = new Image { Source = "warning.png" };
After initialization I put it in an AlarmPageItem object which as an Image property. This gets added to the listview.
I have checked that I matched the bindings with the AlarmPageItem properties. (The text does get shown).
I am at a loss why it wouldn't work...
Things I tried
Checked case of image and source declaration
Checked the build action and copy to output directory
Checked binding matching
Tried building to phone instead of xamarin live player
Checked image location
Code:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="OSIApp.Pages.AlarmPage"
Title="Alarm Log">
<ContentPage.Content>
<StackLayout>
<ListView x:Name="listView"
ItemTapped="OnItemTapped" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<StackLayout>
<Label x:Name="alarmTimeLabel"
HorizontalOptions="Center"
HorizontalTextAlignment="Center"
Text="{Binding DateTime}"/>
<Image x:Name="alarmIconImage"
Source="{Binding AlarmImage}"/>
</StackLayout>
<Label x:Name="alarmTextLabel"
FontSize="14"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center"
HorizontalOptions="FillAndExpand"
FontFamily="Avenir Next"
FontAttributes="Bold"
Text="{Binding AlarmText}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
public partial class AlarmPage : ContentPage
{
private List<AlarmPageItem> listViewItems;
private Image alarmIcon;
public AlarmPage ()
{
listViewItems = new List<AlarmPageItem>();
alarmIcon = new Image { Source = "warning.png" };
PopulateList();
InitializeComponent();
listView.ItemsSource = listViewItems;
}
private void OnItemTapped(object sender, ItemTappedEventArgs e)
{
if (e == null) return; // has been set to null, do not 'process' tapped event
Debug.WriteLine("Tapped: " + e.Item);
((ListView)sender).SelectedItem = null; // de-select the row
}
private void PopulateList()
{
listViewItems.Add(new AlarmPageItem() {AlarmImage = alarmIcon, AlarmText = "The front is too heavy", DateTime = DateTime.Now.ToShortTimeString()});
}
}
You are binding as "Source" of your Image in the XAML an Image object you are creating in your code behind.
Update your AlarmPageItem class by making the AlarmImage property either an ImageSource or just a simple string.
Something like:
class AlarmPageItem
{
public string AlarmImage { get; set; }
public string AlarmText { get; set; }
//Just a suggestion: Change this property name for something different as it
//can create some confusion.
public DateTime DateTime { get; set; }
...
// Add any other properties your class might have.
}
Once the above is updated just change your code to set the name of the image in the PopulateList method
private void PopulateList()
{
listViewItems.Add(new AlarmPageItem() {AlarmImage = "warning.png", AlarmText = "The front is too heavy", DateTime = DateTime.Now.ToShortTimeString()});
}
Hope this helps.-

Xamarin Forms - Binding Listview for lazy loading

Started to dabble in Xamarin Forms.
Two things I cant figure out:
Binding of my Listview:
I have a class with:
public class Mainlist
{
public string Title
{
get;
set;
}
public string Value
{
get;
set;
}
}
My XAML looks like:
<ListView x:Name="mainlist">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<StackLayout Orientation="Vertical">
<Label Text="{Binding Title}" Font="18"></Label>
<Label Text="{Binding Value}" TextColor="Gray"></Label>
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
What happens now is that i have a list of URLS. From every URL I am scraping certain info with HTMLAgilityPack foreach loop, which is working fine.
I would like to add the scraped data after each run of the loop to the listview and have it display. Something like "lazy loading".
Up to now i could only figure out how to set the itemsource after all Urls are scraped and have it display at once with something like this:
//set itemsource to URL collection
mainlist.ItemsSource = new List<Mainlist>() {
new Mainlist()
{
//scraped info from each URL
Title = title.ToString().Trim(),
Value = value.ToString().Trim(),
},
};
First, create a view model class, called MyViewModel.cs:
public class MyViewModel : INotifyPropertyChanged
{
// property changed event handler
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<Mainlist> _list;
public ObservableCollection<Mainlist> List
{
get { return _list; }
set
{
_list = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(List)));
}
}
public MyViewModel()
{
_list = new ObservableCollection<Mainlist>();
}
public async void StartScraping()
{
// assuming you are 'awaiting' the results of your scraping method...
foreach (...)
{
await ... scrape a web page ...
var newItem = new Mainlist()
{
Title = title.ToString().Trim(),
Value = value.ToString().Trim()
};
// if you instead have multiple items to add at this point,
// then just create a new List<Mainlist>, add your items to it,
// then add that list to the ObservableCollection List.
Device.BeginInvokeOnMainThread(() =>
{
List.Add(newItem);
});
}
}
}
Now in your page's xaml.cs code behind file, set the view model as your BindingContext:
public class MyPage : ContentPage // (assuming the page is called "MyPage" and is of type ContentPage)
{
MyViewModel _viewModel;
public MyPage()
{
InitializeComponent();
_viewModel = new MyViewModel();
BindingContext = _viewModel;
// bind the view model's List property to the list view's ItemsSource:
mainList.setBinding(ListView.ItemsSourceProperty, "List");
}
}
And note that in your view model, you'll need to use an ObservableCollection<T> instead of a List<T>, as ObservableCollection<T> will allow the ListView to be updated automatically whenever you add or remove items from it.
Also, to ease a bit of confusion, I'd recommend changing the class name from Mainlist to MainListItem.
I think you could do something like this:
mainlist.ItemsSource = new ObservableCollection<Mainlist>();
foreach (var item in yourDataFromHtmlAgilityPackScraping) {
mainlist.ItemsSource.Add(new Mainlist()
{
//scraped info from each URL
Title = item.title.ToString().Trim(),
Value = item.value.ToString().Trim(),
});
}
The important part here is the ObservableCollection. Which allows the Listview to be updated when a new element is added.

Xamarin.Forms How to two-way bind to a control in Listview

Im having a hard time trying to figure out how to set up a two-way binding for a control inside a listview.
Im using ReactiveUI and Xamarin.Forms.
In this case i would like to load a list of objects that have a quantity. This is set initially when the page loads. However i would like to be able to change these quantity values in the view when the program is run. I used an Entry for that.
Setting up a two-way Binding for the List itself (done in code behind, the reactive way) is not possible. It will error.
Is there another way to observe changes done to the Text property in the Entry control and reflect them to the according item from the list in my viewmodel?
I've been having trouble finding a solution for this and don't really know how to go about this.
Here is my XAML code:
<CustomControls:AutoLoadListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Margin="20,0,0,0" Orientation="Vertical" HorizontalOptions="StartAndExpand">
<Label Margin="0,5,0,-5" Style="{StaticResource ViewCellPrimaryLabelStyle}" x:Name="txt" Text="{Binding itemname}" />
<Label Margin="0,-5,0,5" Style="{StaticResource ViewCellSecondaryLabelStyle}" x:Name="barcode" Text="{Binding productcode}" />
</StackLayout>
<Entry Margin="5,0,5,0" x:Name="quantity" Text="{Binding quantity}">
<Entry.BindingContext>
<ViewModel:AankoopEditViewModel />
</Entry.BindingContext>
</Entry>
<Image Margin="5,5,5,5" x:Name="delete" Source="{Mobile:ImageResource tbin_pos.png}">
<Image.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Path=BindingContext.DeleteCommand,Source={x:Reference Name=AankoopEditPage}}"
CommandParameter="{Binding}" />
</Image.GestureRecognizers>
</Image>
</ViewCell>
</DataTemplate>
</CustomControls:AutoLoadListView.ItemTemplate>
My Viewmodel:
public class AankoopEditViewModel : BaseViewModel
{
private VmPurchase Purchase;
public AankoopEditViewModel()
{
PurchaseList = new ReactiveObservableCollection<AankoopEditListItem>()
{
ChangeTrackingEnabled = true
};
this.WhenAnyValue(x => x.PurchaseID).SubscribeOn(RxApp.MainThreadScheduler).Subscribe((x) =>
{
this.Purchase = DatabaseHelper.Purchase.LoadSingleById<VmPurchase>(PurchaseID);
if (Purchase != null)
{
this.Title = Purchase.supplier.name;
using (PurchaseList.SuppressChangeNotifications())
{
foreach (var detail in Purchase.purchasedetails)
{
PurchaseList.Add(new AankoopEditListItem { productcode = detail.item.code, itemname = detail.item.namenl, identifier = detail.key, quantity = detail.quantity.ToString() });
}
}
}
});
try
{
this.WhenAnyValue(x => x.PurchaseList).SubscribeOn(RxApp.MainThreadScheduler).Subscribe((x) =>
{
Console.WriteLine("The List has changed");
});
}
catch (Exception e)
{
return;
}
}
private string _purchaseID;
public string PurchaseID
{
get { return _purchaseID; }
set { this.RaiseAndSetIfChanged(ref _purchaseID, value); }
}
private ReactiveObservableCollection<AankoopEditListItem> _purchases;
public ReactiveObservableCollection<AankoopEditListItem> PurchaseList
{
get
{
return this._purchases;
}
set
{
this.RaiseAndSetIfChanged(ref _purchases, value);
}
}
My Model :
public class AankoopEditListItem : ReactiveObject
{
public string identifier { get; set; }
public string itemname { get; set; }
public string productcode { get; set; }
public string quantity { get; set; }
}
Be careful, when you do this
<Entry.BindingContext>
<ViewModel:AankoopEditViewModel />
</Entry.BindingContext>
you create a new instance of your view model for each item and you bind your Entry to it. Just remove it and keep the binding as it is (Text="{Binding quantity}") if you want to bind your entry to the row view model

Changing a ViewCell element on click

I have built a List<> of item objects, which are being used as the ItemsSource to a ListView. on my ListView's ItemSelected event, I am trying to change one of the elements of that particular item. The original values are bound from the object.
Please consider the following example:
itemClass.cs
class itemClass
{
public itemClass()
{
}
public string valueIWantToChange {get; set;}
}
MyPage.cs
public MyPage()
{
InitializeComponent();
List<itemClass> listOfItems= new List<itemClass>();
itemClass item1= new itemClass { valueIWantToChange = "UnClicked"};
listOfItems.Add(item1);
BindingContext = this;
lstView.ItemsSource = listOfItems;
lstView.ItemSelected += (sender, e) =>
{
if (e.SelectedItem == null)
{
return;
}
// The below does nothing but highlights what I am trying to achieve
((itemClass)((ListView)sender).SelectedItem).valueIWantToChange = "Clicked" ;
((ListView)sender).SelectedItem = null;
};
}
My XAML
<?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:myProject="clr-namespace:myProject"
x:Class="myProject.MyPage">
<StackLayout>
<ListView x:Name="lstView">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Padding="0" Orientation="Vertical" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
<Label Text="{Binding valueIWantToChange }" TextColor="Black" FontSize="16" VerticalOptions="Center" HorizontalOptions="Center" Margin="5,5,5,5"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
I have attempted to use Binding commands and PropertyChangedEventHandler but with no success. Any assistance in this would be greatly appreciated.
Edit
I have attempted to use PropertyChangedEventHandlers in a similar way, elsewhere in the app, please see below:
public double FontXLarge
{
set
{
someFontsize = value;
OnPropertyChanged("FontXLarge");
}
get
{
someFontsize = scalableFont(10);
return someFontsize;
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
The above allows me to bind a fontsize to that scaling font, however if I were to use something similar for string, would it not effect all of the items in the ListView?
I think the best way to implement what you want, it's to use a ViewModel with INotifyPropertyChanged. You can find a lot of example if you're googling a bit. One good link for generic baseclass is this.
Let me know if you find everything you need.

Categories