Xamarin Forms Picker raises two events - c#

I have picker which is defined as:
<Picker
Title="Identifier"
ItemsSource="{Binding IdentifyUsing}"
SelectedItem="{Binding SelectedId}"
/>
And in ViewModel, SelectedId is configured as below:
public string SelectedId
{
get => _selectedId;
set
{
SetProperty(ref _selectedId, GetAppropriateLabel(value)); <-- This code is executed twice
IsEnabled = true;
}
}
where GetAppropriateLabel() method is defined as below:
private string GetAppropriateLabel(string value)
{
if (value.Contains("Member"))
return "Member ID (ex: 999999999)";
else if (value.Contains("Social"))
return "Social Security Number (ex: 999-99-9999)";
else if (value.Contains("StudentId"))
return "Medicare Number (ex: ********-**)";
else
return value;
}
Now, if I pass the value which is part of if-else chain, setter of SelectedId is executed twice. And second time, the value comes as null and breaks the app. If the value which is not part of if-else chain is passed, then it is works correctly.
I have no idea why setter of SelectedId is executed twice for the values which are not part of is-else chain.
Is this related to two way binding or picker related issues? Did I understand binding incorrectly? Any idea what is going wrong?

Related

Is it better to put my logic in an event handler or in a setter for MVVM (Xamarin `Picker` `SelectedItem` quirks)

