I want to use compiled bindings in my Xamarin Forms app in combination with Prism.
I created a small xamarin forms app with a simple view, viewmodel and prism (prism:ViewModelLocator.AutowireViewModel="True"). Classic binding works as expected.
How should I implemented compiled binding without creating the binding context twice?
Classic binding with prism: HomePage.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:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="CompiledBinding.Views.HomePage">
<StackLayout>
<!-- Place new controls here -->
<Label Text="{Binding Name}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
HomePageViewModel.cs:
using Prism.Mvvm;
using Prism.Navigation;
using System;
using Xamarin.Forms;
namespace CompiledBinding.ViewModels
{
public class HomePageViewModel : BindableBase
{
string _name = "Compiled binding test";
public HomePageViewModel(INavigationService navigationService)
{
var nav = navigationService;
Device.StartTimer(TimeSpan.FromSeconds(1), () =>
{
// Do something
Name = DateTime.Now.ToString("yyyy MMMM dd hh:mm:ss");
return true; // True = Repeat again, False = Stop the timer
});
}
public string Name
{
get { return _name; }
set { SetProperty(ref _name, value); }
}
}
}
Adding the binding context to the xaml page again, is not an option:
<?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:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
xmlns:viewModels="clr-namespace:CompiledBinding.ViewModels"
x:Class="CompiledBinding.Views.HomePage"
x:DataType="viewModels:HomePageViewModel">
<ContentPage.BindingContext>
<viewModels:HomePageViewModel />
</ContentPage.BindingContext>
<StackLayout>
<!-- Place new controls here -->
<Label Text="{Binding Name}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
Besides of defining the binding context again, it also results in the error: no public parameterless constructor.
Do I oversee something? Does anyone know how to work with compiled bindings together with prism?
Related
I've got an app that I am porting over to Maui from XF. I have a toolbaritem in my navigationbar. Here is the basic definition:
<ContentPage.ToolbarItems>
<ToolbarItem Text="New Game" Clicked="NewGame_Clicked" x:Name="NewGame" />
</ContentPage.ToolbarItems>
<VerticalStackLayout><ListView........./></VerticalStackLayout>
Here is my NewGame_Clicked Event:
private void NewGame_Clicked(object sender, EventArgs e)
{
Navigation.PushAsync(new EventSetupPage());
}
Here is the content of the EventSetupPage() overly simplified:
<?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="ScanAndScoringMauiApp.Pages.EventSetupPage"
Title="EventSetupPage">
<VerticalStackLayout>
<Label
Text="Welcome to .NET MAUI!"
VerticalOptions="Center"
HorizontalOptions="Center" />
</VerticalStackLayout>
</ContentPage>
When the EventSetupPage() comes up, it is blank in an android 12 emulator. The constructor is called, so I know that something is happening. It's like the page is being loaded in the wrong thread. I'm doing something wrong. Any thoughts on what I should do to get my page to load? TIA
********** update 1 ************
As an fyi, I am loading the parent page via a messagecenter message. I do this in my xamarin forms app.
MessagingCenter.Subscribe<string>(this, "UpdateMainPage", (sender) =>
{
MainThread.BeginInvokeOnMainThread(() =>
{
MainPage = new NavigationPage(new GameList());
});
});
Trying to follow this example to display a custom popup but using MVVM and Shell gives me the error in the title:
https://www.youtube.com/watch?v=yM7opXlu-MU&ab_channel=GeraldVersluis
namespace MyPopupTest
{
public partial class MyViewModel : ObservableObject
{
public MyViewModel()
{
DisplayMyPopup();
}
private void DisplayMyPopup()
{
var popup = new MyPopup();
Shell.Current.ShowPopup(popup);
}
}
}
the popup
using CommunityToolkit.Maui.Views;
namespace MyPopupTest;
public partial class MyPopup : Popup
{
public MyPopup()
{
InitializeComponent();
}
}
This results in an exception: The Parent must be of type Microsoft.Maui.Handlers.PageHandler.
and the stack trace:
at CommunityToolkit.Maui.Core.Views.MauiPopup.SetElement(IPopup element) in /_/src/CommunityToolkit.Maui.Core/Views/Popup/MauiPopup.macios.cs:line 71
at CommunityToolkit.Maui.Core.Handlers.PopupHandler.ConnectHandler(MauiPopup platformView) in /_/src/CommunityToolkit.Maui.Core/Handlers/Popup/PopupHandler.macios.cs:line 91
at Microsoft.Maui.Handlers.ElementHandler`2[[CommunityToolkit.Maui.Core.IPopup, CommunityToolkit.Maui.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null],[CommunityToolkit.Maui.Core.Views.MauiPopup, CommunityToolkit.Maui.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].OnConnectHandler(Object platformView)
at Microsoft.Maui.Handlers.ElementHandler.ConnectHandler(Object platformView)
at Microsoft.Maui.Handlers.ElementHandler.SetVirtualView(IElement view)
at Microsoft.Maui.Controls.Element.SetHandler(IElementHandler newHandler)
at Microsoft.Maui.Controls.Element.set_Handler(IElementHandler value)
at Microsoft.Maui.Platform.ElementExtensions.ToHandler(IElement view, IMauiContext context)
at CommunityToolkit.Maui.Views.PopupExtensions.CreatePopup(Page page, Popup popup) in /_/src/CommunityToolkit.Maui/Views/Popup/PopupExtensions.shared.cs:line 59
at CommunityToolkit.Maui.Views.PopupExtensions.ShowPopup[LayingTrackPopup](Page page, LayingTrackPopup popup) in /_/src/CommunityToolkit.Maui/Views/Popup/PopupExtensions.shared.cs:line 27
at MyPopupTest.MyViewModel.DisplayLayingPopup() in /Users/…
Are you sure You are following all steps? Like setting all paths in xaml, setting good references. I have recreated this task but was not able to get this error.
Working example:
Downloading from nuget CommunityToolkit.Maui (version 2.0.0), CommunityToolkit.Mvvm (version 8.0.0)
MauiProgram.cs
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
})
.UseMauiCommunityToolkit();
return builder.Build();
}
}
MainPage.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"
xmlns:viewmodels="clr-namespace:MauiPopup"
x:DataType="viewmodels:MainPageViewModel"
x:Class="MauiPopup.MainPage">
<ScrollView>
<VerticalStackLayout
Spacing="25"
Padding="30,0"
VerticalOptions="Center">
<Image
Source="dotnet_bot.png"
SemanticProperties.Description="Cute dot net bot waving hi to you!"
HeightRequest="200"
HorizontalOptions="Center" />
<Label
Text="Hello, World!"
SemanticProperties.HeadingLevel="Level1"
FontSize="32"
HorizontalOptions="Center" />
<Label
Text="Welcome to .NET Multi-platform App UI"
SemanticProperties.HeadingLevel="Level2"
SemanticProperties.Description="Welcome to dot net Multi platform App U I"
FontSize="18"
HorizontalOptions="Center" />
<Button
x:Name="CounterBtn"
Text="Click me"
SemanticProperties.Hint="Counts the number of times you click"
Command="{Binding ShowPopupCommand}"
HorizontalOptions="Center" />
</VerticalStackLayout>
</ScrollView>
</ContentPage>
MainPage.xaml.cs
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
BindingContext = new MainPageViewModel();
}
}
MainPageViewModel.cs
internal class MainPageViewModel : ObservableObject
{
public ICommand ShowPopupCommand { get; }
public MainPageViewModel()
{
//ShowPopupCommand = new Command(ShowPopup);
ShowPopup();
}
private void ShowPopup()
{
var popup = new PopupPage();
Shell.Current.ShowPopup(popup);
}
}
PopupPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<mct:Popup xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:mct="clr-namespace:CommunityToolkit.Maui.Views;assembly=CommunityToolkit.Maui"
x:Class="MauiPopup.PopupPage">
<StackLayout>
<Label Text="Welcome to Maui Popup!"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" />
</StackLayout>
</mct:Popup>
PopupPage.xaml
public partial class PopupPage : Popup
{
public PopupPage()
{
InitializeComponent();
}
}
I am having trouble getting data binding to work with custom components.
I have created an IncrementValue property that gets incremented with every button click.
The changes are reflected when binded to a Label.
However they do not work when I bind it to a Bindable property in a custom component.
In the example, I have built a custom component called Card which has two bindable properties CardTitle and CardIncrement
Is there something I'm missing as I'm new to MAUI and even Xamarin.
Github link of code snippets below: https://github.com/814k31/DataBindingExample
Card.xaml.cs
namespace DataBindingExample;
public partial class Card : VerticalStackLayout
{
public static readonly BindableProperty CardTitleProperty = BindableProperty.Create(nameof(CardTitle), typeof(string), typeof(Card), string.Empty);
public static readonly BindableProperty CardIncrementProperty = BindableProperty.Create(nameof(CardIncrement), typeof(int), typeof(Card), 0);
public string CardTitle
{
get => (string)GetValue(CardTitleProperty);
set => SetValue(CardTitleProperty, value);
}
public int CardIncrement
{
get => (int)GetValue(CardIncrementProperty);
set => SetValue(CardIncrementProperty, value);
}
public Card()
{
InitializeComponent();
BindingContext = this;
}
}
Card.xaml
<?xml version="1.0" encoding="utf-8" ?>
<VerticalStackLayout
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:databindingexample="clr-namespace:DataBindingExample"
x:DataType="databindingexample:Card"
x:Class="DataBindingExample.Card"
Spacing="25"
Padding="30,0"
VerticalOptions="Center"
BackgroundColor="red"
>
<Label
Text="{Binding CardTitle}"
SemanticProperties.HeadingLevel="Level1"
FontSize="32"
HorizontalOptions="Center"
/>
<Label
Text="{Binding CardIncrement}"
SemanticProperties.HeadingLevel="Level1"
FontSize="32"
HorizontalOptions="Center"
/>
</VerticalStackLayout>
MainPage.xml
<?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="DataBindingExample.MainPage"
xmlns:DataBindingExample="clr-namespace:DataBindingExample"
xmlns:ViewModels="clr-namespace:DataBindingExample.ViewModels"
x:DataType="ViewModels:MainPageViewModel"
>
<ScrollView>
<VerticalStackLayout
Spacing="25"
Padding="30,0"
VerticalOptions="Center"
>
<Label
Text="{Binding IncrementedValue}"
SemanticProperties.HeadingLevel="Level2"
FontSize="18"
HorizontalOptions="Center"
/>
<!-- Why doesnt this work? -->
<DataBindingExample:Card CardIncrement="{Binding IncrementedValue}" />
<Button
x:Name="CounterBtn"
Text="Click Me"
SemanticProperties.Hint="Counts the number of times you click"
Command="{Binding IncrementValueCommand}"
HorizontalOptions="Center"
/>
</VerticalStackLayout>
</ScrollView>
</ContentPage>
When making a custom component (that includes XAML), DO NOT set BindingContext = this;.
REASON: You want the component to use the SAME BindingContext as the page it is placed in. This happens automatically, if you do NOT set a BindingContext in the custom component.
HOWEVER, removing this line breaks all your component's xaml Bindings; you'll need to add something to the xaml, to fix this.
Or to put it another way: How refer to the card's Properties from its XAML? See the next section.
ACCESS COMPONENT PROPERTIES VIA x:Name
Solution: Give the card an x:Name, and make that the "Source" of those bindings:
<VerticalStackLayout
...
x:Name="me" <-- IMPORTANT! Change name as desired.
x:Class="DataBindingExample.Card"
>
...
<Label Text={Binding CardIncrement, Source={x:Reference me}}"
...
Notice the two parts to this solution:
In component's xaml header, define x:Name="mynamehere".
In each Binding, say that the component is the source:
, Source={x:Reference mynamehere}.
OPTIONAL: If custom component has a "ViewModel":
To have a custom component be "data-driven", pass in a parameter that controls its behavior.
This parameter could be considered a "ViewModel", but above I have specified:
DO NOT set a BindingContext (so that component has easy access to the page's BindingContext).
So unlike other uses of ViewModel, in this technique, we don't set the ViewModel as the BindingContext.
How access this ViewModel?
By saving it as a property of the component; e.g.:
public partial class MyComponent : ContentView
{
private MyViewModel VM;
public void MyComponent(MyViewModel vm)
{
InitializeComponent();
VM = vm;
}
public class MyViewModel : ObservableObject
{
[ObservableProperty]
SomeType someProperty; // This is field. Property "SomeProperty" is generated.
}
Then in xaml, we access properties of VM, using . notation:
<Label Text={Binding VM.SomeProperty, Source={x:Reference me}}"
I have used a litle XamarinForms before and then i did a
naming to be able to point the c# code to it.
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiTest.MainPage"
BackgroundColor="{DynamicResource SecondaryColor}">
<StackLayout x:name="_stacklayoutname">
<Label Text="" x:Name="_Lable"/>
</StackLayout>
</ContentPage>
And then i did like this in the MainPage.xaml.cs
public MainPage()
{
InitializeComponent();
_stacklayoutname.Children.Add(new Label { Text = "TEST" });
_Lable.Text = "TEST";
}
now it get this, but i can change the _Lable to "Text".
how can i point to a stacklayout.
Severity Code Description Project File Line Suppression State
Error CS0103 The name '_stacklayoutname' does not exist in the current context MauiTest (net6.0-android), MauiTest (net6.0-ios), MauiTest (net6.0-maccatalyst), MauiTest (net6.0-windows10.0.19041) *** 11 Active
this is wrong
<StackLayout x:name="_stacklayoutname">
it should be
<StackLayout x:Name="_stacklayoutname">
I have a xamarin forms application. It has a tabbedpage within it multiple tabs. The tabbedpage and te tabs, each of them has their own viewmodel as a bindingcontext.
In the app.xaml I defined a controltemplate. I use this control template in each tab, because I want each of those tabs to have a button at the bottom of the page.
At this moment: the button in the controltemplate binds with a property defined in each tab. But I want the button to bind at one place. Isn't it possible to create a viewmodel special for the controltemplate and bind the button defined in the controltemplate with that viewmodel?
Current code:
<ControlTemplate x:Key="ActivityStatusButton">
<StackLayout>
<ContentPresenter>
</ContentPresenter>
<StackLayout VerticalOptions="EndAndExpand" HorizontalOptions="Fill" Padding="15">
<Button Style="{StaticResource RedBackGroundWithWhiteTextButtonStyle}" Command="{TemplateBinding BindingContext.ClickOnStatusButton, Mode=TwoWay}" Text="{TemplateBinding BindingContext.ok, Mode=TwoWay}"></Button>
</StackLayout>
</StackLayout>
</ControlTemplate>
A typical tab:
<ContentPage ...>
<ContentPage.Content>
<Label Text="hello"></Label>
</ContentPage.Content>
<!--The control template is placed here (the button) -->
You could create a Custom Control (a subclass of ContentView) like
<?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:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Name="template"
x:Class="App24.MyControlTemplate">
<ContentView.Content>
<StackLayout VerticalOptions="EndAndExpand" HorizontalOptions="Fill" Padding="15">
<Button Clicked="Button_Clicked" Command="{Binding Source={x:Reference template},Path=ButtonCommand}" Text="{Binding Source={x:Reference template},Path=ButtonText}" CommandParameter="{Binding Source={x:Reference template},Path=CommandParameter}" />
</StackLayout>
</ContentView.Content>
</ContentView>
using System;
using System.Windows.Input;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace App24
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MyControlTemplate : ContentView
{
public event EventHandler ButtonClick;
public static readonly BindableProperty ButtonTextProperty =
BindableProperty.Create("ButtonText", typeof(string), typeof(MyControlTemplate), default(string));
public string ButtonText
{
get => ((string)GetValue(ButtonTextProperty));
set => SetValue(ButtonTextProperty, value);
}
public static readonly BindableProperty ButtonCommandProperty =
BindableProperty.Create("ButtonCommand", typeof(ICommand), typeof(MyControlTemplate), null, BindingMode.Default, null);
public ICommand ButtonCommand
{
get => (ICommand)GetValue(ButtonCommandProperty);
set
{
SetValue(ButtonCommandProperty, value);
}
}
public static readonly BindableProperty CommandParameterProperty =
BindableProperty.Create("CommandParameter", typeof(object), typeof(MyControlTemplate), null);
public object CommandParameter
{
get => (object)GetValue(CommandParameterProperty);
set => SetValue(CommandParameterProperty, value);
}
public MyControlTemplate()
{
InitializeComponent();
}
private void Button_Clicked(object sender, EventArgs e)
{
ButtonClick?.Invoke(sender, e);
}
}
}
Now you could add it to any page and binding Text , Command or CommandParameter in code behind .
<StackLayout VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand">
<local:MyControlTemplate ButtonText="{Binding ButtonText}" ButtonCommand="{Binding ClickCommand}" CommandParameter="test" />
</StackLayout>