Async loading of images - image

I want to use best way to show images in my PanoramaPage. I download one page and shows it´s information a then I want to async load another page with images. So I am using HttpWebRequest and I get response. Everything is okay and hope this is best way for these. So I create my GaleryViewModel and for all images at page I add url to my class.
And there is a problem. I can´t see images in view. This i my view:
<ListBox ItemsSource="{Binding Images}" x:Name="listImages" Height="652" Canvas.Top="80">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0,0,0,17">
<Image Height="100" Width="100" Margin="12,0,9,0" >
<Image.Source>
<BitmapImage UriSource="{Binding ImgURL}" CreateOptions="BackgroundCreation"/>
</Image.Source>
</Image>
<TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This is content of my WebResponse event handler:
MovieExt movie = this.DataContext as MovieExt;
HtmlDocument doc = new HtmlDocument();
doc.LoadHtml(response);
var photos = from ti in doc.DocumentNode.Descendants("div")
where ti.Attributes["class"] != null && ti.Attributes["class"].Value == "photo"
select ti;
Regex rgx = new Regex("http://[0-9a-zA-Z_./]+");
foreach (var photo in photos)
{
GaleryViewModel fotka = new GaleryViewModel();
string style = photo.Attributes["style"].Value;
MatchCollection matches = rgx.Matches(style);
if (matches.Count > 0)
{
foreach (Match match in matches)
fotka.ImgURL = match.Value;
}
fotka.LineOne = "Test";
movie.Images.Add(fotka);
}
this.DataContext = movie;
this.listImages.ItemsSource = movie.Images;
and for all GaleryViewModel and MovieExt:
public class GaleryViewModel : INotifyPropertyChanged
{
private string _imgUrl;
public string ImgURL
{
get
{
return _imgUrl;
}
set
{
if (value != _imgUrl)
{
_imgUrl = value;
NotifyPropertyChanged("ImgURL");
}
}
}
private string _lineOne;
public string LineOne
{
get
{
return _lineOne;
}
set
{
if (value != _lineOne)
{
_lineOne = value;
NotifyPropertyChanged("LineOne");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class MovieExt
{
public string Title { get; set; }
public string Year { get; set; }
public string Url { get; set; }
...
public List<GaleryViewModel> Images { get; set; }
...
}
I am not sure what I am doing wrong but I think that is something with binding. Thanks for help

Looking at this it looks like you haven't notified that the MovieExt.Images property has changed. Without doing this your ListBox won't update its Items. To do this MovieExt will also need to support INotifyPropertyChanged.

You have to convert Uri to BitmapImage, because BitmapImage UriSource is a stream. Please take a look at this: Image UriSource and Data Binding

Replace loading method (just for test) to:
MovieExt movie = new MovieExt();
movie.Images = new List<GaleryViewModel>();
GaleryViewModel fotka1 = new GaleryViewModel();
fotka1.LineOne = "line1";
fotka1.ImgURL = "http://proservice.kiev.ua/wp-content/uploads/2011/10/apple-logo.jpg";
GaleryViewModel fotka2 = new GaleryViewModel();
fotka2.LineOne = "line2";
fotka2.ImgURL = "http://www.mykhailenko.com/blog/wp-content/uploads/2010/12/apple-logo-history-2.png";
movie.Images.Add(fotka1);
movie.Images.Add(fotka2);
listImages.ItemsSource = movie.Images;
and it works
I guess the problem is in this line:
if (matches.Count > 0)
You have no matches, so your Url is empty
Can you provide us data your service return to debug code in your context?
Also, why you need loop for this assignment?
foreach (Match match in matches)
fotka.ImgURL = match.Value;

Related

How do i add items to a observablecollection from another list view

Hi I am a beginner with Xamarin forms and need some help. I have tried looking for this method everywhere.
I have a List view with a lot of animal names. When a item is clicked it shows more info about that particular animal. I have added a button that is on each animals info page. I would like to click that "add" button, that would then add the name off the animal to another List view.
But I am stuck on how to do this, any help would be greatly appreciated.
this is the first list page in code behind
public partial class BirdListAZ : ContentPage
{
IEnumerable<birdlistmodel> GetBirds(string searchText = null)
{
var birds = new List<birdlistmodel>
{
new birdlistmodel (){Id = 1, BirdNames = "Apalis, Bar-throated" },
new birdlistmodel (){Id = 2, BirdNames = "Apalis, Yellow-breasted"},//there are alot more birds here
};
if (String.IsNullOrWhiteSpace(searchText))
return birds;
var lowerBirds = searchText.ToLower();
return birds.Where(c => c.BirdNames.ToLower().StartsWith(lowerBirds));
}
public BirdListAZ()
{
InitializeComponent();
blistview.ItemsSource = GetBirds();
}
private void SearchBar_TextChanged(object sender, TextChangedEventArgs e)
{
blistview.ItemsSource = GetBirds(e.NewTextValue);
}
private void blistview_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
var birds = e.SelectedItem as birdlistmodel;
Navigation.PushAsync(new BirdPages(birds.BirdNames, birds.BirdSelect));
}
}
}
this is the content page for that code behind
<ScrollView HeightRequest="3000">
<StackLayout>
<SearchBar
TextChanged="SearchBar_TextChanged"
Placeholder="Search Bird"
PlaceholderColor="Gray"
HorizontalTextAlignment="Center"
FontSize="Small"
FontAttributes="Italic"
VerticalOptions="Center" HorizontalOptions="Start"/>
<ListView x:Name="blistview" BackgroundColor ="AliceBlue" HasUnevenRows="True" ItemSelected="blistview_ItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal" Padding="5">
<Label Text="{Binding BirdNames}" HorizontalOptions="StartAndExpand" VerticalOptions="Center"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ScrollView>
when item is clicked on list it displays that info in a separate page
the contentpage
<ContentPage.Content>
<StackLayout>
<Label x:Name="BirdNameCall" FontSize="30" FontAttributes="Bold"
VerticalOptions="StartAndExpand"
HorizontalOptions="CenterAndExpand" />
<Button Text="+" x:Name="AddToList" VerticalOptions="Center" HorizontalOptions="Center" WidthRequest="200" HeightRequest="100"
FontSize="30" BackgroundColor="Transparent" Clicked="AddToList_Clicked"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
So this is what i have done. I know the filewritealltext makes a new file each time. how can i just add to that file so a new file is not created?
public BirdPages(string BirdNames, Button BirdSelect)
{
InitializeComponent();
BirdNameCall.Text = BirdNames;
AddToList = BirdSelect;
localPath = Path.Combine(FileSystem.AppDataDirectory, birdfile);
}
const string birdfile = "birdlist.txt";
string localPath;
private void AddToList_Clicked(object sender, EventArgs e)
{
string BirdNames = BirdNameCall.Text;
File.WriteAllText(localPath, BirdNames);
DisplayAlert(BirdNames, "added to list", "Ok");
}
So it works when i populate the list view but it will be a letter per row and not the entire string in a row
public partial class myBirdList : ContentPage
{
public myBirdList()
{
InitializeComponent();
localPath = Path.Combine(FileSystem.AppDataDirectory, birdfile);
birdlistview.ItemsSource = File.ReadAllText(localPath);
}
const string birdfile = "birdlist.txt";
string localPath;
}
So this is what I have done. I had to change a bit.
The added Bird Name needs to go to a blank list. So I created a new Observable Collection here. And this is where the Bird Name needs to be added.
public partial class myBirdList : ContentPage
{
public ObservableCollection<string> Birdsadded { get; set; } = new ObservableCollection<string>();
public myBirdList()
{
InitializeComponent();
BindingContext = this;
MessagingCenter.Subscribe<BirdPages, birdlistmodel>(this, "Add", (sender, arg) =>
{
Birdsadded.Add(arg.ToString());
});
}
}
}
Then the sender is where I am having issues. And can't figure out how to send the bird name.
public partial class BirdPages : ContentPage
{
public BirdPages(string BirdNames, Button BirdSelect)
{
InitializeComponent();
BirdNameCall.Text = BirdNames;
AddToList = BirdSelect;
}
private void AddToList_Clicked(object sender, EventArgs e)
{
birdlistmodel bird = xxx// this is what i don't understand, what should be added here
MessagingCenter.Send<BirdListAZ, birdlistmodel>(this, "Add", bird );
}
}
}
thanks for your time and input I appreciate it.
You could use MessagingCenter to achieve this.
Using MessagingCenter.Send to pass the new data in your AddToList_Clicked method of BirdPages
then you will get the callback in another page which you want to add the new items with MessagingCenter.Subscribe method.In the callback method,you could add the new data to the new listview.
For example:
In the listview page which you want display a list and add the new data.
public partial class BirdListAZ : ContentPage
{
public ObservableCollection<string> Birdsadded { get; set; } = new ObservableCollection<string>();
public BirdListAZ()
{
InitializeComponent();
blistview.ItemsSource = GetBirds();
MessagingCenter.Subscribe<BirdListAZ, string>(this, "Add", async (sender, arg) =>
{
Birdsadded.Add(arg);// when you click add button in other page,it will handle this
});
}
}
in the page which you want to add the data:
public partial class BirdPages : ContentPage
{
public BirdPages(string BirdNames, Button BirdSelect)
{
InitializeComponent();
BirdNameCall.Text = BirdNames;
AddToList = BirdSelect;
}
private void AddToList_Clicked(object sender, EventArgs e)
{
string birdname = xxxx;
MessagingCenter.Send<BirdListAZ, string>(this, "Add",birdname );
}
}
OK so i got it to work to some extent. I am not creating and writing to a txt file and then reading from it in mybirdlist page. This is where i write to text file
public BirdPages(string BirdNames, Button BirdSelect)
{
InitializeComponent();
BirdNameCall.Text = BirdNames;
AddToList = BirdSelect;
localPath = Path.Combine(FileSystem.AppDataDirectory, birdfile);
}
const string birdfile = "birdlist.txt";
string localPath;
private void AddToList_Clicked(object sender, EventArgs e)
{
string BirdNames = BirdNameCall.Text;
using (var writer = new StreamWriter(localPath, true))
{
writer.WriteLine(BirdNames);
}
DisplayAlert(BirdNames, "added to list", "Ok");
}
Then i read from it into a list. Last question i have is how can i read each name into a lable. Now all the added birds displays in one label. And is it possible to not duplicate a name? Here is the code
public myBirdList()
{
localPath = Path.Combine(FileSystem.AppDataDirectory, birdfile);
string text = "";
using (var reader = new System.IO.StreamReader(localPath))
{
text = reader.ReadToEnd();
}
InitializeComponent();
birdlistview.ItemsSource = new List<birdlistmodel>
{
new birdlistmodel {Voellist = text }
};
}
const string birdfile = "birdlist.txt";
string localPath;
}