SOLUTION IS IN EDIT OF THE ACCEPTED ANSWER
I have a view in which has two Pickers, I need to have it so that when the SelectedItem property in one Picker changes, the list of Items in the second Picker (ItemSource) changes as well.
Currently I have a bound the SelectedItem and SelectedIndex properties of both pickers to properties in my ViewModel. In the setter(s) for both of them, I perform the logic needed to change the list of Items in the second picker. The list of Items in the second picker changes successfully, but when I set the SelectedIndex (to make it select an Item by default), this fails if the index which I am setting it to is the same as the index which it was on in the previous list. It just shows the Title of the Picker instead, this issue seems to be related to this bug.
My Code:
Both Pickers are bound to an Observable collection of strings FYI
FrameType and DirectionType are Enums
I initially used only the SelectedItem property
Relevant XAML
<Label Grid.Row="1" Grid.Column="0" VerticalTextAlignment="Center"
Text="Direction: " />
<Picker x:Name="PickerDirection" Grid.Row="1" Grid.Column="1"
Title="Select Direction"
ItemsSource="{Binding Directions}"
SelectedItem="{Binding SelectedDirection}"
SelectedIndex="{Binding SelectedDirectionIndex}"></Picker>
<Label Grid.Row="2" Grid.Column="0" VerticalTextAlignment="Center"
Text="Frame: "/>
<Picker x:Name="PickerFrame" Grid.Row="2" Grid.Column="1"
Title="Select Frame"
ItemsSource="{Binding Frames}"
SelectedItem="{Binding SelectedFrame}"
SelectedIndex="{Binding SelectedFrameIndex}"></Picker>
Relevant View Model code:
public string SelectedDirection
{
get { return _selectedDirection; }
set
{
if (Directions.Contains(value))
{
if (_selectedDirection != value)
{
if (EnumUtils.ToEnumFromString<FrameType>(SelectedFrame) == FrameType.Road &&
!DirectionsRoad.Contains(value))
{
_selectedDirection = Directions[Directions.IndexOf(DirectionType.Right.ToString())];
}
else
{
_selectedDirection = value;
}
OnPropertyChanged();
}
}
}
}
public string SelectedFrame
{
get { return _selectedFrame; }
set
{
if (_selectedFrame != value)
{
_selectedFrame = value;
if (EnumUtils.ToEnumFromString<FrameType>(_selectedFrame) == FrameType.Road)
{
Directions = DirectionsRoad;
if (Directions.Contains(SelectedDirection))
{
SelectedDirectionIndex = Directions.IndexOf(SelectedDirection);
}
else
{
SelectedDirectionIndex = Directions.IndexOf(DirectionType.Right.ToString());
}
}else if (EnumUtils.ToEnumFromString<FrameType>(_selectedFrame) == FrameType.Lane)
{
Directions = DirectionsAll;
SelectedDirectionIndex = Directions.IndexOf(SelectedDirection);
}
OnPropertyChanged();
}
}
}
}
public int SelectedDirectionIndex
{
get { return _selectedDirectionIndex; }
set
{
if (_selectedDirectionIndex != value)
{
if (!(value < 0 || value >= Directions.Count))
{
_selectedDirectionIndex = value;
OnPropertyChanged();
}
}
}
}
public int SelectedFrameIndex
{
get { return _selectedFrameIndex; }
set
{
if (_selectedFrameIndex != value)
{
if (!(value < 0 || value >= Frames.Count))
{
_selectedFrameIndex = value;
OnPropertyChanged();
}
}
}
}
The outcome:
I expect it to never be empty since I ensure that the SelectedDirection is always set to something.
Notes:
I initially used just the SelectedItem property, but when I encountered this bug I thought using the SelectedIndex property would help to fix it.
I used ObservableCollection to maintain consistency with the other viewModels in the project, and to ensure that the options in the picker are updated when I make changes (based on my understanding you need to use ObservableCollection to make this possible).
I do plan to refactor the code in the setter for SelectedFrame into smaller functions as soon as I get things to work.
Due to this It seems that using the SelectedIndexChanged event of the Picker would be the only way to fix this. However the comment by ottermatic in this question says that events are unreliable. Hence I felt is was better to perform this logic in the setter.
If someone could comment on what I may be doing wrong in my code which is causing this issue and also comment on the pros/cons and/or whether or not I should use the eventHandler or have the logic in my setter. Thanks
I don't really see why you are using both SelectedItem and SelectedIndex, but I think what you are trying to achieve can be achieved easier.
First of all, I don't think you need ObservableCollection types at all in your example, since you are setting the directions anyway and not modifying the collection. More importantly, fiddling around with the indices is completely unnecessary, as far as I can tell. You are using strings anyway and even though String is not a value type, but a reference type, you cannot practically distinguish two String instances that have the same content, hence assinging the respective values to SelectedDirection and SelectedFrame is sufficient.
The following checks seem redundant to me
if (Directions.Contains(value))
if (EnumUtils.ToEnumFromString<FrameType>(SelectedFrame) == FrameType.Road &&
!DirectionsRoad.Contains(value))
since Directions are set to DirectionsRoad anyway if SelectedFrame has been set to "Road". Hence I'd assume that the second condition won't evaluate to true in any case. Hence the SelectedDirection can be simplified:
public string SelectedDirection
{
get => _selectedDirection;
set
{
if (_selectedDirection != value && Directions.Contains(value))
{
_selectedDirection = value;
OnPropertyChanged();
}
}
}
Within the setter of SelectedFrame there are many things going on, which I'd refactor to methods on it's own right, to improve clarity.
public string SelectedFrame
{
get => _selectedFrame;
set
{
if (_selectedFrame != value)
{
_selectedFrame = value;
UpdateAvailableDirections();
OnPropertyChanged();
}
}
}
private void UpdateAvailableDirections()
{
// store the selected direction
var previouslySelectedDirection = SelectedDirection;
Directions = GetValidDirectionsByFrameType(EnumUtils.ToEnumFromString<FrameType>(SelectedFrame));
SelectedDirection = GetSelectedDirection(previoslySelectedDirection, Directions);
}
private string[] GetValidDirectionsByFrameType(FrameType frameType)
{
return frameType == FrameType.Road ? DirectionsRoad : DirectionsAll;
}
private string GetSelectedDirection(string previouslySelectedDirection, string[] validDirections)
{
return validDirections.Contains(previouslySelectedDirection) ? previouslySelectedDirection : DefaultDirection;
}
By setting the SelectedItem instead of fiddling with the indices, the correct values shall be displayed.
Concerning your question whether this logic may be better suited in an event handler or in the setter depends on your requirements. If all you need is the index, the event SelectedIndexChanged may work out for you, but if the value is needed in several places and methods that are not called by the event handler, the presented solution may be more viable.
Edit
You were correct, it has got nothing to do with the usage of SelectedIndex and SelectedItem. The issue is a bit more subtle.
I build a quick proof-of-concept and found the following:
Assuming SelectedDirection is "Right" (and the index is set accordingly)
When Directions is set, the SelectedItem on the picker seems to be reset
SelectedDirection is set to null
this.Directions.Contains(value) evaluates to false, hence _selectedDirection is not set (this hold true for SelectedDirectionIndex, since the value -1 is filtered by if(!value < 0 || value >= this.Directions.Count))
When SelectedDirection is set afterwards, the value is still "Right", hence OnPropertyChanged is not called (since the values are the same) and the SelectedItem is not set
This way there is a mismatch between the value the Picker actually holds and the property in the viewmodel, which leads to unexpected behavior.
How to mitigate the issue?
I'd still stick with the code without the indices (unless you really need them) and use the string values.
There are other possibilities, but I'd change the setter of SelectedDirection. When you allowed the value to be set to null, PropertyChanged will be raised properly when the value is set to Right afterwards. If you really need to filter what the value is set to, you should still raise OnPropertyChanged, to inform the Picker that the value has changed (preventing a mismatch between the actual Pickers value and the viewmodel)
public string SelectedDirection
{
get => _selectedDirection;
set
{
if (_selectedDirection != value)
{
if(Directions.Contains(value))
{
_selectedDirection = value;
}
else
{
_selectedDirection = DirectionTypes.Right.ToString();
}
OnPropertyChanged();
}
}
}
Found a somewhat hacky fix for this, and it seems to be a Xamarin issue. I have made the following changes to my code"
The relevant changes are in the setter for SelectedFrame:
public string SelectedFrame
{
get { return _selectedFrame; }
set
{
if (_selectedFrame != value)
{
_selectedFrame = value;
if (EnumUtils.ToEnumFromString<FrameType>(_selectedFrame) == FrameType.Road)
{
Directions = DirectionsRoad;
if (Directions.Contains(SelectedDirection))
{
/*Relevant edits*/
var position = Directions.IndexOf(SelectedDirection);
SelectedDirection = Directions[Directions.Count - position -1];
SelectedDirection = Directions[position];
}
else
{
SelectedDirectionIndex = Directions.IndexOf(DirectionType.Right.ToString());
}
}else if (EnumUtils.ToEnumFromString<FrameType>(_selectedFrame) == FrameType.Lane)
{
Directions = DirectionsAll;
/*Relevant edits*/
var position = Directions.IndexOf(SelectedDirection);
SelectedDirection = Directions[Directions.Count - position -1];
SelectedDirection = Directions[position];
}
OnPropertyChanged();
}
}
}
}
It seems that my issue arises when I change the contents of my ObservableCollectoin but the SelectedDirection stays the same.
When I change Directions (which is an ObservableCollection) by assigning it to DirectionsAll (also an ObservableCollection), I need to make sure that the SelectedDirection changes,. The added code ensures that a change actually occurs to SelectionDirection and that fixes it. Seems somewhat hacky but it works.
Outcome:

