Xamarin passing arguments between pages - c#

I'm trying to open a new Page on a tap event, which should display some detailed information on a certain object. For that to work, I need to pass the object itself or its ID to the new page. So I added an argument to the constructor of the detail page as follows.
void onItemTapped(object sender, ItemTappedEventArgs e)
{
if (e.Item != null)
{
bool convOk = Int32.TryParse((string)e.Item, out int id);
if (convOk)
{
Navigation.PushAsync(new DetailPage(id));
}
}
}
And the DetailPage has its own DetailViewModel, which is set as the BindingContext within the code-behind.
DetailPage 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"
x:Class="Foo.Views.DetailPage">
<ContentPage.Content>
<StackLayout Orientation="Vertical">
<Label Text="FooBar" />
<Label Text="{Binding trackID}" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
DetailPage code-behind
namespace Foo.Views
{
public partial class DetailPage : ContentPage
{
public DetailPage(int trackID)
{
InitializeComponent();
BindingContext = new DetailViewModel(trackID);
}
}
}
DetailViewModel:
namespace Foo.ViewModels
{
public class DetailViewModel : BaseViewModel
{
// trackID prop
int _trackID;
int trackID
{
get { return _trackID; }
set
{
_trackID = value;
notifyPropertyChanged(nameof(trackID));
}
}
public TargetDetailViewModel(int tid)
{
trackID = tid;
}
}
}
However, the binding between the DetailPage and the DetailViewModel doesn't seem to work, the page doesn't show anything. The id itself is passed correctly all the way down to the DetailViewModel.
Is this due to the order of initialization? I presume that everything written in XAML will be executed in the DetailPage.InitializeComponent() method? If that's correct, is it safe/correct to instantiate the ViewModel before the DetailPage.InitializeComponent()?
Any hint appreciated.

Your trackID property is not public.
Note: if you watch the application log output you can catch binding problems like this (filter it by the string Binding:)
Log example of a private variable not being bound:
Binding: 'trackID' property not found on 'XXXX.VM', target property: 'Xamarin.Forms.Label.Text'
DetailViewModel Fix:
public class DetailViewModel : BaseViewModel
{
int _trackID;
public int trackID;
{
get { return _trackID; }
set
{
_trackID = value;
notifyPropertyChanged(nameof(trackID));
}
}
~~~~
}

Related

XAML binding to model not updating UI

I am new to .Net Maui. I have follow James Montemagno's video tuorial:
https://www.youtube.com/watch?v=ddmZ6k1GIkM&list=PLdo4fOcmZ0oUBAdL2NwBpDs32zwGqb9DY&index=6 on navigating between pages. I can successfully navigate from one page to the next and also send a complex data object as a parameter. However, on the new page, I want to use the parameter to fetch objects via a webservice in my ViewModel, and bind the xaml to the newly fetched objects.
At first I tried this in the ViewModels' constructor, but the parameters where still null inside the constructor. I then invoked an event that fired when the property I am binding to changes partial void OnLoginResultChanged(LoginResult value). From there I call an async Task to fetch the objects from the webservice and try and bind and display them in the xaml. Unfortunately, I am not getting anything to show in the xaml.
Here is my code:
ViewModel:
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using MealPacApp.Model;
namespace MealPacApp.ViewModel
{
[QueryProperty(nameof(Model.LoginResult), "LoginResult")]
public partial class MainViewModel : ObservableObject
{
IConnectivity connectivity;
Services.MealPacService mealPacService;
[ObservableProperty]
Model.User user = new();
[ObservableProperty]
Model.LoginResult loginResult;
partial void OnLoginResultChanged(LoginResult value)
{
this.GetDataForViewCommand.ExecuteAsync(null);
}
public MainViewModel(IConnectivity connectivity, Services.MealPacService mealPacService)
{
this.connectivity = connectivity;
this.mealPacService = mealPacService;
//Now check to see if there is internet connectivity
if (connectivity.NetworkAccess != NetworkAccess.Internet)
{
Shell.Current.DisplayAlert("Oops!", "No internet is available. Please ensure you have an internet connection before continuing.", "OK");
return;
}
}
[RelayCommand]
async Task GetDataForView()
{
try
{
if (loginResult != null)
{
//Its not empty, so login here
var incmoingUser = await Services.MealPacService.GetUser(loginResult.Reference, loginResult.OrganisationId);
if (incmoingUser != null)
{
user = incmoingUser;
}
}
}
catch (Exception)
{
throw;
}
}
}
}
Here is the View (Xaml):
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MealPacApp.MainPage"
Title="Home"
xmlns:viewmodel="clr-namespace:MealPacApp.ViewModel"
xmlns:model="clr-namespace:MealPacApp.Model"
x:DataType="viewmodel:MainViewModel">
<ScrollView>
<VerticalStackLayout
Spacing="25"
Padding="30,0"
VerticalOptions="Center">
<Label
Text="{Binding User.Name}"
SemanticProperties.HeadingLevel="Level1"
FontSize="32"
HorizontalOptions="Center" />
</VerticalStackLayout>
</ScrollView>
</ContentPage>
Here is the Model I am trying to bind too:
namespace MealPacApp.Model
{
public partial class User
{
public int Userid { get; set; }
public string Reference { get; set; }
public string Name { get; set; }
}
}
Here is my page code behind:
using MealPacApp.ViewModel;
namespace MealPacApp
{
public partial class MainPage : ContentPage
{
public MainPage(MainViewModel vm)
{
InitializeComponent();
BindingContext = vm;
}
}
}
As mentioned, the navigation is working, I am just missing how to get the property change to reflect in the Xaml. I have kept the [ObservableProperty] instead of implementing INotifyPropertyChanged as to my understanding, it takes care of the boiler plate stuff.
Thanks in advance.
This line
user = incmoingUser;
Is updating the private field user
You want to update the public property User which is observable
User = incmoingUser;