Highlight URL using label span - xamarin.forms

I am creating a chat application in xamarin.forms.What I am trying to achieve is whenever user typed message contains a URL, that should be highlighted and provide click to it.For this feature I found Span in Label text.When user click on send button of chat , I will check for URL and make it as another span.I got this idea from Lucas Zhang - MSFT form this question here.
The problem is I am trying to do the spanning in view model and the individual chat bubble is in another view cell which will call as ItemTemplate in my chat listview. Anyway the spanning is not working as I intended ie; it doesn't highlight .
My view Model.
public Queue<Message> DelayedMessages { get; set; } = new Queue<Message>();
public ObservableCollection<Message> Messages { get; set; } = new ObservableCollection<Message>();
public string TextToSend { get; set; }
public ChatPageViewModel()
{
OnSendCommand = new Command(() =>
{
if (!string.IsNullOrEmpty(TextToSend))
{
var urlStr = TextToSend;
int startIndex = 0, endIndex = 0;
if (urlStr.Contains("www."))
{
startIndex = urlStr.IndexOf("www.");
}
if (urlStr.Contains(".com"))
{
endIndex = urlStr.IndexOf(".com") + 3;
}
if (startIndex != 0 || endIndex != 0)
{
var formattedString = new FormattedString();
Span span1 = new Span() { Text = urlStr.Substring(0, startIndex), TextColor = Color.Black };
formattedString.Spans.Add(span1);
Span span2 = new Span() { Text = urlStr.Substring(startIndex, endIndex - startIndex + 1), TextColor = Color.LightBlue };
span2.GestureRecognizers.Add(new TapGestureRecognizer()
{
NumberOfTapsRequired = 1,
Command = new Command(() => {
})
});
formattedString.Spans.Add(span2);
Span span3 = new Span() { Text = urlStr.Substring(endIndex, urlStr.Length - 1 - endIndex), TextColor = Color.Black };
formattedString.Spans.Add(span3);
var message = new Message
{
Text = formattedString.ToString(),
IsIncoming = false,
MessageDateTime = DateTime.Now
};
Messages.Add(message);
TextToSend = string.Empty;
}
else
{
var message = new Message
{
Text = urlStr.ToString(),
IsIncoming = false,
MessageDateTime = DateTime.Now
};
Messages.Add(message);
TextToSend = string.Empty;
}
}
});
}
Single chat Bubble XAML
<Label x:Name="OutgoingMessage" TextColor="White" FormattedText="{Binding Text}" HorizontalOptions="End" >
</Label>
My Chat page XAML
<Grid RowSpacing="0" Margin="0,20,0,0"
ColumnSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="1" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListView Grid.Row="0"
ItemTemplate="{StaticResource MessageTemplateSelector}"
ItemsSource="{Binding Messages,Mode=OneWay}"
Margin="0"
SelectionMode="None"
FlowDirection="RightToLeft"
HasUnevenRows="True" x:Name="ChatList"
VerticalOptions="FillAndExpand"
SeparatorColor="Transparent"
>
</ListView>
<BoxView HorizontalOptions="FillAndExpand"
HeightRequest="1"
BackgroundColor="#F2F3F5"
Grid.Row="1"/>
<partials:ChatInputBarView Grid.Row="2"
Margin="0,0,0,0"
x:Name="chatInput"/>
</Grid>
ChatPage.xaml.cs
public partial class ChatPage : ContentPage
{
ChatPageViewModel vm;
public ChatPage()
{
InitializeComponent();
this.BindingContext = vm= new ChatPageViewModel();
}
}
Messages class
public class Message : ObservableObject
{
string text;
public string Text
{
get { return text; }
set { SetProperty(ref text, value); }
}
DateTime messageDateTime;
public DateTime MessageDateTime
{
get { return messageDateTime; }
set { SetProperty(ref messageDateTime, value); }
}
public string MessageTimeDisplay => MessageDateTime.Humanize();
bool isIncoming;
public bool IsIncoming
{
get { return isIncoming; }
set { SetProperty(ref isIncoming, value); }
}
}
Any Help is appreciated.
EDIT:
This question was actually continuation of question. Previously I used AwesomeHyperLinkLabel fromlink. The problem was I cant manage the click event of that label.Thats why I moved with label span.Thanks to Leo Zhu - MSFT For the render changes.
For Android:
[assembly: ExportRenderer(typeof(AwesomeHyperLinkLabel), typeof(AwesomeHyperLinkLabelRenderer))]
namespace App18.Droid
{
public class AwesomeHyperLinkLabelRenderer : LabelRenderer
{
public AwesomeHyperLinkLabelRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
var view = (AwesomeHyperLinkLabel)Element;
if (view == null) return;
TextView textView = new TextView(Forms.Context);
textView.LayoutParameters = new LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent);
textView.SetTextColor(view.TextColor.ToAndroid());
// Setting the auto link mask to capture all types of link-able data
textView.AutoLinkMask = MatchOptions.All;
// Make sure to set text after setting the mask
textView.Text = view.Text;
AddHyperlinksManually(textView);
//textView.SetTextSize(ComplexUnitType.Dip, (float)view.FontSize);
// overriding Xamarin Forms Label and replace with our native control
SetNativeControl(textView);
}
public static void AddHyperlinksManually(TextView _tv)
{
SpannableStringBuilder currentSpan = new SpannableStringBuilder(_tv.Text);
Linkify.AddLinks(currentSpan, MatchOptions.WebUrls);
var objects = currentSpan.GetSpans(0, currentSpan.Length(), Java.Lang.Class.FromType(typeof(URLSpan)));
var urlSpans = new URLSpan[objects.Length];
for (var i = 0; i < urlSpans.Length; i++)
{
urlSpans[i] = objects[i] as URLSpan;
}
foreach (URLSpan _url in urlSpans)
{
int iStart = currentSpan.GetSpanStart(_url);
int iEnd = currentSpan.GetSpanEnd(_url);
currentSpan.RemoveSpan(_url);
currentSpan.SetSpan(new CustomURLSpan(_url.URL), iStart, iEnd, SpanTypes.InclusiveInclusive);
_tv.SetText(currentSpan, TextView.BufferType.Normal);
_tv.MovementMethod = LinkMovementMethod.Instance;
}
}
public class CustomURLSpan : ClickableSpan
{
string mTargetURL;
public CustomURLSpan(string _url) {
mTargetURL =_url;
}
public override void OnClick(Android.Views.View widget)
{
//here you could handle the click event,and you could use MessagingCenter to send mTargetURL to your Page.
Console.WriteLine("Click");
}
}
}
The mistake was with my model.Changed string to FormattedString and also changed in the viewmodel
public class Message : ObservableObject
{
FormattedString text;
public FormattedString Text
{
get { return text; }
set { SetProperty(ref text, value); }
}
DateTime messageDateTime;
public DateTime MessageDateTime
{
get { return messageDateTime; }
set { SetProperty(ref messageDateTime, value); }
}
public string MessageTimeDisplay => MessageDateTime.Humanize();
bool isIncoming;
public bool IsIncoming
{
get { return isIncoming; }
set { SetProperty(ref isIncoming, value); }
}
}

