Separate viewmodel for page with Pivot into individual viewmodels - windows-phone-7

I am using MVVMLight, i have created standard view and viewmodel. In view i have placed Pivot:
<Grid x:Name="LayoutRoot" Background="Transparent">
<controls:Pivot Title="MY APPLICATION">
<local:FirstPivotItem />
<local:SecondPivotItem />
</controls:Pivot>
</Grid>
Here how my Pivot item looks like:
<controls:PivotItem x:Class="Pivot.WindowsPhoneControl1"
xmlns:controls="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls"
// standard references and settings
d:DesignHeight="480" d:DesignWidth="480" Header="First One">
<Grid x:Name="LayoutRoot">
</Grid>
</controls:PivotItem>
In code-behind
public partial class WindowsPhoneControl1 : PivotItem
{
public WindowsPhoneControl1() {
InitializeComponent();
}
}
I want to create viewmodel for this pivot item, and then work with it as i do with standard views.
I will use
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<mvvm:EventToCommand Command="{Binding PivotChangedCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
to handle selection changing event and than inform appropriate viewmodels by Messanger.
I do not know how can i use possibilities of viewmodel in Pivot Item class, that is inherited from PivotItem but not from ViewModelBase, as i need.

Use the standard PivotItem control for the individual items and put user controls inside each of them. Each user control can then have its viewmodel as the data context.
<Grid x:Name="LayoutRoot"
Background="Transparent">
<controls:Pivot Title="MY APPLICATION">
<controls:PivotItem Header="first">
<local:PivotUserControl1 />
</controls:PivotItem>
<controls:PivotItem Header="second">
<local:PivotUserControl2 />
</controls:PivotItem>
</controls:Pivot>
</Grid>

Depends on whether you know what the pivot items are going to be in advance or if they are dynamically created based on data. I'll assume its the first, judging by what you've written so far.
The way I've done this before is to have a number of PivotItem view models as public properties on the parent viewmodel, one for each pivot.
Then just set the dataContext of each PivotItem to the correct viewmodel property on the parent viewmodel.
this way you may be able to get away without the selection changed event.
You can also subscribed directly to any events on the view models so don't necessarily need to bother with messaging for communication as all communication can go through the parent view model.
The second approach, if you're not sure in advance exactly what pivot items you'll have is to have a collection of PivotItemViewModels on the parent view model- one per pivot item, each inheriting from a common base class or interface. Bind that collection to the ItemSource of the viewmodel.
This is slightly trickier as you then have to determine what control to display but you can do this via a converter, if you have the Templates stored somehwere as a resource :
public class PivotItemToTemplateConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
switch ((int) value)
{
case 1:
return Application.Current.Resources["Control1Template"];
case 2:
return Application.Current.Resources["Control2Template"];
default:
return Application.Current.Resources["Control3Template"];
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
#endregion
}
you can then create a pivot item template for your pivot items and use the converter to get the correct control within it based on a value. in this case imagine the shared base viewmodel has a property ViewModelId which effctively identifies which control you'll be interested in:
<ContentControl
Content="{Binding}"
ContentTemplate="{Binding ViewModelId,
Converter={StaticResource PivotItemToTemplateConverter }}" />

Related

How can I set the BindingContext of each view in a CarouselView?