ZXing ScannerView doesn't stop scanning even when isScanning is set to false

I'm making an app which has a a ZXing ScannerView in a ContentPage.
I've managed to make it read a QR code just fine in a function in my ScanningViewModel.
However, when I try to navigate away from the page with the ScannerView, it crashes.
In the 'Application Output' in Visual Studio, I'm seeing a load of the 'Too soon between frames' error, which is I believe is causing the crashing. I've read that setting delays to 5 might help, but I'm not sure how to do this. This is where I read this: https://github.com/Redth/ZXing.Net.Mobile/issues/721
I've also seen some other StackOverflow articles, but they didn't really answer my question.
Is there a way to fix this?
Edit: This is the other post I read on StackOverflow:
Zxing Mobile doesn't stop analysing on iOS
XAML Page:
<?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:zxing="clr-namespace:ZXing.Net.Mobile.Forms;assembly=ZXing.Net.Mobile.Forms"
xmlns:viewmodel1="clr-namespace:DoorRelease.ViewModel"
xmlns:viewmodel="clr-namespace:GardisMobileApp.ViewModel"
x:Class="GardisMobileApp.QRScanningPage">
<ContentPage.BindingContext>
<viewmodel:ScanningViewModel/>
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout>
<StackLayout>
<Label Text="Welcome to Xamarin.Forms!"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" />
</StackLayout>
<zxing:ZXingScannerView x:Name="scanner" IsScanning="{Binding isScanning}" ScanResultCommand="{Binding GetResultCommand}" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
My Code Behind:
namespace MobileApp
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class QRScanningPage : ContentPage
{
public QRScanningPage()
{
InitializeComponent();
}
}
}
My ScanningViewModel:
namespace MobileApp.ViewModel
{
public class ScanningViewModel : BaseViewModel
{
private static ScanningViewModel _instance = new ScanningViewModel();
public static ScanningViewModel Instance { get { return _instance; } }
public string stsAddress { get; set; }
public string apiAddress { get; set; }
public bool isScanning { get; set; } = true;
public Command GetResultCommand { get; set; }
public ScanningViewModel() : base()
{
Title = "QR Code Scanner";
GetResultCommand = new Command(async(r) => await GetScannedAsync(r));
}
async Task GetScannedAsync(object result)
{
isScanning = false;
try
{
var resultArray = result.ToString().Split(',');
stsAddress = resultArray[0];
apiAddress = resultArray[1];
MainThread.BeginInvokeOnMainThread(async () =>
{
await Application.Current.MainPage.Navigation.PopAsync();
//await Application.Current.MainPage.DisplayAlert("Code scanned", "You've scanned a QR code!", "OK");
});
}
catch(Exception e)
{
await Application.Current.MainPage.DisplayAlert("Error!", e.Message, "OK");
}
}
}
}
From document INotifyPropertyChanged Interface,we know that
The INotifyPropertyChanged interface is used to notify clients, typically binding clients, that a property value has changed.
For change notification to occur in a binding between a bound client and a data source, your bound type should either:
Implement the INotifyPropertyChanged interface (preferred).
Provide a change event for each property of the bound type.
Do not do both.
In your code, if you want to update the UI while changing the value of isScanning , you have to inplement interface INotifyPropertyChanged .
public bool isScanning { get; set; } = true;
Please refer to the following code:
private bool _isScanning;
public bool isScanning
{
get
{
return _isScanning;
}
set
{
SetProperty(ref _isScanning, value);
}
}
And assign an initial value (true) for it in the constructor of class ScanningViewModel:
public ScanningViewModel()
{
//assign an initial value (`true`)
isScanning = true;
// other code
}

