Unable to hide Android Keyboard on Android 9 - c#

Creating an app that on tap of an webview input field, has to do an action. Catching and starting the selected action works fine, but due to it being started by clicking an input field, the keyboard is requested. On Android < Version 9, my currently code works just fine to hide the keyboard, but on Android Version 9, it doesn't.
I have tried all manor or combination of what was deemed the top answer on this post, but none have worked for my app on Android 9
Below is a bit of my code from my MainActivity, where the instance of my keyboard service implementation is created. the MainActivity code is then followed by my Keyboard service implementation made for android.
[Activity(Label = "Dental.App", Icon = "#mipmap/icon", Theme = "#style/MainTheme", MainLauncher = true, ScreenOrientation = ScreenOrientation.SensorLandscape,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation, WindowSoftInputMode = SoftInput.StateAlwaysHidden) ]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
...
DependencyService.Get<IServiceCollection>().SetKeyboardService(new KeyboardService(this, GetInputMethodManager()));
...
}
public InputMethodManager GetInputMethodManager()
{
return (InputMethodManager)GetSystemService(Context.InputMethodService);
}
}
public class KeyboardService : IKeyboardService
{
private InputMethodManager inputMethodManager;
private readonly object mainActivity;
public KeyboardService(object activity, InputMethodManager methodManager)
{
mainActivity = activity;
inputMethodManager = methodManager;
}
public bool IsKeyboardShown => inputMethodManager.IsAcceptingText;
public void HideKeyboard()
{
if (inputMethodManager == null || !(mainActivity is Activity activity)) return;
Logging.Log(LogType.Information, $"Attempting to Hide Keyboard via 1st method...");
//var view = activity.CurrentFocus;
var view = activity.FindViewById(Android.Resource.Id.Content).RootView;
if (view == null) Logging.Log(LogType.Warning, $"Failed to get View from Activity...");
var token = view?.WindowToken;
if (token == null) Logging.Log(LogType.Warning, $"Failed to get Token from View...");
var success = inputMethodManager.HideSoftInputFromWindow(token, HideSoftInputFlags.None);
Logging.Log(LogType.Information,
$"{nameof(inputMethodManager.HideSoftInputFromWindow)} returned => {success}");
if(success) view?.ClearFocus();
if (!IsKeyboardShown)
{
view?.ClearFocus();
return;
}
Logging.Log(LogType.Warning,
$"Failed to Hide Keyboard via {nameof(inputMethodManager.HideSoftInputFromWindow)}...");
HideKeyboardAttemptTwo(activity);
}
private void HideKeyboardAttemptTwo(Activity activity)
{
Logging.Log(LogType.Information, $"Attempting to Hide Keyboard via 2nd method...");
//var view = activity.CurrentFocus;
var view = activity.FindViewById(Android.Resource.Id.Content).RootView;
if (view == null) Logging.Log(LogType.Warning, $"Failed to get View from Activity...");
var token = view?.WindowToken;
if (token == null) Logging.Log(LogType.Warning, $"Failed to get Token from View...");
inputMethodManager.ToggleSoftInputFromWindow(token, ShowSoftInputFlags.None, HideSoftInputFlags.None);
if (!IsKeyboardShown)
{
view?.ClearFocus();
return;
}
Logging.Log(LogType.Warning, $"Failed to Hide Keyboard via {nameof(inputMethodManager.ToggleSoftInputFromWindow)}...");
}
public void ReInitializeInputMethod()
{
inputMethodManager = InputMethodManager.FromContext((Context) mainActivity);
}
None of the null check are coming back true, i.e nothing is null. The variable called success in the method HideKeyboard is returning false in 99% of all cases where it is called on a android version 9. In the 1% of the cases where it is true, the keyboard is still open. If the keyboard is still shown at the end of HideKeyboard, then the code attempts to close the keyboard via toggling it in the method HideKeyboardAttemptTwo. Doing it either of theses ways on Android 9 does not work, however running the exact same code on an Android 7.1 works just fine.
I'm not entirely sure that i have implemented the use of ToggleSoftInputFromWindow correctly, it is intended to only be able to run when the keyboard is open, i.e always used to hide the keyboard.
To reiterate my question: How do it successfully hide the keyboard on an Android 9.
If any additional information is needed, just ask, and i will attempt to find and supply it.

I uses this for my app, give it a try
Interface in main project
namespace *.Services.Interfaces
{
public interface IForceKeyboardDismissalService
{
void DismissKeyboard();
}
}
Phone specific code
using Plugin.CurrentActivity; //Nugget used to get activity
[assembly: Xamarin.Forms.Dependency(typeof(AndroidForceKeyboardDismissalService))]
namespace *.Droid.PhoneSpecific
{
public class AndroidForceKeyboardDismissalService : IForceKeyboardDismissalService
{
public void DismissKeyboard()
{
var imm = InputMethodManager.FromContext(CrossCurrentActivity.Current.Activity.ApplicationContext);
imm?.HideSoftInputFromWindow(CrossCurrentActivity.Current.Activity.Window.DecorView.WindowToken, HideSoftInputFlags.NotAlways);
var currentFocus = CrossCurrentActivity.Current.Activity.CurrentFocus;
if (currentFocus != null && currentFocus is EditText)
currentFocus.ClearFocus();
}
}
}
Usage
DependencyService.Get<IForceKeyboardDismissalService>().DismissKeyboard();
Let me know if its working for you.

To fix my problem i injected some JavaScript into the Webview, wherein i unfocused the input field, that was clicked.
On my Webview class i created a method that, given the string id of an element, would toggle whether or not that element is focused. As a second input, a boolean can be supplied, but defaulted to True, to indicate whether or not, you only want to unfocus the element.
public class AdvancedWebView : HybridWebView
{
...
public void ToggleElementFocus(string elementId, bool onlyUnFocus = true)
{
var js = GetJsInvertFocus(elementId, onlyUnFocus);
InjectJavaScript(js);
// Logging.Logging.Log(LogType.Information, $"Injected Javascript => {js}");
}
...
private string GetJsInvertFocus(string elementId, bool onlyUnFocus)
{
var builder = new StringBuilder();
builder.Append($"if (document.getElementById('{elementId}'))");
builder.Append("{");
builder.Append($"var element = document.getElementById('{elementId}');");
builder.Append($"if (element === document.activeElement)");
builder.Append("{");
builder.Append($"element.blur();");
builder.Append("}");
builder.Append($"else if({onlyUnFocus} == False)");
builder.Append("{");
builder.Append($"element.focus();");
builder.Append("}");
builder.Append("}");
return builder.ToString();
}
...
}
I'm extending the HybridWebview from XLabs, as it already has the functionality to inject JavaScript into the Webview. So that is where i get the InjectJavaScript method from.
On my page in my app, with the Webview, i then have a method that runs, when the element is clicked. To get a click event when clicking the Webview look at this link. During the method i figure out what the element id is from the event arguments, and then use this id to inject the JavaScript shown above, to unfocus the element, causing the keyboard to not appear at all. Below my OnClicked method can be seen.
public partial class DentalWebPage : AdvancedTabbedPage
{
...
private void DentalWebView_OnClicked(object sender, ClickEvent e)
{
try
{
if (LogUserPosition(sender, e)) return;
SwapToScanningTap();
}
catch (Exception ex)
{
Logging.Log(LogType.Exception,
ex.GetType().Namespace == typeof(BaseException).Namespace
? $"{ex.GetType()} => {ex}"
: $"{ex.GetType()} => {ex.Message}; Stacktrace => {ex.StackTrace}");
}
}
private bool LogUserPosition(object sender, ClickEvent e)
{
if (Config.DebugMode) Logging.Log(LogType.Debug, $"WebView was clicked...");
if (Config.DebugMode) Logging.Log(LogType.Debug, $"Element that was clicked is the following one => {e.Element}");
var success = Enum.TryParse(e.Element.Split(' ')[1].Split('=')[1], out clickedInputId);
if (!success && !(clickedInputId == InputId.MainContent_TextBoxInputStr ||
clickedInputId == InputId.MainContent_TextBoxScanOrder ||
clickedInputId == InputId.MainContent_TextBoxSelectProd ||
clickedInputId == InputId.MainContent_TextBoxStockReturn))
return true;
if (Config.DebugMode && webPageEnding == WebsiteControllers.Stock.ToString().ToLowerInvariant())
Logging.Log(LogType.Debug, $"WebView was clicked while on the stock page...");
return false;
}
private void SwapToScanningTap()
{
PerformOnMainThread(() =>
{
CurrentPage = Children[1];
ScanningToggle.IsToggled = true;
try
{
var isKeyboardShown = services.KeyboardService.IsKeyboardShown;
if (Config.DebugMode) Logging.Log(LogType.Debug, $"IsKeyboardShown returns => {isKeyboardShown}");
DentalWebView.ToggleElementFocus(clickedInputId.ToString());
}
catch (ObjectDisposedException)
{
if (DisposedReattempt) throw;
if (Config.DebugMode)
Logging.Log(LogType.Debug,
$"Input Method has been Disposed; Attempting to reinitialize it and rerun the {nameof(SwapToScanningTap)} method ones again");
DisposedReattempt = true;
services.KeyboardService.ReInitializeInputMethod();
SwapToScanningTap();
}
});
}
...
private void PerformOnMainThread(Action action)
{
try
{
Device.BeginInvokeOnMainThread(action);
}
catch (Exception ex)
{
Logging.Log(LogType.Exception,
ex.GetType().Namespace == typeof(BaseException).Namespace
? $"{ex.GetType()} => {ex}"
: $"{ex.GetType()} => {ex.Message}; Stacktrace => {ex.StackTrace}");
}
}
}
If you wish to get a understanding of the format of the string contained in e.Element, then go and look at the link supplied earlier.
Fell free to ask further questions, in case i missed something.

Related

How can I make a custom renderer for ToolbarItem in Xamarin.Forms?

I am working with Xamarin.Forms app. I am trying to add Speech to Text functionality to Xamarin.Forms through a toolbar item click. When I click the button on tool I want the builtin phone speech service to open and convert my speech to text and add to page's label.
Problem: In order to trigger the speech service which is android specific. I need a custom renderer for the toolbar item. So, I can add the code for speech to text in that custom renderer's OnClick method. but I can't seem to find the renderer class for toolbar item.
Here is code of my current attempt at Toolbar Item renderer
VoiceToolbarItemRenderer.cs
[assembly: ExportRenderer(typeof(VoiceToolbarItem), typeof(VoiceToolbarItemRenderer))]
namespace Xamarin_App.Droid
{
public class VoiceToolbarItemRenderer : PageRenderer, Android.Views.View.IOnClickListene
{
private bool isRecording;
private readonly int VOICE = 10;
private MainActivity activity;
private VoiceToolbarItem sharedToolbarItem;
private Toolbar nativeButton;
private SpeechRecognizer mSpeechRecognizer;
private Intent mSpeechRecognizerIntent;
public VoiceToolbarItemRenderer(Context context) : base(context)
{
isRecording = false;
}
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
activity = this.Context as MainActivity;
nativeButton = new global::Android.Widget.Toolbar(Context);
if (e.OldElement == null)
{
// perform initial setup
//SetNativeControl();
nativeButton.Clickable = true;
nativeButton.Focusable = true;
nativeButton.SetOnClickListener(this);
}
if (e.OldElement != null)
{
activity.ActivityResult -= HandleActivityResult;
}
if (e.NewElement != null)
{
activity.ActivityResult += HandleActivityResult;
sharedToolbarItem = e.NewElement.ToolbarItems as VoiceToolbarItem;
}
}
public void OnClick(Android.Views.View view)
{
try
{
string rec = Android.Content.PM.PackageManager.FeatureMicrophone;
if (rec != "android.hardware.microphone")
{
// no microphone, no recording. Disable the button and output an alert
var alert = new AlertDialog.Builder(Context);
alert.SetTitle("You don't seem to have a microphone to record with");
alert.SetPositiveButton("OK", (sender, e) => {
return;
});
alert.Show();
}
else
{
// create the intent and start the activity
var voiceIntent = new Intent(RecognizerIntent.ActionRecognizeSpeech);
voiceIntent.PutExtra(RecognizerIntent.ExtraLanguageModel, RecognizerIntent.LanguageModelFreeForm);
// if there is more then 1.5s of silence, consider the speech over
voiceIntent.PutExtra(RecognizerIntent.ExtraSpeechInputCompleteSilenceLengthMillis, 1500);
voiceIntent.PutExtra(RecognizerIntent.ExtraSpeechInputPossiblyCompleteSilenceLengthMillis, 1500);
voiceIntent.PutExtra(RecognizerIntent.ExtraSpeechInputMinimumLengthMillis, 15000);
voiceIntent.PutExtra(RecognizerIntent.ExtraMaxResults, 1);
// you can specify other languages recognised here, for example
// voiceIntent.PutExtra(RecognizerIntent.ExtraLanguage, Java.Util.Locale.German);
// if you wish it to recognise the default Locale language and German
// if you do use another locale, regional dialects may not be recognised very well
voiceIntent.PutExtra(RecognizerIntent.ExtraLanguage, Java.Util.Locale.Default);
activity.StartActivityForResult(voiceIntent, VOICE);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
private void HandleActivityResult(object sender, ActivityResultEventArgs e)
{
if (e.RequestCode == VOICE)
{
if (e.ResultCode == Result.Ok)
{
var matches = e.Data.GetStringArrayListExtra(RecognizerIntent.ExtraResults);
if (matches.Count != 0)
{
string textInput = matches[0];
// limit the output to 500 characters
if (textInput.Length > 500)
textInput = textInput.Substring(0, 500);
sharedToolbarItem.OnTextChanged?.Invoke(textInput);
//textBox.Text = textInput;
}
else
sharedToolbarItem.OnTextChanged?.Invoke("No speech was recognised");
}
}
}
}
}
If someone has an Idea of making toolbarItem's custom renderer or any other suggestions please let me know.
By toolbar I suppose you mean the navigation bar (or the bar where you get the Page Title , right? )
If that is the case , then you have two options:
Wait for the next release of Xamarin.Forms they are working on being able to add stuff to the toolbar. (for example you can have your own back button)
You can make your own toolbar , just make sure to not show the Navigation bar on the NavigationPage, and make your own toolbar (e.g. a horizontal stacklayout (or flexlayout) where you place the title and the buttons you need . and has a background color .
I have tried to do custom renderer for the toolbar and it seems that it is not an easy job.

Why is this C# code executing out of sequence? Not ASYNC (at least I don't think it is)

Can anyone help me understand why my call to dialogservice executes after the CanNavigateAway function has returned its value? (My goal is to warn the user they are about to navigate away from a view without saving their changes. If they click OK, the navigation is allowed. I'm using MVVM Light.
When I step through the code, it does reach the dialog service, but then proceeds to the end of CanNavigateAway before creating the dialog. The CanNavigateAway method is called by OnNavigatingFrom.
public bool CanNavigateAway()
{
if (!changesSaved && Model.IsModified && !continueNavigation)
{
dialogService.ShowMessage("Are you sure you want to continue?",
"Confirmation",
buttonConfirmText: "Continue", buttonCancelText: "Discard",
afterHideCallback: (confirmed) =>
{
if (confirmed)
{
// User has pressed the "confirm" button.
// ...
continueNavigation = true;
}
else
{
// User has pressed the "cancel" button
// (or has discared the dialog box).
// ...
continueNavigation = false;
}
});
return continueNavigation;
}
}
Here is the OnNavigatingFrom method from the MVVM Light Bindable Page class:
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
var navigableViewModel = this.DataContext as INavigable;
if (navigableViewModel != null)
{
if (!navigableViewModel.CanNavigateAway())
{
e.Cancel = true;
}
}
}
I tried this a different way to get the dialog service out of the mix, but showConfirmationDialogAsync still does not seem to execute in time:
public bool CanNavigateAway()
{
continueNavigation = false;
if (!changesSaved && Model.IsModified && !continueNavigation)
{
showConfirmationDialogAsync();
return continueNavigation;
}
private async void showConfirmationDialogAsync()
{
continueNavigation = false;
ContentDialog noSaveConfirmation = new ContentDialog
{
Title = "Warning",
Content = "You have unsaved changes. Are you sure you want to leave this page without saving?",
PrimaryButtonText = "Leave without saving",
SecondaryButtonText = "Stay and finish"
};
ContentDialogResult result = await noSaveConfirmation.ShowAsync();
if (result == ContentDialogResult.Primary)
{
continueNavigation = true;
}
else if (result == ContentDialogResult.Secondary)
{
continueNavigation = false;
}
}
None of the solutions will work if you require a response from the user. The problem is that when the code is inside the navigation event handler, it is running on the UI thread and the user prompt runs asynchronously, so that the UI is free to present the dialog to the user. This however means that the event handler finishes before the user has a chance to respond.
However, you can use a workaround solution. Add a flag bool field like forceNavigation. Then inside the OnNavigatingFrom display the dialog to the user and set Cancel to true right away and display the user the confirmation dialog. If the user says yes, then set forceNavigaiton to true and trigger the navigation manually again. Now it will skip the confirmation part and navigate right away.
protected async override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
//if navigation is forced, skip all logic
if ( !forceNavigation )
{
var navigableViewModel = this.DataContext as INavigable;
if (navigableViewModel != null)
{
e.Cancel = true;
//display the dialog to the user, if he says yes, set
//forceNavigation = true; and repeat the navigation (e.g. GoBack, ... )
}
}
}

ContinuationManager resuming freezing WP8.1

I am using PickSingleFileAndContinue() method to pick the picture and resuming to my app. In overrided OnActivated() I call RestoreAsync() and after that calling ContinueFileOpenPicker() from ContinuationManager class:
var settingsPage = SimpleIoc.Default.GetInstance<SettingsViewModel>();
if (settingsPage != null && args is FileOpenPickerContinuationEventArgs)
{
settingsPage.ContinueFileOpenPicker(args as FileOpenPickerContinuationEventArgs);
}
To debug app I am using information from this page: https://msdn.microsoft.com/en-us/library/dn631755.aspx
After picking image from provider, application successfully calling ContinueFileOpenPicker with right arguments with StorageFile object, when I am continue to step over by step, at the and of last method at the constructor of the ViewModel I cannot continue to debug, because the app and sometimes VS2013 freezing. I can hold back and Swipe-down app, but whatever need to waiting for app. After that, app is crashing. Please, I cannot catch exception... Help. :(
Your SettingsViewModel should inherit IFileOpenPickerContinuable,
public class SettingsViewModel : Screen, IFileOpenPickerContinuable
Frames are in some cases associated with the View not the ViewModel.
Thus you should add a custom method for this to work:
Add in ContinuationManager.cs
internal void Continue(IContinuationActivatedEventArgs args, IFileOpenPickerContinuable filepickerPage)
{
if (args == null)
throw new ArgumentNullException("args");
if (this.args != null && !handled)
throw new InvalidOperationException("Can't set args more than once");
this.args = args;
this.handled = false;
this.id = Guid.NewGuid();
if (wabPage == null)
return;
switch (args.Kind)
{
case ActivationKind.PickFileContinuation:
if (filepickerPage != null)
{
filepickerPage.ContinueFileOpenPicker(args as FileOpenPickerContinuationEventArgs);
}
break;
case ActivationKind.PickSaveFileContinuation:
break;
case ActivationKind.PickFolderContinuation:
break;
case ActivationKind.WebAuthenticationBrokerContinuation:
break;
}
}
Ensure that the SettingsViewModel that is returned from
var settingsPage = SimpleIoc.Default.GetInstance<SettingsViewModel>();
is the same instance that called PickSingleFileAndContinue, otherwise it will not work, it will keep on suspending and waiting for something to return control.
Then in App.xaml.cs you can add:
protected override void OnActivated(IActivatedEventArgs e)
{
base.OnActivated(e);
// Add all of the Frame code
var continuationEventArgs = e as IContinuationActivatedEventArgs;
continuationManager = new ContinuationManager();
SettingsViewModel settingsPage = SimpleIoc.Default.GetInstance<SettingsViewModel>();
if (continuationEventArgs != null)
{
continuationManager.Continue(continuationEventArgs, settingsPage);
}
}
But should I repeat code from OnLaunched?
No, only the OnActivate code should be called, the rest should stay as is (but you could do whatever you want)

How to set the navigation URI while creating a secondary tile? (Windows Phone 8.1 - Universal)

Let's say I have a specific page, SecondaryTile.xaml. From this page I pin a secondary tile to the startscreen. Now if I tap on the secondary tile I want it to open the SecondaryTile.xaml.
In WP8.0 this was possible by setting the URI of Shell.Create. E.g.:
ShellTile.Create(new Uri("/SecondaryTile.xaml?Parameter=FromTile", UriKind.Relative), NewTileData);
}
But it looks like this is not supported anymore in WinRT.
I saw a sample that uses the parameter to launch (it fetches the parameter in the OnNavigatedTo on the Mainpage.xaml.cs), but with the new app behaviour the app is being suspended so OnNavigatedTo does not always trigger.
Hope someone can help.
Kind regards,
Niels
Shouldn't this be done as application logic?
Examine the LaunchActivatedEventArgs parameter within the OnLaunched method of your app's code-behind:
protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
ApplicationData.Current.LocalSettings.Values[Constants.APP_PARAMETERS] = args.Arguments;
// Do not repeat app initialization when already running, just ensure that
// the window is active
if (args.PreviousExecutionState == ApplicationExecutionState.Running)
{
Window.Current.Activate();
await ViewManager.Instance.LaunchView();
return;
}
Consider implementing some type of ViewManager for managing the startup view:
public class ViewManager
{
#region Singleton
private ViewManager()
{
}
static ViewManager _viewManager = null;
public static ViewManager Instance
{
get
{
if (_viewManager == null)
{
_viewManager = new ViewManager();
}
return _viewManager;
}
}
#endregion
public async Task LaunchView()
{
bool displaySubheader = false;
var displayBackbutton = false;
var arguments = ApplicationData.Current.LocalSettings.Values[Constants.APP_PARAMETERS] as string;
var argumentsExist = !string.IsNullOrEmpty(arguments);
if (!argumentsExist)
{
await UIServices.Instance.Load(typeof(HomePage), null, displaySubheader, displayBackbutton);
}
else
{
displaySubheader = true;
displayBackbutton = false;
await UIServices.Instance.Load(typeof(GroupPage), arguments, displaySubheader, displayBackbutton);
var groupId = new Guid(arguments);
await ReadPost(groupId);
}
}
.
.
.
Here's how I create secondary tiles:
SecondaryTile secondaryTile =
new SecondaryTile(group.GroupId.ToString(),
group.Name,
group.Name,
group.GroupId.ToString(),
TileOptions.ShowNameOnWideLogo,
new Uri("ms-appx:///Assets/Logo.png"),
new Uri("ms-appx:///Assets/WideLogo.scale-100.png"));
var successful = await secondaryTile.RequestCreateAsync();
You are not able to navigate directly to another page from the OnNavigatedTo event. However you can add the navigation as a queued event on your UI thread.
In your OnNavigatedTo eventhandler, check for the following (Your test might need to be a bit more sophisticated as not all eventualities are accounted for in this example, such as for example e.Parameters beeing null).
if (e.Parameter.ToString().Contains("something_from_secondary_tile_arguments") && (e.NavigationMode == NavigationMode.New))
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { Current.Frame.Navigate(typeof(Transactions), "data_for_your_sub_page"); });
}
Also, you need to specify the Arguments property when you create your SecondaryTile.
More info here: Not able to navigate to pages on Windows Metro App using c#

WatiN Handle Confirm dialog in Firefox

I found this code on SO to automatically dismiss a confirm dialog, but it is not working in Firefox.
The problem is, var windowButton = new WindowsEnumerator().GetChildWindows(window.Hwnd, w => w.ClassName == "Button"
&& new WinButton(w.Hwnd).Title == "OK").FirstOrDefault();
Always returns null. Is there another way to get the handle of the dialog button in firefox?
public class OKDialogHandler : BaseDialogHandler {
public override bool HandleDialog(Window window) {
var button = GetOKButton(window);
if (button != null) {
button.Click();
return true;
} else {
return false;
}
}
public override bool CanHandleDialog(Window window) {
return GetOKButton(window) != null;
}
private WinButton GetOKButton(Window window) {
var windowButton = new WindowsEnumerator().GetChildWindows(window.Hwnd, w => w.ClassName == "Button"
&& new WinButton(w.Hwnd).Title == "OK").FirstOrDefault();
if (windowButton == null)
return null;
else
return new WinButton(windowButton.Hwnd);
}
}
The controls on the Firefox alert() dialog are not enumerable. That is, they don't exist as separate windows like they do in IE. The best way to approach this is to create a new DialogHandler class that implements IDialogHandler. In the constructor, you can pass in the Firefox instance for which the dialog appears, and you can use the following codeto send JavaScript across to Firefox to manipulate the dialog:
FFDocument nativeDoc = firefox.NativeDocument as FFDocument;
// ClientPort has several WriteAndRead... functions,
// and takes a variable list of arguments for the script
// to be executed.
nativeDoc.ClientPort.WriteAndRead(script);
You can use the JavaScript below to click on the OK and Cancel buttons on an alert() or confirm() dialog.
private const string DialogIsConfirmScript = "typeof getWindows()[{0}].document.documentElement.getButton('accept') !== 'undefined' && typeof getWindows()[{0}].document.documentElement.getButton('cancel') !== 'undefined';";
private const string DialogIsAlertScript = "typeof getWindows()[{0}].document.documentElement.getButton('accept') !== 'undefined' && typeof getWindows()[{0}].document.documentElement.getButton('cancel') !== 'undefined' && getWindows()[{0}].document.documentElement.getButton('cancel').hidden;";
private const string ClickCancelButtonScript = "getWindows()[{0}].document.documentElement.getButton('cancel').click()";
private const string ClickOKButtonScript = "getWindows()[{0}].document.documentElement.getButton('accept').click()";
private const string WindowClassName = "MozillaDialogClass";
A more complete implementation, which wraps the native IE alert() and confirm() handling in a common interface and adds Firefox handling is available at http://pastebin.com/ZapXr9Yf

Categories