Custom ListView binding not working in Xamarin.Forms

I am trying to bind a response to a custom ListView in Xamarin.Forms but the ListView remains blank even when the ObservableCollection contains results. Please point out my mistake in the code below.
In the below code, I am trying to achieve following result -
The response received from Firebase is serialized and then a custom calendar is created based on the response.
To create the calendar, I am using a Model which will contain all the necessary bindings to be displayed in the View. e.g Color, title, counts etc.
I am using Prism MVVM architecture for building the app.
View
<ListView ItemsSource="{Binding ListItems}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Frame BorderColor="LightGray" Padding="5">
<StackLayout Orientation="Horizontal">
<!--<BoxView BackgroundColor="{Binding HighlightColor}" WidthRequest="10"/>-->
<Label Text="{Binding Date}" FontSize="Medium" Margin="20,0,0,0"/>
<Label Text="{Binding Day}" FontSize="Medium" Margin="20,0,0,0"/>
<Label Text="{Binding LeaveCount}" HorizontalOptions="EndAndExpand" HorizontalTextAlignment="End"/>
</StackLayout>
</Frame>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
ViewModel
public ObservableCollection<CalendarItem> ListItems { get; set; }
public LeaveViewerPageViewModel(INavigationService navigationService, IFirebaseService firebaseService)
: base(navigationService, firebaseService)
{
Title = "View Leaves";
ViewCommand = new DelegateCommand(ViewLeaves);
}
private async void ViewLeaves()
{
ListItems = new ObservableCollection<CalendarItem>();
var x = await FirebaseService.GetAll(_month, _year);
foreach (var item in x)
{
ListItems.Add(item);
}
}
Service
public async Task<List<CalendarItem>> GetAll( string month, string year)
{
List<CalendarItem> ItemList = new List<CalendarItem>();
int iterationLimit = DateTime.DaysInMonth(int.Parse(year), (DateTime.ParseExact(month.Substring(0, 3), "MMM", CultureInfo.InvariantCulture)).Month);
for (int i = 1; i <= iterationLimit; i++)
{
CalendarItem ITEM = new CalendarItem();
DateTime dateTime = new DateTime(int.Parse(year), DateTime.ParseExact(month.Substring(0, 3), "MMM", CultureInfo.InvariantCulture).Month, i);
var res = await GetLeavesAsync(i, month, year);
ITEM.Date = dateTime.Day.ToString();
ITEM.Day = dateTime.DayOfWeek.ToString();
ITEM.LeaveCount = res.Count;
ITEM.Leaves = res;
if (res.Count > 0)
ITEM.HighlightColor = res.Count < 5 ? System.Drawing.Color.Yellow : System.Drawing.Color.Tomato;
ItemList.Add(ITEM);
}
return ItemList;
}
Model
public class CalendarItem
{
public Color HighlightColor { get; set; }
public string Date { get; set; }
public string Day { get; set; }
public int LeaveCount { get; set; }
public List<LeaveDetail> Leaves { get; set; }
}
Based on the code you have here there are actually a couple of issues:
1) Your ListItems property in the ViewModel is not itself implementing INotifyPropertyChanged.
public class ViewAViewModel : BindableBase
{
private ObservableCollection<CalendarItem> _listItems;
public ObservableCollection<CalendarItem> ListItems
{
get => _listItems;
set => SetProperty(ref _listItems, value);
}
}
2) You are newing up a ListItems in the ViewLeaves method and then adding each item one at a time. The result here is that if you followed the first step when you set ListItems it would try to update the UI, and then every time you add an item it will try to update the UI.
3) You have two options for optimizing this:
Use ObservableRangeCollection which James Montemagno has in his MvvmHelpers library. This will allow you to new up the collection in the ctor and then simply dump a collection in like:
private async void ViewLeaves()
{
ListItems.ReplaceRange(await FirebaseService.GetAll(_month, _year));
}
Simply implement the property as shown in the first issue I pointed out but change it to IEnumerable<CalendarItem> and simply set it like:
private async void ViewLeaves()
{
ListItems = await FirebaseService.GetAll(_month, _year);
}
Please try modifying your ListItems as follows:-
private ObservableCollection<CalendarItem> _listItems = new ObservableCollection<CalendarItem>();
public ObservableCollection<CalendarItem> ListItems
{
get => _listItems;
set => SetProperty(ref _listItems, value);
}