Binding content from other files in XAML

So I planning to bind label from two files or more, because I place the label and the cs file in separate way. For example:
SettingServicesPhone.xaml
<Label x:Name="sipLoginStatus"
Width="106"
Height="27"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Content="{Binding SipLoginStatusContent}"
FontSize="13" />
For the SettingServicePhone.xaml.cs I declared public String sipLoginStatusContent;
And I use Settings.xaml and Setting.xaml.cs as a container of all functions.
I've declared public static SettingsServicesPhone setCall = new SettingsServicesPhone(); on Setting.xaml.cs. And also write get set.
public String SipLoginStatusContent
{
get { return setCall.sipLoginStatusContent; }
set
{
if (setCall.sipLoginStatusContent != value)
{
setCall.sipLoginStatusContent = value;
OnPropertyChanged("SipLoginStatusContent"); // To notify when the property is changed
}
}
}
And here the example of onclick button that I stated on Settings.xaml.cs
public void applyBtn_Click(object sender, RoutedEventArgs e)
{
SipLoginStatusContent = "Logging In";
}
It's work fine if I included them in one file. But seems like it doesn't running if I make it separate. Am I doing it wrong way? Thank you.
Set the DataContext of the window where the Label is defined to an instance of the class where the SipLoginStatusContent property is defined:
public partial class Settings : Window
{
public static SettingsServicesPhone setCall = new SettingsServicesPhone();
public Settings()
{
InitializeComponent();
DataContext = this; //<--
}
public String SipLoginStatusContent
{
get { return setCall.sipLoginStatusContent; }
set
{
if (setCall.sipLoginStatusContent != value)
{
setCall.sipLoginStatusContent = value;
OnPropertyChanged("SipLoginStatusContent"); // To notify when the property is changed
}
}
}
public void applyBtn_Click(object sender, RoutedEventArgs e)
{
SipLoginStatusContent = "Logging In";
}
}

Xamarin Forms Binding Issues using Mvvm Light

