.net Maui binding values multiple levels deep - c#

How can I Pass a Binding from a Page to a View?
I have this Page(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:views="clr-namespace:DataBindingTests.Views"
xmlns:model="clr-namespace:DataBindingTests.ViewModels"
x:Class="DataBindingTests.Pages.CoolePage"
Title="CoolePage"
x:DataType="model:CoolesModel">
<VerticalStackLayout>
<Label Text="{Binding YourName}"></Label>
<views:MainView YourName="{Binding YourName}"></views:MainView>
<Button Command="{Binding ChangeNameCommand}"></Button>
</VerticalStackLayout>
</ContentPage>
And its CodeBehind:
using DataBindingTests.ViewModels;
namespace DataBindingTests.Pages;
public partial class CoolePage : ContentPage
{
public CoolePage()
{
this.BindingContext = new CoolesModel();
InitializeComponent();
}
}
If I pass a String into my MainView it works and all events are fired. When I use the binding it doesn't. In this simple test, the app should display two times the same name, but only the Label of the ContentPage has the YourName property printed
<views:MainView YourName="Lars"></views:MainView> <-- Works
<views:MainView YourName="{Binding YourName}"></views:MainView> <-- doesn't work
This is the Xaml of the MainView
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:DataBindingTests.Views"
x:Class="DataBindingTests.Views.MainView">
<VerticalStackLayout>
<Label Text="{Binding YourName}"
VerticalOptions="Center"
HorizontalOptions="Center" />
</VerticalStackLayout>
</ContentView>
This is the CodeBehind of the MainView
namespace DataBindingTests.Views;
public partial class MainView : ContentView
{
public String YourName
{
get
{
String value = (String)GetValue(MainView.YourNameProperty);
return value;
}
set
{
SetValue(MainView.YourNameProperty, value);
}
}
public static readonly BindableProperty YourNameProperty = BindableProperty.Create(nameof(YourName)
, typeof(String)
, typeof(MainView), defaultBindingMode:BindingMode.TwoWay, propertyChanged: OnYourNameChanged);
static void OnYourNameChanged(BindableObject bindable, object oldValue, object newValue)
{
Console.WriteLine(newValue);
}
public MainView()
{
this.BindingContext = this; // Ignore ParentContext
InitializeComponent();
}
}

You can just remove code this.BindingContext = this; from the constructor of MainView.xaml.cs:
public MainView()
{
//this.BindingContext = this;
InitializeComponent();
}
Update:
the above code would only work because the Property in the View and
the Page have the same name.
In this condition, you can modify the code of MainView.xaml as follows:
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiApp929.MainView"
x:Name="TestControlView"
>
<VerticalStackLayout>
<Label Text="{Binding Source={x:Reference TestControlView}, Path=YourName}"
VerticalOptions="Center"
HorizontalOptions="Center" />
</VerticalStackLayout>
</ContentView>
MainView.xaml.cs
public partial class MainView : ContentView
{
public String YourName
{
get
{
String value = (String)GetValue(YourNameProperty);
return value;
}
set
{
SetValue(YourNameProperty, value);
}
}
public static readonly BindableProperty YourNameProperty = BindableProperty.Create(nameof(YourName)
, typeof(String)
, typeof(MainView), defaultBindingMode: BindingMode.TwoWay, propertyChanged: OnYourNameChanged);
static void OnYourNameChanged(BindableObject bindable, object oldValue, object newValue)
{
Console.WriteLine("-----------------> "+newValue);
}
public MainView()
      {
            InitializeComponent();
// this.BindingContext = this;
}
}
CoolesModel.cs
public class CoolesModel
{
// public string YourName { get; set; }
public string Name { get; set; }
public string TestName { get; set; }
public ICommand ChangeNameCommand => new Command(changeMethod);
private void changeMethod()
{
}
public CoolesModel() {
//YourName = "abc";
Name = "abc";
TestName = "test123...";
}
}
MainPage.xaml.cs
<?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:mauiapp929="clr-namespace:MauiApp929"
x:Class="MauiApp929.MainPage">
<ScrollView>
<VerticalStackLayout>
<Label Text="{Binding Name}"></Label>
<mauiapp929:MainView YourName="{Binding TestName}"></mauiapp929:MainView>
<Button Command="{Binding ChangeNameCommand}"></Button>
</VerticalStackLayout>
</ScrollView>
</ContentPage>

In this simple test, the app should display two times the same name, but only the Label of the ContentPage has the YourName property printed
You're overwriting your binding context half way through for some reason, and the context your page binding resolves (the normal way of using it, the parent context) is different than what you actually see on the screen (which is your this.BindingContext = this). And you never set your second context's property.

Related

.NET MAUI Binding Issue

Newbie to .NET MAUI and to MVVM. I've seen other examples out there for this, but mine won't work. When I run the code it shows the string PlayProperty in PlayMCanvas as null. I don't know how to get data into the canvas.
VM
public class ShowViewModel
{
public string TheString {get;set; }
public ShowViewModel()
{
TheString = "test";
}
}
View Code Behind
public partial class ShowPlay : ContentPage
{
public ShowViewModel TheVM;
public ShowPlay()
{
InitializeComponent();
TheVM = new ShowViewModel();
TheVM.TheString = "test2";
BindingContext = TheVM;
}
}
XAML View
<?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:drawables="clr-namespace:PlayMApp.Drawables"
xmlns:local="clr-namespace:PlayMApp"
x:Class="PlayMApp.ShowPlay"
x:DataType="local:ShowViewModel"
Title="Show Play">
<VerticalStackLayout>
<Label
Text="Welcome to .NET MAUI!"
VerticalOptions="Center"
HorizontalOptions="Center" />
<Button
Text="Add Motion"
Clicked="AddMotion"
VerticalOptions="Start"
HorizontalOptions="Center"></Button>
<GraphicsView HeightRequest="300"
WidthRequest="400">
<GraphicsView.Drawable>
<drawables:PlayMCanvas Play="{ Binding TheString }" />
</GraphicsView.Drawable>
</GraphicsView>
</VerticalStackLayout>
</ContentPage>
PlayMCanvas
public class PlayMCanvas : GraphicsView, IDrawable
{
public PlayMCanvas()
{
}
public string Play
{
get => (string)GetValue(PlayProperty);
set => SetValue(PlayProperty, value);
}
public static BindableProperty PlayProperty = BindableProperty.Create(nameof(Play), typeof(string), typeof(PlayMCanvas));
public void Draw(ICanvas canvas, RectF dirtyRect)
{
canvas.StrokeColor = Colors.Red;
canvas.StrokeSize = 6;
canvas.DrawLine(10, 10, 90, 100);
canvas.DrawString(Play,40,30,HorizontalAlignment.Left);
}
}
When I get to the final line (DrawString), I think Play should be "test2", but it's null
I've tried making changes to the code to tweak what is sent in. If I send just a plain literal string through the <drawables:PlayMCanvas Play="test" />, it works, but not with the binding
I figured it out, saw another example where someone was very specific in the component they were binding to and followed that.
Added x:Name to the PlayMCanvas like below:
<drawables:PlayMCanvas x:Name="PlayMCan" Play="{Binding TheString}" />
Changed to this in codebehind:
PlayMCan.BindingContext = TheVM;
And now the binding seems to be working.
Thanks for the comments!

Databinding is not working between two tabbedpage children

I'm new to Xamarin.Forms development and I'm trying to build a tiny Xamarin app to start.
My app currently has one main TabbedPage that has two ContentPages children. On the ListePage, I have a ListView with an ObservableCollection with OlonaModel is an object with an int Numero and a Text string. I would like the Details page to show details of the selected OlonaModel from the listview of ListePage, but the Details page doesn't seem to update to changes when I select an item from the listview.
Both of the content pages are bound to the same ListPageViewModel. The view model updates when I select an item from the listview, but the changes aren't reflected on the Details page and I'm really confused.
How can I make the Details refresh itself when the SelectedItem of the view model gets set ?
The MainPage :
<?xml version="1.0" encoding="utf-8" ?>
<TabbedPage 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"
xmlns:local="clr-namespace:App.Views"
xmlns:viewmodels="clr-namespace:App.ViewModels"
mc:Ignorable="d"
x:Class="App.MainPage">
<TabbedPage.Children>
<local:ListePage/>
<local:Details/>
</TabbedPage.Children>
</TabbedPage>
The ListePage.xaml (ContentPage1 in the post) :
<?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:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="App.Views.ListePage"
Title="Liste">
<ContentPage.Content>
<StackLayout>
<ListView x:Name="ListeMipoitra" ItemSelected="ListeMipoitra_ItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Text="{Binding Numero}" Grid.Column="0" FontSize="Medium" FontAttributes="Bold"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
The ListePage.xaml.cs :
namespace App.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ListePage : ContentPage
{
public static ListePageViewModel viewModel;
public ListePage()
{
InitializeComponent();
viewModel = new ListePageViewModel();
this.BindingContext = viewModel;
ListeMipoitra.ItemsSource = viewModel.listeOlonaVM;
}
private void ListeMipoitra_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
viewModel.setSelected((OlonaModel)e.SelectedItem);
}
}
}
The Details.xaml (ContentPage2 in the post) :
<?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:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="App.Views.Details"
Title="Détails">
<ContentPage.Content>
<StackLayout>
<Label Text="{Binding Text}"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
The Details.xaml.cs :
namespace App.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Details : ContentPage
{
public Details()
{
InitializeComponent();
this.BindingContext = ListePage.viewModel.selected;
}
}
}
The ListePageViewModel.cs :
Note: I'm using Fody and PropertyChanged.Fody weaver, so the RaisePropertyChanged() event (should be) is called automatically when a property changes
namespace App.ViewModels
{
public class ListePageViewModel : INotifyPropertyChanged
{
public ObservableCollection<OlonaModel> listeOlonaVM;
public OlonaModel selected { get; set; }
public ListePageViewModel()
{
listeOlonaVM = new ObservableCollection<OlonaModel>();
listeOlonaVM = ListeOlona.liste;
}
public event PropertyChangedEventHandler PropertyChanged;
public void setSelected(OlonaModel olona)
{
selected = olona;
}
}
}
The model of the Olona object :
namespace App.Models
{
public class OlonaModel
{
public int Numero { get; set; }
public string Text { get; set; }
public OlonaModel(int num, string text)
{
this.Numero = num;
this.Text= text;
}
}
}
The ListeOlona.cs where the model of the list is stored:
The InitializeList() method is called at App Startup.
namespace App.ViewModels
{
public static class ListeOlona
{
public static ObservableCollection<OlonaModel> liste = new ObservableCollection<OlonaModel>();
public static void InitializeList()
{
liste.Add(new OlonaModel(1,
"FirstItem"));
liste.Add(new OlonaModel(2,
"Second Item"));
}
}
}
According to your description, there is tabbedpage, two pages, one is Listpage, another is detailpage, you want to select listview item in listpage, then other detail info will display detailpage, am I right?
If yes, I suggest you can do this like this:
TabbedPage:
<TabbedPage
x:Class="App4.TabbedPage2"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:App4"
x:Name="tabbedpage2">
<!-- Pages can be added as references or inline -->
<TabbedPage.Children>
<local:ListPage Title="Mian page" BindingContext="{Binding}" />
<local:DetailPage Title="Detail page" BindingContext="{Binding select}" />
</TabbedPage.Children>
public partial class TabbedPage2 : TabbedPage
{
public TabbedPage2 ()
{
InitializeComponent();
this.BindingContext = new ListePageViewModel();
}
}
public class OlonaModel
{
public int Numero { get; set; }
public string Text { get; set; }
}
public class ListePageViewModel : ViewModelBase
{
public ObservableCollection<OlonaModel> listeOlonaVM { get; set; }
private OlonaModel _select;
public OlonaModel select
{
get { return _select; }
set
{
_select = value;
RaisePropertyChanged("select");
}
}
public ListePageViewModel()
{
listeOlonaVM = new ObservableCollection<OlonaModel>()
{
new OlonaModel(){Numero=1,Text="first item"},
new OlonaModel(){Numero=2,Text="second item"},
new OlonaModel(){Numero=3,Text="third item"},
new OlonaModel(){Numero=4,Text="fouth item"}
};
select = listeOlonaVM[0];
}
}
ListPage:
<StackLayout>
<ListView ItemsSource="{Binding listeOlonaVM}" SelectedItem="{Binding select}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding Numero}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
DetailPage:
<StackLayout>
<Label
HorizontalOptions="CenterAndExpand"
Text="{Binding Text}"
VerticalOptions="CenterAndExpand" />
</StackLayout>
ViewModelBase implement INotifyPropertyChanged interface.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}