I'm trying to let each view in a CarouselView have the same BindingContext object as the parent ContentPage. I've tried the following which doesn't seem to work. MainPage.xaml is the page that is initialized at runtime. It holds a CarouselView with four ContentViews holding "pages" of functionality I can swipe back and forth between from the MainPage.
MainPage.xaml:
...
<CarouselView Grid.Row="1" Grid.ColumnSpan="4"
Position="{Binding CarouselPosition}"
HorizontalScrollBarVisibility="Never">
<CarouselView.ItemsSource>
<x:Array Type="{x:Type ContentView}">
<local:HomeView></local:HomeView>
<local:NewsView></local:NewsView>
<local:ChartsView></local:ChartsView>
<local:SettingsView></local:SettingsView>
</x:Array>
</CarouselView.ItemsSource>
<CarouselView.ItemTemplate>
<DataTemplate>
<ContentView Content="{Binding .}" BindingContext="{Binding BindingContext, Source={x:Reference mainPage}}"/>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
...
Basically, I want to be able to set the BindingContext for each of these ContentViews to be the same as the parent's. I've also read that the ContentViews should be able to inherit the BindingContext sometimes, but it doesn't seem to be happening in this situation.
Thanks.
This is because this kind of control doesn't inherit its BindingContext from its parent but it has its own BindingContext set to the item of the collection binded to the ItemsSource which makes sense.
Let's say you want to display a collection of User. The DataTemplate will be the user interface of a User. You want to display some properties of a User, not your ViewModel.
If you want to bind to something exposed in the ViewModel and not the User, you have to bind the BindingContext of your element with either a RelativeSource or by targeting an element by its x:Name.
Exemple : to bind a button inside your DataTemplate to a command exposed in your ViewModel (not in User), you need to do that :
<Button
Text="Try me"
Command="{Binding
Path=BindingContext.MyViewModelCommand,
Source={x:Reference xNameOfParentOutsideDataTemplateOrThePage}" />
To make it easier to understand, what it does is : I want to bind to something on the control called xNameOfParentOutsideDataTemplateOrThePage (x:Name="xNameOfParentOutsideDataTemplateOrThePage"). On this control, I want to a property of its BindingContext (most of the time, your view model).
Instead of x:Reference, you can also use a RelativeSource binding.

windows phone 8 longlistselector show and hide button based on recordid

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.

WP7 LonglistSelector databinding - How to trigger an update to a bound item's binding

