Getting validateMenuItem: to fire for NSMenuItem using Xamarin-generated action - c#

I would like to add additional menu items to my context menu. Ideally the items are enabled using validateMenuItem:
[Action("validateMenuItem:")]
public bool ValidateMenuItem(NSMenuItem item)
{
_logger.DebugFormat("Validating {0} menu item with Action {1}", item.Title, item.Action.Name);
var target = item.Target;
var menuItem = ViewModel.ContextMenu.Where(x => x.Title == item.Title).FirstOrDefault();
if (menuItem != null) {
return menuItem.Command.CanExecute();
}
return false;
}
per https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/MenuList/Articles/EnablingMenuItems.html. If I manually create an action this gets called, but if I assign an eventhandler like so:
var nsMenuItem = new NSMenuItem(menuItem.Title,
(sender, e) =>
{
menuItem.Command.ExecuteAsync();
});
nsMenuItem.Target = this;
validateMenuItem: does not get called. The Action that is assigned using this method is __monomac_internal_ActionDispatcher_activated: from https://github.com/xamarin/xamarin-macios/blob/master/src/AppKit/ActionDispatcher.cs (help me please Rolf Bjarne Kvinge). Since I do not have this action in my class (I think), validateMenuItem is never called and my menu item is never activated. How can I make this work?
Update. If I add this to my view controller,
[Action("__monomac_internal_ActionDispatcher_activated:")]
public void MonomacInternalAction(NSObject sender)
{
}
validateMenuItem: gets called for the new items. However, the event handler is replaced by this function. (This problem might not be solvable!) This might be an Export vs Action issue - I see
const string skey = "__monomac_internal_ActionDispatcher_activated:";
[Preserve, Export (skey)]
public void OnActivated (NSObject sender)
{
EventHandler handler = Activated;
if (handler != null)
handler (sender, EventArgs.Empty);
}
Update 2. just found https://bugzilla.xamarin.com/show_bug.cgi?id=51343
Update 3. I can cheat the validateMenuItem by using
public override bool RespondsToSelector(ObjCRuntime.Selector sel)
{
if (sel.Name.Contains("__monomac_internal_ActionDispatcher_activated")) {
return true;
}
return base.RespondsToSelector(sel);
}
now if I could only find a way of calling the original event!

