When using an onscreen keyboard with the autocomplete box, when the dropdown is open you need to click twice on the keyboard to press a key. Once to close the dropdown and once to press the key.
I have figured out that the MouseLeave event is fired when the dropdown is open and you click outside of it or when closed and leave the watermarktextbox. So, I am able to resend the mouse event to avoid clicking twice, but the drop down still closes. This is ok when the search text changes as it will reopen with the new values. However, when the up/down arrow keys are pressed it closes and doesn't reopen with no way to know what is highlighted in the list.
Is there an event or method that could be overridden to keep the dropdown open if focus doesn't change? (The on screen keyboard doesn't take focus)
8 Answers, 1 is accepted
May I ask you some questions in order to better assist you? Can you specify when and how you are opening the onscreen keyboard? Additionally, may I ask you to explain what you meant by "resend the mouse event to avoid clicking twice"? If you are able to share some code samples as to how you are opening the onscreen keyboard and handling the MouseLeave event I will be able to better understand your scenario. Also, providing a video of how the process looks on your side or any other details you find relevant would be of help as well.
I am looking forward to your reply.
Regards,
Vladimir Stoyanov
Progress Telerik
We are using a custom on screen keyboard from mindfusion.eu. I am unable to attach video wav files here, is there somewhere I could uploaded or send them.
What I am really interested in is having the dropdown stay open when clicking on the keyboard. Adding IsDropDownOpen = true to the OnMouseLeave doesn't do it.
We are using asynchronous filtering and here the drop down xaml.
<telerik:RadAutoCompleteBox x:Name="EmailBox" Grid.Row="1" IsTabStop="True" VerticalAlignment="Center" WatermarkContent="Enter email address" MinWidth="350" FontSize="18" SelectedItems="{Binding Path=AdditionalParticipantEmails, Mode=TwoWay}" Margin="0,10,10,10" SelectionMode="Multiple" TextSearchMode="Contains" AutoCompleteMode="Suggest" FilteringBehavior="{StaticResource EmailFilteringBehavior}"> <i:Interaction.Behaviors> <behaviors:EmailRadAutoCompleteBoxBehavior /> </i:Interaction.Behaviors></telerik:RadAutoCompleteBox>
the code for the filtering
public class EmailFilteringBehavior : IFilteringBehavior, IAsyncItemSearch, IDisposable { #region Fields private CancellationTokenSource _CurrentCancellationTokenSource; private string _CurrentSearchText; private AccountSettingsViewModel _Account; #endregion #region IAsyncItemSearch Members public void FindItems(Predicate<object> match) { //Here we ignore the match variable since we don't need this if (_CurrentCancellationTokenSource == null) { _CurrentCancellationTokenSource = new CancellationTokenSource(); } else { _CurrentCancellationTokenSource.Cancel(true); } _CurrentCancellationTokenSource = new CancellationTokenSource(); CancellationToken cancellationToken = _CurrentCancellationTokenSource.Token; Task.Factory.StartNew(() => GetEmailAddressesFromExchange(_CurrentSearchText, _Account, cancellationToken), cancellationToken, TaskCreationOptions.LongRunning, TaskSchedulerPriority.Normal) .ContinueWith(task => ProcessFilteredItems(task.Result), cancellationToken, TaskContinuationOptions.AttachedToParent, TaskScheduler.FromCurrentSynchronizationContext()); } public object GetValue(object item) { throw new NotImplementedException(); } public event EventHandler<AsyncItemSearchEventArgs> ItemsFound; #endregion #region IDisposable Members public void Dispose() { if (_CurrentCancellationTokenSource == null) { return; } _CurrentCancellationTokenSource.Dispose(); } #endregion #region IFilteringBehavior Members public IEnumerable<object> FindMatchingItems(string searchText, IList items, IEnumerable<object> escapedItems, string textSearchPath, TextSearchMode textSearchMode) { _CurrentSearchText = searchText; _Account = MainViewModel.Instance.SecurityManager.CurrentUser; if (MainViewModel.Instance.NewSettings.Calendar.EmailAutoComplete) { FindItems(itemText => true); } return Enumerable.Empty<object>(); } #endregion #region Non-Public Methods protected List<string> GetEmailAddressesFromExchange(string searchText, AccountSettingsViewModel account, CancellationToken cancellationToken) { var searchItems = new List<string>(); if (cancellationToken.IsCancellationRequested) { return searchItems; } if (string.IsNullOrWhiteSpace(searchText)) { return searchItems; } try { if (MainViewModel.Instance.ProductManager.CurrentEdition.Features.CalendarProviders.IsAvailable) { using (IEmailProviderAdapter emailProvider = MainViewModel.Instance.ProductManager.CreateEmailProviderAdapter(account)) { IReadOnlyList<Address> resolvedNames = emailProvider.ResolveName(searchText); searchItems.AddRange(resolvedNames.Select(p => p.Email)); } } } catch (Exception ex) { ex.LogErrorDeep(); } return searchItems; } private void ProcessFilteredItems(List<string> items) { if (ItemsFound != null) { ItemsFound((object) null, new AsyncItemSearchEventArgs(items)); } } #endregion }
And the code for the AutoCompleteBehaviour
public class EmailRadAutoCompleteBoxBehavior : DelayRadAutoCompleteBoxBehavior{ #region Non-Public Methods protected override void OnAttached() { var autoComplete = AssociatedObject as RadAutoCompleteBox; if (autoComplete != null) { autoComplete.KeyDown += OnAutoCompleteBoxKeyDown; autoComplete.LostFocus += OnAutoCompleteBoxLostFocus; autoComplete.SelectionChanged += OnSelectionChanged; autoComplete.IsVisibleChanged += OnIsVisibleChanged; autoComplete.MouseLeave += AutoCompleteOnMouseLeave; } } private void AutoCompleteOnMouseLeave(object sender, MouseEventArgs mouseEventArgs) { var autoComplete = sender as RadAutoCompleteBox; if (autoComplete.IsDropDownOpen) { Point m = Mouse.GetPosition(autoComplete); Point p = autoComplete.PointToScreen(m); User32.LeftMouseClick(Convert.ToInt32(p.X), Convert.ToInt32(p.Y)); } } private void OnAutoCompleteBoxKeyDown(object sender, KeyEventArgs e) { var autoComplete = AssociatedObject as RadAutoCompleteBox; if (e.Key == Key.OemSemicolon) { if (RegexExtensions.IsValidEmail(autoComplete.SearchText) && autoComplete.DataContext != null) { var selectedItems = new ObservableCollection<string>(autoComplete.SelectedItems.Cast<string>()) { autoComplete.SearchText }; autoComplete.SelectedItems = selectedItems; autoComplete.SearchText = string.Empty; } e.Handled = true; //Debug.WriteLine(string.Format("EmailRadAutoCompleteBoxBehavior.OnAutoCompleteBoxKeyDown e.Key: {0} SearchText:{1}", e.Key, autoComplete.SearchText)); } } private void OnAutoCompleteBoxLostFocus(object sender, RoutedEventArgs e) { var autoComplete = AssociatedObject as RadAutoCompleteBox; if (RegexExtensions.IsValidEmail(autoComplete.SearchText) && autoComplete.DataContext != null) { var selectedItems = new ObservableCollection<string>(autoComplete.SelectedItems.Cast<string>()) { autoComplete.SearchText }; autoComplete.SelectedItems = selectedItems; autoComplete.SearchText = string.Empty; //Debug.WriteLine(string.Format("EmailRadAutoCompleteBoxBehavior.OnAutoCompleteBoxLostFocus SearchText:{0}", autoComplete.SearchText)); } } protected override void OnDetaching() { var autoComplete = AssociatedObject as RadAutoCompleteBox; if (autoComplete != null) { autoComplete.KeyDown -= OnAutoCompleteBoxKeyDown; autoComplete.LostFocus -= OnAutoCompleteBoxLostFocus; autoComplete.IsVisibleChanged -= OnIsVisibleChanged; autoComplete.MouseLeave -= AutoCompleteOnMouseLeave; } } private void OnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) { var autoComplete = AssociatedObject as RadAutoCompleteBox; if (autoComplete.Visibility == Visibility.Visible) { autoComplete.SearchText = string.Empty; //Debug.WriteLine(string.Format("EmailRadAutoCompleteBoxBehavior.OnIsVisibleChanged SearchText:{0}", autoComplete.SearchText)); } } private void OnSelectionChanged(object sender, SelectionChangedEventArgs e) { var autoComplete = AssociatedObject as RadAutoCompleteBox; var selectedItems = new ObservableCollection<string>(autoComplete.SelectedItems.Cast<string>()); autoComplete.SelectedItems = selectedItems; autoComplete.SearchText = string.Empty; } #endregion}Thank you for the provided code snippets.
I am afraid I am still unable to reproduce the described scenario on my side. I am attaching a sample project which shows the windows onscreen keyboard in the GotFocus event of the RadAutoCompleteBox. I was able to type in the RadAutoCompleteBox from the onscreen keyboard with a single click. Can you share what you are doing differently on your side? This way I will be able to investigate further.
Regards,
Vladimir Stoyanov
Progress Telerik
By default the RadAutoCompleteBox's drop down is closed when the mouse is captured away from the RadWatermarkTextBox which is inside it. This happens when the up/down keys of the on screen keyboard are pressed.
The only suggestion I can offer in order to prevent this is to handle the PreviewMouseDownEvent event of the RadAutocompleteBox. Then you can set the e.Handled="true" if the up/down button of the virtual keyboard was pressed and the dropdown is open. Please note that this would require knowing that the virtual keyboard's up/down keys were pressed in the PreviewMouseDownEvent.
Regards,
Vladimir Stoyanov
Progress Telerik
Vladimir, I am able to process successfully with PreviewMouseDownEvent and PreviewTouchDownEvent. I was able to check if the event occurs over our custom on screen keyboard, press the correct key and mark the event as handled.
Thanks for all the help.
Hi Damien,
Good to hear that you were able to resolve the issue.
I am facing same issue with mind fusion key board with rad auto complete. Can u please elaborate how did u solve this issue.
Thanks in advance
Regards,
MReddy
Thanks to Vladimir & Damien, your inputs helped me to resolve the issue.
Regards,
MReddy