CarouselView.Content is empty

I try study is new componet CarouselView and с# in total.
But I have problem.
This code display is empty content in CarouselView. Why?
I send is fill code my colutions.
<?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:carousel="clr-namespace:CarouselView.FormsPlugin.Abstractions;assembly=CarouselView.FormsPlugin.Abstractions"
xmlns:local="clr-namespace:App6"
x:Class="App6.MainPage">
<StackLayout>
<Image Source="home.png"/>
<carousel:CarouselViewControl x:Name="MyCV" BackgroundColor="Black">
<carousel:CarouselViewControl.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Text="{Binding TestLine}"></Label>
</StackLayout>
</DataTemplate>
</carousel:CarouselViewControl.ItemTemplate>
</carousel:CarouselViewControl>
</StackLayout>
</ContentPage>
namespace App6
{
public partial class MainPage : ContentPage
{
class CustomCell
{
public string TestLine { get; set; }
}
public MainPage()
{
InitializeComponent();
List<CustomCell> myCarousel = new List<CustomCell>();
myCarousel.Add(new CustomCell { TestLine = "Line 1" });
myCarousel.Add(new CustomCell { TestLine = "Line 2" });
MyCV.ItemsSource = myCarousel;
}
}
}
Your code is correct, so your problem is in the visual layer. Try to define the height and width wherever possible and it should resolve the problem. CarouselView isn't the official control and isn't up to standards of Xamarin's built-in controls so it has some somewhat unexpected behaviors like this.

