access and open DisplayActionSheet from view model - xamarin

i have a toolbar in my content page where there is one item called add , on clicking over add i want to open DisplayActionSheet
i have created ContentPage Toolbar in xaml and attached ICommand to it in view model. Now DisplayActionSheet is accessible only in View hence i am not sure how will i able to access it and render it from view model.
xaml file
<ContentPage.ToolbarItems>
<ToolbarItem Name="" Icon="ic_add.png" Order="Primary" Priority="0" Command="{Binding OnAddContactCommand}"/>
<ToolbarItem Name="" Icon="ic_search.png" Order="Primary" Priority="1" Command="{Binding OnContactSearchCommand}" />
</ContentPage.ToolbarItems>
View model
public ICommand OnContactSearchCommand => new Command(OnContactSearch);
public ICommand OnAddContactCommand => new Command(OnAddContactSearch);
events
private async void OnAddContactSearch()
{
//var action = await DisplayActionSheet(AppResources.select_contact_source, AppResources.cancel, null, AppResources.manual, AppResources.phonebook);
}
private void OnContactSearch()
{
Debug.WriteLine("OnContactSearch");
}

Like #Alessandro said Application.Current.MainPage works fine for action sheets and alerts as well. To hide view specific stuff from view model I created an IMessageBoxService which is injected into the view models' contructors that need it. Note that I am using the Autofac IoC container. For Xamarin's DependencyService you have change the constructors and look up the service in code.
IMessageBoxService.cs
public interface IMessageBoxService
{
void ShowAlert(string title, string message, Action onClosed = null);
// ...
Task<string> ShowActionSheet(string title, string cancel, string destruction, string[] buttons = null);
}
MessageBoxService.cs
public class MessageBoxService : IMessageBoxService
{
private static Page CurrentMainPage { get { return Application.Current.MainPage; } }
public async void ShowAlert(string title, string message, Action onClosed = null)
{
await CurrentMainPage.DisplayAlert(title, message, TextResources.ButtonOK);
onClosed?.Invoke();
}
public async Task<string> ShowActionSheet(string title, string cancel, string destruction = null, string[] buttons = null)
{
var displayButtons = buttons ?? new string[] { };
var action = await CurrentMainPage.DisplayActionSheet(title, cancel, destruction, displayButtons);
return action;
}
}
AppSetup.cs
protected void RegisterDependencies(ContainerBuilder cb)
{
// ...
cb.RegisterType<MessageBoxService>().As<IMessageBoxService>().SingleInstance();
}
Usage
public class EditProductViewModel : AddProductViewModel
{
private IMessageBoxService _messageBoxService;
public ICommand DeleteCommand { get; set; }
public EditProductViewModel(IPageNavigator navigator, IMessenger messenger,
IMessageBoxService messageBoxService, TagDataStore tagDataStore) : base(navigator, messenger, tagDataStore)
{
_messageBoxService = messageBoxService;
DeleteCommand = new Command(DeleteItem);
}
...
private async void DeleteItem()
{
var action = await _messageBoxService.ShowActionSheet(TextResources.MenuTitleDeleteProduct,
TextResources.ButtonCancel, TextResources.ButtonDelete);
if (action == TextResources.ButtonDelete)
{ } // delete
If you are doing viewmodel first navigation (s. Xamarin or Jonathan Yates' blog) you may chose to make this part of the Navigator service. It's a matter of taste

try with
Application.Current.MainPage.DisplayActionSheet();

Related

ImgButton click in mvvvm xamarin

I have a code written in xaml.cs file, But i want to do it in viewmodel file, but i don't know how can i do it, i tried to do command but this is didn't worked and i'm just a beginner so don't have many ideas about this pattern and commadns
this is my code :
private async void ItemImageButton_Clicked(object sender, EventArgs e)
{
var appoitment = (Appoitment)((ImageButton)sender).CommandParameter;
AppintmentService appintmentService = new AppintmentService();
await appintmentService.DeleteFollowup(appoitment.PatientID, appoitment.ID);
await DisplayAlert("Delted", "The patient Deleteed", "Ok");
await Navigation.PushAsync(new PatientProfileFollowUpPage());
}
Any help please ?
You could binding Command for the ImageButton. A simple code below for your reference.
Xaml:
<ImageButton Command="{Binding ImgButtonCommand}"></ImageButton>
ViewModel:
public class Page1ViewModel
{
public Command ImgButtonCommand { get; set; }
public Page1ViewModel()
{
ImgButtonCommand = new Command(() =>
{
//something you want to do
});
}
}
Binding for the page:
public partial class Page1 : ContentPage
{
public Page1()
{
InitializeComponent();
this.BindingContext=new Page1ViewModel();
}
}

Xamarin forms turns dark and appshell doesn't work anymore when switching language

I implemented language changing trough AppResources.resx files, I have 2 of these: AppResources.resx and AppResources.fr.resx. Switching the language with the following code:
private void Language_switch(object sender, EventArgs e)
{
var lang_switch = Lang.Text;
if (lang_switch == "FR")
{
CultureInfo language = new CultureInfo("fr");
Thread.CurrentThread.CurrentUICulture = language;
AppResources.Culture = language;
}
else
{
CultureInfo language = new CultureInfo("");
Thread.CurrentThread.CurrentUICulture = language;
AppResources.Culture = language;
}
Application.Current.MainPage = new NavigationPage(new PointsPage());
}
The language switches fine but whenever I do, the app turns dark and the AppShell seems to break, it only shows the top bar with toolbar items i made (in what seems to be the standard xamarin color) and showing what seems to be it trying to show the navigation at the bottom, but this only looks like a bar but doesn't seem to have navigation on it and doesn't have any of the labels for navigation.
The content on the page also seems to overlap with this bar going over it if I scroll down. If I press the switch button again it does still switch languages but stay in this dark mode. I don't have any of the dark color set in my app and don't have a dark mode implemented.
It also seems to be doing this on every single page I do it on. How can i stop this from happening so it uses the layout i have made for my app and doesn't turn dark?
Edit: I found out that problem isn't in the language switch. When I go to another page just using
Application.Current.MainPage = new NavigationPage(new PointsPage());
and removing the the language switch code it still does the weird thing where it changes colors. From what it looks like to me is that the page gets put on top without the AppShell moving to be on top of that. Is there a way to reload the AppShell?
Edit2: I managed to fix it. As I suspected the AppShell wasn't reloading and wasn't being put on top of the reloaded page. I added
Application.Current.MainPage = new AppShell();
underneath the page reload and now everything is working
When you use the .resx file to make the localization, create the resx file with the matched file name, when you change the system language, reopen the app would show the matched resource your set in .resx.
For more details about it, you could refer to the MS docs. https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/localization/text?pivots=windows
Code sample: https://github.com/xamarin/xamarin-forms-samples/tree/main/UsingResxLocalization
If you want to change it at runtime, you could use ResourceManager. It provides convenient access to culture-specific resources at runtime.
I make a simple example for your reference.
MainPage: String1, Settings are the key in .resx file.
<StackLayout>
<Label Text="{Binding Resources[String1]}"
VerticalOptions="Center"
HorizontalOptions="Center" />
<Button Text="{Binding Resources[Settings]}"
HorizontalOptions="Center"
Clicked="Button_Clicked" />
</StackLayout>
Code behind:
public MainPage()
{
InitializeComponent();
this.BindingContext = new MainPageViewModel();
}
private void Button_Clicked(object sender, EventArgs e)
{
Navigation.PushAsync(new SettingsPage());
}
SettingsPage:
<StackLayout>
<Label Text="{Binding Resources[PickLng]}" />
<Picker ItemsSource="{Binding Languages}" SelectedItem="{Binding SelectedLanguage, Mode=TwoWay}" />
</StackLayout>
Code behind:
public SettingsPage()
{
InitializeComponent();
BindingContext = new SettingsViewModel();
}
ViewModel:
public class CultureChangedMessage
{
public CultureInfo NewCultureInfo { get; private set; }
public CultureChangedMessage(string lngName)
: this(new CultureInfo(lngName))
{ }
public CultureChangedMessage(CultureInfo newCultureInfo)
{
NewCultureInfo = newCultureInfo;
}
}
public class LocalizedResources : INotifyPropertyChanged
{
const string DEFAULT_LANGUAGE = "en";
readonly ResourceManager ResourceManager;
CultureInfo CurrentCultureInfo;
public string this[string key]
{
get
{
return ResourceManager.GetString(key, CurrentCultureInfo);
}
}
public LocalizedResources(Type resource, string language = null)
: this(resource, new CultureInfo(language ?? DEFAULT_LANGUAGE))
{ }
public LocalizedResources(Type resource, CultureInfo cultureInfo)
{
CurrentCultureInfo = cultureInfo;
ResourceManager = new ResourceManager(resource);
MessagingCenter.Subscribe<object, CultureChangedMessage>(this,
string.Empty, OnCultureChanged);
}
private void OnCultureChanged(object s, CultureChangedMessage ccm)
{
CurrentCultureInfo = ccm.NewCultureInfo;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Item"));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class ViewModelBase : INotifyPropertyChanged
{
public LocalizedResources Resources
{
get;
private set;
}
public ViewModelBase()
{
Resources = new LocalizedResources(typeof(AppResources), App.CurrentLanguage);
}
public void OnPropertyChanged([CallerMemberName] string property = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class MainPageViewModel : ViewModelBase
{ }
public class SettingsViewModel : ViewModelBase
{
public List<string> Languages { get; set; } = new List<string>()
{
"EN",
"FR"
};
private string _SelectedLanguage;
public string SelectedLanguage
{
get { return _SelectedLanguage; }
set
{
_SelectedLanguage = value;
SetLanguage();
}
}
public SettingsViewModel()
{
_SelectedLanguage = App.CurrentLanguage;
}
private void SetLanguage()
{
App.CurrentLanguage = SelectedLanguage;
MessagingCenter.Send<object, CultureChangedMessage>(this,
string.Empty, new CultureChangedMessage(SelectedLanguage));
}
}

Xamarin.forms- pass object while navigation with Shell

I am using shell and I want tabs on all the pages. so I am following standard way of shell navigation but the problem is I don't know how to pass an object along with the navigation.
await Shell.Current.GoToAsync(page.ToString());
doing this for navigation
Routing.RegisterRoute("TesProject.Views.DetailView", typeof(DetailView));
Registering Route like this
I want to pass a complete object from my list view to detail view. How can I do that?
Xamarin.Forms Shell Navigation Hierarchy with parameters
I saw this but I don't think this will work in my case because I can't pass a complete model object like this.
I wrote a small example for your reference.
In the sending class, you can pass parameters like $"AferPage?param={param}".
Here is the sending code:
public partial class BeforePage : ContentPage
{
public BeforePage()
{
InitializeComponent();
}
private async void Button_Clicked(object sender, EventArgs e)
{
string param = myEntry.Text;
await Shell.Current.GoToAsync($"AferPage?param={param}");//like this
}
}
Here is the receiving class code(Implements IQueryAttributable interface):
public partial class AferPage : ContentPage, IQueryAttributable
{
public string param {get;set;}
public void ApplyQueryAttributes(IDictionary<string, string> query)
{
param = HttpUtility.UrlDecode(query["param"]);
receive.Text = param;
}
public AferPage()
{
InitializeComponent();
}
}
Using Newtonsoft.Json you can:
In List View:
var jsonStr = JsonConvert.SerializeObject([Model]);
await Shell.Current.GoToAsync($"{nameof([DetailsViewPage])}?Param={jsonStr }");
In Details View Page:
Add QueryProperty:
[QueryProperty(nameof(Param), nameof(Param))]
Convert to Model again:
var bonusesFilterData = JsonConvert.DeserializeObject<[Model]>(Param);
A solution is shown in this video: https://www.youtube.com/watch?v=YwnEpiJd0I8
You can pass objects through as a Dictionary.
For example if this is the data object you want to send:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
Set the query property on the view model of the page:
[QueryProperty(nameof(Person), nameof(Person))]
public partial class DetailViewModel
{
[ObservableProperty]
Person person;
}
Assuming your page constructor looks something like this so it will automatically set the value on your context:
public partial class DetailView : ContentPage
{
public DetailView(DetailViewModel vm)
{
InitializeComponent();
BindingContext = vm;
}
Then when you navigate to the page, pass the object in a dictionary:
Routing.RegisterRoute(nameof(DetailView), typeof(DetailView));
await Shell.Current.GoToAsync(nameof(DetailView), {
new Dictionary<string, object> {
[nameof(Person)] = person
});
Now you can access your object in bindings:
<Label Text="{Binding Person.Name}"/>
<Label Text="{Binding Person.Age}"/>
P.S. Notice the use of nameof to avoid hard coded strings.
You can user stored preferences if your json is complex like:
private async void OnItemSelected(Item item)
{
if (item == null)
return;
var jsonstr = JsonConvert.SerializeObject(item);
//Clear the shared preferences in case there is any
Preferences.Clear();
//Store your complex json on a shared preference
Preferences.Set("Data", jsonstr);
await Shell.Current.GoToAsync(nameof(DetailsPage));
}
On the details page where you retrieve your data you can have the following code:
bool hasKey = Preferences.ContainsKey("Data");
var content = Preferences.Get("Data", string.Empty);
Details details = hasKey ? JsonConvert.DeserializeObject<Model>(content) : null;

BindingContext not working for navigation in a button clicked event

I am attempting to navigate to a registration page from a login page using a button_clicked event. A new view model is created during the event and set as the BindingContext. However it does not appear to work
When I debug on the registration page I have an ICommand in the registration page view model that is attached to a button. The code is
public ICommand RegisterAppUser
{
get
{
return new RelayCommand(RegisterUser, () => Email == ConfirmEmail && Password == ConfirmPassword);
}
}
private async void RegisterUser()
{
var userDto = new RegistrationUserDto(this);
App.User = await App.PlayerManager.RegisterUserAsync(userDto);
}
I assumed the the "this" would represent the view model attached to the view. However when I reach the RegistrationUserDto(this) line I can see that the "this" is an unintialized instance of the registration view model
public partial class LoginPage : ContentPage
{
public LoginPage()
{
InitializeComponent();
}
private void Button_Clicked(object sender, EventArgs e)
{
Navigation.PushModalAsync(new RegistrationView() { BindingContext = new RegistrationViewModel() }); ;
}
}
No compilation errors but the action clearly can't work with no data

Xamarin ListView binding is not working

I have been trying to bind my ListView to my View model. The view model successfully retrieves 5 records from the database and the Listview seems to display 5 blank rows, however it is not showing binding for each field within each row.
I have spent a couple of days searching internet but I don't seem to be doing anything different. I was using master detail pages so I thought that it may be the issue so I set my Events page as first navigation page without master/detail scenario but to no avail. Please note that I am using Portable Ninject for my dependencies/IoC.
My App.Xamal.cs is is as follows:
public App (params INinjectModule[] platformModules)
{
InitializeComponent();
var eventsPage = new NavigationPage(new EventsPage());
//Register core services
Kernel = new StandardKernel(new MyAppCoreModule(), new MyAppNavModule(eventsPage.Navigation));
//Register platform specific services
Kernel.Load(platformModules);
//Get the MainViewModel from the IoC
eventsPage.BindingContext = Kernel.Get<EventsViewModel>();
((BaseViewModel)(eventsPage.BindingContext)).Init();
MainPage = eventsPage;
}
My EventsPage.Xaml is provided below:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.Views.EventsPage"
Title="Events">
<ContentPage.Content>
<ListView x:Name="Events" ItemsSource="{Binding Events}" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding EventID}" BackgroundColor="Red" TextColor="White"
FontAttributes="Bold" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
</ContentPage>
My EventsPage.xaml.cs is provided below:
namespace MyApp.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class EventsPage : ContentPage, IBaseViewFor<EventsViewModel>
{
public EventsPage ()
{
InitializeComponent ();
}
EventsViewModel _vm;
public EventsViewModel ViewModel
{
get => _vm;
set
{
_vm = value;
BindingContext = _vm;
}
}
}
}
My EventsViewModel is as follows, it successfully retrieves 5 records and OnPropertyChanged is fired for Events property:
namespace MyApp.ViewModels
{
public class EventsViewModel : BaseViewModel, IBaseViewModel
{
ObservableCollection<Event> _events;
readonly IEventDataService _eventDataService;
public ObservableCollection<Event> Events
{
get { return _events; }
set
{
_events = value;
OnPropertyChanged();
}
}
public EventsViewModel(INavService navService, IEventDataService eventDataService) : base(navService)
{
_eventDataService = eventDataService;
Events = new ObservableCollection<Event>();
}
public override async Task Init()
{
LoadEntries();
}
async void LoadEntries()
{
try
{
var events = await _eventDataService.GetEventsAsync();
Events = new ObservableCollection<Event>(events);
}
finally
{
}
}
}
}
My BaseViewModel is as follows:
namespace MyApp.ViewModels
{
public abstract class BaseViewModel : INotifyPropertyChanged
{
protected INavService NavService { get; private set; }
protected BaseViewModel(INavService navService)
{
NavService = navService;
}
bool _isBusy;
public bool IsBusy
{
get
{
return _isBusy;
}
set
{
_isBusy = value;
OnPropertyChanged();
OnIsBusyChanged();
}
}
protected virtual void OnIsBusyChanged()
{
}
public abstract Task Init();
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
// Secod BaseViewModel abstract base class with a generic type that will be used to pass strongly typed parameters to the Init method
public abstract class BaseViewModel<TParameter> : BaseViewModel
{
protected BaseViewModel(INavService navService) : base(navService)
{
}
public override async Task Init()
{
await Init(default(TParameter));
}
public abstract Task Init(TParameter parameter);
}
}
IBaseViewModel is just a blank interface:
public interface IBaseViewModel
{
}
IBaseViewFor is given below:
namespace MyApp.ViewModels
{
public interface IBaseViewFor
{
}
public interface IBaseViewFor<T> : IBaseViewFor where T : IBaseViewModel
{
T ViewModel { get; set; }
}
}
My Event model is as follows:
namespace MyApp.Models
{
public class Event
{
public int EventID;
}
}
Finally, the image of the output, as you can see that 5 rows are created with red background but EventID is not binding in each row. I have checked the data and EventID is returned. I have even tried to manually add records into Events list but to no avail, see the manual code and image below:
async void LoadEntries()
{
try
{
Events.Add((new Event() { EventID = 1 }));
Events.Add((new Event() { EventID = 2 }));
Events.Add((new Event() { EventID = 3 }));
Events.Add((new Event() { EventID = 4 }));
Events.Add((new Event() { EventID = 5 }));
}
finally
{
}
}
I have spent a lot of time on it but unable to find a reason for this anomaly, can someone please cast a fresh eye and provide help!?
You can only bind to public properties - ie, you need a getter
public class Event
{
public int EventID { get; set; }
}

Resources