Xaml Bindings - updating my backer variable based on the TargetNullValue - xaml

When Binding, I know it is possible to create a fallback value; If your xaml can't find the property you are binding to, it will 'fall back' to whatever you put here. You can also create a targetNullValue, just in case your binding returns null.
Many people seem to struggle with how you to go about setting up a fallback value - (see here or here), but these don't quite answer my question, which is:
If my binding evaluates to Null, is there a way to update it to the targetNullValue? - I don't just want it to display on the UI, I want it to change my backing variable itself from Null to the fallback value.
Once IntitializeComponent(); is called, I want myBackerString to return "overrideNull";
<ContentControl
Content="{Binding myBackerString,
TargetNullValue='overrideNull',
Mode=TwoWay}"
/>
public String myBackerString
{
{ get { return (String)GetValue(testBackerProperty); }
{ set { SetValue(testBackerProperty); }
}
public static readonly DependencyProperty testBackerProperty =
DependencyProperty.Register("testBacker", typeof(String), typeof(myClass), new PropertyMetadata(null));

Related

Specify signature when using x:Bind

UWP allows you to bind to a static method. I'm trying to get a time string by using
<TextBlock Text={x:Bind MyDateTime.ToString(MyPatternString)} />
where MyPatternString is "h:mm tt".
The problem is that DateTime's ToString() method has several different signatures. The first one receives an IFormatProvider. Because of this, I get a build error:
Function parameter 'provider' is invalid or missmatched
Is there any way to tell it that I wish to use the signature which accepts a string? I'd have thought it would automatically know this.
You can just add a method to your ViewModel and use that instead!
That way your binding expression can be changed to this:
<TextBlock Text={x:Bind FormatDateToString(MyDateTime)} />
Be advised that this works with the Windows 10 Anniversary Update only! More info on this matter here!
Found your question after searching for the answer myself; didn't find much help posted anywhere but did figure it out after some trial and error.
Function parameter 'provider' is invalid or mismatched
The reason for this that in XAML, a specific overload is being called which is DateTimeProperty.ToString(string, IFormatProvider).
In my case, any value I display is within a User Control so for each I added a CultureInfo dependency property and bound it to common source on my view model.
If C#, add:
using System.Globalization;
Then
public static readonly DependencyProperty CultureInfoProperty = DependencyProperty.Register(
"CultureInfo", typeof(CultureInfo), typeof(XyzReadoutView), new PropertyMetadata(default(CultureInfo)));
public CultureInfo CultureInfo
{
get { return (CultureInfo) GetValue(CultureInfoProperty); }
set { SetValue(CultureInfoProperty, value); }
}
This creates a local instance required for x:Bind, compile errors occur if using a static property.
And XAML:
<TextBlock Text={x:Bind MyDateTime.ToString('h:mm tt', CultureInfo)} />
Notice the format is surrounded with ' and not ".
Also, this will only update once as the mode for x:Bind has a default of Mode=OneTime; if you want changes on the DateTime or CultureInfo to propagate, the mode must be changed to Mode=OneWay.
<TextBlock Text={x:Bind MyDateTime.ToString('h:mm tt', CultureInfo), Mode=OneWay} />
If the format is user changeable, I would create a dependency property for it as well for updates and easy control binding back to a view model but that's just my personal preference.
public static readonly DependencyProperty DateTimeFormatProperty = DependencyProperty.Register(
"DateTimeFormat", typeof(string), typeof(XyzReadoutView), new PropertyMetadata(default(string)));
public string DateTimeFormat
{
get { return (string) GetValue(DateTimeFormatProperty); }
set { SetValue(DateTimeFormatProperty, value); }
}
And XAML:
<TextBlock Text={x:Bind MyDateTime.ToString(DateTimeFormat, CultureInfo), Mode=OneWay} />
You need to use an IValueConverter to format the text for display. Create a class which inherits from IValueConverter.
public class DateFormatter : IValueConverter
{
// This converts the DateTime object to the string to display.
public object Convert(object value, Type targetType,
object parameter, string language)
{
// Retrieve the format string and use it to format the value.
string formatString = parameter as string;
if (!string.IsNullOrEmpty(formatString))
{
return string.Format(
new CultureInfo(language), formatString, value);
}
// If the format string is null or empty, simply call ToString()
// on the value.
return value.ToString();
}
// No need to implement converting back on a one-way binding
public object ConvertBack(object value, Type targetType,
object parameter, string language)
{
throw new NotImplementedException();
}
}
Register it as resource on the Page and then you can specify the converter in the binding
<TextBlock Text={x:Bind MyDateTime, Converter={StaticResource DateFormatConverter}}, ConverterParameter="mm/dd/yyyy"}"/>
https://learn.microsoft.com/en-us/uwp/api/Windows.UI.Xaml.Data.IValueConverter

Ignore the Binding initialization

The inital problem is coming from a personal project about the polyline of the Xamarin.Forms.Map where the initialization is realized by a binding from the XAML part..
Let me be clear by an example :
I have an object CustomMap.cs which inherit from Xamarin.Forms.Map (This file is in the PCL part -> CustomControl/CustomMap.cs)
public class CustomMap : Map, INotifyPropertyChanged
{
public static readonly BindableProperty PolylineAddressPointsProperty =
BindableProperty.Create(nameof(PolylineAddressPoints), typeof(List<string>), typeof(CustomMap), null);
public List<string> PolylineAddressPoints
{
get { return (List<string>)GetValue(PolylineAddressPointsProperty); }
set
{
SetValue(PolylineAddressPointsProperty, value);
this.GeneratePolylineCoordinatesInner();
}
}
// ...
}
As you can see, I have a bindable property with an assessor and the XAML doesn't seem to use this assessor..
So the MainPge.xaml part of the page, where the control is called, looks like that:
<?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:control="clr-namespace:MapPolylineProject.CustomControl;assembly=MapPolylineProject"
x:Class="MapPolylineProject.Page.MainPage">
<ContentPage.Content>
<control:CustomMap x:Name="MapTest" PolylineAddressPoints="{Binding AddressPointList}"
VerticalOptions="Fill" HorizontalOptions="Fill"/>
</ContentPage.Content>
</ContentPage>
The MainPge.xaml.cs part:
public partial class MainPage : ContentPage
{
public List<string> AddressPointList { get; set; }
public MainPage()
{
base.BindingContext = this;
AddressPointList = new List<string>()
{
"72230 Ruaudin, France",
"72100 Le Mans, France",
"77500 Chelles, France"
};
InitializeComponent();
//MapTest.PolylineAddressPoints = AddressPointList;
}
}
So, everything is fine if I edit the PolylineAddressPoints from the object instance (if the commented part isnt' commented..), but if I init the value from the XAML (from the InitializeComponent();), it doesn't work, the SetValue, in the Set {}, of the CustomMap.PolylineAddressPoints, isn't called..
I then searched on the web about it and get something about the Dependency Properties? or something like that. So I tried some solutions but, from WPF, so some methods, such as DependencyProperty.Register();. So yeah, I can't find the way to solve my problem..
I also though about something, if DependencyProperty.Register(); would exists in Xamarin.Forms, then it means I would have to do it for each values? Because, if every value has to be set by a XAML binding logic, it would not work, I would have to register every value, doesn't it?
I'm sorry if I'm not clear, but I'm so lost about this problem.. Please, do not hesitate to ask for more details, thank in advance !
Finaly, the initial problem is that I'm trying to set a value of an object/control, from the XAML. Doing this by a Binding doesn't work, it seems like it ignored.. However, it does work if I do the following:
MapTest.PolylineAddressPoints = AddressPointList;
There are multiple questions in this:
Why is the property setter never called when using Xaml ?
Am I properly defining my BindableProperty ?
Why is my binding failing ?
Let me answer them in a different order.
Am I properly defining my BindableProperty ?
The BindableProperty declaration is right, but could be improved by using an IList<string>:
public static readonly BindableProperty PolylineAddressPointsProperty =
BindableProperty.Create(nameof(PolylineAddressPoints), typeof(IList<string>), typeof(CustomMap), null);
but the property accessor is wrong, and should only contains this:
public IList<string> PolylineAddressPoints
{
get { return (IList<string>)GetValue(PolylineAddressPointsProperty); }
set { SetValue(PolylineAddressPointsProperty, value); }
}
I'll tell you why while answering the next question. But you want to invoke a method when the property has changed. In order to do that, you have to reference a propertyChanged delegate to CreateBindableProperty, like this:
public static readonly BindableProperty PolylineAddressPointsProperty =
BindableProperty.Create(nameof(PolylineAddressPoints), typeof(IList<string>), typeof(CustomMap), null,
propertyChanged: OnPolyLineAddressPointsPropertyChanged);
And you have to declare that method too:
static void OnPolyLineAddressPointsPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
((CustomMap)bindable).OnPolyLineAddressPointsPropertyChanged((IList<string>)oldValue, (IList<string>)newValue);
}
void OnPolyLineAddressPointsPropertyChanged(IList<string> oldValue, IList<string> newValue)
{
GeneratePolylineCoordinatesInner();
}
Why is the property setter never called when using Xaml ?
The property, and the property accessors, are only meant to be invoked when accessing the property by code. C# code.
When setting a property with a BindablePrperty backing store from Xaml, the property accessors are bypassed and SetValue() is used directly.
When defining a Binding, both from code or from Xaml, property accessors are again bypassed and SetValue() is used when the property needs to be modified. And when SetValue() is invoked, the propertyChanged delegate is executed after the property has changed (to be complete here, propertyChanging is invoked before the property change).
You might wonder why bother defining the property if the bindable property is only used by xaml, or used in the context of Binding. Well, I said the property accessors weren't invoked, but they are used in the context of Xaml and XamlC:
a [TypeConverter] attribute can be defined on the property, and will be used
with XamlC on, the property signature can be used to infer, at compile time, the Type of the BindableProperty.
So it's a good habit to always declare property accessors for public BindableProperties. ALWAYS.
Why is my binding failing ?
As you're using CustomMap as both View and ViewModel (I won't tell the Mvvm Police), doing this in your constructor should be enough:
BindingContext = this; //no need to prefix it with base.
As you're doing it already, your Binding should work once you've modified the BindableProperty declaration in the way I explained earlier.