updating progress bar using Task parallel library

I am trying to update the list of progress bars to show the image download progress using Task Parallel Library in Xamarin.Forms
For now I have written a block of code to simulate the download process by using a delay.
Here is my Xaml file where a ListView named MediaList resides with one caption and progress bar per item.
<?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="ImageTask.View.ImageTaskView">
<ContentPage.Content>
<ListView ItemsSource="{Binding MediaList}" CachingStrategy = "RecycleElement" VerticalOptions="FillAndExpand" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical">
<Label Text = "{Binding mediaName}" FontSize="22" />
<ProgressBar Progress="{Binding mediaProgress}"></ProgressBar>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
</ContentPage>
Here is my view model in which I have created one action block that takes the entire list of media object and tries to update the progress bar.
However, my main issue is I am not able to see the progress updated in my UI , so I don't know how shall I update my UI.
public class ImageTaskViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private IList<MediaInfo> _mediaList;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public IList<MediaInfo> MediaList
{
get { return _mediaList; }
set
{
_mediaList = value;
OnPropertyChanged("MediaList");
}
}
public ImageTaskViewModel()
{
Action<IList<MediaInfo>> progressActionBlock = mediaInfoList =>
{
// infinite loop to simulate download
while (true)
{
IEnumerator<MediaInfo> dataList = mediaInfoList.GetEnumerator();
Task.Delay(2000).Wait();
while (dataList.MoveNext())
{
MediaInfo mediaInfo = dataList.Current;
Debug.WriteLine("media name " + mediaInfo.mediaName + " progress " + mediaInfo.mediaProgress);
if (mediaInfo.mediaProgress == 1)
{
Debug.WriteLine("media name " + mediaInfo.mediaName + " Done ");
break;
}
else
{
mediaInfo.mediaProgress = mediaInfo.mediaProgress + 0.1;
}
}
}
};
var opts = new ExecutionDataflowBlockOptions()
{
MaxDegreeOfParallelism = 2
};
var progressAction = new ActionBlock<IList<MediaInfo>>(progressActionBlock, opts);
MediaList = new List<MediaInfo>();
for (int i = 1; i < 6; i++)
{
MediaInfo mediaInfo = new MediaInfo();
mediaInfo.mediaName = i.ToString();
MediaList.Add(mediaInfo);
}
// Exectue Action block
progressAction.Post(MediaList);
}
}
Model of MediaInfo:
public class MediaInfo
{
public string mediaId { get; set; }
public string mediaName { get; set; }
public string mediaPath { get; set; }
public byte[] mediaStream { get; set; }
public double mediaProgress { get; set; } = 0.1;
}
In your View model you don't have mediaProgress. You should add
Double _mediaProgress;
public Double mediaProgress{
get{
return _mediaProgress;
} set{
_mediaProgress = value;
OnPropertyChanged("Email");
}
}
And then in your all method you use mediaProgress to set the progress.