This issue is a bug in the Xamarin.Mac implementation - https://bugzilla.xamarin.com/show_bug.cgi?id=51343. I ended up with a workaround. Instead of using an eventhandler or a separate action for each menu item (couldn't figure out how to register), I created a single action to handle all menu items.
[Action("contextAction:")]
public void contextAction(NSObject sender)
{
if (sender is NSMenuItem)
{
var nsMenuItem = (NSMenuItem)sender;
var wrapper = nsMenuItem.RepresentedObject as NSObjectWrapper;
if (wrapper != null) {
var menuItem = wrapper.Context as MenuItem;
if (menuItem != null) {
menuItem.Command.ExecuteAsync();
}
}
}
}
then
foreach (var menuItem in ViewModel.ContextMenu)
{
var selector = new ObjCRuntime.Selector("contextAction:");
var nsMenuItem = new NSMenuItem(menuItem.Title, selector, "");
nsMenuItem.RepresentedObject = new NSObjectWrapper(menuItem);
NSObjectWrapper comes from Monotouch: convert an Object to NSObject.

Related

Unable to hide Android Keyboard on Android 9

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.

tying multiple events to one handler:a function with 2 object params

This is my handler function:
protected static void textChange(object sender,Label labe1, EventArgs e)
{
var text = sender as TextBox;
if (text.Text != "")
labe1.Visible = false;
else
labe1.Visible = true;
}
Im trying to do this:
this.textBox1.Click += new System.EventHandler(textChange);
for multiple textboxes.I have tried making both params as objects and then interpreting them as label/textbox inside the function using a variable,ive tried making both of them label/textbox correspondingly in the params declaration.The only way it worked was by having only one object parameter while I need 2.
Assuming you're trying to associate each text box with a different label, you'll need to write a method that constructs an EventHandler for the relevant label, e.g.
public EventHandler CreateVisibilityHandler(Label label)
{
return (sender, args) => label.Visible = ((TextBox) sender).Text == "";
}
Then you can use:
textBox1.Click += CreateVisibilityHandler(label1);
textBox2.Click += CreateVisibilityHandler(label2);
// etc

Programmatically click a button in Xamarin.Forms

I have 2 buttons on a xamarin form,
scannerButton and checkOrderButton
scannerButton opens the scanner page, scans a QRCode and populates it into a order entry field
checkOrderButton reads whatever is in the order entry field and processes validations and sends it to server for verification
what I want - is to call the checkOrderButton.Click from within the scannerButton.Click - after it has scanned the text
code:
private async void scanCameraButton_Clicked(object sender, EventArgs e)
{
var options = new ZXing.Mobile.MobileBarcodeScanningOptions();
options.PossibleFormats = new List<ZXing.BarcodeFormat>() {
ZXing.BarcodeFormat.QR_CODE,ZXing.BarcodeFormat.EAN_8, ZXing.BarcodeFormat.EAN_13
};
var scanPage = new ZXingScannerPage(options);
scanPage.OnScanResult += (result) =>
{
//stop scan
scanPage.IsScanning = false;
Device.BeginInvokeOnMainThread(() =>
{
//pop the page and get the result
Navigation.PopAsync();
orderNoEntry.Text = result.Text;
});
//invoke checkOrderButton.Click here
};
what would be the best approach to do this?
one alternate is to dump all the functionality from checkOrderButton.Click handler into a function and then call that function from both button clicks, but I'm interested in learning how I can invoke the click event programmatically
What I would do is having a viewmodel with a command that performs whatever logic would be done when pressing the button .
Then bind the Command property of the button to the command property in the ViewModel.
At this stage you will have a command that you can execute programmatically just as if you called "Button.Click()" if there will be such thing .
I wrote an extension method for this purposes. This not only works in Xamarin-Forms-projects but also in WPF projects.
using System;
using System.Reflection;
using System.Windows;
using System.Windows.Input;
#if !NETSTANDARD
using System.Windows.Controls;
#else
using Xamarin.Forms;
#endif
public static class ButtonExtensions
{
public static void PerformClick(this Button sourceButton)
{
// Check parameters
if (sourceButton == null)
throw new ArgumentNullException(nameof(sourceButton));
// 1.) Raise the Click-event
#if !NETSTANDARD
sourceButton.RaiseEvent(new RoutedEventArgs(System.Windows.Controls.Primitives.ButtonBase.ClickEvent));
#else
sourceButton.RaiseEventViaReflection(nameof(sourceButton.Clicked), EventArgs.Empty);
#endif
// 2.) Execute the command, if bound and can be executed
ICommand boundCommand = sourceButton.Command;
if (boundCommand != null)
{
object parameter = sourceButton.CommandParameter;
if (boundCommand.CanExecute(parameter) == true)
boundCommand.Execute(parameter);
}
}
#if NETSTD
private static void RaiseEventViaReflection<TEventArgs>(this object source, string eventName, TEventArgs eventArgs) where TEventArgs : EventArgs
{
var eventDelegate = (MulticastDelegate)source.GetType().GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(source);
if (eventDelegate == null)
return;
foreach (var handler in eventDelegate.GetInvocationList())
{
#if !(NETSTANDARD1_6 || NETSTANDARD1_5 || NETSTANDARD1_4 || NETSTANDARD1_3 || NETSTANDARD1_2 || NETSTANDARD1_1 || NETSTANDARD1_0)
handler.Method?.Invoke(handler.Target, new object[] { source, eventArgs });
#else
handler.GetMethodInfo()?.Invoke(handler.Target, new object[] { source, eventArgs });
#endif
}
}
#endif
}
In fact you should pass two parameters, so it's not just supposed to be Button_Click(), you should call it like this: Button_Click(null, null) since it needs sender and e as two required parameters, take a look at the method definition: Button_Click(object sender, EventArgs e){..}
As Sami already mentions I would suggest to extract the functionality of the checkOrderButton.Click to a separate method so you can call that some method from the checkOrderButton.Click as wel as the scannerButton.Click.

How to override event?

I have a class that inherits from ContextMenuStrip. I use the OnOpening event to enable/disable menu items as required:
protected override void OnOpening(CancelEventArgs e)
{
base.OnOpening(e);
var count = bindingSource.Count;
var item = (T)bindingSource.Current;
removeMenuItem.Enabled = item != null;
removeAllMenuItem.Enabled = count > 0;
}
The above works fine for standard requirements. However, now I have a situation where I want to override the above method within the parent form.
So I tried to do the following:
myContextStrip.Opening += (s, ea) =>
{
var current = bindingSource.Current as MyItem;
myContextStrip.RemoveMenuItem.Enabled = !current?.IsDeleted ?? true;
myContextStrip.RemoveAllMenuItem.Enabled = bindingSource.Count > 0;
};
Implementing the above doesn't work as expected because the class 'OnOpening' method is still being called after the above is called.
Is there a better way to override the OnOpening event within the parent form and stop it from further calling the code in the derived contextmenu class?

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)

Categories