OnPropertyChanged is being called twice for a bindable property - xaml

In Xamarin Forms, I created a bindable property like so:
public static readonly BindableProperty SelectedItemProperty = BindableProperty.Create("SelectedItem", typeof(MyItem), typeof(MyGrid), default(MyItem));
public MyItem SelectedItem
{
get { return (MyItem)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
Here's my constructor:
public MyView()
{
InitializeComponent();
PropertyChanged += OnPropertyChanged;
}
protected void OnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
if (propertyChangedEventArgs.PropertyName == "SelectedItem")
{
// called twice
}
}
Can somebody explain why property changed event is firing twice? If I create a changed handler in the definition of the bindable property, then the handler is called once.
public static readonly BindableProperty SelectedItemProperty = BindableProperty.Create("SelectedItem", typeof(MyItem), typeof(MyGrid), default(MyItem), null, SelectedItemChanged);
I have noticed the issue exists only in code-behind. If I set the property directly in XAML, property changed event fires once.

We don't really have enough information to answer this question with certainty, but I can speculate.
Without seeing your SetValue method, my assumption would be that it lacks a short circuit, e.g. "don't do anything if the new value and the old value are the same".
Then my second assumption would be that the control that is being bound to this property is setting it (after being bound). This can happen with list-type controls when SelectedItem is bound.
The resulting chain of events might be something like:
Code sets property
PropertyChanged event is fired
Binding sets the value on a control
Control reports it's value has been changed, and two-way binding sets the value on the ViewModel again
The lack of a short circuit causes the PropertyChanged event to be raised again
The binding sets the value on the control again (to the same value as before)
The control does not report a change, because it's property is short-circuited properly
My guess is that if you were to short circuit your setter (by checking against the existing value and bailing out if they are the same) this behavior would stop.

Related

How to receive property change events in an AbstractInformationControl

I am working on an editor plugin and now implementing support for code hovers.
I have extended AbstractInformationControl (and implemented IInformationControlExtension2) to create a new control for showing the hover info.
It works almost fine, but I am unable to receive property change events in my information control. What I try is like this, but the event handler does not fire:
public class MyHoverInfoControl extends AbstractInformationControl implements IInformationControlExtension2 {
public MyHoverInfoControl(Shell parentShell, String string) {
super(parentShell, string);
create();
Activator.getDefault().getPreferenceStore().addPropertyChangeListener(new IPropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent event) {
// TODO Auto-generated method stub
}
});
}
The control itself contains a StyledText. I have also tried to add a KeyListener to this StyledText to see if key events are received, and it only seemed to work if I click into the control (it is not enough to hover over a text to show the control). But property change event does not seem to be received even after the click.
Is it possible to receive property change events for an AbstractInformationControl subclass?

Inherit UserControl and hooking up to basic property events

I'm making a custom TextBox for UWP to simplify Win2D outlined text solution, for that I created a UserControl that contains only a canvas on which I'll draw the text.
Of course I need some properties, like text, outline thickness and color, etc...
I also need some properties that are already exposed by the inherited UserControl like Foreground, FontSize, FontFamily...
So far so good, it seems like I won't need to implement each one of those common properties.
The problem is that I can't find a way to hook up an event when one of those properties changes, as I have to call the Canvas.Invalidate() method to redraw it when the format changes.
Looks like I have to hide all those properties and create new Dependency Properties to call Canvas.Invalidate().
There is no way to do it faster?
Nevermind, the answer was behind the corner.
In the constructor, you can call
RegisterPropertyChangedCallback(DependencyProperty dp, DependencyPropertyChangedCallback callback);
For example:
public OutlinedText()
{
InitializeComponent();
RegisterPropertyChangedCallback(FontFamilyProperty, OnPropertyChanged);
RegisterPropertyChangedCallback(FontSizeProperty, OnPropertyChanged);
}
private void OnPropertyChanged(DependencyObject sender, DependencyProperty dp)
{
OutlinedText instance = sender as OutlinedText;
if (instance != null)
{
//Caching the value into CanvasTextFormat for faster drawn execution
if (dp == FontFamilyProperty)
instance.TextFormat.FontFamily = instance.FontFamily.Source;
else if (dp == FontSizeProperty)
instance.TextFormat.FontSize = (Single)instance.FontSize;
instance.needsResourceRecreation = true;
instance.canvas.Invalidate();
}
}

Xaml Behavior DP not updated

I have a UWP application which uses the Managed UWP Behavior SDK.
I wrote a custom behavior which has two dependency properties, one of which is a ObservableCollection.
Whenever I update an item in the collection, I make sure that PropertyChanged is called for the collection.
However, the Dependency property is not being updated.
My code:
<trigger:CustomBehavior ItemIndex="{x:Bind ItemIndex}"
Presences="{Binding ElementName=Box,
Path=DataContext.CustomCollection,
UpdateSourceTrigger=PropertyChanged, Converter={StaticResource TestConverter}}" />
My TestConverter shows me that when I update an item in the collection, the updatesource trigger is working. The dependency property in my behavior however, is not firing the Changed event. When I change the entire custom collection, the DP is updated, when I just change one item, it isn't.
Research so far says that DependencyObject.SetValue just checks to see if the object has changed and if one item changed, it will just think that the collection didn't change at all? Is this true, and if so, how can I overcome this?
Thanks
A collection-type dependency property should usually be declared as the most basic collection type, IEnumerable. This way you can assign a variety of actual collection types to the property, including those that implement INotifyCollectionChanged, like ObservableCollection<T>.
You would check at runtime if the collection type actually implements the interface, and possibly attach and detach a handler method for the CollectionChanged event.
public class CustomBehavior : ...
{
public static readonly DependencyProperty PresencesProperty =
DependencyProperty.Register(
"Presences", typeof(IEnumerable), typeof(CustomBehavior),
new PropertyMetadata(null,
(o, e) => ((CustomBehavior)o).OnPresencesPropertyChanged(e)));
private void OnPresencesPropertyChanged(DependencyPropertyChangedEventArgs e)
{
var oldCollectionChanged = e.OldValue as INotifyCollectionChanged;
var newCollectionChanged = e.NewValue as INotifyCollectionChanged;
if (oldCollectionChanged != null)
{
oldCollectionChanged.CollectionChanged -= OnPresencesCollectionChanged;
}
if (newCollectionChanged != null)
{
newCollectionChanged.CollectionChanged += OnPresencesCollectionChanged;
// in addition to adding a CollectionChanged handler, any
// already existing collection elements should be processed here
}
}
private void OnPresencesCollectionChanged(
object sender, NotifyCollectionChangedEventArgs e)
{
// handle collection changes here
}
}

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.

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.
}