How to use bindable property by Binding in xamarin forms? - c#

I am trying to create a content view that contain picker with special design, and wrote all bindable properties like this:
<?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="ShobeekClientApp.Custom.Views.Picker">
<ContentView.Content>
<Grid HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<Picker x:Name="FSPicker" Title="{Binding PickerTitle,Source={x:Reference this}"
ItemsSource="{Binding PickerItemsSource,Source={x:Reference this}}"
ItemDisplayBinding="{Binding PickerItemDisplayBinding,Source={x:Reference this}}"
HorizontalOptions="FillAndExpand"/>
</Grid>
</ContentView.Content>
</ContentView>
The code behind of contentView:
using System;
using System.Collections;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace ShobeekClientApp.Custom.Views
{
///for more information follow this tutorial
///https://mindofai.github.io/Creating-Custom-Controls-with-Bindable-Properties-in-Xamarin.Forms/
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Picker : ContentView
{
#region selected index property
public static readonly BindableProperty PickerSelectedIndexProperty = BindableProperty.Create(
nameof(PickerSelectedIndex), typeof(int), typeof(Picker), -1, BindingMode.TwoWay, propertyChanged: selctedIndexChanged);
private static void selctedIndexChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (Picker)bindable;
control.FSPicker.SelectedIndex = (int)newValue;
}
public int PickerSelectedIndex
{
get { return (int)GetValue(PickerSelectedIndexProperty); }
set { SetValue(PickerSelectedIndexProperty, value); }
}
#endregion
#region title Property
public static readonly BindableProperty PickerTitleProperty = BindableProperty.Create(
nameof(PickerTitle), typeof(string), typeof(Picker), defaultValue: "", defaultBindingMode : BindingMode.TwoWay,
propertyChanged: titleChanged);
private static void titleChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (Picker)bindable;
control.FSPicker.Title = newValue.ToString();
}
public string PickerTitle
{
get { return (string)GetValue(PickerTitleProperty); }
set { SetValue(PickerTitleProperty, value); }
}
#endregion
#region items source property
public static readonly BindableProperty PickerItemsSourceProperty = BindableProperty.Create(
nameof(PickerItemsSource), typeof(IList), typeof(Picker), null, BindingMode.TwoWay, propertyChanged: ItemsSourceChanged);
private static void ItemsSourceChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (Picker)bindable;
control.FSPicker.ItemsSource = (IList)newValue;
}
public IList PickerItemsSource
{
get { return (IList)GetValue(PickerItemsSourceProperty); }
set { SetValue(PickerItemsSourceProperty, value); }
}
#endregion
public static readonly BindableProperty PickerItemDisplayBindingProperty = BindableProperty.Create(nameof(PickerItemDisplayBinding), typeof(BindingBase), typeof(Picker));
public BindingBase PickerItemDisplayBinding
{
get { return (BindingBase)GetValue(PickerItemDisplayBindingProperty); }
set { SetValue(PickerItemDisplayBindingProperty, value); }
}
public Picker ()
{
try
{
InitializeComponent();
BindingContext = this;
//FSPicker.SetBinding(FSPicker.ItemsSource, new Binding(nameof(Property), source: BindingContext));
//SetBinding(PickerSelectedIndexProperty, );
}
catch (Exception ex)
{
var msg = ex.Message;
}
}
}
}
Using the control code:
<customViwes:Picker PickerTitle="{Binding PickerTitle,Mode=TwoWay}" `PickerItemsSource="{Binding pickerData,Mode=TwoWay}" PickerItemDisplayBinding="{Binding .}"/>`
I want to bind on this property from another design that use this control using {Binding}
If I use absolute value it appears successfully and I will use it with binding as I use MVVM structure, it does not work

You need to define a name for your root and then reference the binding of the root.
<?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:Name="Root"
x:Class="ShobeekClientApp.Custom.Views.Picker">
<ContentView.Content>
<Grid HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<Picker x:Name="FSPicker" Title="{Binding Source={ x:Reference Root }, Path=PickerTitle}"
ItemsSource="{Binding Source={ x:Reference Root }, Path=PickerItemsSource}"
ItemDisplayBinding="{Binding Source={ x:Reference Root }, Path=PickerItemDisplayBinding}"
HorizontalOptions="FillAndExpand"/>
</Grid>
</ContentView.Content>
</ContentView>