The longlistselector control appears to be just what I need for my application as I have a very long list that I need to display and I like the grouping features that it implements, but I'm really struggling with the databinding aspect of working with it. It took a while to get the basic databinding working, but I have that fully functional. If an item is removed or added the UI updates properly because the datasource derives from ObservableCollection.
The problem that I am having is that I have a flag in the individual dataitems that I am binding to that indicates if an individual list item should display or hide a graphic. The idea is that when the user performs a hold gesture on an item it will toggle the graphic on or off based on changes to the bound item's properties.
Visibility="{Binding Converter={StaticResource isFavoriteToVisibility}}
The LongListSelector databinding requirements for getting the group headers and such requires a datasource like ObservableCollection<ObservableCollection<MyItem>> (which is confusing just to look at!). Essentially, the outer collection is the Groups and the the inner collection contains the displayed items. My items even implement the INotifyPropertyChanged interface. What I'd expected was for updates to my item's properties to be automatically reflected in my databinding, not just the addition or removal of items from the collection.
If I toggle the property value nothing whatsoever happens until I manually refresh the binding which requires a full reload. It is possible that this control doesn't respond the propertychanged events of the individual items.
What I need is help figuring out what I can do to trigger an update of an individual list item after I have changed a property of it's bound object?
Update:
Claus, the code that you created was VERY helpful (sorry, I don't have enough rep to vote you up yet!) Using that code I was able to significantly simplify my code, and do more tests, but it still doesn't solve my problem.
What I've now determined is that my binding is fine. If I change the property when bound simply to a text field text="{Binding IsFavorite}" it updates flawlessly. What doesn't seem to work is when I update that same property when bound to the IValueConverter Visibility="{Binding Converter={StaticResource isFavoriteToVisibility}}" it doesn't update. The converter works fine at initial binding, but not on property change. To go farther with testing, I bound the same bool property to both a text field and the IValueConverter. When I change the bool value the text field updates, but not the field bound using the converter.
public class VisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Chemical chem = value as Chemical;
if (chem == null)
return Visibility.Collapsed;
if (chem.IsFavorite)
return Visibility.Visible;
else
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML:
<myNS:ChemicalToFavoriteVisibilityConverter x:Key="isFavoriteToVisibility" />
<Rectangle Grid.Column="0"
Fill="{StaticResource PhoneContrastBackgroundBrush}"
Height="26" Stroke="Black" Width="26"
Visibility="{Binding Converter={StaticResource isFavoriteToVisibility}}">
<Rectangle.OpacityMask>
<ImageBrush ImageSource="/Images/star_big.png"/>
</Rectangle.OpacityMask>
</Rectangle>
<StackPanel Grid.Column="1" Margin="0,0,0,0">
<TextBlock Text="{Binding IsFavorite}"
TextWrapping="Wrap"
Style="{StaticResource PhoneTextLargeStyle}"
Foreground="{StaticResource PhoneForegroundBrush}"/>
</StackPanel>
I solved the problem. The problem was that I was passing the entire object to the IValueConverter instead of the specific property that changed within the object. I changed the converter to specifically handle conversion of bool to Visibility and fixed the binding:
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool boolValue = (bool)value;
return boolValue ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
The binding changed from:
Visibility="{Binding Converter={StaticResource isFavoriteToVisibility}}"
to:
Visibility="{Binding Converter={StaticResource isFavoriteToVisibility}, Path=IsFavorite}"
Can you verify that in your binding you are not using OneTime as the Binding Mode (instead of OneWay)?
I've used data binding without problems in items in a list bound to the LongListSelector - I suspect that the issue won't lie with the LongListSelector, but with your binding or your INotifyPropertyChanged ... can you post some code (ideally the binding, and also the objects being bound).

WP7: Binding to an element outside pivot.itemtemplate

I've been struggling for a while on this. I'm a bit of a newbie, but i lurked a lot and still couldn't find a solution to my problem, so I decided to post my question here.
I'm binding a pivot to a collection of objects I want to display to create a gallery. Here is my pivot control bound to a list called gallery, where each object contains 2 strings (url and description).
<controls:Pivot ItemsSource="{Binding gallery}" Grid.Row="0" x:Name="galleryPivot">
<controls:Pivot.ItemTemplate>
<DataTemplate>
<StackPanel>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Image Source="{Binding url}" />
<Grid Visibility="{Binding ElementName=galleryPivot, Path=DataContext.ShowDetail}">
<ListBox>
<ListBoxItem>
<StackPanel>
<TextBlock Text="{Binding description}" />
</StackPanel>
</ListBoxItem>
</ListBox>
</Grid>
</Grid>
</StackPanel>
</DataTemplate>
</controls:Pivot.ItemTemplate>
</controls:Pivot>
The datacontext is the viewmodel and initialized in the constructor of the page.
Here is my ViewModel:
public class GalleryViewModel : INotifyPropertyChanged
{
public List<Gallery> gallery
{
get { return Globals.pictures; }
}
private Visibility _showDetail = Visibility.Collapsed;
public Visibility ShowDetail
{
get { return _showDetail; }
set {
_showDetail = value;
RaisePropertyChanged("ShowDetail");
}
}
public GalleryViewModel()
{ }
public event PropertyChangedEventHandler PropertyChanged = delegate { return; };
protected void RaisePropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
The gallery object is a list in my ViewModel, as the ShowDetail property. As ShowDetail is outside the scope, I tried to set ElementName as explained here.
The pivot binds well to the gallery list, but when I change the value of ShowDetail, the grid won't hide. I also tried to set ElementName to LayoutRoot but it still won't work.
My question, how can I bind the visibility when it is outside the scope of the itemtemplate?
Within a DataTemplate the ElementName binding refers only to the names of elements that are within that DataTemplate. The data context within your data template is the Gallery instance, not the GalleryViewModel. You could move the ShowDetail property down to the Gallery class instead.
If you'd rather not do that, then an alternative would be to use a proxy for the data context, which you add as a resource to the page and bind to the page's data context (a GalleryViewModel instance presumably). You can then reference that resource as you would any other resource to get at the parent data context.
If you're not familiar with this proxy concept, then Dan Wahlin's post on the subject should help.

What is the proper way to perform page navigation on ListBox selection changes

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.

Resources