I want to databind an ObservableCollection to a Pivot contronl in WP7 so that each object in my ObservableCollection becomes a PivotItem. This is the code I use:
<controls:Pivot x:Name="MainPivot" ItemsSource="{Binding Persons}">
<controls:Pivot.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding FullName}"/>
</DataTemplate>
</controls:Pivot.HeaderTemplate>
<controls:Pivot.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17" Width="432">
<TextBlock Text="{Binding FirstName}"/>
<TextBlock Text="{Binding LastName}"/>
<TextBlock Text="{Binding HomeTown}"/>
</StackPanel>
</DataTemplate>
</controls:Pivot.ItemTemplate>
</controls:Pivot>
This works and with tre items in my ObservableCollection I get three PivotItems. But when everything gets loaded the binding inside the DataTemplate won´t get updated. It is only when I scroll to the next PivotItem that the FirstName, LastName and HomeTown gets loaded.
Why is that? What am I missing?
Thanks
Check this discussion:
DataBound Pivot control is not creating the first PivotItem
I had the same problem, but the workaround with setting SelectedIndex=1 didn't suit me.
I found the other solution: when you adding the Item to your Persons collection you should first create a temp element and only when you fill all data add it to your Persons collection.
Person tempPers = new Person() { FullName = "Abduvaliev Edem", FirstName = "Edem", LastName = "Abduvaliev", HomeTown = "Sevastopol"};
Pesrons.Add(tempPers);
After doing a simple test i cannot reproduce this behavior. I i put a breakpoint inside the get block of the equivalent of FirstName with two items in my ObservableCollection i get two hits.
How did you detect that it is not bound? You cannot see the "next" pivotitems content, so how?
Sounds like there's some problem with the loading order - or with the notification code.
Check that you are correctly firing the PropertyChanged event when you set the properties on each of your FirstName, LastName and HomeTown members.
Here's how I do it. The problem for me is that the collection updates asynchronously in response to a web method call.
void Page_Loaded(object sender, RoutedEventArgs e)
{
_log.Debug("Page loaded.");
var vm = this.GetViewModel<TrendsViewModel>();
if (!vm.IsInitialized)
{
vm.PivotItems.CollectionChanged += (origin, args) =>
{
this.PivotControl.DataContext = null;
this.PivotControl.DataContext = vm;
};
vm.Initialize(this);
}
}
The key is hooking up an observer of the collection to which the Pivot items are bound and giving the data context a shake when it updates.
Related
I have a books list displayed in my long list selector like this
<DataTemplate x:Key="BooksItemTemplate">
<StackPanel Grid.Column="1" Grid.Row="0" VerticalAlignment="Top">
<TextBlock Name="booktitle" Text="{Binding BookTitle,Mode=OneWay}" Style="{StaticResource PhoneTextNormalStyle}" TextWrapping="Wrap" FontFamily="{StaticResource PhoneFontFamilySemiBold}"/>
<TextBlock Text="{Binding AuthorName,Mode=OneWay}" Style="{StaticResource PhoneTextNormalStyle}" TextWrapping="Wrap" FontFamily="{StaticResource PhoneFontFamilySemiLight}"/>
<Button Content="Add To Favourites" Tag="{Binding BookId,Mode=OneWay}" Click="Button_Click_1" ></Button>
</StackPanel>
</Grid>
</DataTemplate>
<phone:LongListSelector x:Name="bookslist" Grid.Row="1"
ListFooter="{Binding}"
ItemsSource="{Binding BooksList}"
Background="Transparent"
IsGroupingEnabled="False"
ListFooterTemplate ="{StaticResource booksListFooter}"
ItemTemplate="{StaticResource BooksItemTemplate}"/>
so there is an add to favourites button beside every book in the list . pressing that button i am entering the pressed book's id in my isolatedstoragesetting like this
private void Button_Click_1(object sender, RoutedEventArgs e)
{
Button bt = (Button)sender;
IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;
List<long> ListFavourites;
if (settings.Contains("ListFavourites"))
{
ListFavourites = settings["ListFavourites"] as List<long>;
}
else
{
ListFavourites = new List<long>();
}
if(!ListFavourites.Contains(Convert.ToInt64(bt.Tag)))
{
ListFavourites.Add(Convert.ToInt64(bt.Tag));
}
settings["ListFavourites"] = ListFavourites;
settings.Save();
}
problem:
now when loading the above book list(longlistselector) when the page loads i want to show or hide the add to favorites button based on whether the bookid is present in the isolatedstoragesetting or not. what i have tried here is that i have tried to bind the converter to the add to favourite button and also bind the convertparameter with bookid. but the convertparameter doesn't support binding.
so what is the technique i can use to show or hide the add to favourite button based on the book id presence in the favourite list in the isolatedstoragesetting?
how can i hide the button based when clicking it based on the bookid?
You are almost there in thinking to use a converter. The actual idea, when materialized, should look something like this.
First, you will need to implement a converter, in this case, you will need to convert the bookid to a Visibility value.
public class BookIdToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
//value is booking id here, which means that you just need to check against the isolatedStorageSettings
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return null;
}
}
In your DataTemplate, the binding should take place like this:
<Button Content="Add To Favourites" Tag="{Binding BookId,Mode=OneWay}"
Click="Button_Click_1" Visibility={Binding BookId,Converter={StaticResource TheConverterCreatedAbove}} >
</Button>
That should do the trick.
The MVVM way would be to expand your ViewModel. It would be much better to add an AddToFavoritesCommand to BookViewModel instead of putting the logic in code behind. If the button is bound to that Command it would automatically go disabled when the Command would properly (with CanExecuteChanged) switch CanExecute to false.
In your case, you can add a property IsFavourite or CanAddToFavoirtes and then use a standard BoolToVisibility converter (the Command execution would set this property and the BookViewModel would be initialized with the correct value read from IsolatedStorage).
All logic behind the presentation of Book and functionalities related to Book belong to BookViewModel.
I am binding a collection of images that I retrieve from IsolatedStorage to my View, but I am having issues with changing the order in which images are displayed. There is a timestamp associated with each image, and I would like to be able to sort by either ascending or descending order. As of now the binding works, but when I attempt to change the sort order before binding to my listbox, nothing shows on the UI.
MainPage.xaml
<ListBox x:Name="Recent" ItemsSource="{Binding Pictures}" Margin="8"
SelectionChanged="recent_SelectionChanged" toolkit:TiltEffect.IsTiltEnabled="True"
ItemContainerStyle="{StaticResource MyStyle}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
MainPage.xaml.cs
// Constructor
public MainPage()
{
InitializeComponent();
//This is working, but is not sorted
//DataContext = PictureRepository.Instance;
//Attempt at sorting before binding
//from SettingsPage, if AscendingSort = True then sort ascending
if (Settings.AscendingSort.Value)
{
//Give no errors, but does not display on UI
DataContext = PictureRepository.Instance.Pictures.OrderBy(s => s.DateTaken);
}
else
{
DataContext = PictureRepository.Instance.Pictures.OrderByDescending(s => s.DateTaken);
}
}
Not sure exactly what the issue is here?
Note, when debugging, I can see that PictureRepository.Instance contains the images in which to display in the view.
Try this,
listItems = new ObservableCollection<YourType>(listItems.OrderBy(x => x.DateTaken));
I'm using MVVM in this project, I have a listbox which bind to a collection of Customers. I want to create an event to navigate a detailsPage using id of the elementselected:
<ListBox ItemsSource="{Binding Customers}" x:Name="state_list" SelectionChanged="state_list_SelectionChanged">
<i:Interaction.Triggers>
<i:EventTrigger EventName="selectionchanged">
<cmd:EventToCommand Command="{Binding stateSelectedCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding nom}" />
<!--TextBlock Text="{Binding LastName}" />
<TextBlock Text="{Binding Text, ElementName=tbCount}" /-->
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I can't figure out how to get the selected item to add it to the uri and then use it to get data. An example or tutorial would be helpful. Thanks :)
I would create a "SelectedCustomer" property in the ViewModel (next to you Customers property) and bind it to the SelectedItem. Then, on the setter of that property you can navigate to your desired page. This way you eliminate the messy events and command.
<ListBox x:Name="state_list
ItemsSource="{Binding Customers}"
SelectedItem="{Binding SelectedCustomer, Mode=TwoWay}">
...
public Customer SelectedCustomer
{
get
{
return _selectedCustomer;
}
set
{
if (value != null)
{
_selectedCustomer = value;
//Navigate to your page here, either with Navigator class or any other mechanism you have in place for changing content on screen
}
}
}
AlexDrenea gives you a good way of binding SelectedItem to a property on your viewmodel. If you are wanting to navigate based on this in an MVVM architecture, I would suggest using messaging to get it done.
I cover this in a blog post I did a while back, but the short summary of doing this within MVVMLight, is to create a Navigator class that sits at the application level.
public class Navigator
{
private PhoneApplicatoinFrame RootFrame;
public Navigator(PhoneApplicationFrame frame)
{
RootFrame = frame;
RegisterMessages();
}
private void RegisterMessages()
{
Messenger.Default.Register<ShowTrackerMessage>(this, ShowTracker);
}
private void ShowTracker(ShowTrackerMessage msg)
{
RootFrame.Navigate(new Uri("/Views/ItemLocationCompassView.xaml", UriKind.RelativeOrAbsolute));
}
}
Then, as part of your application start-up, create it and pass it a reference to your RootFrame:
private static Navigator _navigator;
public static Navigator Nav
{
get { return _navigator; }
}
...
_navigator = new Navigator(this.RootFrame);
Then, you have a couple choices on how you send the Navigation message.
Option 1: In your ViewModel, hook into the PropertyChanged event (part of INotifyPropertyChanged), and send the appropriate message when your SelectedItem property changes.
Option 2: Tie into the SelectionChanged event of your ListBox. I use the MVVMLight's EventToCommand to send that event to a RelayCommand in my ViewModel, then react appropriately to send the message to the Navigator object.
I cover this in more detail at: http://www.smartchitecture.com/?p=27
i have a Panorama control and ListBox controls inside the Panorama. is there any "event" that i can hook on to or any way to detect when all the data binding or UI display associated with the Panorama and/or ListBox controls are finished?
the reason i need to detect this event is because i want to show the ApplicationBar only after the Panorama and/or ListBox controls have completely binded and finished rendering.
for example, my XAML is defined as the following.
<controls:Panorama Name="panorama">
<controls:Panorama.ItemTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding Details}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Field1}"/>
<TextBlock Text="{Binding Field2}"/>
...
<TextBlock Text="{Binding FieldN}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</controls:Panorama.ItemTemplate>
</controls:Panorama>
my plain-old CLR object (POCO) looks like the following.
public class MyPoco {
List<Detail> Details { get; set; }
}
public class Detail {
public string Field1 { get; set; }
public string Field1 { get; set; }
...
public string FieldN { get; set; }
}
in my c# code-behind, i bind the data as follows.
List<MyPoco> pocos = GetMyPocosFromSomewhere();
panorama.ItemsSource = myList;
ApplicationBar.IsVisible = true; //i only want to make this visible after the Panorama and ListBox controls have finished binding and rendering
right now, the code as i have sketched out above works, but the ApplicationBar is always visible before the Panorama/ListBox controls have rendered. to me, this makes the user experience awkward.
any help is appreciated.
Short answer would be "no, you can't detect it".
But a good solution is to add the command to the UI work queue aka. the Dispatcher. Like this:
Dispatcher.BeginInvoke(() => ApplicationBar.IsVisible = true);
That way, it'll first render it, when all the other UI tasks are done, and the experience shouldn't be so awkward.
I'm trying the MVVM Light Toolkit. Though I still think having multiple ViewModels for such small apps is overkill, I like the concepts. What I still can't quite understand is how (or I should say "what is the recommended way") to navigate from one page to another when the selection changes in a ListBox.
The big problem with this toolkit is that it forces you to learn MVVM via other sources before using it, rather than show you what (its vision of) MVVM is from within the framework, accompanying samples and documentation. Are there samples out there showing the different concepts? And please, no videos.
Have you tried modifying your ListBox ItemTemplate to have each item be a HyperlinkButton and just setting the NavigateURI attribute to the Page you want to navigate to?
I still have not figured out how to do this (navigate to a details page upon selection changed in a listbox) without any codebehind in the view. However, if you are OK with having just a little codebehind in the view here's what I recommend:
<ListBox x:Name="MainListBox" Margin="0,0,-12,0" ItemsSource="{Binding Items}"
SelectionChanged="MainListBox_SelectionChanged"
SelectedItem="{Binding Path=SelectedListItem, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17" Width="432">
<TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
<TextBlock Text="{Binding LineTwo}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
First, per above bind to the SelectedItem property of the Listbox with a TwoWay binding to a property in your ViewModel (SelectedListItem in the above).
Then in your codebehind for this page implement the handler for MainListBox_SelectionChanged:
// Handle selection changed on ListBox
private void MainListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// If selected index is -1 (no selection) do nothing
if (MainListBox.SelectedIndex == -1)
return;
// Navigate to the new page
NavigationService.Navigate(new Uri("/DetailsPage.xaml", UriKind.Relative));
}
This is the only codebehind you need in your main view.
In your main ViewModel you need a SelectedListItem property:
public const string SelectedListItemPropertyName = "SelectedListItem";
private ItemViewModel _SelectedListItem;
/// <summary>
/// Sample ViewModel property; this property is used in the view to display its value using a Binding
/// </summary>
/// <returns></returns>
public ItemViewModel SelectedListItem
{
get
{
return _SelectedListItem;
}
set
{
if (value != _SelectedListItem)
{
_SelectedListItem = value;
RaisePropertyChanged(SelectedListItemPropertyName);
}
}
}
Now, the trick to getting the context passed to your details page (the context being what list item was selected) you need to setup the DataContext in your Details view:
public DetailsPage()
{
InitializeComponent();
if (DataContext == null)
DataContext = App.ViewModel.SelectedListItem;
}
Hope this helps.
eventually you'll want to do more than just navigate, potentially navigate after setting a custom object.
Here is a MVVM-light way of doing this.
You'll first want to bind your listbox selected item to a property in your viewmodel
<ListBox ItemsSource="{Binding Events}" Margin="0,0,-12,0" SelectedItem="{Binding SelectedEvent, Mode=TwoWay}">
Declare your SelectedEvent property
public const string SelectedEventPropertyName = "SelectedEvent";
private Event _selectedEvent;
public Event SelectedEvent
{
get {return _selectedEvent;}
set
{
if (_selectedEvent == value)
{
return;
}
var oldValue = _selectedEvent;
_selectedEvent = value;
// Update bindings and broadcast change using GalaSoft.MvvmLight.Messenging
RaisePropertyChanged(SelectedEventPropertyName, oldValue, value, true);
}
}
You can then define an interaction trigger bound to the tap event
<i:Interaction.Triggers>
<i:EventTrigger EventName="Tap">
<cmd:EventToCommand Command="{Binding EventPageCommand, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
In your viewmodel, define your EventPageCommand as a RelayCommand:
public RelayCommand EventPageCommand { get; private set; }
public MainViewModel()
{
EventPageCommand = new RelayCommand(GoToEventPage);
}
and finally declare your GoToEventPage method
private void GoToEventPage()
{
_navigationService.NavigateTo(new Uri("/EventPage.xaml", UriKind.Relative));
}
note that you can do other actions before navigating to your new page, plus your selected item from your list box is currently set in the property you bound it too.