Related

Unable to disable a button which has IsEnabled bound to a property

I have a problem where I can't seem to disable a button which has IsEnabled bound to a property.
I have the following XAML:
<Button Text="Skip" IsEnabled="{Binding SkipEnabled}" ... />
It is being bound to this BindableProperty:
public static readonly BindableProperty SkipEnabledProperty = BindableProperty.Create(
propertyName: nameof(SkipEnabled), returnType: typeof(bool?), declaringType: typeof(WizardMasterPage), propertyChanged: SetSkipEnabled,
defaultValue: null, defaultBindingMode: BindingMode.TwoWay);
public bool? SkipEnabled
{
get { return (bool?)GetValue(SkipEnabledProperty); }
set { SetValue(SkipEnabledProperty, value); }
}
The problem is the button is always enabled.
The propertyChanged event isn't firing on the pages where the property is set to false - presumably because it is false when the page is created, so doesn't think it has changed and hence the binding is not updated. I notice that it does fire on the pages where I set the property to True.
Has anyone else seen this? Are there any workarounds?
Note: I tried just bool as well and that didn't work either.
Thanks
Is this Button included in a ContentView, right?
If yes, then you can refer to the following code.
I achieved this function and I added a Button on the current page to reset the value, then the UI could refresh automatically.
ChildView.xaml.cs
public partial class ChildView : 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(ChildView), defaultBindingMode: BindingMode.TwoWay, propertyChanged: OnYourNameChanged);
public bool SkipEnabled
{
get
{
bool value = (bool)GetValue(SkipEnabledProperty);
return value;
}
set
{
SetValue(YourNameProperty, value);
}
}
public static readonly BindableProperty SkipEnabledProperty = BindableProperty.Create(nameof(SkipEnabled)
, typeof(bool)
, typeof(ChildView), defaultBindingMode: BindingMode.TwoWay, propertyChanged: OnYourNameChanged);
static void OnYourNameChanged(BindableObject bindable, object oldValue, object newValue)
{
Console.WriteLine("-----------------> " + newValue);
}
public ChildView()
{
InitializeComponent();
}
}
ChildView.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"
x:Class="MauiContentViewApp.ChildView"
x:Name="TestControlView"
>
<VerticalStackLayout>
<Label Text="{Binding Source={x:Reference TestControlView}, Path=YourName}"
VerticalOptions="Center"
HorizontalOptions="Center" />
<Button Text="Skip" IsEnabled="{Binding Source={x:Reference TestControlView}, Path=SkipEnabled}" />
</VerticalStackLayout>
</ContentView>
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:mauiapp="clr-namespace:MauiContentViewApp"
x:Class="MauiContentViewApp.MainPage">
<ContentPage.BindingContext>
<mauiapp:MyViewModel></mauiapp:MyViewModel>
</ContentPage.BindingContext>
<VerticalStackLayout
Spacing="25"
Padding="30,0"
VerticalOptions="Center">
<mauiapp:ChildView YourName="{Binding TestName}" SkipEnabled="{Binding TestBoolvalue}" ></mauiapp:ChildView>
<Button Text="reset" Command="{Binding ResetValueCommand}"/>
</VerticalStackLayout>
</ContentPage>
MyViewModel.cs
public class MyViewModel: INotifyPropertyChanged
{
private string _testName;
public string TestName
{
set { SetProperty(ref _testName, value); }
get { return _testName; }
}
private bool _testBoolValue;
public bool TestBoolvalue
{
set { SetProperty(ref _testBoolValue, value); }
get { return _testBoolValue; }
}
public ICommand ResetValueCommand => new Command(resetMethod);
private void resetMethod(object obj)
{
TestName = "reset test string";
TestBoolvalue = !TestBoolvalue;
}
public MyViewModel()
{
TestName = "test string";
TestBoolvalue = false;
}
bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (Object.Equals(storage, value))
return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}