How to avoid flickering when using an asynchronous binding

I've created an example to illustrate my problem.
ViewModel:
public class VM : INotifyPropertyChanged
{
private double _value = 1;
public double Value
{
get { return _value; }
set
{
_value = value;
OnPropertyChanged();
}
}
public VM()
{
var timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromTicks(1);
timer.Tick += (s, e) => { Value += 1; };
timer.Start();
}
// OnPropertyChanged stuff ...
}
}
View:
<Window.DataContext>
<namespace:VM/>
</Window.DataContext>
<Grid>
<TextBox Text="{Binding Value, IsAsync=True, FallbackValue=Test}"/>
</Grid>
When running my application the text in the textbox flickers. During the update process the FallbackValue is displayed, which makes no sense to me.
Does anyone knows the purposes or what are the benefits that during the update process the FallbackValue is displayed? Is there a way to display the old Value during an async update process?
This seems normal to me, given that you are using IsAsync=True in your binding. From the documentation:
While waiting for the value to arrive, the binding reports the FallbackValue, if one is available
When the PropertyChanged event is raised, WPF initiates the process of updating the target of the binding. Normally this would happen synchronously, with the property getter called immediately to update the value.
But you are using IsAysnc=True, so instead WPF fills in the target with the fallback value, and starts an asynchronous request to retrieve the actual property value later. Until that request has completed, the fallback value is displayed.
Does anyone knows the purposes or what are the benefits that during the update process the FallbackValue is displayed?
Per the documentation, the intent behind the IsAsync=True setting is that it's used when the property getter is, or could be, slow. Your code has told WPF that the property value has changed, so it knows the old value is no longer valid. Your code has also told (via the IsAsync in the XAML) that the property getter could take some time to provide the new value, so it defers retrieving that value until later.
In the meantime, what should WPF display? That's what the fallback value is there for.
Is there a way to display the old Value during an async update process?
If you don't want the behavior that is designed for this feature in WPF, you should just retrieve the new data asynchronously yourself, and update the property via the setter when you have it. It's not a good idea for a property getter to be slow anyway, so this would be a better design in any case.
I had the same problem but with an image source. I've removed the IsAsync on the binding and I have made my getter async:
// This field hold a copy of the thumbnail.
BitmapImage thumbnail;
// Boolean to avoid loading the image multiple times.
bool loadThumbnailInProgress;
// I'm using object as the type of the property in order to be able to return
// UnsetValue.
public object Thumbnail
{
get {
if (thumbnail != null) return thumbnail;
if (!loadThumbnailInProgress) {
// Using BeginInvoke() allow to load the image asynchronously.
dispatcher.BeginInvoke(new Action(() => {
thumbnail = LoadThumbnail();
RaisePropertyChanged(nameof(Thumbnail));
}));
loadThumbnailInProgress = true;
}
// Returning UnsetValue tells WPF to use the fallback value.
return DependencyProperty.UnsetValue;
}
}
Sometimes a binding will fail, failure is important to consider. Fallback value option presents users a message if an error occurs, rather than nothing happening. If you would like your fallbackvalue to display the previous value contained, I could think of a few ways of trying : possibly saving the value in a reference string and/or to another control, then binding to that control
But if you don't want the fallbackvalue displayed at all, you need to do a code inspection to see how your binding is failing/or is slow, and contain it in your code behind
I've found an approach to avoid flickering by just inheriting from textbox and overriding it's textproperty-metadata.
Custom TextBoxControl
public class CustomTextBox : TextBox
{
static CustomTextBox()
{
TextProperty.OverrideMetadata(typeof(CustomTextBox), new FrameworkPropertyMetadata(null, null, CoerceChanged));
}
private static object CoerceChanged(DependencyObject d, object basevalue)
{
var tb = d as TextBox;
if (basevalue == null)
{
return tb.Text;
}
return basevalue;
}
}
View
<Window.DataContext>
<namespace:VM/>
</Window.DataContext>
<Grid>
<namespace:CustomTextBox Text="{Binding Value, IsAsync=True}"/>
</Grid>
It's important to have the text-binding without a fallbackvalue. So during update process the text is set to the textproperty defalut value - so in this case to null.
The CoerceChanged handler checks whether the new value is null. If it's so he returns the old value so that during update process there is still the old value displayed.

