Binding a custom Entry in Xamarin Forms - c#

I'm trying to extract this Entry in a Frame into a custom element in Xamarin, to get a reusable Entry with border only on top and on bottom:
<Frame xmlns="..."
HasShadow="False"
CornerRadius="0"
Padding="0, 1, 0, 1"
BackgroundColor="#c0c0c0">
<Entry Padding="20, 10, 20, 10"
Placeholder="{Binding Placeholder}"
Text="{Binding Text}"
BackgroundColor="#ffffff" />
</Frame>
Code Behind:
public partial class CbSingleEntry : Frame
{
public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(CbSingleEntry));
public static readonly BindableProperty PlaceholderProperty = BindableProperty.Create("Placeholder", typeof(string), typeof(CbSingleEntry));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public string Placeholder
{
get { return (string)GetValue(PlaceholderProperty); }
set { SetValue(PlaceholderProperty, value); }
}
public CbSingleEntry()
{
InitializeComponent();
BindingContext = this;
}
}
When I try to use this custom field, the Placeholder and Text property are correctly set, but I can't bind them to attributes in my class:
// this one works fine
<local:CbSingleEntry Placeholder="Company" Text="My Company" />
// Placeholder works, but Text is always empty
<local:CbSingleEntry Placeholder="Company" Text="{Binding Company}" />
I can confirm that Company has a value, because with a normal text field it works correctly:
// This one works as expected, Text is displayed from binded attribute
<Entry Placeholder="Company" Text="{Binding Company}" />

Cause : in your case , you set the BindingContext in CbSingleEntry
BindingContext = this;
So the binding in ContentPage will not work any more .
Solution:
You could modify the code in CbSingleEntry
in xaml
<Frame 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="CustomView" // set the name here
x:Class="xxx">
<Entry
Placeholder="{Binding Source={x:Reference CustomView},Path=Placeholder}"
Text="{Binding Source={x:Reference CustomView},Path=Text}"
BackgroundColor="#ffffff" />
</Frame>
in code behind
public partial class CbSingleEntry : Frame
{
public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(CbSingleEntry));
public static readonly BindableProperty PlaceholderProperty = BindableProperty.Create("Placeholder", typeof(string), typeof(CbSingleEntry));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public string Placeholder
{
get { return (string)GetValue(PlaceholderProperty); }
set { SetValue(PlaceholderProperty, value); }
}
public CbSingleEntry()
{
InitializeComponent();
// BindingContext = this; don't need to set it any more
}
}

Related

Custom control bindable Property not getting hit when set from a view

