I'm struggling to figure out something that I feel should be very basic and I hope someone can help me.
I'm trying to make a custom control visible depending on whether or not data has been provided to it.
For example:
I have a main page that uses a custom control as follows:
<?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:controls="clr-namespace:XamarinDependantProperty.Controls;assembly=XamarinDependantProperty"
x:Class="XamarinDependantProperty.Pages.MainPage">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS" Value="0,20,0,0"/>
</OnPlatform>
</ContentPage.Padding>
<StackLayout>
<Label Text="Welcome to Xamarin Forms!" VerticalOptions="Center" HorizontalOptions="Center" />
<controls:CustomEntry TextValue="Test" VerticalOptions="Center" HorizontalOptions="Center"></controls:CustomEntry>
</StackLayout>
</ContentPage>
The custom control looks as follows:
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamarinDependantProperty.Controls.CustomEntry" IsVisible="{Binding TextIsVisible}">
<StackLayout>
<Label Text="{Binding TextIsVisible}" />
<Label Text="{Binding TextValue}" />
</StackLayout>
</ContentView>
And the code behind is as follows:
using System;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace XamarinDependantProperty.Controls
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class CustomEntry : ContentView
{
public CustomEntry()
{
InitializeComponent();
BindingContext = this;
}
public string TextValue
{
get { return (string)GetValue(TextValueProperty); }
set { SetValue(TextValueProperty, value); }
}
public static BindableProperty TextValueProperty = BindableProperty.Create(nameof(TextValue), typeof(string), typeof(CustomEntry));
public bool TextIsVisible => !String.IsNullOrEmpty(TextValue);
}
}
If I set the CustomEntry's TextValue to "Test" the the output is:
Welcome to Xamarin Forms!
False
Test
If I put in an empty string, then there is no output at all, the application starts but nothing is displayed.
If I set the default value of the TextValueProperty to null then the output is:
Welcome to Xamarin Forms!
From the outputs it appears that when I set the TextValue, the TextIsVisible value is working in the first binding (IsVisible) even though the second binding (Text) outputs False, but why is it false?
If I don't provide a value and I don't tell it that null is an acceptable empty value then it screws up completely but it doesn't say anything to that regard. No error, not output, nothing. Is there a way to see that something went wrong? (testing on a iphone simulator)
And then if I take this concept out of this test situation and put it into a real situation. Then setting TextValue and outputting TextIsVisible is still false but it isn't displaying.
What am I doing wrong? What am I not understanding?
You need to raise the propertychanged event for TextIsVisible to notify the View that this property has changed.
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class CustomEntry : ContentView
{
public CustomEntry()
{
InitializeComponent();
BindingContext = this;
}
public string TextValue
{
get { return (string)GetValue(TextValueProperty); }
set{SetValue(TextValueProperty, value);OnPropertyChanged(nameof(TextIsVisible));}
}
public static BindableProperty TextValueProperty = BindableProperty.Create(nameof(TextValue), typeof(string), typeof(CustomEntry),propertyChanged:OnTextChanged);
private static void OnTextChanged(BindableObject bindable, object oldvalue, object newvalue)
{
var entry = bindable as CustomEntry;
entry?.OnPropertyChanged(nameof(TextIsVisible));
}
public bool TextIsVisible => !String.IsNullOrEmpty(TextValue);
}
Related
I created a new class library in MAUI in which a user control is defined. It is a simple user control composed by a checkbox and a label. It should be noted that in MAUI checkbox have only the box and not the associated label. The following is the XAML code
<?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="MauiLib_CustomControls.CheckBoxExt">
<HorizontalStackLayout>
<CheckBox x:Name="myCheckBox"/>
<Label
x:Name="myLabel"
VerticalOptions="Center"
HorizontalOptions="Center" />
</HorizontalStackLayout>
</ContentView>
and this is the related c# code:
namespace MauiLib_CustomControls;
public partial class CheckBoxExt : ContentView
{
public CheckBoxExt()
{
InitializeComponent();
}
public CheckBox CheckBox => myCheckBox;
public Label Label => myLabel;
}
I compiled this project and generated the dll which I imported in my main project. Once added in the references, I add the namespace in the xaml file
xmlns:MauiLib_CustomControls = "clr-namespace:MauiLib_CustomControls;assembly=MauiLib_CustomControls"
and use the user control in a grid
<MauiLib_CustomControls:CheckBoxExt x:Name="myCheckBoxExt"/>
Currently, if I want to set some properties or events I have to write them in the C# file because they are not available in the xaml. For example
this.myCheckBoxExt.Label.Text = "FROM";
this.myCheckBoxExt.CheckBox.CheckedChanged += myCheckBoxExt_CheckedChanged;
So, how can I set the properties of this user control directly from the xaml code as for the native controls? Consider that I would like to have available all properties and events associated to the two children controls (the checkbox and the label).
One way of doing this would be as follows using BindableProperty. You can modify your custom control like this:
Code behind
public partial class CheckBoxExt : ContentView
{
public bool Checked
{
get => (bool)GetValue(CheckedProperty);
set => SetValue(CheckedProperty, value);
}
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public event EventHandler<CheckedChangedEventArgs> CheckedChanged;
public static readonly BindableProperty CheckedProperty = BindableProperty.Create(nameof(Checked), typeof(bool), typeof(CheckBoxExt), propertyChanged: OnCheckedPropertyChanged);
private static void OnCheckedPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
((CheckBoxExt)bindable).CheckedChanged?.Invoke((CheckBoxExt)bindable, new CheckedChangedEventArgs((bool)newValue));
}
public static readonly BindableProperty TextProperty = BindableProperty.Create(nameof(Text), typeof(string), typeof(CheckBoxExt));
public CustomCheckBox()
{
InitializeComponent();
}
}
XAML
<?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:customControls="clr-namespace:MauiLib_CustomControls"
x:Class="MauiLib_CustomControls.CheckBoxExt">
<HorizontalStackLayout>
<CheckBox
IsChecked="{Binding Checked, Source={RelativeSource AncestorType={x:Type customControls:CustomCheckBox}}}"/>
<Label
Text="{Binding Text, Source={RelativeSource AncestorType={x:Type customControls:CustomCheckBox}}}"
VerticalOptions="Center"
HorizontalOptions="Center" />
</HorizontalStackLayout>
</ContentView>
Usage
In the using XAML code you would simply add your control:
<MauiLib_CustomControls:CheckBoxExt
Text="My Text"
CheckedChanged="OnCheckedChanged"/>
and then just add the event handler:
private async void OnCheckedChanged(object sender, CheckedChangedEventArgs e)
{
await DisplayAlert("Checkbox changed", $"new value: {e.Value}", "OK");
}
Note: Depending on the type of control you are developing, it may be necessary to register a MAUI handler for your control. If you need assistance with that, let me know.
I walked into an issue where I wanted to use a DataTemplateSelector for a single item. In this GitHub issue I came accross an answer that extends a contentview and allows for this to happen. I now use this with two Data Templates. The bindingcontext I pass into these works great. But as soon as I want to use another bindingcontext it just says it can't find it.
My DataTemplate:
<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewmodels="clr-namespace:Universal_ONE.ViewModels">
<DataTemplate x:Key="AirpointSettingsDataTemplate">
<StackLayout Orientation="Horizontal"
x:Name="StackName"
Spacing="10">
<Label Text="TestAirpoint" TextColor="Black" FontSize="30" FontFamily="Roboto"/>
<Label Text="{Binding U}" TextColor="Black" FontSize="30" FontFamily="Roboto">
<Label.BindingContext>
<viewmodels:AirpointSettingsViewModel/>
</Label.BindingContext>
</Label>
</StackLayout>
</DataTemplate>
</ResourceDictionary>
The viewmodel:
public class AirpointSettingsViewModel : INotifyPropertyChanged
{
public AirpointSettingsViewModel()
{
Debug.WriteLine("ViewModel Airpoint settings");
}
private string u = "sometext";
public string U
{
get => u;
set
{
u = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(U)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
When putting breakpoints in the viewmodel it does also not hit those breakpoints.
I also tested with the viewmodel as a bindingcontext of the stacklayout, but that doesn't do anything either.
The error:
[0:] Binding: 'U' property not found on 'Universal_ONE.ViewModels.AirpointSettingsViewModel', target property: 'Xamarin.Forms.Label.Text'
I have also tested with a totally unrelated viewmodel on a random property. It still won't find it then. Thus the problem is not that it can't find the viewmodel or that it is faulty.
As the title suggests, I'm trying to pass in a Person object to a custom control instead of passing in each property separately.
So this:
<controls:PersonControl
Person="{Binding Person}"
ControlTemplate="{StaticResource PersonControlTemplate}">
</controls:PersonControl>
instead of this (which works, based on this implementation)
<controls:PersonControl
Name="{Binding Person.Name}"
Age="{Binding Person.Age}"
ControlTemplate="{StaticResource PersonControlTemplate}">
</controls:PersonControl>
I've tried changing the bindable property signature on the PersonControl code behind but it's not working. I actually just get a blank screen.
So:
1 - Is this even possible (i know it's called a bindable property but does it take objects as well?
and
2 - If not what is the recommended approach?
The reason I want to do this is the person object may grow over time and I would rather just update the custom control instead of the consuming page AND it's view model.
Update:
Here's the PersonControl Code:
public partial class PersonControl : ContentView
{
public static readonly BindableProperty PersonProperty = BindableProperty.Create(
nameof(Person),
typeof(Person),
typeof(PersonControl),
string.Empty);
public string Name
{
get { return this.Person.Name; }
}
public Person Person
{
get { return (Person)GetValue(PersonProperty); }
set { SetValue(PersonProperty, value); }
}
public PersonControl()
{
InitializeComponent();
}
}
And here's the PersonControl xaml:
<ContentView.Content>
<StackLayout>
<Label Text="{TemplateBinding Person.Name, Mode=OneWay}"/>
</StackLayout>
</ContentView.Content>
and lastly the consuming page:
<ContentPage.Resources>
<ControlTemplate x:Key="PersonControlTemplate">
<controls:PersonControl></controls:PersonControl>
</ControlTemplate>
</ContentPage.Resources>
<ContentPage.Content>
<StackLayout Spacing="10" x:Name="layout">
<controls:PersonControl
Person="{Binding Person}"
ControlTemplate="{StaticResource PersonControlTemplate}"></controls:PersonControl>
</StackLayout>
</ContentPage.Content>
The person object is a property on the page's viewmodel as per mvvm pattern.
Thanks in advance for your help.
Update: Ive followed this tutorial and tried to replace the bindable string type with an object but still no joy
Yes you can, I used a Entry, type some text, then transfer text to the Label in ContentView. Here is running GIF.
First of all I add a property called PersonName like following code
public partial class PersonControl : ContentView
{
public PersonControl()
{
InitializeComponent();
}
public static BindableProperty PersonNameProperty = BindableProperty.Create(
propertyName: "PersonName",
returnType: typeof(string),
declaringType: typeof(PersonControl),
defaultValue: "",
defaultBindingMode: BindingMode.OneWay);
public string PersonName
{
get { return (string)GetValue(PersonNameProperty); }
set { SetValue(PersonNameProperty, value); }
}
}
Then, here is code about layout of ContentView. Please do not forget to add x:Name="ParentControl" in ContentView tab.
<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="ParentControl"
x:Class="CustomDemoControl.PersonControl">
<ContentView.Content>
<StackLayout>
<Label Text="{Binding Source={x:Reference ParentControl}, Path=PersonName}"/>
</StackLayout>
</ContentView.Content>
</ContentView>
Then I use it in the other page.
<StackLayout>
<Entry x:Name="myEntry" Text="1"/>
<local:PersonControl
BindingContext="{x:Reference Name=myEntry}"
PersonName="{Binding Path=Text, StringFormat='Welcome Mr {0}'}"
></local:PersonControl>
</StackLayout>
So, it turns out the answer was to pass a default object in the bindable property signature (in the custom control's cs) like so:
public static readonly BindableProperty PersonProperty =
BindableProperty.Create(
nameof(Person),
typeof(Person),
typeof(PersonProperty ),
default(Person)); //this was the fix
Now, I missed this because I wasn't aware of errors in the control. It just wasn't showing up in the output window. Can anyone point me in the direction of how to comprehensively debug xamarin.forms apps properly? Or at least catch these kind of errors in future?
Thank you
I'm developing a Xamarin Forms application, which uses Prism for MVVM, and I have some problems, more precisely in establishing a connection between two ViewModels.
In my MainPage I have a UserControl and a Label(TextBlock), the UserControl have a picker(ComboBox).
What I want to achieve is to establish a connection between the MainPageViewModels and UserControlViewModel, so that each time the user changes the selection in the picker, the UserControlViewModel informs MainPageViewModels then, it can change the value of the Label(TextBlock).
There is my code:
MainPage:
<?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:views="clr-namespace:MyNewApp.Views"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="MyNewApp.Views.MainPage">
<ContentPage.Content>
<StackLayout>
<views:UserControl x:Name="UserControl"/>
<Label Text="{Binding NumberChosen}"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
MainPageViewModel:
using Prism.Events;
using Prism.Mvvm;
namespace MyNewApp.ViewModels
{
class MainPageViewModel : BindableBase
{
private int _NumberChosen;
public int NumberChosen
{
get { return _NumberChosen; }
set { SetProperty(ref _NumberChosen, value); }
}
public MainPageViewModel()
{
// Must Read the value on the picker of the UserControl.
NumberChosen = 0;
}
}
}
UserControl:
<?xml version="1.0" encoding="utf-8" ?>
<Grid 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="MyNewApp.Views.UserControl">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label Grid.Row="0" Text="Pick a number:"/>
<Picker Grid.Row="1"
ItemsSource="{Binding NumericSource}"
SelectedItem="{Binding NumberSelected}"
/>
</Grid>
UserControlViewModel
using Prism.Events;
using Prism.Mvvm;
using System.Collections.ObjectModel;
namespace MyNewApp.ViewModels
{
public class UserControlViewModel : BindableBase
{
public ObservableCollection<int> NumericSource { get; set; }
private int _NumberSelected;
public int NumberSelected
{
get { return _NumberSelected; }
set { SetProperty(ref _NumberSelected, value); }
}
public UserControlViewModel()
{
var source = SourceCreator();
NumericSource = source;
NumberSelected = source[0];
}
ObservableCollection<int> SourceCreator()
{
ObservableCollection<int> sourceCreator = new ObservableCollection<int>();
for (int i = 21; i < 99; i++)
{
sourceCreator.Add(i);
}
return sourceCreator;
}
}
}
Appreciate your help.
1- Define bindable property in UserControl called NumberChosen
2- in UserControl's Xaml, bind NumberChosen to NumberSelected (SelectedItem of your picker) ( You can bind it SelectedItem of your picker directly too)
https://www.c-sharpcorner.com/article/binding-control-to-control-in-xamarin-forms/
3- In main page, if you want to show that in your view only, you can bind control to control as mentioned before.
4- You can also bind ChosenNumber of user control to a property in your main view model too
I have began learning XAML a few days ago and I have a trouble with wrapping my head around this problem.
In Xamarin Forms, I want to create a user control which will contain a label and a button and be able to bind a command to usercontrol in XAML from another page which uses my user control.
I am currently getting exception:
Xamarin.Forms.Xaml.XamlParseException: 'Position 8:24. Cannot assign property "Test1": Property does not exists, or is not assignable, or mismatching type between value and property'
Here is a dumbed down version of it I am currently trying to get to work.
My Custom Control XAML
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="App3.Controls.Control1">
<StackLayout>
<Button Command="{Binding Test1}" Text="Test"/>
</StackLayout>
</ContentView>
Control using my Custom Control 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:local="clr-namespace:App3"
xmlns:controls="clr-namespace:App3.Controls"
x:Class="App3.MainPage">
<controls:Control1 Test1="{Binding Test2}" />
</ContentPage>
My Custom Control Codebehind
using System.Windows.Input;
using Xamarin.Forms;
namespace App3.Controls
{
public partial class Control1 : ContentView
{
private static readonly BindableProperty controlProperty = BindableProperty.Create("Test1", typeof(ICommand), typeof(Control1), null);
public Control1()
{
InitializeComponent();
}
public ICommand Test1
{
get
{
return (ICommand)GetValue(controlProperty);
}
set
{
SetValue(controlProperty, value);
}
}
}
}
View Model of page using Custom Control
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
using Xamarin.Forms;
namespace App3
{
public class MainPageViewModel : INotifyPropertyChanged
{
public MainPageViewModel()
{
this.Test2 = new Command(test);
}
public void test()
{
Application.Current.MainPage.DisplayAlert("it works", "it works", "it works");
}
public ICommand Test2
{
get; set;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
There are a few naming convention to follow if you want your BindableProperty detected, and bindable, from Xaml:
When you create the identifier field, name this field by the name of the property as you registered it, plus the suffix Property. This field is your identifier for the dependency property, and it will be used later as an input for the SetValue and GetValue calls you will make in the wrappers, by any other code access to the property by your own code, by any external code access you allow, by the property system, and potentially by XAML processors.
and
Define a DependencyProperty identifier as a public static readonly field on the owner type.
(this is the documentation for DependencyProperty, but it applies to BindableProperties quite well in this case. See https://msdn.microsoft.com/en-us/library/ms753358(v=vs.110).aspx)
You can fix your code by replacing
private static readonly BindableProperty controlProperty = BindableProperty.Create("Test1", typeof(ICommand), typeof(Control1), null);
public ICommand Test1
{
get { return (ICommand)GetValue(controlProperty); }
set { SetValue(controlProperty, value); }
}
by
public static readonly BindableProperty Test1Property = BindableProperty.Create("Test1", typeof(ICommand), typeof(Control1), null);
public ICommand Test1
{
get { return (ICommand)GetValue(Test1Property); }
set { SetValue(Test1Property, value); }
}
That should prevent the property detection issue.
to get your full stuff working, the Button command binding should have it's source set to the control itself, and you should set your MainPageViewModel as BindingContext for MainPage.
Thank you very much both of you, i have talked offline with Krzysztof, and after all those changes, one thing is left to make it working.
Now when you click a button in custom control (Control1) binded command will not be called, the command saved in the binding is null, but even if you notify the view to get fresh command from bindablecontext it will not work because it will not found the proper property, because we dont have specified bindablecontext so the framework will search our property somewhere else (in parent object). Dont try to set bindablecontext of custom control to this, it will brake the binding ability to this control (you will not be able to bind properly to those dependency properties).
In this situation the easiest way to solve it is to set proper binding source in xaml.
Set the name of your content view and specify the binding source in binding all bindings (Here is only one).
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="App3.Controls.Control1"
x:Name="MySpecifiedName">
<StackLayout>
<Button Command="{Binding Test1, Source={x:Reference MySpecifiedName}}" Text="Test"/>
</StackLayout>
</ContentView>
I have wasted few hours on digging how to fix it so, i am writing here to save others developers which are facing those problems, time.