I'm using view models for my ContentPage's in my Xamarin Forms 5 app and typically call an Init() method in my view model from the OnAppearing() method in code behind.
I tried the same approach in my ContentView but it's never hitting the OnAppearing() method.
This is my ContentView code:
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:vm="clr-namespace:MyApp.ViewModels"
x:Class="MyApp.MyContentView">
<ContentView.BindingContext>
<vm:MyViewModel/>
</ContentView.BindingContext>
<ContentView.Content>
<StackLayout
BackgroundColor="{StaticResource PrimaryDark }"
HeightRequest="200">
<Label
Text="{Binding User.FullName}"
TextColor="White"
FontSize="Medium"
FontAttributes="Bold"
HorizontalOptions="CenterAndExpand"/>
</StackLayout>
</ContentView.Content>
</ContentView>
The view model for this content view looks like this:
public class MyViewModel : BaseViewModel
{
User user;
public MyViewModel()
{
}
public User User
{
get => user;
set
{
if (user == value)
return;
user = value;
OnPropertyChanged();
}
}
public async void Init()
{
// Get user info
var data = await _dbService.GetUser();
if(data != null)
{
User = data;
OnPropertyChanged(nameof(User));
}
}
}
And in my code behind, this is what I'm doing:
public partial class MyContentView : ContentView
{
MyViewModel _vm;
public MyContentView()
{
InitializeComponent();
_vm = new MyViewModel();
BindingContext = _vm;
}
protected virtual void OnAppearing()
{
_vm.Init();
}
}
This pattern is working nicely in my content pages but not working in a content view. What am I doing wrong here?
The content view doesn't have the lifecycle methods like the content page. So when the content view shows or displays on the screen, the OnAppearing() and OnDisAppearing method developer custom will not invoke.
So you can call the the page's OnAppearing() method to do that if there is only a content view in your page. And if there is not only one contentview, you can call the _vm.Init(); method when you use the instance of the content view.
Here's what I've done and it seems to be working fine.
First, I created a ContentView to display the flyout header which includes user's avatar and name. Notice that I set the view model for this content view in the XAML file -- see below:
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
xmlns:vm="clr-namespace:MyApp.ViewModels"
x:Class="MyApp.Views.FlyoutHeader">
<ContentView.BindingContext>
<vm:AppViewModel/>
</ContentView.BindingContext>
<ContentView.Content>
<StackLayout
BackgroundColor="{StaticResource PrimaryDark }"
HeightRequest="200">
<xct:AvatarView
Source="{Binding UserInfo.AvatarUrl}"
Size="100"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand"/>
<Label
Text="{Binding UserInfo.FullName}"
TextColor="White"
FontSize="Medium"
FontAttributes="Bold"
HorizontalOptions="CenterAndExpand"
Margin="0,0,0,30"/>
</StackLayout>
</ContentView.Content>
</ContentView>
I then created a view model named AppViewModel that I intend to use in multiple places, including the FlyoutHeader.xaml that I shared above. Here's what AppViewModel looks like:
public class AppViewModel : BaseViewModel
{
User user { get; set; }
public AppViewModel()
{
}
public User UserInfo
{
get => user;
set
{
if (user == value)
return;
user = value;
OnPropertyChanged();
}
}
public async void Init()
{
if(user == null || user.Id == Guid.Empty)
{
var data = await _dbService.GetUser();
if(data != null)
{
UserInfo = data;
OnPropertyChanged();
}
}
}
}
Finally, in the code behind for FlyoutHeader.xaml.cs, I call the Init() method of the view model in the constructor:
public partial class FlyoutHeader : ContentView
{
AppViewModel _vm;
public FlyoutHeader()
{
InitializeComponent();
_vm = new AppViewModel();
_vm.Init();
BindingContext = _vm;
}
}
I'm actually a bit concerned that there maybe tight coupling with the UI and the async call being initiated in the constructor may tie up the UI thread and delay it. Please let me know if there's a better way to handle this.
Related
With the Xamarin Forms ListView, the ItemSelected event is fired each time an element is selected in the list.
Is there a way to cancel out of this event so that the new item isn't selected and the old item remains selected?
The use case is a master/detail type of view where selecting an item in the list changes the detail. But the detail view from the previous selection might have been altered and the user needs to decide to discard or save the previous changes before changing the List's SelectedItem.
#SushiHangover's suggestion to control the SelectionMode property and disable/enable the selection of the ListView is a good one. However, I have an alternate solution that will revert the ListView's selected item to the previous item for anyone who might have a similar need.
I will only post snippets of the solution, but they should be complete enough for someone else to learn and implement.
First, I am using FreshMVVM which provides (amongst many things), essentially, syntactic sugar over binding the View to the ViewModel. Also, the PropertyChanged nuget package creates the INotifyPropertyChanged boilerplate code at compile time. That is why you don't see the familiar XF patterns you normally see with that interface. AddINotifyPropertyChanged handles all that.
The solution to my problem is a dedicated, generic ListViewModel that can be bound to any ListView that needs the ability "roll back" a selection changed event. It binds to the Items collection. Additionally the SelectedItem property is bound to the control as well.
The constructor takes a Func which is called to determine if it's ok to move the selection or not.
[AddINotifyPropertyChangedInterface]
public class ListViewModel<T>
{
private Func<bool> _beforeChangeValidator;
private Action _afterChange;
public ListViewModel(Func<bool> beforeChangeValidator, Action afterChange)
{
_beforeChangeValidator = beforeChangeValidator;
_afterChange = afterChange;
_changing = false;
}
public int SelectedIndex { get; set; }
public T SelectedItem { get; set; }
public ObservableCollection<T> Items { get; set; }
private bool _changing;
public Command SelectedItemChanged
{
get
{
return new Command((args) =>
{
if (!_changing)
{
if (_beforeChangeValidator())
{
SelectedIndex = ((SelectedItemChangedEventArgs)args).SelectedItemIndex;
}
}
_changing = false;
});
}
}
public void RevertSelectedItemChanged()
{
_changing = true;
SelectedItem = Items[SelectedIndex];
}
}
And the code in the parent ViewModel has the Func (TagListBeforeChange) that determines if it's ok to move the selection or not. In this case I am checking if the last selected item has been changed, and if it has, prompt the user for what to do.
public override void Init()
{
TagListViewModel = new ListViewModel<Tag>(TagListBeforeChange, null);
}
private bool TagListBeforeChange()
{
if (ActiveTag.HasChanged)
{
var confirmConfig = new ConfirmConfig()
{
Message = "Current tag has changed. Discard changes and continue?",
OkText = "Discard Changes",
CancelText = "Cancel",
OnAction = (result) =>
{
if (result)
{
_mapper.Map(TagListViewModel.SelectedItem, ActiveTag);
}
else
{
TagListViewModel.RevertSelectedItemChanged();
}
}
};
_userDialogs.Confirm(confirmConfig);
return false;
}
_mapper.Map(TagListViewModel.SelectedItem, ActiveTag);
return true;
}
And finally, here is the ListView control declaration...
<ListView ItemsSource="{Binding TagListViewModel.Items}"
SelectedItem="{Binding TagListViewModel.SelectedItem, Mode=TwoWay}">
<ListView.Behaviors>
<behaviors:EventHandlerBehavior EventName="ItemSelected">
<behaviors:InvokeCommandAction Command="{Binding TagListViewModel.SelectedItemChanged}" />
</behaviors:EventHandlerBehavior>
</ListView.Behaviors>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ContentView Padding="8">
<Label Text="{Binding DisplayValue}" />
</ContentView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The code below shows a simple example of a CollectionView. I am not receiving the event for the SelectionChangedCommand. Can someone see what I am doing wrong?
btw, the complete source for this can be found on GitHub here.
MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:ControlDemo"
x:Class="ControlDemo.MainPage">
<StackLayout>
<CollectionView SelectionMode ="Single"
ItemsSource="{Binding Tags}"
SelectionChangedCommand="{Binding SelectedTagChanged}">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Text="{Binding .}" />
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</ContentPage>
MainPageModel.cs
public class MainPageModel : FreshBasePageModel
{
public override void Init(object initData)
{
Tags = new List<string>() { "A", "B", "C" };
base.Init(initData);
}
public List<string> Tags { get; set; }
public Command SelectedTagChanged
{
get
{
return new Command(() =>
{
});
}
}
}
Couple of things that worked at my side (additionally to the SelectionMode = Single):
Make sure your Command' signature is <object> at your PageModel and do any cast according to your needs (specially if your collection becomes more complex).
Also at your XAML you want to give your CollectionView a name and use the SelectedItem property.
SelectionChangedCommandParameter="{Binding SelectedItem, Source={x:Reference cvTagsCollectionView}}"
I use your code and created a demo on my side, I add the widthRequest and HeightRequest to make the collectionView work:
<CollectionView
HeightRequest="170"
WidthRequest="200"
SelectionMode="Single"
SelectionChangedCommand="{Binding SelectedTagChangedCommand}"
ItemsSource="{Binding Tags}"
>
The SelectionChangedCommand did triggered after I click different items in the CollectionView.
I uploaded a sample here and you can check it: collectionView-selectItemChanged-xamarin.forms
If you want to use your ViewModel, then you should use the Binding for the SelectedItem:
<CollectionView ItemsSource="{Binding Monkeys}"
SelectionMode="Single"
SelectedItem="{Binding SelectedMonkey, Mode=TwoWay}">
...
</CollectionView>
and, in your ViewModel:
Monkey selectedMonkey;
public Monkey SelectedMonkey
{
get
{
return selectedMonkey;
}
set
{
if (selectedMonkey != value)
{
selectedMonkey = value;
}
}
}
So everytime you select a new object, the SelectedMonkey will be updated.
If you want to track the SelectionChanged, then, it should be in the code-behind (not sure how to implement within the viewmodel, nothing about that in the docs)
<CollectionView ItemsSource="{Binding Monkeys}"
SelectionMode="Single"
SelectionChanged="OnCollectionViewSelectionChanged">
...
</CollectionView>
And, in your Page.xaml.cs:
void OnCollectionViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var previous = e.PreviousSelection;
var current = e.CurrentSelection;
...
}
I improved Fabricio P. answer in this way:
Used {RelativeSource Self} for SelectionChangedCommandParameter. It helps to omit named collection views.
So your xaml part will be something like this:
<CollectionView
ItemsSource="{Binding Objects}"
SelectionMode="Single"
SelectionChangedCommand="{Binding SelectObjectCommand}"
SelectionChangedCommandParameter="{Binding SelectedItem, Source={RelativeSource Self}}">
And in your view model:
public ICommand SelectObjectCommand => new Command<string>(i => { Debug.WriteLine("Selected: " + i); });
public IEnumerable<string> Objects { get; set; }
It doesn't look like you set the SelectionMode property. According to the docs:
By default, CollectionView selection is disabled. However, this behavior can be changed by setting the SelectionMode property value to one of the SelectionMode enumeration members:
None – indicates that items cannot be selected. This is the default value.
Single – indicates that a single item can be selected, with the selected item being highlighted.
Multiple – indicates that multiple items can be selected, with the selected items being highlighted.
Adding SelectionMode = Single to the CollectionView will resolve your problem.
if you are using the Model for it you can use the following method
My C# Model View Class
public class MainView : INotifyPropertyChanged
{
public ICommand SelectionChangedCommands => new Command<GroupableItemsView>((GroupableItemsView query) =>
{
GO_Account test = query.SelectedItem as GO_Account;
});
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And this is my XAML
<CollectionView x:Name="myAccounts"
SelectionMode="Single"
ItemsSource="{Binding Products}"
SelectionChangedCommand="{Binding SelectionChangedCommands}"
SelectionChangedCommandParameter="{Binding Source={x:Reference myAccountsModel}}">
</CollectionView>
Hello! The code I give below works fine on an IOS simulator, but does not work on a real device (Iphone 6S Plus) - the tag collection (what it is in my case - you look further) does not fill out. (Xamarin.Forms, IOS project, Prism, CarouselViewController)
I apologize in advance for a large amount of code - this is part of a large program being developed, I immediately decided to bring all the necessary listing.
1.There are PageA/PageAViewModel (linked BindableBase (Prism)), PageB/PageBViewModel (linked BindableBase (Prism)) and logical ViewModelС. On PageAViewModel a logical ViewModelС is created and transmitted to PageBViewModel as a navigation parameter:
async Task GoPageB()
{
var navigationParams = new NavigationParameters();
navigationParams.Add("ViewModelС", ViewModelС);
await NavigationService.NavigateAsync(new System.Uri("http://.../PageB",
System.UriKind.Absolute), navigationParams);
}
2. In the course of executing the code, it is collected by PageB, then its PageBViewModel, then it works out the PageB method of receiving navigation parameters in which the PageBViewModel receives the ViewModelC:
public override void OnNavigatingTo(INavigationParameters parameters)
{
var modelInParameters = parameters["ViewModelC"] as ViewModelC;
MyViewModelC = modelInParameters;
}
3.ViewModelC is in PageBViewModel as a property, while in the constructor it is not declared, but is primarily obtained and assigned in the OnNavigatingTo method
public ViewModelC MyViewModelC
{
get => _myViewModelC;
set => SetProperty(ref _myViewModelC, value);
}
4.PageB/PageBViewModel is actually an onboarding built on CarouselViewControler (https://github.com/alexrainman/CarouselView). In XAML, it looks like this:
<ContentPage.Content>
<abstractions:CarouselViewControl x:Name="OnBrdngPg_CrslVwCntrl"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand"
Orientation="Horizontal"
InterPageSpacing="0"
IsSwipeEnabled="True"
IndicatorsTintColor="{StaticResource ClrGreyLight}"
CurrentPageIndicatorTintColor="{StaticResource ClrLightorange}"
AnimateTransition="True"
Position="0"
ShowIndicators="True"
ShowArrows="False">
<abstractions:CarouselViewControl.ItemsSource>
<x:Array Type="{x:Type ContentView}">
<ContentView x:Name="CW1"/>
<ContentView x:Name="CW2"/>
<ContentView x:Name="CW3"/>
<ContentView x:Name="CW4"/>
<ContentView x:Name="CW5"/>
</x:Array>
</abstractions:CarouselViewControl.ItemsSource>
</abstractions:CarouselViewControl>
</ContentPage.Content>
5. CarouselViewControl in x:Array includes five ContentViews. Each of ContentView has five variants of views (the implementation of the Statecontainer - five states of one page Normal, Loading, Error, NoInternet, NoData - the page state is set in the code depending on the program logic, in XAML the Statecontainer subscribes to the state change and shows the corresponding ContentView:
<ContentView x:Name="CW2"
Style="{DynamicResource ContentViewBoardingStyle}"
BackgroundColor="{StaticResource ClrGeneralwhite}">
<ContentView.Content>
<Grid BackgroundColor="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.Children>
<StackLayout Grid.Row="0"
Style="{StaticResource StLt_BaseStyle}">
<StackLayout.Children>
<Images:ImageFit Source="{Binding HeaderImageSource}"/>
</StackLayout.Children>
</StackLayout>
<stateContainer:StateContainer State="{Binding OnBoardingInState}"
Grid.Row="1"
Style="{StaticResource StateContainerNormalStateStyle}"
BackgroundColor="{StaticResource ClrGeneralwhite}">
<stateContainer:StateCondition State="Normal">
<stateContainer:StateCondition.Content>
<StackLayout x:Name="OnbdngPg_RootStack2"
Orientation="Vertical"
Spacing="12">
<StackLayout.Children>
<StackLayout Padding="0,15,0,10">
<StackLayout.Children>
<labels:LblLvl2 Text="{markupExtension:Translate OnBrdPg_Pg2_HdrTxt}"
TextColor="{StaticResource ClrGeneraldark}"
HorizontalTextAlignment="Start"/>
</StackLayout.Children>
</StackLayout>
<StackLayout>
<StackLayout.Children>
<custom:GdyStackPanel x:Name="CustomControl"
CustomViewModel="{Binding ViewModelC, Mode=OneTime}"/>
</StackLayout.Children>
</StackLayout>
</StackLayout.Children>
</StackLayout>
</stateContainer:StateCondition.Content>
</stateContainer:StateCondition>
<stateContainer:StateCondition State="Error"/>
<stateContainer:StateCondition State="Loading"/>
<stateContainer:StateCondition State="NoInternet"/>
<stateContainer:StateCondition State="NoData"/>
</stateContainer:StateContainer>
</Grid.Children>
</Grid>
</ContentView.Content>
</ContentView>
6. As you can see on CW2, there is a CustomControl - this is a custom Grid, among other things, having a tag cloud - a collection of buttons that it generates from the ObservableCollection CustomControlTagsCollection, located in ViewModelC (in control it is BindableProperty CustomViewModel).
public static readonly BindableProperty CustomViewModelProperty = BindableProperty.Create(
nameof(CustomViewModel),
typeof(ViewModelC),
typeof(CustomControl),
null,
BindingMode.TwoWay,
propertyChanged: (bindable, oldValue, newValue) =>
{
Device.BeginInvokeOnMainThread(() =>
{
var panel = bindable as CustomControl;
var oldbinding = oldValue as ViewModelC;
var newbinding = newValue as ViewModelC;
if (oldbinding == null)
{
panel.CustomViewModel = newbinding;
panel.ButtonNewTag.BindingContext = newbinding;
panel.EntryNewTag.BindingContext = newbinding;
}
// This method collects the tag cloud from the collection.
panel.CreateStackPanelAsync();
});
});
public ViewModelC CustomViewModel
{
get => (ViewModelC)GetValue(CustomViewModelProperty);
set => SetValue(CustomViewModelProperty, value);
}
SUMMARY
Summarizing, we can say that:
PageA/PageAViewModel transmits ViewModelC on PageB/PageBViewModel, which receives ViewModelC as a "navigation parameter"a method OnNavigatingTo (originally in the constructor PageBViewModel ViewModelC not defined in PageBViewModel as public property PageBViewModel originally initialized and is in OnNavigatingTomethod). For this reason,PageB/PageBViewModel is initially built
without ViewModelС
respectively PageB/PageBViewModel → CarouselViewControler →
ContentView x:Name:"CW2" → CustomControl at initial initialization receives an empty CustomControlTagsCollection and the tag cloud is not going to
when PageB/PageBViewModel receives ViewModelС, along the path PageB/PageBViewModel → CarouselViewController -> ContentView x:Name="CW2" → CustomControl receives the transmitted ViewModelС, it executes the propertyChanged BindableProperty CustomViewModelProperty, the binding is updated and the panel.CreateStackPanelAsync () method is called, which fills the collection with tags. In a sense, this "late binding".
I would appreciate any help.
I apologize for Google Translate.
Good and clean code to all of you (us :))!
Hello!
I solved my problem in the context of working with Prism:
I replaced the logical model ViewModelC with ServiceC, at the same time I created IServiceC and ServiceC, registered them in App.xaml.cs:
containerRegistry.Register<IServiceC, ServiceC>();
All the logic of work from ViewModelC was transferred to ServiceC.
Interested ViewModels that work with ServiceC data implemented it:
//Declaration
private IServiceC _serviceC;
//Initialization
public ViewModelB(ServiceC serviceC)
{
__serviceC = serviceC;
}
In ViewModels, where ServiceC is required for binding through xaml, implemented it like this (not sure if this is correct in terms of security):
//Declaration
private IServiceC _serviceC;
public IServiceC ServiceC
{
get => _serviceC;
set => SetProperty(ref _serviceC, value);
}
//Initialization
public ViewModelB(ServiceC serviceC)
{
ServiceC = serviceC;
}
BindableProperty in CustomControl:
public static readonly BindableProperty serviceCProperty = BindableProperty.Create(
nameof(MyServiceC),
typeof(ServiceC),
typeof(CustomControl),
null,
BindingMode.OneTime,
propertyChanged: (bindable, oldValue, newValue) =>
{
var panel = bindable as CustomControl;
Device.BeginInvokeOnMainThread(() =>
{
var oldbinding = oldValue as ServiceC;
var newbinding = newValue as ServiceC;
if (oldbinding == null)
{
panel.MyServiceC = newbinding;
panel.ButtonNewTag.BindingContext = newbinding;
panel.EntryNewTag.BindingContext = newbinding;
//This is where the panel is filled with tags
panel.CreateStackPanelAsync();
}
});
});
public ServiceC MyServiceC
{
get => (ServiceC)GetValue(ServiceCProperty );
set => SetValue(serviceCProperty, value);
}
When exiting the viewmodels, implemented the Destroy method (for reasons of avoiding memory leaks):
public override void Destroy()
{
base.Destroy();
this.ServiceC = null;
}
Result - everything works fine on the simulator and on the real
device.
Findings:
I realized that when working with Prism and designing the view / viewmodels data exchange logic with third-party essential entities, it is not necessary to try to implement these entities through classes that are declared in viewmodels (MyClass myClass = new MyClass () {}), and then pass links or clones of this class between viewmodels - this leads to confusion and possible loss (timely underload) of data at one of the stages.
If my further studies of the security and performance of services in Prism confirm the assumption of stable and resource-saving work, I recommend this way.
I would be grateful for any advice, comments and advice on this matter.
Thank you all!
I have a Xamarin.Forms Application based on a Listview populated by an ObservableCollection<Item> and bound to a SQLite model exposed as List<Item>.
Now I have difficulties figuring out how I can update the data via the web.
The whole process is supposed to run in the background. If everything runs as desired, the update process would be based on async ... await tasks and new items would appear one by one on the screen as they are pulled in.
Would anyone please guide me how to lay out my application and how to implement such a background update task?
Notes:
More experienced colleagues warned me that such a concept cannot be done in Xamarin at all, since, so they say, ObservableCollection "does not support to be updated by a background task". I did some research on this, and found indeed some indication that this could be true, but the infos were long outdated (from 2008), things have very likely changed since then.
For performance reasons I cannot simply pull in the complete list and throw away the existing list, but I need to implement a record based update looking at the items one by one. To accomplish this records have an unique Id, and are timestamped. My app knows when it has last seen the web service, and can query all items which have changed since then. I already have a REST service pulling in the changed Items data from the backend as a List, but cannot figure out how to refresh the ObservableCollection and the underlying database.
I do all my updates on change to the ListView. Here I have a button in the a list view which when clicked updates a property which persists by saving it to the sql database. It assumes you have your database set up.
Database:
Function which updates item if exist or saves new. This is a Task so can be called asynchronously.
public Task<int> SaveItemAsync(Item item)
{
if(item.ItemId != 0)
{
return database.UpdateAsync(item);
}
else
{
return database.InsertAsync(itme);
}
}
Xaml
List View which binds to an Observable collection created from the item database.
GestureRecognizers is set up on the image and is bound to a tapCommand in the ViewModel - The code behind the Xaml defines the binding context
Code behind
ItemViewModel viewModel;
public MessagePage ()
{
InitializeComponent ();
viewModel = new ItemViewModel();
BindingContext = viewModel;
}
And then the Xaml
Bind to the ObsevableCollection "ItemsPassed" and this set as the binding context within it. As a result you need to go back to the
page BindingContext so note the binding path for the TapCommand.
Pass the ItemId through as a parameter
<ListView ItemsSource="{Binding ItemsPassed}"
HasUnevenRows="True"
x:Name="ItemListView">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ContentView>
<StackLayout Padding="20">
<StackLayout Orientation="Horizontal">
<StackLayout Orientation="Vertical" HorizontalOptions="Start">
<Label Text="{Binding ItemText}"
FontAttributes="Bold"/>
</StackLayout>
<Image Source="{Binding Favourited, HeightRequest="12" HorizontalOptions="EndAndExpand">
<Image.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Path=BindingContext.TapCommand, Source={x:Reference ItemListView}}"
CommandParameter="{Binding ItemId}"/>
</Image.GestureRecognizers>
</Image>
</StackLayout>
</StackLayout>
</ContentView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
ViewModel
Define the ICommand and assign a function to it
Find the item in the observable collection and change the property
This also needs to be changed in the database using the await App.Database.SaveItemAsync(item) - because of this the function called of the command must be async
public class ItemsViewModel : INotifyPropertyChanged
{
public ObservableCollection<Item> ItemsPassed { get; set; } = new ObservableCollection<Item>();
// Get items out of yourdatabase
public async void GetItems()
{
List<Item> itemsOut = await App.Database.GetItemsAsync();
foreach (Item i in itemsOut)
{
ItemsPassed.Add(i);
}
}
ICommand tapCommand;
public ItemsViewModel()
{
GetItems();
tapCommand = new Command(ExecuteTap);
}
public ICommand TapCommand
{
get { return tapCommand; }
}
// Find the item selected and change the property
private async void ExecuteTap(object obj)
{
var item = ItemsPassed.FirstOrDefault(i => i.ItemId.ToString() == obj.ToString());
if (item != null)
{
if (item.Favourited == true)
{
item.Favourited = false;
}
else
{
item.Favourited = true;
}
await App.Database.SaveItemAsync(item);
Console.WriteLine("update the database");
}
}
}
You then want to make sure the changes occur in the view - this is done through extending INotifyPropertyChange in the Model and calling it when the property changes.
Model
When the item is changed in the viewmodel the OnPropertyChanged is fired which cause the view to update.
public class Item : INotifyPropertyChanged
{
[PrimaryKey, AutoIncrement]
public int ItemId { get; set; }
public string ItemText { get; set; }
public string Author { get; set; }
private bool _favourited;
public bool Favourited
{
get { return _favourited; }
set
{
_favourited = value;
OnPropertyChanged("Favourited");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Hope this helps. Sorry I had to put things in snippets the code section wasn't working properly probably because some of the code is wrong, but its just for example.
<TextBlock Name="currency" TextWrapping="Wrap" VerticalAlignment="Center"/>
<TextBlock Margin="5,0" Text="{Binding Text, ElementName=currency" TextWrapping="Wrap" VerticalAlignment="Center" FontSize="22" />
I am using the above code for binding property of one field to another in my WP7 application.
i want to do the similar task from back-end. any suggestions??
Bindings are working in a specified data context. You can set the data context of your layout root to the page instance, then you can bind to any of your properties. (DataContext is inherited through the child FrameworkElements.) If you want your binding to update its value whenever you change your property from code, you need to implement INotifyPropertyChanged interface or use Dependency properties.
<Grid x:Name="LayoutRoot">
<TextBox Text="{Binding Test, Mode=TwoWay}" />
</Grid>
public class MainPage : PhoneApplicationPage, INotifyPropertyChanged
{
private string test;
public string Test
{
get { return this.test; }
set
{
this.test = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Test"));
}
}
public MainPage()
{
InitializeComponents();
LayoutRoot.DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
}
This is a stupid example since you can access your TextBox any time from MainPage, this has much more sense if you are displaying model objects with DataTemplates.
(I typed this on phone, hope it compiles..)
i got my solution as: var b = new Binding{ElementName = "currency", Path = new PropertyPath("Text")}; Textblock txt = new TextBlock(); txt.SetBinding(TextBlock.TextProperty, b);