I've created a control and it has a bindable property, but when I try to set its value, it does not set, when I check its setter, it's not getting hit while debugging, not sure what am I doing wrong.
public decimal MetricValue
{
get => (decimal)GetValue(MetricValueProperty);
set => SetValue(MetricValueProperty, value);
}
public static readonly BindableProperty MetricValueProperty =
BindableProperty.Create(
propertyName: nameof(MetricValue),
returnType: typeof(decimal),
declaringType: typeof(HeightSelection),
defaultBindingMode: BindingMode.TwoWay,
propertyChanged: MetricValuePropertyChanged);
I also have a propertychanged, which is not getting raised
<controls:customControl
CurrentSystemOfMeasure="{Binding CurrentSystemOfMeasure}"
MetricValue="{Binding CurrentHeight}"
TextAlignment="Start"
OnHeightSelectedCommand="{Binding HeightSelectionCommand}"
IsValid="True" />
any inputs would be helpful
have you set context in the xaml view?
Here my way:
public static readonly BindableProperty CancelButtonVisibleAvailableProperty = BindableProperty.Create(
propertyName: "CancelButtonVisible",
returnType: typeof(bool),
declaringType: typeof(SelphiDetailPhotosView),
defaultValue: true);
public bool CancelButtonVisible
{
get { return (bool)GetValue(CancelButtonVisibleAvailableProperty); }
set { SetValue(CancelButtonVisibleAvailableProperty, value); }
}
ContentView needs to have a x:name to set the context in the property IsVisible in your case you need to set the context in Text Property of label to show decimal value
I have set the context making reference to my custom view selphiDetailPhotosView
<Button IsVisible="{Binding Source={x:Reference selphiDetailPhotosView}, Path=CancelButtonVisible}"
Margin="27,0,27,18"
BackgroundColor="{StaticResource PlatinumColor}"
Command="{Binding CancelCommand}"
Text="Cancelar"
TextColor="White"
TextTransform="None" />
<ContentView
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="confia.Views.Views.OnBoardingAndLivenessSharedViews.SelphiDetailPhotosView"
xmlns:svg="clr-namespace:FFImageLoading.Svg.Forms;assembly=FFImageLoading.Svg.Forms"
xmlns:transformation="clr-namespace:FFImageLoading.Transformations;assembly=FFImageLoading.Transformations"
xmlns:views="clr-namespace:confia.Views.Views"
xmlns:converters="clr-namespace:confia.Views.Converters"
x:Name="selphiDetailPhotosView"
>
Finally call your customcontrol in your ContentPage and bind with your ViewModel
<customControl:SelphiDetailPhotosView CancelButtonVisible="{Binding CanCancel}"/>
I have replicated your scenario and this work for me.
CodeBehind Custom Control
public partial class CustomControl : ContentView
{
public CustomControl()
{
InitializeComponent();
}
public static readonly BindableProperty MetricValueProperty = BindableProperty.Create(
propertyName: nameof(MetricValue),
returnType: typeof(decimal),
declaringType: typeof(CustomControl),
defaultBindingMode: BindingMode.TwoWay,
defaultValue: 0m);
public decimal MetricValue
{
get { return (decimal)this.GetValue(MetricValueProperty); }
set { this.SetValue(MetricValueProperty, value); }
}
}
CustomControl Xaml
<ContentView
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ferreplace.Views.Controls.CustomControl"
x:Name="this"
>
<ContentView.Content>
<StackLayout HorizontalOptions="CenterAndExpand" >
<Label Text="Metric Value" FontSize="40"/>
<Label FontSize="20" Text="{Binding Source={x:Reference this}, Path=MetricValue}" ></Label>
</StackLayout>
</ContentView.Content>
ContenPage to call Custom control
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:ferreplace.Views.Controls"
x:Class="ferreplace.MainPage">
<StackLayout>
<controls:CustomControl MetricValue="{Binding MetricValueBinding}"/>
</StackLayout>
</ContentPage>
ViewModel
public class MainPageViewModel: BindableBase, IInitializeAsync
{
private decimal _metricValueBinding;
public decimal MetricValueBinding
{
get
{
return _metricValueBinding;
}
set
{
SetProperty(ref _metricValueBinding, value);
}
}
public MainPageViewModel()
{
}
public async Task InitializeAsync(INavigationParameters parameters)
{
MetricValueBinding = 30m;
}
}
Decimal Binding Result
Maybe you forgot default value in bindable property creation

OnPropertyChanged not being triggered when BindableProperty is set