Xamarin Cross Platform Picker not binding correctly

I am new to Xamarin Cross Platform apps
I am trying to bind a Picker ItemSource to a List and display one property, with out success! My reference has been from here
Please can some one advise where my error is, my View or Xmal please (or probably both)
The List is a List of StdGrades defined as
namespace FitRestults_Dev1
{
class StdGrade
{
public string Gradelbl
{ get; set; }
public string Grade
{ get; set; }
public static List<StdGrade> Grades()
{
List<StdGrade> GradesList = new List<StdGrade>(){
new StdGrade(){ Gradelbl="10th Gup (White belt)", Grade="G10"},
new StdGrade(){ Gradelbl="9th Gup (Organge belt)", Grade="G9"},
new StdGrade(){ Gradelbl="8th Gup (Organge belt 1 tag)", Grade="G8"},
... };
return GradesList;
}
public List<StdGrade> GradesList => Grades();
public static string GetGrade(string Input)
{
List<StdGrade> GradesList = Grades();
var result = (from r in GradesList where r.Gradelbl == Input select r).First();
return result.Grade;
}
}
For the Content page I have defined a simple view as
namespace FitRestults_Dev1
{
class AddStudentView
{
List<StdGrade> _GradeList;
public List<StdGrade> GradeList
{ get => _GradeList;
set
{
_GradeList = StdGrade.Grades();
}
}
}
}
My content page xmal is
<?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="FitRestults_Dev1.AddStudent"
xmlns:src="clr-namespace:FitRestults_Dev1"
>
<ContentPage.BindingContext>
<src:AddStudentView/>
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout Padding="10" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<Grid>
…
<Picker x:Name="GradePicker" Title="Select a Grade" Grid.Row="2" Grid.Column="1" MinimumWidthRequest="100" FontSize="12"
ItemsSource="GradeList" SelectedIndex="0" ItemDisplayBinding="{Binding Gradelbl}">
</Picker>
</StackLayout>
</ContentPage.Content>
</ContentPage>
You are providing the itemsSource the incorrect way it should be a binding
ItemsSource={Binding GradeList}
Also Stop using Generic.List for binding, MVVM with Xamarin Forms should have ObservableCollections as it inherits from INotifyPropertyChanged and INotifyCollectionChanged

What's wrong with my Xamarin.Forms project?

I'm trying to make a Xamarin.Forms project where I have a BoxView and an Entry field and a Button. I want to enter the name of a color into my Entry field, press the button, and have my BoxView change to the color that I input. Here is the code I have written till now:
Views/MainView.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="TestGround.MainView">
<ContentPage.Content>
<StackLayout VerticalOptions="Center">
<Label
Text="Enter a color:"
VerticalOptions="Center"
HorizontalOptions="Center"
/>
<BoxView
Color="{Binding Color}"
/>
<Entry
Text="{Binding Name}"
/>
<Button
Text="Enter"
Command="{Binding SetColorCommand}"
/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
ViewModels/MainViewModel.cs
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Forms;
namespace TestGround
{
public class MainViewModel :INotifyPropertyChanged
{
private string _color; //backing field for Greeting
public string Color //implementation for Greeting method
{
get { return _color; }
set
{
_color = value;
OnPropertyChanged ("Color"); //Notify view that change has taken place
}
}
public string Name { get; set; } //Name method for Entry field
public ICommand SetColorCommand { get; set; } //ICommand binds to buttons in XAML
public void SetColor() //Need a regular method to add to ICommand
{
Color = Name;
}
//Main VIEW MODEL
public MainViewModel ()
{
//Color = Name;
Name = "Enter color here";
SetColorCommand = new Command(SetColor); //Regular command added to ICommand
}
#region PropertyChangedRegion
public void OnPropertyChanged (string propertyName)
{
if (PropertyChanged != null)
PropertyChanged (this, new PropertyChangedEventArgs (propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
}
Here is the error I get:
Java.Lang.RuntimeException: java.lang.reflect.InvocationTargetException
I want to know if my approach is wrong and how can I go about fixing it and making this pretty simple program.
According to the BoxView Documentation, the property "Color" must actually be a color... where as you have it defined as a string named color. Your types are mixed up. It should be something like Colors.Blue.
You can use class ColorTypeConverter for change string to Color.
I 've simplified your problem to this source code
//You simplified model
public class bModel : BindableObject
{
private Color _realColor;
public Color Color
{
get { return _realColor; }
set
{
_realColor = value;
OnPropertyChanged ("Color");
}
}
public string _stringColor;
public string StringColor {
get {
return _stringColor;
}
set {
_stringColor = value;
Color = (Color)(new ColorTypeConverter ()).ConvertFrom (_stringColor);
}
}
public bModel ()
{
StringColor = "Blue";
}
}
}
//Your simplified page 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="s2c.MyPage">
<ContentPage.Content>
<BoxView x:Name="box" Color="{Binding Color}"/>
</ContentPage.Content>
</ContentPage>
//Your simplified page csharp
public partial class MyPage : ContentPage
{
public MyPage ()
{
InitializeComponent ();
this.BindingContext = new bModel ();
}
}

Categories