INotifyPropertyChanged Xamarin string value just disappears

I have a Xamarin.Forms project with a ListView populated with an ObservableCollection. The class (object) that is each item in the ObservableCollection implements INotifyPropertyChanged. A Color property toggles fine in the UI but a string property disappears and never returns.
I get the initial values from a webservice but then just do a completely static change of the values, for debugging and I still can't figure it out.
At the top of the my ContentPage class I have this:
public ObservableCollection<GroceryItem> oc;
After my webservice has returned with the data I put the data in the ObservableCollection and make that, the ItemsSource for the listview. Like this:
lvGroceries.ItemsSource = oc;
All that works great.
The XAML
<ListView x:Name="lvGroceries" ItemTapped="GroceryPdsItemTapped" >
<ListView.ItemTemplate >
<DataTemplate>
<ViewCell>
<AbsoluteLayout VerticalOptions="Fill">
<Label Text="{Binding GroceryName}" AbsoluteLayout.LayoutBounds="0,0,200,40" ></Label>
<Label Text="{Binding strHomeLoc}" AbsoluteLayout.LayoutBounds="200,0,100,40" ></Label>
<Label Text="{Binding isNeeded}" AbsoluteLayout.LayoutBounds="300,0,50,40" ></Label>
<Label Text="someText" BackgroundColor="{Binding myBackgroundColor}" AbsoluteLayout.LayoutBounds="350,0,50,40" ></Label>
</AbsoluteLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The class - GroceryItem
public class GroceryItem : INotifyPropertyChanged
{
public GroceryItem() { }
public event PropertyChangedEventHandler PropertyChanged;
private string privateIsNeeded;
public string isNeeded
{
get { return privateIsNeeded; }
set
{
privateIsNeeded = value;
OnPropertyChanged();
}
}
private Color theColor;
public Color myBackgroundColor
{
get { return theColor; }
set
{
theColor = value;
OnPropertyChanged();
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The click handler. I grab an item from the ObservableCollection and change the two properties.
public void GroceryPdsItemTapped(object obj, ItemTappedEventArgs e)
{
if (e.Item == null)
{
return;
}
var g = ((GroceryItem)e.Item);
foreach (var gr in oc)
{
if (gr.GroceryId == "27769")
{ // the UI changes because the myBackgroundColor property in the GroceryItem class is watching for a value change
gr.myBackgroundColor = (gr.myBackgroundColor == Color.Yellow) ? Color.Blue : Color.Yellow;
gr.isNeeded = (gr.isNeeded == "true" || gr.isNeeded == "blah" || gr.isNeeded == "false") ? "notblah" : "blah";
}
}
}
The Color toggles fine in the UI but the isNeeded string value disappears on the first tap and never re-appears
Ideas?
A couple issues here. First is that you need to make your ObservableCollection run OnPropertyChanged() when it has been changed like so:
private ObservableCollection<GroceryItem> _oc;
public ObservableCollection<GroceryItem> oc {
get { return _oc ?? (_oc = new ObservableCollection<GroceryItem>()); }
set {
if(_oc != value) {
_oc = value;
OnPropertyChanged();
}
}
}
Now you should really have all of this in a ViewModel but since you do not, you need to set your ContentPage as the BindingContext from within your code-behind like this:
public partial class MyGroceryPage : ContentPage {
public MyGroceryPage() { BindingContext = this; }
}
You also need to be binding your ObservableCollection to your ListView.ItemSource instead of assigning it. That looks like this:
<ListView ItemSource="{Binding oc}"/>
If you do the above and then you go into your code behind and execute lvGroceries.ItemsSource = oc; then that would overwrite the binding that you did in your XAML, so do not do that. Instead, when you get data from your web service, you would just assign it to your existing ObservableCollection:
public async Task GetGroceryData() {
List<GroceryItem> myData = await GroceryService.GetGroceriesAsync();
oc = new ObservableCollection<GroceryItem>(myData);
}
Try all of that first and if your items are still not updating you might want to try removing them from your ObservableCollection, changing the properties, then adding them back in:
public void GroceryPdsItemTapped(object obj, ItemTappedEventArgs e) {
if (e.Item == null) { return; }
var g = ((GroceryItem)e.Item);
foreach (var gr in oc.ToList()) {
if (gr.GroceryId == "27769") { // the UI changes because the myBackgroundColor property in the GroceryItem class is watching for a value change
oc.Remove(gr);
gr.myBackgroundColor = (gr.myBackgroundColor == Color.Yellow) ? Color.Blue : Color.Yellow;
gr.isNeeded = (gr.isNeeded == "true" || gr.isNeeded == "blah" || gr.isNeeded == "false") ? "notblah" : "blah";
oc.Add(gr);
}
}
}

Resources