I am having trouble with setting the BindableProperty IconName in a custom ContentView in Xamarin.Forms. It is never set and stays empty. The OnPropertyChanged method is executed for every property except the IconName. When I change the type from enum to a string, it works as expected. What am I missing?
The OnPropertyChanged method is needed otherwise non of the properties are set and remain their default value..
<?xml version="1.0" encoding="UTF-8" ?>
<ContentView
x:Class="Peripass.Mobile.Framework.UIControls.PeripassIcon"
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">
<ContentView.Resources>
</ContentView.Resources>
<ContentView.Content>
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<Image>
<Image.Source>
<FontImageSource x:Name="_icon" Glyph="" Color="Black" Size="20" FontFamily="{OnPlatform Android=PeripassIcon.ttf#}" />
</Image.Source>
</Image>
</StackLayout>
</ContentView.Content>
</ContentView>
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Peripass.Mobile.Framework.UIControls
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class PeripassIcon : ContentView
{
public static readonly BindableProperty IconSizeProperty = BindableProperty.Create(nameof(IconSize), typeof(double), typeof(PeripassIcon), 26.0);
public static readonly BindableProperty IconColorProperty = BindableProperty.Create(nameof(IconColor), typeof(Color), typeof(PeripassIcon), Color.White);
public static readonly BindableProperty IconNameProperty = BindableProperty.Create(nameof(IconName), typeof(IconType), typeof(PeripassIcon), IconType.NuclearExplosion);
public PeripassIcon()
{
InitializeComponent();
}
public IconType IconName
{
get => (IconType)GetValue(IconNameProperty);
set => SetValue(IconNameProperty, value);
}
public double IconSize
{
get => (double)GetValue(IconSizeProperty);
set => SetValue(IconSizeProperty, value);
}
public Color IconColor
{
get => (Color)GetValue(IconColorProperty);
set => SetValue(IconColorProperty, value);
}
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == IconNameProperty.PropertyName) {
_icon.Glyph = $"&#x{(int)IconName:X4};";
}
if (propertyName == IconColorProperty.PropertyName) {
_icon.Color = IconColor;
}
if (propertyName == IconSizeProperty.PropertyName) {
_icon.Size = IconSize;
}
}
}
}
<StackLayout Grid.Row="1" Grid.Column="0">
<uiControls:PeripassIcon IconName="NuclearExplosion" IconColor="#EE4022" IconSize="85.5"/>
</StackLayout>
The problem you are experiencing has to do with the fact that OnPropertyChanged is only triggered when a property's value changes (it is not enough for it to be set!).
In your code you set the default value of IconName to IconType.NuclearExplosion. Later on, when you call the control you set
<uiControls:PeripassIcon IconName="NuclearExplosion" IconColor="#EE4022" IconSize="85.5"/>
which sets IconName to the default value, again... so its value does not really changes, and thus OnPropertyChanged is not triggered.
WARNING
That being said, i have to mention that the "normaly" you would use bindings between BindableProperties and the counterparts in the view. For instance, you would bind the value of Color property in FontImageSource in your ContentView to BindableProperty IconColor. For details see the documentation on ContentView.

Xamarin UWP seems to bind to the wrong view model

