.NET MAUI Binding Issue - c#

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!

Related

.net Maui binding values multiple levels deep

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.

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.

Access Method from other Class in Xamarin

I'm stuck with a Xamarin problem. I have a XAML ContentPage file which consists of two ContentView (vm:) in a StackLayout:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Proj1"
xmlns:vm="clr-namespace:Proj1.ViewModels"
x:Class="Proj1.MyMain">
<StackLayout BackgroundColor="{StaticResource MainBG}" Spacing="1">
<vm:DisplayArea />
<vm:ButtonArea />
</StackLayout>
</ContentPage>
The two vm: presents two ContentView areas for labels and buttons. I separated these for simplicity and to keep the XAML files smaller.
So, the general, merged XAML structure looks like this:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Proj1"
xmlns:vm="clr-namespace:Proj1.ViewModels"
x:Class="Proj1.MyMain">
<StackLayout BackgroundColor="{StaticResource MainBG}" Spacing="1">
<ContentView>
...
<Label Grid.Row="0" Grid.Column="1" x:Name="InpRegX" />
...
</ContentView>
<ContentView>
...
<Button ... Clicked="BtnClicked" />
...
</ContentView>
</StackLayout>
</ContentPage>
But I want to have the two ContentView in separate files.
DisplayArea consists among others of a label RegX:
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Proj1.ViewModels.DisplayArea">
...
<Label Grid.Row="0" Grid.Column="1" x:Name="InpRegX" />
...
</ContentView>
namespace Proj1.ViewModels
{
public partial class DisplayArea : ContentView
{
public readonly MyClass RegX; // made public for simplicity
public DisplayArea ()
{
InitializeComponent ();
RegX = new MyClass(InpRegX);
}
}
}
Now I want to execute a method .AddChar() of DisplayArea.RegX from a button clock.
namespace Proj1.ViewModels
{
public partial class ButtonArea : ContentView
{
public ButtonArea ()
{
InitializeComponent ();
}
private void BtnClicked(object sender, EventArgs e)
{
var btn = (Button)sender;
DisplayArea.RegX.AddChar(btn.Text); // ERROR!
}
}
}
This creates a compiler error:
An object reference is required for the non-static field, method, or property 'DisplayArea.RegX
This is because I reference RegX via its class, not the real object instance. But how can I find the name the compiler creates for the instance?
The standard in OOP is to create a static class for the utilities with static methods that are global and accessable as you did without creating an instance of the class every time you want to access a variable or a method.
Example:
public static class Util
{
public static string GlobalString = "Hello World";
public static string GetCurrentLanguage()
{
string SelectedLangProp;
if (Application.Current.Properties.ContainsKey("SelectedLangProp"))
{
SelectedLangProp = Application.Current.Properties["SelectedLangProp"] as string;
}
else
{
SelectedLangProp = "AR";//default language
}
return SelectedLangProp;
}
}
You can access static variables from anywhere using:
String TestGlobal = Util.GlobalString; //"Hello World"
Same goes for method calls:
String MethodResult = Util.GetCurrentLanguage();
There is an alternative way which is closer to what you asked which is:
DisplayArea display = new DisplayArea();
String Result = display.RegX.AddChar(btn.Text);
This will work but it will create a new instance of the class which is not recommended especially because you are using a contentview class and doing the logic in the code behind instead of using MVVM is the the recommended structure for building Xamarin apps.
in your XAML, assign a name
<vm:DisplayArea x:Name="MyDisplayArea />
then in your xaml.cs
private void BtnClicked(object sender, EventArgs e)
{
var btn = (Button)sender;
MyDisplayArea.RegX.AddChar(btn.Text); // ERROR!
}

Xamarin Forms Refresh Layout after Observable Collection changes

I'm having a though time trying to "refresh" one of the views where I'm using a WrapLayout. Even though I change the items inside the ObservableCollection the page does not show the changes made.
Code below (some obfuscation needed due to confidentiality issues but I think the most important part is all there). Any help would be greatly appreciated.
Thanks.
ItemCardsViewModel.cs
// INotifyPropertyChanged implemented on BaseViewModel
public class ItemCardsViewModel : BaseViewModel
{
public ObservableCollection<ItemViewModel> Items { get; set; }
public ICommand RefreshCardsCommand { get; private set; }
public Action OnItemsChanged { get; internal set; }
public ItemCardsViewModel()
{
(...)
this.RefreshCardsCommand = new Command(RefreshCards);
}
private void RefreshCards(object x)
{
this.Items = new ObservableCollection<ItemViewModel>(
this.Items.Select(x =>
{
x.IsVisible = false;
return x;
}));
OnPropertyChanged(nameof(this.Items));
if (this.OnItemsChanged != null)
OnItemsChanged();
}
(...)
}
ItemCards.xaml.cs
public partial class ItemCards : ContentPage
{
ItemCardsViewModel ViewModel => ((ItemCardsViewModel)this.BindingContext);
public ItemCards()
{
InitializeComponent();
foreach (var item in ViewModel.Items)
{
var cell = new ItemView { BindingContext = item };
CardsLayout.Children.Add(cell);
}
ViewModel.OnItemsChanged += CardsLayout.ForceLayout;
}
}
ItemCards.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" (...)>
<ContentPage.Content>
<Grid>
(...)
<ScrollView Orientation="Vertical" Padding="0,5,0,5" Grid.Column="0" Grid.Row="2">
<ScrollView.Content>
<local:WrapLayout x:Name="CardsLayout" Spacing="5" HorizontalOptions="Start" VerticalOptions="Start" />
</ScrollView.Content>
</ScrollView>
</Grid>
</ContentPage.Content>
</ContentPage>
EDIT: Forgot to mention but I'm using Prism so the ViewModel is automatically wired up to the view.
EDIT 2: Just a quick update on this one... The issue persists even if I don't create a new Instance of the ObservableCollection on the RefreshCards method but rather loop through the records and set the IsVisible property one by one. Also tried to add a new ItemViewModel to the collection. Always the same result, no changes are shown on the page.

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