I have a small project using XamarinForms and Prism MVVM.
On the settings page I save Author's ID from the Picker.
When I return to the settings page I want that Author to be selected by default in the picker.
This is my Picker in Xaml:
<Picker x:Name="authorPicker" Title="Select Author" FontSize="Medium"
HorizontalOptions="StartAndExpand" VerticalOptions="Center"
ItemsSource="{Binding NoteAuthors}"
ItemDisplayBinding="{Binding Name}"
SelectedItem="{Binding SelectedAuthor, Mode=TwoWay}"
Grid.Row="0" Grid.Column="1" />
When Author is selected I got this in ViewModel and it works fine:
private NoteAuthor _selectedAuthor;
public NoteAuthor SelectedAuthor
{
get { return _selectedAuthor; }
set
{ if (_selectedAuthor != value)
{
SetProperty(ref _selectedAuthor, value);
}
}
}
In the ViewModel > OnNavigatingTo function I call the GetAuthor function which returns Author based on previously saved ID.
public async void GetAuthor(int author_id)
{
NewNoteAuthor = await App.Database.GetAuthorById(author_id);
if(NewNoteAuthor != null && NewNoteAuthor.ID > 0)
{
SelectedAuthor = NewNoteAuthor;
}
}
How can I "jump" to this Author when page opens? The assignment in GetAuthor function doesn't work for me.
After you retrieved NoteAuthors from database, you've to set SelectedAuthor by referencing one of them. Picker uses reference equality, so loading another instance of author from database in GetAuthor won't work at all. Following code solves this problem, and it also improves your code's performance.
NoteAuthors = await // read them from db ...
SelectedAuthor = NoteAuthors.SingleOrDefault(a => a.Id == author_id); // don't load it from database again.
Related
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.
Creating a Registration page, I need to get the following data from user.
First Name
Last Name
Username
Email
Password
Date of Birth
Gender
User Role
For the last two parameters, I am unable to find how to use radio buttons in Xamarin.Forms. Following is my code for the Registration Page.
<StackLayout BackgroundColor="#30af91" Padding="60">
<Entry Text="{Binding FirstName}" Placeholder="First Name"/>
<Entry Text="{Binding LastName}" Placeholder="Last Name"/>
<Entry Text="{Binding UserName}" Placeholder="Last Name"/>
<Entry Text="{Binding Email}" Placeholder="Email" />
<Entry Text="{Binding Password}" Placeholder="Password" IsPassword="True"/>
<Entry Text="{Binding ConfirmPassword}" Placeholder="Confirm Password" IsPassword="True"/>
<DatePicker MinimumDate="1/1/1948" MaximumDate="12/31/2007"/>
<!--Radio buttons for Gender
1. Male 2.Female-->
<!--Radio Buttons for UserRole
1. Admin 2.Participant-->
<Button Command="{Binding RegisterCommand}" Text="Register"/>
<Label Text="{Binding Message}" />
</StackLayout>
Xamarin forms does not provide Radio Button.
You can either use
1)Switch
2)Picker
or any other component to fulfill your requirement
UPDATE
The xamarin forms update version 4.6 has introduced the Radio button control, Here is the official documentation
I think there is a simpler solution that is fairly easy and requires no libraries. Really a a radio group is just a fancy ListView. You would just need to create a viewModel for each radio button that has a IsSelected flag and switch between 2 images. I had a need to allow a user to select how long a token persisted:
XAML
<ListView
HasUnevenRows="True"
HorizontalOptions="FillAndExpand"
ItemsSource="{Binding Durations}"
ItemSelected="ListView_ItemSelected"
SelectedItem="{Binding SelectedDuration}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout
Orientation="Horizontal">
<Image
HeightRequest="18"
IsVisible="{Binding IsSelected}"
Source="radioButtonChecked.png"
WidthRequest="18"/>
<Image
HeightRequest="18"
IsVisible="{Binding IsUnselected}"
Source="radioButtonUnchecked.png"
WidthRequest="18"/>
<Label
Margin="8,0,0,0"
Text="{Binding Caption}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
We create a listview in our content page and listen for the ItemSelected event. Each list item is a horizontal stack panel where we flip between two images depending on the selected state
Code Behind
public partial class LoginPage : ContentPage
{
LoginPageViewModel LoginPageViewModel { get; }
public LoginTwoFactorFrequencyPage ()
{
BindingContext = LoginPageViewModel = new LoginPageViewModel();
InitializeComponent ();
}
private void ListView_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
LoginPageViewModel.UpdateSelected(e.SelectedItem as PersistenceDuration);
}
}
The page's code behind instantiates a view model and calls an UpdateSelected method with the newly selected item on the page's view model*
RadioButton ViewModel
The view model for each radio button:
public class PersistenceDuration : ViewModelBase
{
bool isSelected;
public string Caption { get; set; }
public TwoFactorTokenPersistenceDuration Duration { get; set; }
public bool IsSelected
{
get => isSelected;
set
{
isSelected = value;
OnPropertyChanged();
OnPropertyChanged("IsUnselected");
}
}
public bool IsUnselected => !IsSelected;
public PersistenceDuration(string caption, TwoFactorTokenPersistenceDuration duration)
{
Caption = caption;
Duration = duration;
IsSelected = false;
}
}
The radio button view model holds selection info and the caption. We make sure to fire OnPropertyChanged whenever the selected state changes
Page ViewModel
public class LoginPageViewModel : ViewModelBase
{
PersistenceDuration duration;
PersistenceDuration selectedDuration;
public ObservableCollection<PersistenceDuration> Durations { get; }
public PersistenceDuration SelectedDuration
{
get => selectedDuration;
set
{
if (value != null)
{
duration = value;
UpdateSelected(duration);
}
OnPropertyChanged();
}
}
public LoginTwoFactorFrequencyViewModel()
{
Durations = new ObservableCollection<PersistenceDuration>(
new List<PersistenceDuration>()
{
new PersistenceDuration(AppResources.Save_code__forever, TwoFactorTokenPersistenceDuration.Forever),
new PersistenceDuration(AppResources.ChatRequireEvery30Days, TwoFactorTokenPersistenceDuration.ThirtyDays),
new PersistenceDuration(AppResources.ChatRequireEveryLogin, TwoFactorTokenPersistenceDuration.None),
});
}
public void UpdateSelected(PersistenceDuration persistenceDuration)
{
foreach (var item in Durations)
item.IsSelected = persistenceDuration == item;
}
}
In the page view model we create a list of radio button view models that the XAML binds to. When we UpdateSelected() all the IsSelected states are updated which trigger binding updates which flip the image.
You will still need to do something about the highlight when someone selects an item, but that is easy enough to find on the internet :)
You can use XLabs plugin from manage NuGets package. After installing you can use like this:
In Xaml:
controls:BindableRadioGroup x:Name="Radiobtn"
In C#:
string[] gender = {"MAlE","FEMALE"}
Radiobtn.Add(gender)
Refer Link
https://github.com/XLabs/Xamarin-Forms-Labs/tree/master/samples/XLabs.Samples/XLabs.Samples/Pages/Controls
You can get the radio button effect without a package. Use Labels with text unicode circle \u26AA or \u25CB. Attach a tab gesture recognizer to each label.
When tapped, change the text of the selected button to unicode circle bullet \u29BF and change the text of the other button(s) back to unicode circle \u26AA.
Test on your preferred platforms as each platform may display somewhat differently. You may need to adjust the font size as you change the text.
If you want real radiobuttons you can xlabs their package (https://github.com/XLabs/Xamarin-Forms-Labs/tree/master/src/Forms/XLabs.Forms/Controls/RadioButton)
Personally I'd just use a picker, Xlabs package hasn't been updated in a while so their might be some bugs in the radiobutton
You can use image as a radio button. When tou you click on it, it can change. It is not a good way to do it though.
This is xaml code:
<Image Scale="0.7" HorizontalOptions="Start" x:Name="radioButton" Source="unRadioBtn.png">
<Image.GestureRecognizers>
<TapGestureRecognizer Tapped="radioButton_Clicked"></TapGestureRecognizer>
</Image.GestureRecognizers>
</Image>
And this is .cs:
private void radioButton_Clicked(object sender, EventArgs e)
{
radioButton.Source = "radioBtn.png";
}
Xamarin.Forms 4.6 introduced a new RadioButton control. You can find the documentation here: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/radiobutton
You can use the switch component. Also you can see the implementation for a checkbox component from the XLabs project which is now discontinued, get the code and modify it as you need.
Hint: You're gonna need the custom renderers per platform for it to work .
You need to use Picker
https://developer.xamarin.com/api/type/Xamarin.Forms.Picker/
Actually it is the best alternative to RadionButton On Xamarin.forms
XLabs RadioButton and BindableRadioGroup work well: XLabs RadioButton for Xamarin Forms
Here's a simple Yes/No radio using the BindableRadioGroup:
var answers = new List<string>();
answers.Add("Yes");
answers.Add("No");
var RadioGroup = new XLabs.Forms.Controls.BindableRadioGroup()
{
ItemsSource = answers,
Orientation = StackOrientation.Horizontal
};
Xamarin Forms now provides a Radio Button control.
See docs here:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/radiobutton
As of XF 4.8 this is still experimental and I've not yet used this feature so can't comment on its stability.
<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);
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'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.