I have a Xamarin project for Android and UWP. This issue seems to only happen on UWP.
In my Xamarin project I have ContentPage with a view model bound as context. In this ViewModel there's an ObservableCollection with another kind of view model. When I create a new instance of this underlying ViewModel and add to my ObservableCollection, sometimes the ContentPage works as expected, showing an item in my ListView. But sometimes there's an empty element added, that I can see when hovering over the list. When this happens I get a bunch of warnings in the Output tab.
My DownloadsPage:
<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:local="clr-namespace:Downloader.Converters"
mc:Ignorable="d"
x:Class="Downloader.Views.DownloadsPage">
<ContentPage.Resources>
<ResourceDictionary>
<local:DownloadStatusToColorConverter x:Key="downloadStatusToColor" />
</ResourceDictionary>
</ContentPage.Resources>
<RefreshView IsRefreshing="{Binding IsBusy, Mode=TwoWay}" Command="{Binding LoadItemsCommand}">
<ListView x:Name="DownloadsListView" SelectionMode="None" ItemsSource="{Binding Downloads}" RowHeight="70">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Padding="10" BackgroundColor="{Binding DownloadStatus, Converter={StaticResource downloadStatusToColor}}">
<Label Text="{Binding Name}"
d:Text="{Binding .}"
LineBreakMode="NoWrap"
Style="{DynamicResource ListItemTextStyle}"
FontSize="16" />
<Grid Grid.Row="0" Grid.Column="0" Padding="10,0,10,0">
<ProgressBar BackgroundColor="Transparent" Progress="{Binding PercentDownloaded}" HorizontalOptions="FillAndExpand" HeightRequest="20">
</ProgressBar>
<Label Text="{Binding PercentString}" HorizontalTextAlignment="Center"></Label>
</Grid>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</RefreshView>
</ContentPage>
DownloadsViewModel is set as context in the code-behind like this:
public partial class DownloadsPage : ContentPage
{
private readonly DownloadsViewModel _viewModel;
public DownloadsPage()
{
InitializeComponent();
BindingContext = _viewModel = new DownloadsViewModel();
Device.StartTimer(TimeSpan.FromSeconds(1), () =>
{
Device.BeginInvokeOnMainThread(() => _viewModel.RefreshDownloads());
return true;
});
}
}
The bound DownloadsViewModel:
public class DownloadsViewModel : BaseViewModel
{
public ObservableCollection<DownloadViewModel> Downloads { get; set; } = new ObservableCollection<DownloadViewModel>();
public Command LoadItemsCommand { get; set; }
public DownloadsViewModel()
{
Title = "Downloads";
LoadItemsCommand = new Command(() => {
IsBusy = true;
Downloads.Clear();
RefreshDownloads();
IsBusy = false;
});
}
public void RefreshDownloads()
{
foreach (var download in DownloadManager.GetDownloads())
{
var existingDownload = Downloads.FirstOrDefault(d => d.Id == download.Id);
if (existingDownload != null)
{
existingDownload.UpdateValues(download);
}
else
{
Downloads.Add(new DownloadViewModel(download));
}
}
}
}
And the ObservableCollection contains DownloadViewModel that looks like this:
public class DownloadViewModel : BaseViewModel
{
private IDownload _download;
public DownloadViewModel(IDownload download)
{
UpdateValues(download);
}
private string _id;
public string Id
{
get { return _id; }
set { SetProperty(ref _id, value); }
}
private string _name;
public string Name
{
get { return _name; }
set { SetProperty(ref _name, value); }
}
private DownloadStatus _status;
public DownloadStatus DownloadStatus
{
get { return _status; }
set { SetProperty(ref _status, value); }
}
public double PercentDownloaded
{
get
{
return _download.DownloadedBytes == -1
? 0f
: (double)_download.DownloadedBytes / _download.TotalBytes;
}
}
public string PercentString { get => $"{(int)(PercentDownloaded * 100)} %"; }
public void UpdateValues(IDownload download)
{
_download = download;
Id = _download.Id;
Name = _download.Name;
DownloadStatus = _download.Status;
}
}
The error I sometimes get which causes items in my ListView to be empty:
Binding: 'DownloadStatus' property not found on 'Downloader.ViewModels.DownloadsViewModel', target property: 'Xamarin.Forms.StackLayout.BackgroundColor'
Binding: 'Name' property not found on 'Downloader.ViewModels.DownloadsViewModel', target property: 'Xamarin.Forms.Label.Text'
Binding: 'PercentDownloaded' property not found on 'Downloader.ViewModels.DownloadsViewModel', target property: 'Xamarin.Forms.ProgressBar.Progress'
Binding: 'PercentString' property not found on 'Downloader.ViewModels.DownloadsViewModel', target property: 'Xamarin.Forms.Label.Text'
When debugging I've confirmed that the item is added to my ObservableCollection as expcted.
How come sometimes it's looking for DownloadStatus, Name, PercentDownloaded and PercentString on DownloadsViewModel instead of DownloadViewModel?
Xamarin UWP seems to bind to the wrong view model
I checked your code sample and it works as expect. But I found the progress value does not update automatically that cause the listview item can't display, I have update the IDownload interface add PercentDownloaded property. For the testing it could works in uwp platform.
The problem was that the ViewModels did not have setters with INotifyPropertyChanged implemented for all properties. The source code is available on Github, and the commit that fixes the issue is this one.

Multiple ContentPresenters in one ControlTemplate

