My XAML ComboBox control is stuck in an endless loop when a selection is changed in the UI. The ComboBox sets the value of the bound property. When the property has changed, it raises a property changed event. This in turn causes the databinder to update the property again. This keeps looping until I get a stack overflow exception.
<ComboBox x:Name="OriginCountryCode" Grid.ColumnSpan="2" Grid.Column="2" SelectedValue="{x:Bind Mode=TwoWay, Path=ViewModel.OriginCountryCode}" DisplayMemberPath="Value" SelectedValuePath="Key" ItemsSource="{x:Bind ViewModel.CountryCodes}" />
The control is bound to the following properties.
private static Dictionary<string, string> _countryCodes = null;
public Dictionary<string, string> CountryCodes
{
get
{
if (_countryCodes != null) return _countryCodes;
_countryCodes = new Dictionary<string, string>();
var cultures = CultureInfo.GetCultures(CultureTypes.SpecificCultures);
foreach (var culture in cultures)
{
var region = new RegionInfo(culture.LCID);
_countryCodes[region.TwoLetterISORegionName] = region.DisplayName;
}
return _countryCodes;
}
}
public string OriginCountryCode
{
get => _origin.CountryCode;
set
{
_origin.CountryCode = value; RaisePropertyChanged(nameof(OriginCountryCode));
}
}
This behavior is odd as all my other controls do not exhibit this behavior. The BAML generated connector code for the ComboBox is different. One is is updated when there's a focus change and the other when the SelectedValue changes.
case 15: // Views\QuotesPage.xaml line 77
this.obj15 = (global::Windows.UI.Xaml.Controls.TextBox)target;
(this.obj15).LostFocus += (global::System.Object sender, global::Windows.UI.Xaml.RoutedEventArgs e) =>
{
if (this.initialized)
{
// Update Two Way binding
this.dataRoot.ViewModel.DestinationPostalCode = this.obj15.Text;
}
};
break;
case 16: // Views\QuotesPage.xaml line 78
this.obj16 = (global::Windows.UI.Xaml.Controls.ComboBox)target;
(this.obj16).RegisterPropertyChangedCallback(global::Windows.UI.Xaml.Controls.Primitives.Selector.SelectedValueProperty,
(global::Windows.UI.Xaml.DependencyObject sender, global::Windows.UI.Xaml.DependencyProperty prop) =>
{
if (this.initialized)
{
// Update Two Way binding
this.dataRoot.ViewModel.DestinationCountryCode = (global::System.String)this.obj16.SelectedValue;
}
});
break;
Don't raise PropertyChanged when the property value hasn't changed.
public string OriginCountryCode
{
get => _origin.CountryCode;
set
{
if (_origin.CountryCode != value)
{
_origin.CountryCode = value;
RaisePropertyChanged(nameof(OriginCountryCode));
}
}
}
Related
I have 2 entries. When I tap anything on entry 1 I would like to get "Yess" on entry 2 and when I type anything on entry 2 I would like to get "Noo"
The problem:
When I tap on entry 1, entry 2 change and get the value "Noo" but entry 1 change too and get the value "yess".
Question :
How can make entry 2 change when tapping on entry 1 without changing entry 1. And the same for entry 2
Here is Xaml code :
<Entry ClassId="1" x:Name="myWord1"TextChanged="OnEntryTextChange"/>
<Entry ClassId="2" x:Name="myWord2" TextChanged="OnEntryTextChange"/>
Code :
private async void OnEntryTextChange(object sender, TextChangedEventArgs e)
{
var EntryTapped = (Xamarin.Forms.Entry)sender;
Device.BeginInvokeOnMainThread(() => {
if (EntryTapped.ClassId == "1")
{
myWord2.Text="Noo";
}
else if (EntryTapped.ClassId == "2")
{
myWord1.Text="yess";
}
});
}
Thanks for your help
You could use the Focused event instead of TextChanged event.
<StackLayout>
<Entry ClassId="1" x:Name="myWord1" Focused="EntryFocused"/>
<Entry ClassId="2" x:Name="myWord2" Focused="EntryFocused"/>
</StackLayout>
private void EntryFocused(object sender, FocusEventArgs e)
{
var EntryTapped = (Xamarin.Forms.Entry)sender;
if (EntryTapped.ClassId == "1")
{
myWord2.Text = "Noo";
}
else if (EntryTapped.ClassId == "2")
{
myWord1.Text = "yess";
}
}
There are several ways of doing this:
Using bindings
In this case you would have 2 private variables and 2 public variables, and the entries binded to each one. Check this link how to implement INotifyPropertyChanged
private string entry1String;
private string entry2String;
public string Entry1String {
get => entry1String;
set
{
entry2String = "Noo";
entry1String = value;
OnPropertyChanged(Entry1String);
OnPropertyChanged(Entry2String);
}
}
public string Entry2String {
get => entry2String;
set
{
entry1String = "Yees";
entry2String = value;
OnPropertyChanged(Entry1String);
OnPropertyChanged(Entry2String);
}
}
Another way could be using a variable as a Semaphore. While the variable is True, the method cannot be fired at the same time by another.
private bool semaphoreFlag=false;
private async void OnEntryTextChange(object sender, TextChangedEventArgs e)
{
if(semaphoreFlag) return;
semaphoreFlag=true;
var EntryTapped = (Xamarin.Forms.Entry)sender;
Device.BeginInvokeOnMainThread(() => {
if (EntryTapped.ClassId == "1")
{
myWord2.Text="Noo";
}
else if (EntryTapped.ClassId == "2")
{
myWord1.Text="yess";
}
});
semaphoreFlag=false;
}
From my previous post, it helped be to determine how to bind to selecteditems, How to bind to autocomplete selecteditem with ObservableCollection But now I'm trying to enhance that logic.
I'm trying to have items preselected when my View is initialized. I've tried multiple options, but I can't seem to get items preselected. May I get some assistance. My current code below
Keyword Class
public class Keyword : ObservableObject
{
private string _value;
public string Value
{
get { return _value; }
set { SetProperty(ref _value, value); }
}
}
ViewModel
private ObservableCollection<object> _selectedKeywords = new ObservableCollection<object>();
private ObservableCollection<Keyword> _keywords = new ObservableCollection<Keyword>();
public TestViewModel()
{
Keywords = new ObservableCollection<Keyword>()
{
new Keyword { Value = "Apples" },
new Keyword { Value = "Bananas" },
new Keyword { Value = "Celery" }
};
SelectedKeywords = new ObservableCollection<object>(Keywords.Where(x => x.Value == "Apples"));
}
public ObservableCollection<object> SelectedKeywords
{
get { return _selectedKeywords; }
set { SetProperty(ref _selectedKeywords, value); }
}
public ObservableCollection<Keyword> Keywords
{
get { return _keywords; }
set { SetProperty(ref _keywords, value); }
}
View
<autocomplete:SfAutoComplete MultiSelectMode="Token"
HorizontalOptions="FillAndExpand"
VerticalOptions="EndAndExpand"
TokensWrapMode="Wrap"
Text="{Binding Keyword, Mode=TwoWay }"
IsSelectedItemsVisibleInDropDown="false"
Watermark="Add..."
HeightRequest="120"
SelectedItem="{Binding SelectedKeywords}"
DataSource="{Binding Keywords}">
</autocomplete:SfAutoComplete>
We have prepared sample from your code snippet and you have missed to add DisplayMemberPath property in the code snippet. Please find the sample from below location.
http://www.syncfusion.com/downloads/support/directtrac/general/ze/AutoCompleteSample-270923957.zip
Note: I work for Syncfusion.
Regards,
Dhanasekar
To make it preselected in your View Model set a value to the binding that you have binded on your View basically assign a value to SelectedKeywords
Something like:
SelectedKeywords = Keywords.FirstOrDefault();
You might need two-way binding not sure cause never used this control:
SelectedItem="{Binding SelectedKeywords, Mode=TwoWay}"
I have a binding of the following form in XAML,
Title="{Binding SelectedNewsItems[0].Title}"
Note that it refers to a particular element in the SelectedNewsItems which is an ObservableCollection. (I have a collection of nine buttons of various sizes, each styled, and sized differently and so a more standard ListView is not appropriate.)
When I reassign SelectedNewsItems I raise a PropertyChanged event for SelectedNewsItems, however, this does not appear to cause the bindings to update for the individual bound elements and their properties. I have tried the following,
public ObservableCollection<NewsItem> _selectedNewsItems;
public ObservableCollection<NewsItem> SelectedNewsItems
{
get
{
return this._selectedNewsItems;
}
set
{
if (this._selectedNewsItems != value)
{
this._selectedNewsItems = value;
this.NotifyPropertyChanged();
for (int i = 0; i < this._selectedNewsItems.Count; i++)
{
this.NotifyPropertyChanged(String.Format("SelectedNewsItems[{0}].Content", i));
this.NotifyPropertyChanged(String.Format("SelectedNewsItems[{0}].Title", i));
this.NotifyPropertyChanged(String.Format("SelectedNewsItems[{0}].Id", i));
this.NotifyPropertyChanged(String.Format("SelectedNewsItems[{0}].Image", i));
}
}
}
}
Hmm, I cannot exacly say where your code is wrong (as I see only part of it), but maybe you haven't set your DataContex or something else. For the purpose of research I've made simple example, which works quite fine. Take a look at it and maybe it will help you:
In Xaml:
<Button x:Name="first" VerticalAlignment="Top" Content="{Binding SelectedNewsItems[0].Name}" Grid.Row="0"/>
<Button x:Name="second" VerticalAlignment="Center" Content="{Binding SelectedNewsItems[1].Name}" Grid.Row="1"/>
In code behind (I put all the code - yeah quite a lot of, but I cannot guess what is wrong with your code):
public class NewsItem
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
}
public partial class MainPage : PhoneApplicationPage, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaiseProperty(string property = null)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(property));
}
private ObservableCollection<NewsItem> _selectedNewsItems = new ObservableCollection<NewsItem>();
public ObservableCollection<NewsItem> SelectedNewsItems
{
get
{
return this._selectedNewsItems;
}
set
{
if (this._selectedNewsItems != value)
{
this._selectedNewsItems = value;
this.RaiseProperty();
for (int i = 0; i < this._selectedNewsItems.Count; i++)
{
this.RaiseProperty(String.Format("SelectedNewsItems[{0}].Name", i));
}
}
}
}
public MainPage()
{
NewsItem firstT = new NewsItem() { Name = "First" };
NewsItem secondT = new NewsItem() { Name = "Second" };
SelectedNewsItems.Add(firstT);
SelectedNewsItems.Add(secondT);
InitializeComponent();
this.DataContext = this;
first.Click += first_Click;
second.Click += second_Click;
}
private void first_Click(object sender, RoutedEventArgs e)
{
NewsItem change = new NewsItem() { Name = "Changed by First" };
SelectedNewsItems[1] = change;
}
private void second_Click(object sender, RoutedEventArgs e)
{
NewsItem change = new NewsItem() { Name = "Changed by Second" };
SelectedNewsItems[0] = change;
}
}
As I click on buttons the bindigs work, so maybe it will help you.
I'm using AutocompleteBox control which gets data from REST service. My code looks like that:
MODEL:
public class Word()
{
public int wordId {get;set}
public int wordName {get;set;}
}
XAML:
<toolkit:AutoCompleteBox x:Name="AutoDictionaryTB" Populating="AutoDictionaryTB_Populating_1" HorizontalAlignment="Left" Margin="63,10,0,0" Grid.Row="1" VerticalAlignment="Top" Width="383">
</toolkit:AutoCompleteBox>
CODE BEHIND:
private void AutoDictionaryTB_Populating_1(object sender, PopulatingEventArgs e)
{
var client = new RestClient("http://XXX.XXXX.XX");
var request = new RestRequest("/XXX.XXX.XX", Method.GET);
request.AddParameter("XX", "XX");
request.AddParameter("XX", "XX");
request.AddParameter("XXX", (sender as AutoCompleteBox).Text);
var response2 = client.ExecuteAsync<List<Word>>(request, response =>
{
if (response != null && response.Data != null)
{
AutoDictionaryTB.ItemsSource = response.Data;
AutoDictionaryTB.PopulateComplete();
}
else
{
MessageBox.Show("Unknow error occured. Check your Internet connection or try later.");
}
});
}
IN THE LINE:
AutoDictionaryTB.ItemsSource = response.Data;
I have all walues I need so service is working perfectly. Why the control is not showing these walues? It's empty although response.Data is full of words.
Try adding ValueMemberBinding="{Binding wordName}" to the AutoCompleteBox to tell the component which property of the class it should use. If it doesn't work, add an ItemTemplate.
I installed nudget autocomplete toolkit but unfortunately I found it wierd that this control doesn't have enough property to serve as autocomplete. It has ItemsSource but it doesn't show the list of items filtered when you type something. I am also looking for something like textChanged so that I can invoke my service and get the result again and bind the itemsource.
Here's my implementation used in Group Contacts:
XAML:
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:behaviors="using:MyNamespace.Behaviors"
.
.
<TextBox x:Name="Searchbox" PlaceholderText="contact's name" Width="250"
IsTextPredictionEnabled="False"
IsSpellCheckEnabled="False"
VerticalAlignment="Center">
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior EventName="KeyUp">
<behaviors:FilterContactAction />
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</TextBox>
Code:
public class FilterContactAction : DependencyObject, IAction
{
string _previousResult = null;
public object Execute(object sender, object parameter)
{
var textbox = sender as TextBox;
var keyEventArgs = parameter as KeyRoutedEventArgs;
var noChanges = textbox.Text == _previousResult;
var deletionOccurred = keyEventArgs.Key == VirtualKey.Back ||
keyEventArgs.Key == VirtualKey.Delete;
if (noChanges || deletionOccurred)
{
return null;
}
var viewModel = ResourceLocator.Instance[typeof(HomeViewModel)] as HomeViewModel;
viewModel.CanSearch = FindMatch(textbox, viewModel.Contacts);
return null;
}
private bool FindMatch(TextBox textbox, ObservableCollection<Contact> contacts)
{
foreach (var contact in contacts)
{
var suggestionDisplayed = DisplaySuggestion(textbox, contact);
if (suggestionDisplayed)
{
return true;
}
}
return false;
}
private bool DisplaySuggestion(TextBox textbox, Windows.ApplicationModel.Contacts.Contact contact)
{
var characterCount = textbox.Text.Count();
var suggestionDisplayed = false;
var isMatch = contact.DisplayName.ToUpper().StartsWith(textbox.Text.ToUpper());
if (isMatch)
{
textbox.Text = contact.DisplayName;
textbox.SelectionStart = characterCount;
textbox.SelectionLength = textbox.Text.Length - textbox.SelectionStart;
_previousResult = textbox.Text;
suggestionDisplayed = true;
}
return suggestionDisplayed;
}
}
TextBoxExt control from Syncfusion WinRT Studio has enough features to work as an AutoComplete. It has more than 15 suggestion modes including custom filter option. Hope this helps.
http://www.syncfusion.com/products/winrt/controls
Not sure what you used. Can't tell you why it doesn't work either. however last year I wanted AutoCompleteTextBox and ended up writing it myself.
you can find it here.
https://github.com/hermitdave/HermitDaveWinRTControls