Xamarin.Forms. Custom Property Binding issue

I created custom controls in Xamarin. One of it is ValidatedEntryCell:
public class ValidatedEntryCell: EntryCell
{
public static readonly BindableProperty ShowBorderProperty = BindableProperty.Create(nameof(ShowBorder), typeof(bool), typeof(ValidatedEntryCell), false);
public bool ShowBorder
{
get { return (bool)GetValue(ShowBorderProperty); }
set
{
SetValue(ShowBorderProperty, value);
OnPropertyChanged();
}
}
}
I want to change that ShowBorder property in case if Text is empty. I tried several cases how to invoke that property:
Invoking that property straight forward in xaml. Smth like: renderers:ValidatedPicker x:Name="Gender" Title="Gender" ShowBorder="True"
That case works fine.
<renderers:ValidatedEntryCell ShowBorder="{Binding FirstNameIsNotValid}" x:Name="FirstName"... (I setup FirstNameIsNotValid = true in codebehind)
I used converters <renderers:ValidatedEntryCell x:Name="LastName" ShowBorder="{Binding Source={x:Reference LastName}, Converter={StaticResource EmptyTextToBoolConverter}, Path=Text.Length}" that always return true
Tried to use style triggers for property.
Unfortunately anything helps here. Will be glad for any advice
Edit: partially helps that link. For now I can setup property in constructor in codebehind and changes made will be visible.