Is it possible to have multiple ContentPresenters in a ControlTemplate?
I created a CustomControl with two BindableProperties of type View: ReadonlyContent and WritableContent.
The ControlTemplate Wraps two ContentPresenters where the Content is bound to either ReadonlyContent or WritableContent.
Misteriously it only shows the content of one ContentPresenter in that case always ReadonlyContent uneffected by order of ContentPresenters or whatever.
So the question again: Is it possible to have two or more ContentPresenters in a ControlTemplate?
The ControlTemplate looks like this:
<ContentView.ControlTemplate>
<ControlTemplate>
<Grid HorizontalOptions="Fill" VerticalOptions="Fill" RowSpacing="0" Margin="0" Padding="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="40" />
</Grid.ColumnDefinitions>
<Button
Grid.Column="2"
ImageSource="{TemplateBinding IsReadonly, Converter={StaticResource BooleanToImageSourceConverter}}"
BackgroundColor="Transparent"
WidthRequest="40"
HeightRequest="25"
Padding="0"
Clicked="OnToggleIsReadonly"
x:Name="btnToggleEditMode"
Margin="0" />
<StackLayout Grid.Column="1" Orientation="Vertical">
<ContentPresenter Content="{TemplateBinding ReadonlyContent, Mode=OneWay}" />
<ContentPresenter Content="{TemplateBinding WriteableContent, Mode=OneWay}" />
</StackLayout>
</Grid>
</ControlTemplate>
</ContentView.ControlTemplate>
while the code behind of the control looks like this:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ActivatableContent : ContentView
{
public static readonly BindableProperty IsReadonlyProperty = BindableProperty.Create(
"IsReadonly",
typeof(bool),
typeof(ActivatableContent),
true,
BindingMode.TwoWay,
propertyChanged: OnIsReadonlyChanged);
public static readonly BindableProperty ReadonlyContentProperty = BindableProperty.Create(nameof(ReadonlyContent), typeof(View), typeof(ActivatableContent), propertyChanged: OnReadonlyContentChanged);
public static readonly BindableProperty WritableContentProperty = BindableProperty.Create(nameof(WritableContent), typeof(View), typeof(ActivatableContent), propertyChanged: OnWritableContentChanged);
public bool IsReadonly
{
get { return (bool)GetValue(IsReadonlyProperty); }
set
{
SetValue(IsReadonlyProperty, value);
}
}
public View ReadonlyContent
{
get { return (View)GetValue(ReadonlyContentProperty); }
set { SetValue(ReadonlyContentProperty, value); }
}
public View WritableContent
{
get { return (View)GetValue(WritableContentProperty); }
set { SetValue(WritableContentProperty, value); }
}
public ActivatableContent()
{
InitializeComponent();
}
private static void OnIsReadonlyChanged(BindableObject bindable, object oldvalue, object newvalue)
{
((ActivatableContent)bindable).IsReadonly = (bool)newvalue;
}
private static void OnReadonlyContentChanged(BindableObject bindable, object oldvalue, object newvalue)
{
var readonlyContent = (View)newvalue;
((ActivatableContent)bindable).ReadonlyContent = readonlyContent;
SetInheritedBindingContext(readonlyContent, bindable.BindingContext);
}
private static void OnWritableContentChanged(BindableObject bindable, object oldvalue, object newvalue)
{
var writableContent = (View)newvalue;
((ActivatableContent)bindable).WritableContent = writableContent;
SetInheritedBindingContext(writableContent, bindable.BindingContext);
}
private void OnToggleIsReadonly(object sender, EventArgs e)
{
IsReadonly = !IsReadonly;
}
/// <summary>Method that is called when the binding context changes.</summary>
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
var controlTemplate = ControlTemplate;
if (ReadonlyContent != null && controlTemplate != null)
{
SetInheritedBindingContext(ReadonlyContent, BindingContext);
}
if (WritableContent != null && controlTemplate != null)
{
SetInheritedBindingContext(WritableContent, BindingContext);
}
}
}
ContentPresenter always points to the control's content by default you can't define two different content.
However, we could do this in custom control. You could download folder of ContentPresenterDemo from GitHub for reference.
https://github.com/WendyZang/Test.git
First, define two different bindable properties in your custom control
public static readonly BindableProperty ReadonlyContentProperty = BindableProperty.Create(nameof(ReadonlyContent), typeof(View), typeof(CustomContentView));
public View ReadonlyContent
{
get { return (View)GetValue(ReadonlyContentProperty); }
set { SetValue(ReadonlyContentProperty, value); }
}
public static readonly BindableProperty WritableContentProperty = BindableProperty.Create(nameof(WritableContent), typeof(View), typeof(CustomContentView));
public View WritableContent
{
get { return (View)GetValue(WritableContentProperty); }
set { SetValue(WritableContentProperty, value); }
}
Please note, do not forget to change ContentPage to ContentView in xaml.
And then define two views with template in Application.Resources.
<Application.Resources>
<ControlTemplate x:Key="MyTemplate">
<StackLayout>
<ContentView Content="{TemplateBinding WritableContent}"/>
<ContentView Content="{TemplateBinding ReadonlyContent}"/>
</StackLayout>
</ControlTemplate>
<ContentView x:Key="MyContentView">
<StackLayout>
<Label Text="MyContentView" BackgroundColor="Red"></Label>
<!--code here...-->
</StackLayout>
</ContentView>
<ContentView x:Key="MyContentView2">
<StackLayout>
<Label Text="MyContentView2" BackgroundColor="Green"></Label>
<!--code here...-->
</StackLayout>
</ContentView>
And then use it in page.
<StackLayout>
<local:CustomContentView ReadonlyContent="{StaticResource MyContentView}"
WritableContent="{StaticResource MyContentView2}"
ControlTemplate="{StaticResource MyTemplate}" />
</StackLayout>
Or you could use Picker to do Multiple ContentPresenters.
Define a Picker with multiple ContentPresenters.
<Picker x:Name="picker" Title="Select a template" SelectedIndexChanged="SelectedIndexChanged">
<Picker.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>Template 1</x:String>
<x:String>Template 2</x:String>
<x:String>Template 3</x:String>
<x:String>Template 4</x:String>
</x:Array>
</Picker.ItemsSource>
</Picker>
You could download from the GitHub.
https://github.com/CrossGeeks/ControlTemplateSample