How do I bind a switch to a property in Xamarin in an Android app in a content view?

I'm creating my first Android App in VS 2022 with Xamarin and now I have to bind a switch to a label to show the user what on/off means. Multiple times so I made a content view:
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:App1.Controls" x:DataType="controls:SwitchWithText"
x:Class="App1.Controls.SwitchWithText"
x:Name="root">
<ContentView.Content>
<StackLayout Orientation="Horizontal">
<Label x:Name="isOkLbl" Text="is not ok" Margin="10"/>
<Switch x:Name="switch1" IsToggled="{Binding Path=IsToggled}"/>
</StackLayout>
</ContentView.Content>
</ContentView>
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace App1.Controls
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class SwitchWithText : ContentView
{
public static readonly BindableProperty isToggledProperty = BindableProperty.Create(
nameof(isToggledProperty), //name of property
typeof(bool), //type of property
typeof(SwitchWithText), //type of owning object
defaultValue: false,
propertyChanged: IsToggledChanged);
public bool IsToggled
{
get => (bool)GetValue(isToggledProperty);
set => SetValue(isToggledProperty, value);
}
private static void IsToggledChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (SwitchWithText)bindable;
if (control != null && control.switch1.IsToggled)
{
control.isOkLbl.Text = "is ok";
}
else
{
control.isOkLbl.Text = "is not ok";
}
}
public SwitchWithText()
{
BindingContext = this;
InitializeComponent();
}
}
}
Some stuff is auto complete by Visual Studio 2022 and looks like it'd do what I need but nothing happpens when I toogle the switch. :(
Or is there an even better way to do this? I saw pictures of switches with text on it but couldn't find something like that in Xamarin.
Is this your desired outcome?
I think what you may want is a value converter class that will take the IsToggled binding and convert it to a string for your label. I posted the working code on GitHub if you'd like to see the functional demo. (I put it directly in the xaml for the ContentPage but of course the same principle will work in a ContentView as in your post.)
using System;
using System.Globalization;
using Xamarin.Forms;
namespace App1
{
public partial class MainPage : ContentPage
{
public MainPage()
{
BindingContext = this;
InitializeComponent();
}
bool _IsToggled = false;
public bool IsToggled
{
get => _IsToggled;
set
{
if (_IsToggled != value)
{
_IsToggled = value;
OnPropertyChanged(nameof(IsToggled));
}
}
}
}
/// <summary>
/// Value converter class
/// </summary>
public class BoolToTextConverter : IValueConverter
{
public string IfFalse { get; set; }
public string IfTrue { get; set; }
public virtual object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((bool)value)
{
return IfTrue;
}
else
{
return IfFalse;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Then to use the value converter, you would make it a Static Resource in your xaml like this:
<?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:app1="clr-namespace:App1"
x:Class="App1.MainPage">
<ContentPage.Resources>
<app1:BoolToTextConverter x:Key="SwitchToText"
IfTrue="is ok"
IfFalse="is not ok" />
</ContentPage.Resources>
<StackLayout>
<Label Text="{Binding Path=IsToggled, Converter={StaticResource SwitchToText}}" FontSize="Title" Padding="30,10,30,10"/>
<Switch x:Name="switch1"
IsToggled="{Binding Path=IsToggled}" />
</StackLayout>
</ContentPage>
So my suggestion is to try a Value Converter to do what you want.

How to send an argument to ContentView?

I'm trying to create a ContentPage that contains a TabView from XamarinCommunityToolkit.
Lets say that the Tabs define an ObservableCollection of Categories, and every TabViewItem should load a ContentView and passes a GroupId as an Argument / Property, and then I use that GroupId to filter Products list.
What's the best way to passe an argument to the ContentView ?
Update :
I've tried to use BindablePropertiy but, in the debugger, I can see the newValue recieved, but nothing shows in the Label :
MainPage.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:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-
compatibility/2006"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
mc:Ignorable="d"
x:Class="mynamespace.Views.MainPage"
Title="{Binding Title}"
xmlns:local="clr-namespace:mynamespace.Views"
xmlns:vm="clr-namespace:mynamespace.ViewModels"
xmlns:model="clr-namespace:mynamespace.Models"
x:Name="MainPage">
<ContentPage.Content>
<xct:TabView Grid.Row="0"
TabStripPlacement="Top"
TabStripBackgroundColor="White"
TabStripHeight="48"
TabIndicatorColor="Orange"
TabIndicatorHeight="2"
TabItemsSource="{Binding Categories}">
<xct:TabView.TabViewItemDataTemplate>
<DataTemplate>
<Grid>
<Label Text="{Binding Name}"
FontAttributes="Bold"
VerticalOptions="Center"
Padding="6, 0"/>
</Grid>
</DataTemplate>
</xct:TabView.TabViewItemDataTemplate>
<xct:TabView.TabContentDataTemplate>
<DataTemplate>
<local:GroupView GroupId="{Binding Id}" />
</DataTemplate>
</xct:TabView.TabContentDataTemplate>
</xct:TabView>
</ContentPage.Content>
GroupView.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace mynamespace.Views
{
public partial class GroupView : ContentView
{
public string GroupId
{
get { return (string)GetValue(GroupIdProperty); }
set { SetValue(GroupIdProperty, value); }
}
public static readonly BindableProperty GroupIdProperty = BindableProperty.Create(
nameof(GroupId),
typeof(string),
typeof(GroupView),
"Default_V",
defaultBindingMode: BindingMode.OneWay,
propertyChanged: GroupIdChanged
);
private static void GroupIdChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (ProductListPage)bindable;
control.GroupId = newValue?.ToString();
}
public GroupView()
{
InitializeComponent();
BindingContext = this;
}
}
}
GroupView.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="mynamespace.Views.GroupView">
<ContentView.Content>
<StackLayout>
<Label Text="{Binding GroupId}" /> <!-- Shows nothing -->
</StackLayout>
</ContentView.Content>
</ContentView>
Category class :
public class Category
{
private string id;
private string name;
private string description;
public string Id { get => id; set => id = value; }
public string Name { get => name; set => name = value; }
public string Description { get => description; set => description = value; }
}
ProductListViewModel.cs
public class ProductListViewModel : BaseViewModel
{
public string GroupId { get; set; }
public ProductListViewModel()
{
}
public ProductListViewModel(string groupId)
{
GroupId = groupId;
}
}
Update :
[0:] Binding: 'GroupId' property not found on 'mynamespace.Models.Category', target property: 'Xamarin.Forms.Label.Text'
Don't assign bindings internally inside custom controls. You could do like this:
public partial class GroupView : ContentView
{
GroupViewModel _viewModel;
public string GroupId
{
get { return (string)GetValue(GroupIdProperty); }
set { SetValue(GroupIdProperty, value); }
}
public static readonly BindableProperty GroupIdProperty = BindableProperty.Create(
nameof(GroupId),
typeof(string),
typeof(GroupView),
"Default_V",
defaultBindingMode: BindingMode.OneWay,
propertyChanged: GroupIdChanged
);
private static void GroupIdChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (GroupView)bindable;
control.GroupId = (string)newValue;
control.label.Text = control.GroupId;
}
public GroupView()
{
InitializeComponent();
}
}
then in 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="mynamespace.Views.GroupView">
<ContentView.Content>
<StackLayout>
<Label x:Name="label" />
</StackLayout>
</ContentView.Content>
</ContentView>

Bindable Entry is not working inside custom control in Xamarin.forms?

I have an entry field inside custom control. I've created bindable property for the same.
Here's my code :
public string MyEntry
{
get => (string)GetValue(MyEntryProperty);
set => SetValue(MyEntryProperty, value);
}
public static BindableProperty MyEntryProperty = BindableProperty.Create(
propertyName: "MyEntry",
returnType: typeof(string),
declaringType: typeof(MyView),
defaultValue: string.Empty,
defaultBindingMode: BindingMode.TwoWay,
propertyChanged:MyEntryPropertyChanged );
public static void MyEntryPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var cView = (MyView)bindable;
cView.myent.Text = (string)newValue;
}
Then on my actual view xaml :
<MyView MyEntry={Binding Something}/>
But it's not working.? Any suggestions?
Maybe you do not set the binding correctly. I make a code sample for your reference.
MyView:
<?xml version="1.0" encoding="utf-8" ?>
<ContentView
x:Class="XamarinDemo.Custom_Control.MyView"
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">
<StackLayout>
<Entry
x:Name="myent"
HorizontalOptions="CenterAndExpand"
Text="Welcome to Xamarin.Forms!"
VerticalOptions="CenterAndExpand" />
<Button />
</StackLayout>
</ContentView>
Code Behind: Same with yours.
public partial class MyView : ContentView
{
public MyView()
{
InitializeComponent();
}
public string MyEntry
{
get => (string)GetValue(MyEntryProperty);
set => SetValue(MyEntryProperty, value);
}
public static BindableProperty MyEntryProperty = BindableProperty.Create(
propertyName: "MyEntry",
returnType: typeof(string),
declaringType: typeof(MyView),
defaultValue: string.Empty,
defaultBindingMode: BindingMode.TwoWay,
propertyChanged: MyEntryPropertyChanged);
public static void MyEntryPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var cView = (MyView)bindable;
cView.myent.Text = (string)newValue;
}
}
Usage: Page1.xaml
<ContentPage.Content>
<StackLayout>
<local:MyView MyEntry="{Binding Something}"></local:MyView>
</StackLayout>
</ContentPage.Content>
Binding:
public partial class Page1 : ContentPage
{
public string Something { get; set; }
public Page1()
{
InitializeComponent();
Something = "Hello";
this.BindingContext = this;
}
}
Screenshot:
I have had this issue with a custom control of mine, the main problem was my binding context was not set correctly.
The way that I used to check this was
I gave my control a x:name in the xaml
I went to the pagename.cs (code behind) in the constructor after the InializeComponents() method I typed this
Console.WriteLine($"Control-{ControlXname.BindingContext();}");
Console.WriteLine($"Page-{this.BindingContext();}");
After i excuted my code i headed to the output panel and i pressed ctrl + f to search for the word "Control-" and "Page-" only then i have found out that thier binding contexts was different.
You are only setting value to Entry, you are not reading back the changes from Entry. You have to add following in your constructor.
// ... add this in your constructor...
myent.SetBinding(Entry.TextProperty, new Binding{
Path = "MyEntry",
Source = this,
Mode = BindingMode.TwoWay
});
public string MyEntry
{
get => (string)GetValue(MyEntryProperty);
set => SetValue(MyEntryProperty, value);
}
// remove onPropertyChanged
public static BindableProperty MyEntryProperty = BindableProperty.Create(
propertyName: "MyEntry",
returnType: typeof(string),
declaringType: typeof(MyView),
defaultValue: string.Empty,
defaultBindingMode: BindingMode.TwoWay);

How to work with ContentProperty in Xamarin.Forms?

I have following code
My 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"
xmlns:effects="clr-namespace:Coodo.Effects;assembly=Coodo"
x:Class="Coodo.Pages.MessagePage">
<effects:ContainerWithShadow>
<Label Text="123" />
</effects:ContainerWithShadow>
</ContentPage>
And ContainerWithShadow class for storage view
using Xamarin.Forms;
namespace Coodo.Effects
{
[ContentProperty("ContainerContent")]
public class ContainerWithShadow : ContentView
{
public View ContainerContent { get; set; }
}
}
But my Label from XAML not binding to ContainerWithShadow.ContainerContent. Code don't stop on setter if I set breakpoint.
ContainerContent property must be BindableProperty. Need to change code on following:
[ContentProperty("Conditions"), Preserve(AllMembers = true)]
public class ContainerWithShadow : ContentView
{
public static readonly BindableProperty StateProperty = BindableProperty.Create(nameof(ContainerWithShadowChild), typeof(object), typeof(View), propertyChanged:PropertyChanged);
private static void PropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
{
}
public View ContainerWithShadowChild
{
get => (View)GetValue(StateProperty);
set => SetValue(StateProperty, value);
}
}
And code will work correctly.

Categories

Resources