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.
Related
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.
I'm trying to bind a property to a view model.
I get the following error:
Error XFC0009 No property, BindableProperty, or event found for "ViewModel", or mismatching type between value and property.
public abstract class BaseTestView : ContentView
{
public BaseVm ViewModel
{
get => (BaseVm)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, BindingContext = value);
}
public static BindableProperty ViewModelProperty { get; set; } = BindableProperty.Create(nameof(ViewModel), typeof(BaseVm), typeof(BaseTestView));
}
<v:BaseTestView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:vm="clr-namespace:MyProject.ViewModels"
xmlns:v="clr-namespace:MyProject.Views"
x:Class="MyProject.Views.ChildTestView"
x:DataType="vm:ChildTestVm">
<v:BaseTestView.Content>
<StackLayout>
<Label Text="{Binding Foo}" />
</StackLayout>
</v:BaseTestView.Content>
</v:BaseTestView>
public partial class ChildTestView : BaseTestView
{
public ChildTestView() : base()
{
InitializeComponent();
}
}
public class ChildTestVm : BaseVm
{
public string Foo { get; set; }
public ChildTestVm()
{
Title = "Test";
Foo = "some stuff";
}
}
public class HomeVm : BaseVm
{
public ChildTestVm Tested { get; set; }
}
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:vm="clr-namespace:MyProject.ViewModels"
xmlns:v="clr-namespace:MyProject.Views"
x:Class="MyProject.Pages.HomePage"
x:DataType="HomeVm">
<ContentPage.Content>
<StackLayout>
<v:ChildTestView ViewModel="{Binding Tested}" />
<!-- ^ Error here /-->
</StackLayout>
</ContentPage.Content>
</ContentPage>
public partial class HomePage : ContentPage
{
}
Any idea of what this error means and how to fix it?
I tried some experiments, but failed to figure out why it gave that complaint - every variation I tried also gave that error.
Instead, do it this way:
First, set the BindingContext of ChildTestView:
<v:ChildTestView BindingContext="{Binding Tested}" />
That data-binds ChildTestView to the ChildTestVm from Tested.
If you also need access to the Vm for code behind, do it this way:
ChildTestView.xaml.cs:
private ChildTestVm ViewModel => (ChildTestVm)BindingContext;
Now in methods of ChildTestView, you can use ViewModel.Foo.
NOTE: If you dynamically change Tested:
If you have code anywhere that does Tested = ... AFTER HomePage is loaded and visible, then getting that to work requires Tested setter to do OnPropertyChanged(); (or other MVVM data binding mechanism). This is necessary to inform XAML of changes.
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>
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);
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>