The databinding of a usercontrol doesn't update the source model

I'm trying to implement a custom textbox which has a placeholder text. The content of the 'FirstName' property of my model appears in the textbox, as intended. The problem I'm having is when I change the text of the textbox, it isn't updated back in the source model. Why is that?
I've tried setting the binding mode to "TwoWay", but it doesn't change anything. Is there something I'm doing wrong?
Edit: Silly me! As it turns out, I had to put Mode="TwoWay" on both bindings, not just the usercontrol's. I'll mark as answered as soon as possible.
Model.cs
public class Student
{
public string FirstName { get; set; }
}
MainWindow.xaml
<grid>
<ui:prettyTextbox Text="{Binding FirstName}" PlaceholderText="#Enter your name">
</grid>
PrettyTextbox.xaml
<UserControl x:Name="prettyTextbox">
<Grid>
<TextBlock Text="{Binding Path=PlaceholderText, ElementName=prettyTextbox}"
Visibility="{Binding Path=Text, ElementName=prettyTextbox, Converter={StaticResource StringLengthToVisibilityConverter}}"/>
<TextBox Text="{Binding Path=Text, ElementName=prettyTextbox, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</UserControl>
PrettyTextbox.xaml.cs
public partial class PrettyTextbox : INotifyPropertyChanged
{
public static readonly DependencyProperty PlaceholderTextProperty =
DependencyProperty.Register("PlaceholderText", typeof (string),
typeof(PrettyTextbox), new FrameworkPropertyMetadata(default(string)));
public string PlaceholderText
{
get { return (string)GetValue(PlaceholderTextProperty); }
set
{
SetValue(PlaceholderTextProperty, value);
OnPropertyChanged();
}
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string),
typeof(PrettyTextbox), new FrameworkPropertyMetadata(default(string)));
public string Text
{
get { return (string)GetValue(TextProperty); }
set
{
SetValue(TextProperty, value);
OnPropertyChanged();
}
}
public PrettyTextbox()
{
InitializeComponent();
}
}
}
You forgot to make the text property bind two way by default, so you need to change this part:
<ui:prettyTextbox Text="{Binding FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
or change FrameworkPropertyMetadata of the text property to:
new FrameworkPropertyMetadata
{
DefaultValue = null,
BindsTwoWayByDefault = true
}

Categories