Good afternoon everyone. I have just begun to work with Xamarin Forms as I will need it for my job and wanted to learn best practices from the beginning so I went for Mvvm Light. My current problem is that, whatever I do, I really can't make binding work (NOTE: it worked for my first project, a simple page where I had a button which was used to navigate to another page). Now, for my current project, an EventApp I tried everything I could, looking on lots of forums and tried various settings and implementations but couldn't make the binding work again. I will show you some of my current code for a test page I've done just for testing purposes.
App.cs class:
public partial class App : Application
{
private static ViewModelLocator _locator;
public static ViewModelLocator Locator { get { return _locator ?? (_locator = new ViewModelLocator()); } }
public App()
{
InitializeComponent() //Tried both with and without this
registerNavigationService();
}
protected override void OnStart()
{
}
protected override void OnSleep()
{
// Handle when your app sleeps
}
protected override void OnResume()
{
// Handle when your app resumes
}
public void registerNavigationService()
{
NavigationService nav = new NavigationService();
nav.Configure(PageNames.GeneralNotificationPage, typeof(GeneralNotificationPage));
nav.Configure(PageNames.CreateNotificationPage, typeof(CreateNotificationPage));
nav.Configure(PageNames.SelectSongPage,typeof(SelectSongPage));
SimpleIoc.Default.Register<INavigationService>(() => nav);
var firstPage = new NavigationPage(new SelectSongPage());
nav.Initialize(firstPage);
MainPage = firstPage;
}
}
`
App.xaml:
<?xml version="1.0" encoding="utf-8"?>
<Application xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="EventAppMvvm.App"
>
</Application>
-Note: here I tried to set a static resource for my ViewModelLocator the next way:
xlmns:vm="clr-namespace:EventAppMvvm.ViewModel;assembly=EventAppMvvm"
and then in resources
<vm:ViewModelLocator x:Key="Locator"/>
doesn't work, if I try in one of my pages to set the DataContext, it says it hasn't found any bindable property. If I set the BindingContext to that I get an NPE when I run the app as there is no object created. So I ended up using the code behind to do the BindingContext and instantiate the locator in my App.cs file so I won't receive an NPE.
ViewModelLocator:
public class ViewModelLocator
{
/// <summary>
/// Initializes a new instance of the ViewModelLocator class.
/// </summary>
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
////if (ViewModelBase.IsInDesignModeStatic)
////{
//// // Create design time view services and models
//// SimpleIoc.Default.Register<IDataService, DesignDataService>();
////}
////else
////{
//// // Create run time view services and models
//// SimpleIoc.Default.Register<IDataService, DataService>();
////}
SimpleIoc.Default.Register<CreateNotificationPageViewModel>();
SimpleIoc.Default.Register<SelectSongPageViewModel>();
SimpleIoc.Default.Register<GeneralNotificationPageViewModel>();
}
public CreateNotificationPageViewModel CreateNotificationPageVM
{
get
{
return ServiceLocator.Current.GetInstance<CreateNotificationPageViewModel>();
}
}
public SelectSongPageViewModel SelectSongPageVM
{
get
{
return ServiceLocator.Current.GetInstance<SelectSongPageViewModel>();
}
}
public GeneralNotificationPageViewModel GeneralNotificationPageVM
{
get
{
return ServiceLocator.Current.GetInstance<GeneralNotificationPageViewModel>();
}
}
public static void Cleanup()
{
// TODO Clear the ViewModels
}
}
One of my ViewModels(test one):
public class SelectSongPageViewModel : ViewModelBase
{
RelayCommand _commandTest;
Song _selectedSong;
INavigationService navigation;
string clicked = "3";
string _message = "I ve not been clicked yet";
public SelectSongPageViewModel(INavigationService navigation)
{
this.navigation = navigation;
}
RelayCommand CommandTest
{
get
{
if(_commandTest == null)
{
_commandTest= new RelayCommand(() =>
{
Message = _message;
//navigation.NavigateTo(PageNames.CreateEventPage);
});
}
return _commandTest;
}
}
public string Message
{
get
{
return clicked;
}
set
{
clicked = value;
RaisePropertyChanged("Message");
}
}
}
Actual page of that ViewModel:
Notes: if I try to use DataContext (defined as static resource previously in App.xaml), no matter if I give the correct namespace and assembly, it says that it hasn't found any binding, property there, if I use BindingContext, it works, I don't get any errors at compile time but I can't make the buttons do something as it is like it doesn't recognize my defined commands but for some reason it just binds simple data like my Message property.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="EventAppMvvm.Views.SelectSongPage"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
>
<ContentPage.Content>
<StackLayout>
<Label Text="{Binding Message}"></Label>
<Button Text="Press me" Command="{Binding CommandTest}"></Button>
</StackLayout>
</ContentPage.Content>
</ContentPage>
And the code behind for this page.
namespace EventAppMvvm.Views
{
public partial class SelectSongPage : ContentPage
{
public SelectSongPage()
{
InitializeComponent();
var vm = App.Locator.SelectSongPageVM;
BindingContext = vm;
}
}
}
Whatever I do, I can't make the commands/Complex binding work again for some reason but still simple property binding works, I'm using VisualStudio for Mac if it matters.

Pass a method as parameter to an XAML object

I'm currently working with a CustomObject that needs a CustomObjectRenderer for each platform.
I would like to pass a method as parameter to this object, from the XAML side, so I would be able to use this callback, from my renderer.
<control:CustomObject Callback="CallbackFunction"/>
The CallbackFunction(object param) is then declared in the MainPage.xaml.cs of the PCL part.
public partial class MainPage : ContentPage
{
public MainPage()
{
base.BindingContext = this;
}
public void CallbackFunction(object param)
{
Debug.WriteLine((object as Element).Name);
}
}
So, if I'm understanding well, my CustomObject have to be like that:
public CustomObject : Object
{
public Action<object> Callback { get; set; }
}
But I have an error about XAML parsing.. I don't get why this error is thrown..
At the end, what I want to do, it's to call this method from the renderer, and then handle things, do actions from the MainPage.xaml.cs, from the PCL part.
public class CustomObjectRenderer : ObjectRenderer
{
NativeObject nativeObject;
CustomObject customObject;
protected override void OnElementChanged(ElementChangedEventArgs<CustomObject> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
customObject = e.NewElement as CustomObject;
nativeObject = Control as NativeObject;
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
}
// Etc etc ....
private void METHOD_CALLED_BY_EVENT(object o)
{
// This method get call by the renderer event and then, I want to call
// the method CallbackFunction(object); and do actions.
customObject.Callback(o as OBJECT_PARAM);
}
}
Ok, it's a bit hard for me to explain my problem to you, so if you don't understand something, let me know.
You can achieve this by using events.
MyView
public class MyView : View
{
public event EventHandler<string> MyEvent;
public void RaiseEvent(string parameter)
{
MyEvent?.Invoke(this, parameter);
}
}
Page.xaml
<local:MyView MyEvent="MyView_OnMyEvent"></local:MyView>
Page.xaml.cs
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
private void MyView_OnMyEvent(object sender, string e)
{
Debug.WriteLine(e);
}
}
Renderer
public class MyViewRenderer : ViewRenderer<MyView, SomeNativeView>
{
private void METHOD_CALLED_BY_EVENT(string param)
{
Element.RaiseEvent(param);
}
}
After lot of tried, which didn't work, I had an idea, I tried and it works as I wanted by asking my question.
First, create your custom object !
CustomView
public class CustomView : View
{
public static readonly BindableProperty MainPageCallbackProperty =
BindableProperty.Create(nameof(MainPageCallback), typeof(Action<object>), typeof(CustomMap), null);
public Action<object> MainPageCallback
{
get { return (Action<object>)GetValue(MainPageCallbackProperty); }
set { SetValue(MainPageCallbackProperty, value); }
}
}
We so use Action which is a container for a method/callback. But in my example, we will use Action<object>. Why? Because it will allows us to have an object has paramter to our callback, so we will be able to bring data back from the renderer.
Then, create a page called MainPage.xaml by example. In the XAML part of this new page, add the following code:
<?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:control="clr-namespace:Project.CustomControl;assembly=Project"
x:Class="Project.Page.MainPage">
<ContentPage.Content>
<control:CustomView MainPageCallback="{Binding MainPageCallbackAction}"
VerticalOptions="Fill" HorizontalOptions="Fill"/>
</ContentPage.Content>
</ContentPage>
About this XAML, two parts interest us.
XAML 'References'
xmlns:control="clr-namespace:Project.CustomControl;assembly=Project"
By these this xmlns, you can access your custom control.
Content of the page
<ContentPage.Content>
<control:CustomView MainPageCallback="{Binding MainPageCallbackAction}"
VerticalOptions="Fill" HorizontalOptions="Fill"/>
</ContentPage.Content>
Now, we bind the MainPageCallback of our object to the MainPageCallbackAction, declared in the C# side.
After that, our MainPage.xaml.cs would seems like that:
public partial class MainPage : ContentPage
{
public Action<object> MainPageCallbackAction { get; set; }
public MainPage()
{
base.BindingContext = this;
MainPageCallbackAction = MainPageCallbackMethod;
InitializeComponent();
}
private void MainPageCallbackMethod(object param)
{
Device.BeginInvokeOnMainThread(() =>
{
Debug.WriteLine("Welcome to the Callback :)");
Debug.WriteLine("Emixam23 - Example");
});
}
}
Now, the last thing to look at is the CustomViewRenderer !
public class CustomViewRenderer : ViewRenderer<CustomView, NativeView>
{
CustomView customView;
NativeView nativeView;
protected override void OnElementChanged(ElementChangedEventArgs<CustomView> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
customView = e.NewElement as CustomView;
nativeView = Control as NativeView;
NativeView.CLicked += METHOD_CALLED_BY_EVENT;
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
}
private void METHOD_CALLED_BY_EVENT(object sender, EventArgs ea)
{
customView.MainPageCallback(ea.something.information);
}
}
And then, take a look at the output, you'll be able to see the following:
Welcome to the Callback :)
Emixam23 - Example
I hope this answer is clear and helps you !

Categories

Resources