How do you pass parameters in MAUI without using a ViewModel? - visual-studio-2022

I have this on one page:
await Shell.Current.GoToAsync(nameof(Pages.StartPage), true, new Dictionary<string, object>
{
{ "LoginData", result }
});
result is an object/class
In my Pages.StartPage I want to get that object. I have tried using [QueryProperty... but that always returns a null. E.g.
[QueryProperty(nameof(GetLoginData), "LoginData")]
public partial class StartPage : ContentPage
...
private JsonApiResult GetLoginData { set { _loginData = value; } }
I've just started using MAUI, and I am converting an app from Xamarin to MAUI. The pages I have built take care of themselves, so I don't want to use ViewModels, I just need a value from that passed-in object for the page to do its stuff. I don't want to have to rewrite all my pages unless there is no other way
Any help would be much appreciated. I've watched loads of videos on this, and I can't make it work, what am I missing?
UPDATE
I should add that to make matters more complex for myself, I am also using Dependency Injection (DI)

here it comes an example!
Main page .xaml:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiApp1.MainPage">
<ScrollView>
<VerticalStackLayout
Spacing="25"
Padding="30,0"
VerticalOptions="Center">
<Button
x:Name="CounterBtn"
Text="Click me"
SemanticProperties.Hint="Counts the number of times you click"
Clicked="OnCounterClicked"
HorizontalOptions="Center" />
</VerticalStackLayout>
</ScrollView>
On main page .cs:
public MainPage()
{
InitializeComponent();
}
private async void OnCounterClicked(object sender, EventArgs e)
{
List<Student> myStudentsList = new List<Student>
{
new Student {Name="Carlos",Course="IT",Age=18},
new Student {Name="Juan",Course="IT",Age=19},
new Student {Name="Leandro",Course="IT",Age=20}
};
await Navigation.PushAsync(new PageWithStudentsList(myStudentsList));
}
PageWithStudentsList .cs :
public partial class PageWithStudentsList : ContentPage
{
public PageWithStudentsList(List<Student> students)
{
Console.WriteLine(students);
InitializeComponent();
}
}
And you dont need to use viewmodel!
EDIT: in case you need another example with SHELL NAVIGATION, here is a microsoft example in their docs! Hope it helps!

private JsonApiResult _loginData;
public JsonApiResult LoginGetData {
get => _loginData;
set { _loginData = value; }
}
It seems this was the solution though I can't see why. I'll dig into it another time but right now its working so I can crack on

Related

.Net Maui MVVM - What is the best approach to populating a CollectionView upon a Page/View opening?

I'm new to .Net Maui but have completed James Montemagno's 4 hour Workshop. Included in the Workshop was:
Creating a Page with a CollectionView
Creating a ViewModel
Creating an async method which calls a data service to retrieve data
Configuring the async method as a ICommand
Binding the data model list to the CollectionView
Binding the Command to a Button
Clicking the button works and populates the CollectionView. How would I go about removing the button and performing this action when the page opens? Note I tried modifying the method by removing the "[ICommand]" which did not work. Also, should this action be done in the Code Behind or in the ViewModel?
Thanks in advance for assistance!
(ModelView)
public partial class FieldAssignedWbsViewModel : BaseViewModel
{
FieldAssignedWbsService fieldAssignedWbsService;
public ObservableCollection<FieldAssignedWbs> WbsList { get; set; } = new();
public FieldAssignedWbsViewModel(FieldAssignedWbsService fieldAssignedWbsService)
{
Title = "Wbs Assigned";
this.fieldAssignedWbsService = fieldAssignedWbsService;
}
[ICommand]
async Task GetFieldAssignedWbsListAsync()
{
if (IsBusy)
return;
try
{
IsBusy = true;
var wbsList = await fieldAssignedWbsService.GetFieldAssignedWbsList();
if (WbsList.Count != 0)
WbsList.Clear();
foreach (var wbs in wbsList)
WbsList.Add(wbs);
}
catch (Exception ex)
{
Debug.WriteLine(ex);
await Shell.Current.DisplayAlert("Error!",
$"Undable to get monkeys: {ex.Message}", "OK");
}
finally
{
IsBusy = false;
}
}
}
(CollectionView Binding)
<CollectionView BackgroundColor="Transparent"
ItemsSource="{Binding WbsList}"
SelectionMode="None">
(Code behind page with incorrect call to Command Method)
public partial class FieldAssignedWbsPage : ContentPage
{
public FieldAssignedWbsPage(FieldAssignedWbsViewModel viewModel)
{
InitializeComponent();
BindingContext = viewModel;
//The following call does not work
//Hover message: Non-invocable member... cannot be called like a method
await viewModel.GetFieldAssignedWbsListCommand();
}
}
Although the original answer is very valid, I'd recommend installing the CommunityToolkit.Maui (by Microsoft), then using its EventToCommand features.
After installing, add builder.UseMauiCommunityToolkit() to CreateMauiApp() method in MauiProgram.cs.
Then, in relevant XAML page, add this namespace xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit" and then you should be able to use this block of code to do what you want:
<ContentPage.Behaviors>
<toolkit:EventToCommandBehavior
EventName="Appearing"
Command="{Binding GetFieldAssignedWbsListCommand}" />
</ContentPage.Behaviors>
Sorry that this is a bit late, I just believe that it is a slightly cleaner solution as it avoids populating the code-behind with any code and keeps UI handling purely between the viewmodel and the view.
use OnAppearing. You may also need to make the GetFieldAssignedWbsList public
protected override async void OnAppearing()
{
await viewModel.GetFieldAssignedWbsListCommand.Execute(null);
}

Set Tag value in xaml

In Xamarin, Is there any way to set tag to button in xaml file?
I tried this,
<Button Text="OK"
Tag="{Binding Email}"/>
but Tag property doesn't supporting in xaml
what property is there in xaml to support to set tag value to view
You can easily create your own custom Button and add a Tag BindableProperty.
public class ButtonWithTag : Button
{
public object Tag
{
get { return (object)GetValue(TagProperty); }
set { SetValue(TagProperty, value); }
}
public static readonly BindableProperty TagProperty =
BindableProperty.Create(nameof(Tag), typeof(object), typeof(ButtonWithTag), null);
}
You use it like this in XAML
xmlns:local="clr-namespace:YourProjectName"
<local:ButtonWithTag
Text="Ok"
Tag="{Binding Email}" />
You can use x:Name="MyButton", and then access the button in your code-behind .cs file by simply using MyButton.Text = "updated text", for example. Assuming thats what you meant.
I think the best solution for this is CommandParameter. You can do this using CommandParameter. Try this,
.xaml
<Button Text="OK"
Command="{Binding TapCommand}"
CommandParameter="EmailAddress"/>
.cs
ICommand tapCommand;
public ICommand TapCommand {
get { return tapCommand; }
}
public ConstructorName {
tapCommand = new Command (OnTapped);
}
void OnTapped (object s) {
Debug.WriteLine ("parameter: " + s);
}

ReactiveUI Binding does not work in Release build in a UWP app

I'm hitting a weird issue with ReactiveUI and binding where the binding works fine in Debug build but not in Release build.
Here I have the code for a sample app that shows the issue. In Debug builds, as I type something in the textbox the InputText property in the view model gets updated accordingly and when I tap the button it shows the updated input text back to me in a message dialog. But the same code in Release build does not work, as InputText always remains empty.
Anyone knows what's going on here?
<Page x:Class="RxBind.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<TextBox x:Name="MyTextBox" Margin="10"/>
<Button x:Name="MyButton" Content="Show Dialog" Margin="10"/>
</StackPanel>
</Page>
public sealed partial class MainPage : IViewFor<MainPageViewModel>
{
public MainPage()
{
InitializeComponent();
ViewModel = new MainPageViewModel();
this.WhenActivated(d =>
{
d(this.BindCommand(ViewModel, vm => vm.MyButtonCommand, v => v.MyButton));
d(this.Bind(ViewModel, vm => vm.InputText, x => x.MyTextBox.Text));
});
}
#region IViewFor impl
object IViewFor.ViewModel
{
get { return ViewModel; }
set { ViewModel = (MainPageViewModel)value; }
}
public MainPageViewModel ViewModel { get; set; }
#endregion //IViewFor impl
}
public class MainPageViewModel : ReactiveObject
{
private string _inputText = string.Empty;
public string InputText
{
get { return _inputText; }
set { this.RaiseAndSetIfChanged(ref _inputText, value); }
}
public ReactiveCommand<Unit, Unit> MyButtonCommand { get; }
public MainPageViewModel()
{
MyButtonCommand = ReactiveCommand.CreateFromTask(async () =>
{
await new MessageDialog($"InputText={InputText}").ShowAsync();
});
}
}
This isn't really an answer to your question of "what's going on here," but I'm the guy who filed that other bug Matt Whilden linked to and I worked around it for the time being by referencing the text on the button event and calling the command from there too instead of binding the command directly to the button, sort of like this:
d(Observable.FromEventPattern<RoutedEventHandler, object, RoutedEventArgs>(h => MyButton.Click += h, h => MyButton.Click -= h).Subscribe(x=>
{
ViewModel.InputText = MyTextBox.Text;
ViewModel.MyButtonCommand.Execute(null);
}));
Not elegant but it works for me because I don't really need to update on property changed - just on button click. Maybe this will work for you as well before the issue is solved.
As Matt Whilden mentioned in this thread, using runtime directive approach solves the problem so I'm marking this as the right answer. Many thanks Matt Whilden.

Can't get access to controls from code behind in pcl part of xamarin forms project

I've got Xamarin.Forms project with pcl-part and native win, ios and android parts.
All page structure and view-models are in pcl-part. App work's fine, but when I'm trying for example to hide Grid from code behind - it do nothing. Here is code example:
Xaml:
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SomeNamespase.SomePage">
<Grid x:Name="InnerGrid" BackgroundColor="Green">
<Frame x:Name="InnerContent"/>
</Grid>
</ContentPage>
.cs :
using System;
namespace SomeNamespase
{
public partial class SomePage : ContentPage
{
public void SomeMethod()
{
this.InnerGrid.IsVisible = false;
this.InnerContent.BackgroundColor = Color.Aqua;
}
}
}
I've also tried this.FindByName<Grid>("InnerGrid"); the same result
Note: if I am trying to get controls from action in PCL everything is good. Nothing going on when I'm trying to get controls from ViewPresenter in windows (or other platforms) project.
You need to make sure you are properly implementing INotifyPropertyChanged
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Please try the below code, as in your code I can't see the constructor.
using System;
namespace SomeNamespase
{
public partial class SomePage : ContentPage
{
public SomePage()
{
SomeMethod() ;
}
public void SomeMethod()
{
this.InnerGrid.IsVisible = false;
this.InnerContent.BackgroundColor = Color.Aqua;
}
}
}

XAML Binding - App.cs

Question about binding in XAML with WP8.
In my App.cs I declare a public property for class Setting. In other xaml pages I need to access that propery and pass that property to a ConverterParameter. I can't say I've found a clean way of doing this. Below is my current method of how I accomplish this, but it just feels dirty. Any other ways out there?
So what's happening with code below? In app the settings data gets loaded. Any time the settings gets loaded or the a setting changes it Removes/Adds App.Current.Resource. This then allows me to data bind it {StaticResource {resourceName}}
Again, this works 100%...but is there a better/another way to accomplish this?
App.cs
private static Settings _settings = null;
public static Settings Settings
{
get { return _settings; }
private set { _settings = value; }
}
private async void Application_Launching(object sender, LaunchingEventArgs e)
{
if (Settings == null)
Settings = await FlightPath.Core.Data.LoadSettingsAsync();
App.Current.Resources.Add("Settings", App.Settings);
Settings.SettingsChanged += Settings_SettingsChanged;
}
private void Settings_SettingsChanged(object sender, EventArgs e)
{
if (App.Current.Resources["Settings"] == null)
App.Current.Resources.Add("Settings", App.Settings);
else
{
App.Current.Resources.Remove("Settings");
App.Current.Resources.Add("Settings", App.Settings);
}
}
Application Page XAML using Converter / ConverterParameter
<TextBlock Text="{Binding observation_time,
Converter={StaticResource ZuluToLocalTimeConverter},
ConverterParameter={StaticResource Settings}}"
Style="{StaticResource PhoneTextNormalStyle}"
Margin="-4,0,0,0"/>
if you are using MVVM you can Create a SettingManager class which having a Singleton instance. Then declare its propert in ViewModelBase class. Finally use it into your xaml code
XAML
C#
class ViewModelBaseClass: InotifyPropertyChanged
{
public SettingManager Settings{get{return SettingManager.Instance;}}
}
class SettingManager
{
public static Instance{get{...}}
public string this[string sName]
{
return "whatever you need";
}
}
class MYViewModel: ViewModelBase
{
}