Xamarin Forms - Binding Listview for lazy loading - c#

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.

Related

How do you bind an observable collection to a listview located in a modal/window?

I have a wpf application that I want to be able to launch a separate window in which I will have a listview bound to an observable collection. However I am unable to get the collection values to appear in the list view. Here is some of the code.
Window (Named WizardView):
(Data context defined like so at top of xaml):
d:DataContext="{d:DesignInstance Type=viewModels:MainViewModel}"
<Border Grid.Row="0" Grid.Column="0" Grid.RowSpan="3" BorderBrush="Black">
<ListView BorderThickness="0" ItemsSource="{Binding TestModel.FailedTests}">
<Label Content="Introduction" FontWeight="Bold" />
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding }"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Border>
MainViewModel Code:
public class MainViewModel : BaseViewModel
{
public MainViewModel()
{
TestModel = new TestViewModel();
WizardModel = new WizardViewModel(TestModel);
}
private WizardViewModel _wizardModel;
public WizardViewModel WizardModel
{
get
{
return _wizardModel;
}
set
{
_wizardModel = value;
RaisePropertyChanged();
}
}
private TestViewModel _testViewModel;
public TestViewModel TestModel
{
get
{
return _testViewModel;
}
set
{
_testViewModel = value;
RaisePropertyChanged();
}
}
WizardViewModel Code:
public class WizardViewModel : TestViewModel
{
internal TestViewModel TestModel;
public WizardViewModel(TestViewModel testModel)
{
TestModel = testModel;
(TroubleShootCommand is defined in seperate UC, and launches fine)
TestModel.TroubleShootCommand = new DelegateCommand(Load, CanTroubleShoot);
}
public void Load()
{
(Sync Root is used because it is running on worker thread. Issue somewhere here?)
_syncRoot.Send(o =>
{
var troubleShootWizard = new WizardView();
troubleShootWizard.Owner = Application.Current.MainWindow;
troubleShootWizard.ShowDialog();
}, null);
}
Observable Collection in TestViewModel (Initialized in ctor):
private ObservableCollection<string> _failedTests;
public ObservableCollection<string> FailedTests
{
get { return _failedTests; }
set
{
_failedTests = value;
RaisePropertyChanged();
}
}
Any Help is appreciated, I feel like I have tried everything. I have watched values through the watch window under TestModel.FailedTests in the collection right before and right after launch.
First,
(Data context defined like so at top of xaml): d:DataContext="{d:DesignInstance Type=viewModels:MainViewModel}"
This is a mistake, this way d: you are defining the DataContext at design time..
You can create the viewmodel inside .xaml this way:
<WizardView.DataContext>
<viewModels:MainViewModel/>
</WizardView.DataContext>
Using the design time declaration can help in many ways like knowing the viewmodel in case you are creating it and assigning it in C# (or via a IoC mechanism), also it helps tools like IntelliSense and ReSharper to analyze your bindings and warn you if you misspell a property's name in xaml, auto-completion, etc... (more on this can be found here, and here)
Second, if you are assigning the WizardViewModel in your .xaml the same way (i.e. design-time), then you can either do it in your Load() function (add troubleShootWizard.DataContext = this;) or assign it in .xaml the same way I've mentioned before.

Best way to display view that includes many other views in Xamarin.Forms

I have created view AddonPickerControl that is a horizontal StackLayout with AddonControls. The problem is that Pages that includes AddonPickerListView loads about 2sec, its too long.
I have tried to achive same result with binding addons to a ListView, but the problem is that each cell have to have a counter that describes how much specific addon has been picked. I have no Idea how to do this in ViewCell, so I decided to StackLayout.
public partial class AddonPickerControl : ContentView
{
public AddonPickerControl (AddonPicker addonPicker)
{
InitializeComponent ();
_addonPicker = addonPicker;
BindingContext = _addonPicker;
}
private readonly AddonPicker _addonPicker;
protected override void OnAppearing()
{
foreach (var addon in _addonPicker.AvailableAddons)
{
var addonControl = new AddonControl(addon);
addonControl.AddonPicked += OnAddonPicked;
AddonContainer.Children.Add(addonControl);
}
}
...
}
public partial class AddonControl : ContentView
{
public AddonControl (Addon addon)
{
InitializeComponent ();
_addon = addon;
this.BindingContext = _addon;
}
private readonly Addon _addon;
...
}
How should I display an AddonPickerControl? Filling StackLayout with other views takes too much time. Or maybe it is possible to create a ViewCell that will have a counter that describes how much binded addon has been picked.
Here is an example of how you can have buttons in every item in a list view to update a count for that item.
First, here is a simple list view with a view cell with 3 labels and two buttons:
<ListView x:Name="listView" ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding ItemName}" />
<Label Text="Count:" />
<Label Text="{Binding Count}" />
<Button Text="+" Command="{Binding BtnClickPlusCommand}" />
<Button Text="-" Command="{Binding BtnClickMinusCommand}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Then in the code behind:
public partial class MainPage : ContentPage
{
public ObservableCollection<Item> Items { get; set; } = new ObservableCollection<Item>();
public MainPage()
{
InitializeComponent();
for (int i=1; i<11; i++)
{
Item item = new Item { ItemName = $"Item {i}", Count = "5" };
Items.Add(item);
}
BindingContext = this;
}
}
And the Item class which will have your click handlers and is a simple view model as it implements INotifyPropertyChanged:
public class Item : INotifyPropertyChanged
{
public string ItemName { get; set; }
int _count;
public ICommand BtnClickPlusCommand { get; private set; }
public ICommand BtnClickMinusCommand { get; private set; }
public Item()
{
BtnClickPlusCommand = new Command(btnClickPlus);
BtnClickMinusCommand = new Command(btnClickMinus);
}
void btnClickPlus()
{
Count = (++_count).ToString();
}
void btnClickMinus()
{
Count = (--_count).ToString();
}
public string Count
{
get
{
return _count.ToString();
}
set
{
int j;
if (Int32.TryParse(value, out j))
{
_count = j;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count"));
}
else
Console.WriteLine("value could not be parsed to int");
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
So, in this case we have essentially created a view model for each item so we can have the command that will handle the button click in the actual Item object that is associated with the button, so we just have to update the count. And using bindings, the UI is updated automatically with the new count. The results:

Why is my view not updating when adding data from my service to my viewmodel?

So I just created a new project and I am trying to add some data to the collection in my ViewModel, however when I add data to it, it does add but it wont update the UI.
This is where I set the DataContext and where I am trying to add some content to the collection
ProxyService ps;
public MainWindow()
{
InitializeComponent();
DataContext = new BaseViewModel();
ps = new ProxyService();
ps.AcceptConnection();
}
Keep in mind, it does add it to the collection there are no errors I've debugged it and it's infact in the collection.
ProxyServer.cs
public class ProxyService : MessageViewModel
{
public void AcceptConnection()
{
Messages.Add(new MessageModel { Message = "Awaiting connection..." });
Here is the BaseViewModel
public class BaseViewModel
{
public MessageViewModel MessageViewModel { get; set; } = new MessageViewModel();
}
And the MessageViewModel of course
public class MessageViewModel : ObservableObject
{
private ObservableCollection<MessageModel> _messages;
public ObservableCollection<MessageModel> Messages
{
get { return _messages; }
set
{
_messages = value;
OnPropertyChanged();
}
}
public MessageViewModel()
{
Messages = new ObservableCollection<MessageModel>();
}
}
And here is the XAML for the ScrolLViewer to which I am adding the data
<ScrollViewer Height="380"
Margin="10"
>
<StackPanel>
<ItemsControl ItemsSource="{Binding MessageViewModel.Messages}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock FontFamily="Consolas"
Foreground="#61d73d"
Text="{Binding Message}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
The issue I am facing is that it's not updating the UI when it adds something
However! If I add something in the constructor it works just fine. As an example this works just fine, it shows it in the view accoringly
public MessageViewModel()
{
Messages = new ObservableCollection<MessageModel>();
Messages.Add(new MessageModel { Message = "Hello World!" });
}
My best guess is that it's adding to another instance of some sort but I am not sure, I really don't want to have to use singleton because I feel like that will ruin the MVVM pattern.
Three points of note.
You're setting the Window's DataContext to one instance of BaseViewModel, and then creating a separate instance of the ProxyServiceClass.
Your binding for the ItemsControl.ItemsSource should just be binding to a property of the DataContext, in this case Messages.
Don't keep recreating the ObservableCollection - just create it once and add / remove items as required. Any bound control will detect that it implements INotifyCollectionChanged and refresh itself automatically when the collection is updated.
BaseViewModel is one class and ProxyService another one. You can't expect MessageModel objects that you add to the latter affect the former and vice versa. Try to set the DataContext to a BaseViewModel:
public MainWindow()
{
InitializeComponent();
ps = new ProxyService();
ps.AcceptConnection();
DataContext = new BaseViewModel { MessageViewModel = ps };
}

Adding/removing rows of controls

I have a panel and my idea is to have it populated by a stack panel containing two text boxes. When the user enters something in the left box, something should be generated in the right one, as follows.
<StackPanel Orientation="Horizontal">
<TextBox Name="Lefty" LostFocus="FillMyBuddy" />
<TextBox Name="Righty" LostFocus="FillMyBuddy" />
</StackPanel>
However, I'd like to add an option to add/remove rows and, since I wish not to limit myself to the number of such, I get a bit uncertain regarding the approach on two points.
Manipulating DOM (well, it's XAML/WPF but you see what I'm aiming at).
Event handling.
Is it a big no-no to programmatically affect the mark-up structure of the window? Or is it OK to add/remove panels during run-time?
What would the recommended way to be if I want the Lefty number 3 change stuff in Righty number 3? Anything more neat than checking the sender and pulling its siblings from the parent? I want to use a single event handler for any and all rows (knowing that the operations are always intra-row-wise).
You will want to follow MVVM, and have no code in your code-behind (programmatically affect the mark-up structure) files. The concept is easy when you grasp it, so learn it before you start writing your code.
In short, you are going to want to have a view model (something that implements INotifyPropertyChanged (INPC)) which holds your collection of items (which are going to be models, or view models in pure-MVVM). In "hybrid"-MVVM you could just have your models implement INPC.
Then, through the use of commands, you'd implement the logic to remove items from the list that its in. You can pass references, raise notification, using event bubbling, etc. (it's your preference) to have the item actually removed. In my case, I just passed a "manager" to the hybrid-model and held a reference to that. When the command is called (button is clicked), the model calls for the reference to remove itself from the list.
After you do that you define a DataTemplate to define what an "item" should look like one the View. You use a ItemsControl to show a collection of items, and bind to its ItemsSource so the collection of items are shown. Set your ItemsControl.ItemTemplate to the DataTemplate you created, and anything added to the collection bound to ItemsSource of the type defined in DataTemplate.DataType will render as you specify in the DataTemplate.
At the end of the day, you should learn about MVVM design, DataContext, INPC, Commands, Control types and their "main" properties, e.g. everything that inherits from ItemsControl has an ItemsSource property.
Here is a working example, where changing the original string, will reverse it and put it in the read-only right side text box:
MainWindow.xaml.cs (code-behind)
public partial class MainWindow : Window
{
StructureVm _struct = new StructureVm("Test");
public MainWindow()
{
InitializeComponent();
DataContext = _struct;
}
}
MainWindow.xaml (View)
<Window x:Class="DataTemplateWithCommands.MainWindow"
xmlns:local="clr-namespace:DataTemplateWithCommands"
Title="MainWindow" Height="350" Width="525" Background="Orange">
<Window.Resources>
<DataTemplate DataType="{x:Type local:Model}"
x:Key="VmItem">
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Original, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding Encoded}"
IsReadOnly="True" />
<Button Content="X"
Command="{Binding RemoveMeCommand}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Items}"
ItemTemplate="{StaticResource VmItem}">
</ItemsControl>
</Grid>
</Window>
Interface (helpful for Dependency Injection)
public interface IStructureManager
{
bool RemoveItem(Model itemToRemove);
}
ViewModel
public class StructureVm : IStructureManager
{
private readonly ObservableCollection<Model> _items;
private readonly string _title;
public StructureVm(string title)
{
_title = title;
_items = new ObservableCollection<Model>
{
new Model(this, "12"),
new Model(this, "23"),
new Model(this, "34"),
new Model(this, "45"),
new Model(this, "56"),
new Model(this, "67"),
new Model(this, "78"),
new Model(this, "89"),
};
}}
public ObservableCollection<Model> Items
{
get
{
return _items;
}
}
public string Title
{
get
{
return _title;
}
}
public bool RemoveItem(Model itemToRemove)
{
return _items.Remove(itemToRemove);
}
}
Model (not pure-MVVM, pure MVVM models don't implement INPC, and don't have Command in them)
public class Model : INotifyPropertyChanged
{
private readonly RelayCommand _removeMe;
private string _original;
private string _encoded;
private readonly IStructureManager _manager;
public string Original
{
get
{
return _original;
}
set
{
_original = value;
Encoded = ReverseString(_original);
NotifyPropertyChanged();
}
}
public string Encoded
{
get
{
return _encoded;
}
set
{
_encoded = value;
NotifyPropertyChanged();
}
}
public ICommand RemoveMeCommand
{
get
{
return _removeMe;
}
}
public Model(IStructureManager manager, string original)
{
Original = original;
_manager = manager;
_removeMe = new RelayCommand(param => RemoveMe(), param => CanRemoveMe);
}
private void RemoveMe()
{
_manager.RemoveItem(this);
}
private bool CanRemoveMe
{
get
{
//Logic to enable/disable button
return true;
}
}
private string ReverseString(string s)
{
char[] arr = s.ToCharArray();
Array.Reverse(arr);
return new string(arr);
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName]string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
RelayCommand implementation
From here on out all you have to do is change the attributes of your controls to whatever you're happy with and call it good. The example might be ugly, but I'm leaving it as an exercise for you to figure out other properties/attributes of WPF controls.

Passing binded listbox data to pivot page? Windows Phone 7

I am having an absolute headache figuring this out. I badly need some help with this.
I have a listbox populated with items called with a public static void RSS feed class. Once the listbox populates with the databound items, I click on an item and it passes it through to my pivot page. However, when I flick left or right, all I get is the same image. That is my problem, and what I would like to have happen is if the user flicks left, it loads the previous RSS image. I would like it to also go to the next picture if the If the user scrolls right.
The community has been helpful in providing links to some things, or saying to not use the listbox, etc. However while I am new to all of this, I would just like concrete help with the code i have to achieve what I have in mind. It's nothing personal -- I just need to take babysteps with this before I get worked up with other things I have no clue about.
Here is all my relevant code.
Page 1 Xaml:
<ListBox x:Name="listbox" HorizontalContentAlignment="Stretch" ItemsSource="{Binding items}" SelectionChanged="listbox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<Image Stretch="Fill" Height="60" Width="85" Source="{Binding Url}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Page1 C# Code Behind:
namespace Imaged
{
public partial class UserSubmitted : PhoneApplicationPage
{
private const string Myrssfeed = "http://feeds.bbci.co.uk/news/rss.xml";
public UserSubmitted()
{
InitializeComponent();
//This next function calls the RSS service, and returns the (items) and binds it to
//{listbox.ItemsSource = items;}. I am unable to reference the count of the items, or
//the array of it for some reason? The images load once the page loads.
RssService.GetRssItems(Myrssfeed, (items) => { listbox.ItemsSource = items; }, (exception) => { MessageBox.Show(exception.Message); }, null);
}
}
}
Once the listbox fills I am now trying to pass the selection by the user to a pivot page. I want that same image to show up in the pivot, and when the user pivots left or right, it shows the previous image or next image in the collection.
The Pivot Page I am trying to pass this to, XAML:
<Grid x:Name="LayoutRoot" Background="Transparent">
<!--Pivot Control-->
<controls:Pivot Title="{Binding Title}">
<!--Pivot item one-->
<controls:PivotItem x:Name="item1">
<Image Source="{Binding Url}"/> <!--I take it this is causing the pics to be the same?-->
</controls:PivotItem>
<!--Pivot item two-->
<controls:PivotItem x:Name="item2">
<Image Source="{Binding Url}"/>
</controls:PivotItem>
<!--Pivot item three-->
<controls:PivotItem x:Name="item3">
<Image Source="{Binding Url}"/>
</controls:PivotItem>
</controls:Pivot>
</Grid>
The RSS Service Class being called:
namespace WindowsPhone.Helpers
{
public class RssService
{
public static void GetRssItems(string rssFeed, Action<IList<RssItem>> onGetRssItemsCompleted = null, Action<Exception> onError = null, Action onFinally = null)
{
WebClient webClient = new WebClient();
// register on download complete event
webClient.OpenReadCompleted += delegate(object sender, OpenReadCompletedEventArgs e)
{
try
{
// convert rss result to model
IList<RssItem> rssItems = new List<RssItem>();
Stream stream = e.Result;
XmlReader response = XmlReader.Create(stream);
{
SyndicationFeed feeds = SyndicationFeed.Load(response);
foreach (SyndicationItem f in feeds.Items)
{
RssItem rssItem = new RssItem(f.Title.Text, f.Summary.Text, f.PublishDate.ToString(), f.Links[0].Uri.AbsoluteUri);
rssItems.Add(rssItem);
}
}
// notify completed callback
if (onGetRssItemsCompleted != null)
{
onGetRssItemsCompleted(rssItems);
}
}
finally
{
// notify finally callback
if (onFinally != null)
{
onFinally();
}
}
};
webClient.OpenReadAsync(new Uri(rssFeed));
}
}
}
and finally the RSSItem Class:
namespace WindowsPhone.Helpers
{
public class RssItem
{
public RssItem(string title, string summary, string publishedDate, string url)
{
Title = title;
Summary = summary;
PublishedDate = publishedDate;
Url = url;
// Get plain text from html
PlainSummary = HttpUtility.HtmlDecode(Regex.Replace(summary, "<[^>]+?>", ""));
}
public string Title { get; set; }
public string Summary { get; set; }
public string PublishedDate { get; set; }
public string Url { get; set; }
public string PlainSummary { get; set; }
}
}
Disclaimer: I don't think that binding this many items to a Pivot control is necessarily the right thing to do. Your mileage may vary, but I think a more virtualized solution would be more efficient. For my tests, it seemed to perform OK, but my little voice tells me that there be dragons here...
I recreated your project to the best of my ability and made some enhancements to get it to do what you wanted. Basically, the trick was using a ViewModel that was shared between both the main list page (UserSubmitted.xaml) and the page with the Pivot items on it (PivotPage1.xaml). By setting both page's DataContext property to the same object, we were able to bind both lists to the same source, thus eliminating the need to pass anything around.
In App.xaml.cs:
public static ViewData ViewModel { get; private set; }
private void Application_Launching(object sender, LaunchingEventArgs e)
{
// note: you should properly Tombstone this data to prevent unnecessary network access
ViewModel = new ViewData();
}
Here is how ViewData is defined:
public class ViewData : INotifyPropertyChanged
{
private string _FeedTitle;
private RssItem _SelectedItem = null;
private ObservableCollection<RssItem> _feedItems = new ObservableCollection<RssItem>();
private const string MyRssfeed = "http://feeds.bbci.co.uk/news/rss.xml";
public ViewData()
{
RssService.GetRssItems(
MyRssfeed,
(title, items) =>
{
App.Current.RootVisual.Dispatcher.BeginInvoke(() =>
{
FeedTitle = title;
FeedItems = new ObservableCollection<RssItem>(items);
});
},
(exception) =>
{
MessageBox.Show(exception.Message);
},
null);
}
public ObservableCollection<RssItem> FeedItems
{
get { return _feedItems; }
set
{
if (_feedItems == value)
return;
_feedItems = value;
NotifyPropertyChanged(this, new PropertyChangedEventArgs("FeedItems"));
}
}
public string FeedTitle
{
get { return _FeedTitle; }
set
{
if (_FeedTitle == value)
return;
_FeedTitle = value;
NotifyPropertyChanged(this, new PropertyChangedEventArgs("FeedTitle"));
}
}
public RssItem SelectedItem
{
get { return _SelectedItem; }
set
{
if (_SelectedItem == value)
return;
_SelectedItem = value;
NotifyPropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(object sender, PropertyChangedEventArgs args)
{
if (PropertyChanged != null)
PropertyChanged(sender, args);
}
}
Once this is established, it's relatively easy to wire up both page's data context properties to App.ViewModel.
Last item was the scrolling and positioning of the selected item when navigating. When you select an item from the list page, the SelectedItem property of the shared ViewModel is bound to the SelectedItem property on the ListBox. After navigation to the details page, we have to find the selected item in the pivot and make it visible:
public PivotPage1()
{
InitializeComponent();
Loaded += (sender, e) =>
{
this.DataContext = App.ViewModel;
var selectedItem = App.ViewModel.SelectedItem;
var pi = ItemPivot.Items.First(p => p == selectedItem);
ItemPivot.SelectedItem = pi;
};
}
Setting the SelectedItem property of the Pivot control scrolls the pivot to the proper item and makes it visible.
The full sample is posted at http://chriskoenig.net/upload/imaged.zip if you want to see it in action.
If I got you correctly, you need to bind listbox in following way:
<ListBox ItemsSource="{Binding items}" SelectedItem="{Binding SelectedFeed, Mode=TwoWay}" />
And then bind Pivot in same way:
<Pivot ItemsSource="{Binding items}" SelectedItem="{Binding SelectedFeed, Mode=TwoWay}" />
Try the following for the pivot (based on Alex's code)
<Pivot ItemsSource="{Binding items}" SelectedItem="{Binding SelectedFeed, Mode=TwoWay}">
<Pivot.ItemTemplate>
<DataTemplate>
<Image Source="{Binding Url}"/>
</DataTemplate>
</Pivot.ItemTemplate>
</Pivot>
It assumes on the pivot page DataContext there is the same object "items" providing access to all the feeditems, and a property SelectedFeed which (as Alex mentioned) supports INotifyPropertyChanged

Categories