Setting the initial selected item when binding to a ListView's SelectedItem property

I have a Xamarin.Forms xaml page in which I am using a ListView to allow the user to pick a single item out of a list. I have bound the ListView's SelectedItem property to a property on my ViewModel and this works fine. As soon as the user changes the selected item the property in my viewmodel updates as well.
However, even though I initially set the property in my ViewModel to one of the values from the list, when the page loads the ListView's SelectedItem property is null, which in turn sets the ViewModel property to null as well.
What I need is the other direction, I want the ListView to initially select the item that i've set in the VM property.
I can hack together a solution by writing extra code in the code behind file to explicitly set the initial selected item, but this introduces additional properties and complexity and is quite ugly.
What is the correct way to set the initial selected item of a ListView who's selected item is bound to a viewmodel property?
-EDIT-
I was asked to provide the code that I'm using for my binding.
It's very simple, standard:
<ListView x:Name="myList" ItemsSource="{Binding Documents}" SelectedItem="{Binding SelectedDocument}">
the view model that is set as the binding context for the listview is instantiated before the page is created and looks like this:
public class DocumentSelectViewModel : ViewModelBase
{
private Document selectedDocument;
public List<Document> Documents
{
get { return CachedData.DocumentList; }
}
public Document SelectedDocument
{
get { return selectedDocument; }
set { SetProperty(ref selectedDocument, value);
}
public DocumentSelectViewModel()
{
SelectedDocuement = CachedData.DocumentList.FirstOrDefault();
}
}
SetProperty is a function which simply rasies the INotifyPropertyChanged event if the new value is different from the old one, classical binding code.
I am a little rusty on XAML but don't you need to make the binding two-way?
E.G.
{ Binding SelectedDocument, Mode=TwoWay }
As long as the SelectedDocument property change raises the INotifyPropertyChanged event then you should get the desired effect.
If you replace
public DocumentSelectViewModel()
{
SelectedDocument = CachedData.DocumentList.FirstOrDefault();
}
By
public DocumentSelectViewModel()
{
SelectedDocument = Documents.FirstOrDefault();
}
Does it work for you ?
I had a similar problem that has been resolved this way...
You can use ctor DocumentSelectViewModel for set initial value. Honestly I dont like to make some job in ctor block but Xamarin.... You dont need DocumentSelectViewModel method. It will work.
public DocumentSelectViewModel ()
{
SelectedDocument = Documents[0]; //or any your desired.
}

Command parameter in EventTrigger

I'm working on WinRT with MvmmCross v3 framework and Windows.UI.Interactivity.
I want a TextBox with an EventTrigger on the event TextChanged which launch a Command. And also, I want in CommandParameter the text of the textBox.
So I have this code
<i:EventTrigger EventName="TextChanged">
<i:InvokeCommandAction Command="{Binding UpdateText}" CommandParameter="{Binding Text}"/>
</i:EventTrigger>
public ICommand UpdateText
{
get
{
return new MvxCommand<object>((textSearch) =>
{
// code...
});
}
}
But my textSearch parameter equals to "{Windows.UI.Xaml.Controls.TextChangedEventArgs}" with all of these properties NULL.
I Tried also to declare explicitly my ElementName in the binding like
CommandParameter="{Binding Path=Text, ElementName=tes}
But it failed too.
Thanks
Do you really need to handle TextChanged event? You could be notified of the changes by just binding to the Text property:
<TextBox Text="{Binding TextValue, Mode=TwoWay}" />
And then in the view model:
private string _textValue;
public string TextValue
{
get
{
return _textValue;
}
set
{
if (_textValue == value)
return;
_textValue = value;
OnTextChanged(value); // react to the changed value
}
}
EDIT:
There are two things you need to be aware of, if you want to get to the Text value from inside your Command:
First, you need to fix the CommandParameter binding. By using {Binding Text} you are actually trying to bind to a property in your view model, i.e. you would first need to bind the TextBox.Text property to the same view model property. As you've said in the comment, that's no good for you because you need the info on every change and not only on lost focus, so the value you get is not up to date enough.
The right approach would therefore be your second attempt, i.e. binding directly to the TextBox using the ElementName syntax. Unfortunately triggers are not a part of the visual tree therefore they don't get access to the XAML name scope (you can read more about it in this blog post). You can work around that by using NameScopeBinding from MVVMHelpers.Metro package:
<Behaviors:NameScopeBinding x:Key="MyTextBox"
Source="{Binding ElementName=MyTextBox}" />
Now you can make the ElementName binding work:
<i:InvokeCommandAction Command="{Binding UpdateText}"
CommandParameter="{Binding Source.Text, Source={StaticResource MyTextBox}}"/>
You still have the second problem. The Text value that you are binding to only updates on lost focus so you don't get the right value when handling TextChanged event. The solution is to bind to the TextBox itself:
<i:InvokeCommandAction Command="{Binding UpdateText}"
CommandParameter="{Binding Source, Source={StaticResource MyTextBox}}"/>
And then inside the command get the Text property directly from the TextBox:
private void OnUpdateText(object parameter)
{
var value = ((TextBox) parameter).Text;
// process the Text value as you need to.
}
EDIT 2:
To make the above code work with the view model being in a PCL, there a couple of approaches you could take.
The simplest hack would be to use reflection. Since it is available in PCL you could get to the Text property and read its value:
private void OnUpdateText(object parameter)
{
var textProperty = textSearch.GetType().GetProperty("Text");
var value = textProperty.GetValue(parameter, null) as string;
// process the Text value as you need to.
}
A cleaner solution would be to put the WinRT specific code into a separate assembly abstracted via an interface. You would first create an interface in the PCL library:
public interface IUiService
{
string GetTextBoxText(object textBox);
}
And modify view model to accept this interface in the constructor:
public ViewModel(IUiService uiService)
{
_uiService = uiService;
}
In the command handler you would than use the method in the interface:
private void OnUpdateText(object parameter)
{
var value = _uiService.GetTextBoxText(parameter);
// process the Text value as you need to.
}
You would implement that interface in a WinRT library:
public class UiService : IUiService
{
public string GetTextBoxText(object textBox)
{
var typedTextBox = textBox as TextBox;
return typedTextBox.text;
}
}
In the application you reference this library and pass the implementation to view model:
var viewModel = new ViewModel(new UiService);
My favorite approach is different: I'd create a Behavior exposing a Text property automatically updated every time TextChanged event is triggered:
public class TextChangedBehavior : Behavior<TextBox>
{
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
"Text",
typeof(string),
typeof(TextChangedBehavior),
new PropertyMetadata(null));
public string Text
{
get { return (string) GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.TextChanged += OnTextChanged;
Text = AssociatedObject.Text;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.TextChanged -= OnTextChanged;
}
private void OnTextChanged(object sender, TextChangedEventArgs textChangedEventArgs)
{
Text = AssociatedObject.Text;
}
}
Now I could bind a TextValue property to this behavior and react to its change in the property setter as already suggested at the very beginning of this long answer:
<TextBox>
<i:Interaction.Behaviors>
<b:TextChangedBehavior Text="{Binding TextValue, Mode=TwoWay}" />
</i:Interaction.Behaviors>
</TextBox>