Why is my PropertyChanged event firing twice?

I'm using wpf, mvvmlight and EF. I have two properties on my entity object which are loosely linked and in my XAML, I would like to have one change when the other changes. Right now, I'm having a problem with the first property changed event firing twice.
My xaml setup is like this, I have DataGrid up top on my xaml, I have my
ItemsSource="{Binding MonthlyDonorDetails}" IsSynchronizedWithCurrentItem="True"
The textbox is like,
<TextBox Grid.Column="0" Text="{Binding BankCustomerID}"/>
Next, in my VM, I load up the data like this,
int rowID = 1;
foreach(var row in monthlyDonorsQuery)
{
row.RowID = rowID++;
row.PropertyChanged += new PropertyChangedEventHandler(MonthlyDonorDataRow_PropertyChanged);
}
MonthlyDonorDetails = new ObservableCollection<MonthlyDonorFundCode>(monthlyDonorsQuery);
This affords me a row id on each row to help user differentiate, and (I'm thinking) allows me to setup a handler for when individual fields change within the row. Then I set the backing collection for the grid. And this is all working just fine.
NOW, I want to be able to keep two fields in lock-step with each other, an AccountID and an CustomerID. When the user manually\typing\input changes the AccountID fields, I want to have some code to change the CustomerID, but I don't want this firing needlessly multiple times. Right now its firing twice, and I don't know why? Can anyone see my mistake please?
private void MonthlyDonorDataRow_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName == "FirstName" || e.PropertyName == "LastName")
RaisePropertyChanged("DonorName");
if(e.PropertyName == "AccountID")
{
MonthlyDonorFundCode monthlyDonation = sender as MonthlyDonorFundCode;
int customerID = GetCimsIdFromBankCustomerID(monthlyDonation.AccountID);
Debug.WriteLine("should be setting to " + customerID);
}
}
When I'm debugging this, all I can see is the EF setter is getting called twice, but I don't know why cause I'm not setting that value. Any help\guidance would be appreciated. Thank you very much.
I think I found out my problem. But it raises another problem for me. I'm calling the method which adds a PropertyChanged handler, twice, while loading. That's another problem I have to figure out.
Thank you Lavr for trying to help me out.
Put breakpoint in AccountID setter and look at stack trace then setter is called. You can find "who" updating your property twice.
Also you can update AccountID setter like this:
private int _accountId;
public int AccountID
{
get { return this._accountId; }
set
{
if (this._accountId == value) return;
this._accountId = value;
this.RisePropertyChanged("AccountID");
}
}
I have the same problem with twice invocation of propertychanged. The same thing when I try to catch the extra invoke of the propertychanged, the call stack says it is called from the [External Code]. After I upgraded my Xamarin version from 4.0 to latest (Xamarin 5.0).

(ASP.NET User control) - problems setting properties

I am creating several .NET User Controls and I am trying to figure out the best way to go about setting properties. I have an address control and I am trying to create a property called ShowCountry which will either hide or show the control's country ddl.
I have been trying to set most of my properties similar to the below code:
public bool ShowCountry
{
get { return (bool)ViewState["ShowCountry"]; }
set
{
ViewState["ShowCountry"] = value;
pnlCountry.Visible = value;
}
}
How would I set a default value for this property? When I run my page with the control on it, it instantly errors out in the "get{}" when ShowCountry is used in one of my functions because I never set ShowCountry="false" in the control's tag. If I set this property when declaring the control everything works fine.
Also is what I am doing with the ViewState a good way to keep property values across postbacks?
Could someone show me how they would write this property?
The specs are:
Must keep value across postbacks, Must default to false
you can try this in order to avoiding error..
public bool ShowCountry
{
get {
if(ViewState["ShowCountry"] != null ){
return (bool)ViewState["ShowCountry"];
}
else {
//return the default value
return false;
}
}
set
{
ViewState["ShowCountry"] = value;
pnlCountry.Visible = value;
}
}
i think view state is best approach alternatively you can use hidden field in order to save value against post back.

RaisePropertyChanged fails to update the UI

I've been working on an application in MVVM Light lately. I have a TextBox in my XAML bound to my UI. I'd like to validate any input and ensure that only numbers are entered. I've tried the following code:
My TextBox:
<TextBox TabIndex="1" Height="23" MinWidth="410" DockPanel.Dock="Left"
HorizontalAlignment="Left"
Text="{Binding Input, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding IsEnabled}"
AcceptsReturn="False"
local:FocusExtension.IsFocused="{Binding IsFocused}">
And in my ViewModel:
private string input;
public string Input
{
get { return this.input; }
set
{
decimal test;
if(decimal.TryParse(value, out test))
{
this.input = value;
}
else
{
this.input = "";
}
RaisePropertyChanged("Input");
}
}
This fails to update the UI. If I enter "B" and check the debugger, it runs through the setter, but fails to actually update the UI.
Curiously, if I set this.input = "TEST"; in the else block, the UI updates, but, if I attempt to set it to "", string.Empty, or the value of input before the validation, the UI fails to update.
Is this by design? Possibly a bug? Is there something I'm doing wrong?
Edit I mistakenly forgot to include RaisePropertyChanged in my example code. I've updated it. Raising it isn't the problem as I've watched the debugger run all the way through raising it and returning input via the getter.
Way you use strign type property and then convert to decimal, easier to change lik this:
public decimal Input
{
get { return this.input; }
set
{
this.input = value;
RaisePropertyChanged("Input");
}
}
And for validate use IDataErrorInfo (read more: http://blogs.msdn.com/b/wpfsdk/archive/2007/10/02/data-validation-in-3-5.aspx)
What we have done is created a Custom Control, since we use it for a Currency Text Box. I warn you I have no validation that this is a good idea, or falls in line with MVVM model because all manipulation of the control are done in code behind.
In the control on the textbox we have an event on PreviewTextInput that does this
e.Handled = Functions.DoubleConverter(Convert.ToChar(e.Text), ((TextBox)sender).Text.Replace("$", ""));
Then for the function (which isnt perfect, I have a few issues with it still) is:
static public bool DoubleConverter(char c, string str)
{
if (!char.IsDigit(c))
{
if (c == '.' && (str.Contains('.')))
{
return true;
}
else if (c != '.')
{
return true;
}
}
return false;
}
Please use this as a reference, not exactly as is because it is a very crude implementation.

Categories

Resources