Programmatically click a button in Xamarin.Forms - c#

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.

Related

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

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.

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

How to know the input buffer of the serial port has information, in C#?

I am building a program in C# to be used in one of my course at a college to demonstrate how Asynchronous connections work using RS-232 and two computers connected together. My course is not about programming, but data networks, so the connectivity is what I am looking for.
picture 1 - sample layout of GUI using Visual Studio 2015
One of the features I want to implement in my program is to show how a Master-slave, simplex connection works (i.e. the program can choose between been a master to send input from the keyboard; or slave to only receive information and print it on a textbox).
What I have already is the capability of initializing the serial port with specific characteristics (baud rate, data bits, stop bits, etc). This features are selected using combo boxes from the GUI, and assigned to the port when the user clicks a button to "open the port".
What I don't know is how to create the "slave" part of the program. My idea of what I could do is, after you choose the program to be "slave", you open the port waiting for some sort of flag or event to trigger when the input buffer has data stored.
I've been reading several forums and I can't find anything similar to what I need. I have, however, tested multiple alternatives that I believed would bring me closer to what I need with little to no result. I come to ask for an idea of what I could be doing wrong, or suggestions on how to tackle this problem. The problematic lines are bolded (or 2 stars ( * ) ):
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;
namespace SerialCommTester
{
public partial class frmSerialComm : Form
{
static SerialPort _PuertoSerial;
public frmSerialComm()
{
InitializeComponent();
getAvailablePorts();
}
//---------------------------------my functions--------------------------------------
void getAvailablePorts()
{
string[] ports = SerialPort.GetPortNames();
cmbPortList.Items.AddRange(ports);
}
void activatePort()
{
//Note that all the combo boxes are named somewhat accordingly to what the information they are meant to display.
if (cmbPortList.Text != "" && cmbBaudRate.Text != "" && cmbParity.Text != "" && cmbStopBits.Text != "")
{
_PuertoSerial.PortName = cmbPortList.Text;
_PuertoSerial.BaudRate = Convert.ToInt32(cmbBaudRate.Text);
_PuertoSerial.RtsEnable = true;
_PuertoSerial.DtrEnable = true;
_PuertoSerial.DataBits = Convert.ToInt32(cmbDataBits.Text);
if (cmbParity.Text == "Even") { _PuertoSerial.Parity = Parity.Even; }
else if (cmbParity.Text == "Odd") { _PuertoSerial.Parity = Parity.Odd; }
else if (cmbParity.Text == "Space") { _PuertoSerial.Parity = Parity.Space; }
else if (cmbParity.Text == "Mark") { _PuertoSerial.Parity = Parity.Mark; }
else { _PuertoSerial.Parity = Parity.None; }
if (cmbStopBits.Text =="2") { _PuertoSerial.StopBits = StopBits.Two; }
else if (cmbStopBits.Text == "1.5") { _PuertoSerial.StopBits = StopBits.OnePointFive; }
else { _PuertoSerial.StopBits = StopBits.One; }
if (cmbHandShake.Text == "Software Flow Control") { _PuertoSerial.Handshake = Handshake.XOnXOff; }
else if (cmbHandShake.Text == "Hardware Flow Control") { _PuertoSerial.Handshake = Handshake.RequestToSend; }
else { _PuertoSerial.Handshake = Handshake.None; }
_PuertoSerial.ReadTimeout = 500;
_PuertoSerial.WriteTimeout = 500;
_PuertoSerial.Open();
//in my understanding, this line of code is needed to handle data being received. Does it trigger a flag or something?
**_PuertoSerial.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);**
}
else
{
txtRecieve.Text = "Input selection missing 1 or more characteristics";
}
}
**
private static void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
SerialPort testing = (SerialPort)sender;
txtRecieve.AppendText(testing.ReadExisting()); //txtRecieve cannot be reached within this function. It indicates the following error: "An object reference is required for the non-static field, method, or property 'frmSerialComm.txtRecieve'
}
**
void enableDisableGUI(bool[] input)
{
grpConnection.Enabled = input[0];
grpCharacteristics.Enabled = input[1];
btnOpenPort.Enabled = input[2];
btnClosePort.Enabled = input[3];
txtSend.Enabled = ((cmbControlMasterSlave.Text == "Slave") ? false : true);
}
//----------------------------C# objects / functions--------------------------------------
private void btnOpenPort_Click(object sender, EventArgs e)
{
try
{
_PuertoSerial = new SerialPort();
activatePort();
}
catch(Exception ex)
{
MessageBox.Show(ex.Message, "Message ", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
bool[] format = { false, false, false, true};
enableDisableGUI(format);
}
private void btnClosePort_Click(object sender, EventArgs e)
{
_PuertoSerial.Close();
bool[] format = { true, true, true, false};
enableDisableGUI(format);
}
private void txtSend_KeyPress(object sender, KeyPressEventArgs e)
{
_PuertoSerial.Write(e.KeyChar.ToString()); //this is how I send data through the serial port.
}
private void btnClearTxts_Click(object sender, EventArgs e)
{
txtRecieve.Clear();
txtSend.Clear();
}
} //class closes
} //program closes
I am not an experienced programmer, I just want to create something useful for my students. Any constructive criticism will be highly appreciated.
I don't have any definitive answers for you. You code looks like it should provide what you need once you get past the two possible glitches.
I think you should attach your SerialDataReceivedEventHandler BEFORE
you call _PuertoSerial.Open().
It may have no effect since event handlers can normally be enabled/disabled dynamically, but I base the advice on the following comment taken from the .Net source code for SerialPort on MSDN.
// all the magic happens in the call to the instance's .Open() method.
// Internally, the SerialStream constructor opens the file handle, sets the device control block and associated Win32 structures, and begins the event-watching cycle.
The "object reference" error might be resolved by removing the
static modifier from your DataReceivedHandler. If not, or if that
static modifier is necessary for some reason, then perhaps the
txtRecieve control has a private modifier which needs to be changed
to internal or public. You should be able to use Visual Studio in
debug mode to step into the InitializeComponent() method and see
where txtRecieve is being instantiated.
Well, I believe that I needed to read more. This is how I solved the problem (if this is not the real solution, at least is working for now):
I moved the "SerialDataReceivedEventHandler" line before the _PuertoSerial.open();
I followed the suggestions from this article:
https://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k(EHInvalidOperation.WinForms.IllegalCrossThreadCall);k(TargetFrameworkMoniker-.NETFramework,Version%3Dv4.5.2);k(DevLang-csharp)&rd=true
So my funtions (one existings + a new one) look like this:
void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
printReceivedText(_PuertoSerial.ReadExisting());
}
private void printReceivedText(string text)
{
if (this.txtSend.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(printReceivedText);
this.Invoke(d, new object[] { text });
}
else
{
this.txtRecieve.AppendText(text);
_PuertoSerial.DiscardInBuffer();
}
}
For now seems to be working fine. The final testing will come when I connect another terminal and see the program interacting with each other.

Done button not firing Completed event on Xamarin Entry

After I've added the Done button on iOS numeric keyboard in Xamarin Forms, I encountered another problem: the Done button not firing Completed event (like return button does).
On my way to implement this, I found the following code on Xamarin Forums:
using System;
using System.Drawing;
using System.Reflection;
using Xamarin.Forms.Platform.iOS;
using Xamarin.Forms;
using UIKit;
using KeyboardTest.iOS;
using KeyboardTest;
[assembly: ExportRenderer(typeof(EntryDone), typeof(EntryDoneRenderer))]
namespace KeyboardTest.iOS
{
public class EntryDoneRenderer : EntryRenderer
{
// used to cache the MethodInfo so we only have the reflection hit once
private MethodInfo baseEntrySendCompleted = null;
public EntryDoneRenderer ()
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged (e);
if (this.Element.Keyboard == Keyboard.Numeric)
{
// only want the Done on the numeric keyboard type
UIToolbar toolbar = new UIToolbar (new RectangleF (0.0f, 0.0f, (float)Control.Frame.Size.Width, 44.0f));
var doneButton = new UIBarButtonItem (UIBarButtonSystemItem.Done, delegate {
this.Control.ResignFirstResponder ();
Type baseEntry = this.Element.GetType();
if(baseEntrySendCompleted==null)
{
// use reflection to find our method
baseEntrySendCompleted = baseEntry.GetMethod("SendCompleted",BindingFlags.NonPublic|BindingFlags.Instance);
}
try
{
baseEntrySendCompleted.Invoke(this.Element,null);
}
catch (Exception ex)
{
// handle the invoke error condition
}
});
toolbar.Items = new UIBarButtonItem[] {
new UIBarButtonItem (UIBarButtonSystemItem.FlexibleSpace),
doneButton
};
this.Control.InputAccessoryView = toolbar;
}
}
}
}
I don't know why, but I receive the error:
System.NullReferenceException: Object reference not set to an instance of an object
at myGame.iOS.DoneEntryRenderer.<OnElementChanged>m__0 (System.Object , System.EventArgs ) [0x0005d] in /Users/silviu/Projects/myGame/iOS/DoneEntry.cs:37
On that line I have the code:
baseEntrySendCompleted.Invoke(this.Element, null);
I’ve tried to debug the problem and I found that the SendCompleted method does not exist, but I don’t understand how to solve this problem in the lastest version of Xamarin, because I think on the moment when that guy posted the code, worked.
Thanks!
SendCompleted() was actually added for IEntryController so you don't need to use reflection for this any longer. In fact it appears that way no longer works. Just call SendCompleted() directly from your button press like so.
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
var toolbar = new UIToolbar(new CGRect(0.0f, 0.0f, Control.Frame.Size.Width, 44.0f));
toolbar.Items = new[]
{
new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace),
new UIBarButtonItem(UIBarButtonSystemItem.Done, delegate {
Control.ResignFirstResponder();
((IEntryController)Element).SendCompleted();
})
};
this.Control.InputAccessoryView = toolbar;
}

Parameter count mismatch. - Threading

I am trying to create a multi-client / server chat small application using WPF but I have some problems. Unfortunately when I press the Connect button the program crashes.
Well, I done that so far with the client program(with the thread):
public delegate void UpdateText(object txt);
I got that method:
private void UpdateTextBox(object txt)
{
if (msg_log.Dispatcher.CheckAccess())
{
Dispatcher.Invoke(new UpdateText(UpdateTextBox),txt);
}
else
{
msg_log.Dispatcher.Invoke(new UpdateText(UpdateTextBox), txt);
}
}
And I am using a Button_Click event to connect to the server like that:
private void connect_Click(object sender, RoutedEventArgs e)
{
if ((nick_name.Text == "") || (ip_addr.Text == "") || (port.Text == ""))
{
MessageBox.Show("Nick name, IP Address and Port fields cannot be null.");
}
else
{
client = new Client();
client.ClientName = nick_name.Text;
client.ServerIp = ip_addr.Text;
client.ServerPort = port.Text;
Thread changer = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(UpdateTextBox));
changer.Start();
client.OnClientConnected += new OnClientConnectedDelegate(client_OnClientConnected);
client.OnClientConnecting += new OnClientConnectingDelegate(client_OnClientConnecting);
client.OnClientDisconnected += new OnClientDisconnectedDelegate(client_OnClientDisconnected);
client.OnClientError += new OnClientErrorDelegate(client_OnClientError);
client.OnClientFileSending += new OnClientFileSendingDelegate(client_OnClientFileSending);
client.OnDataReceived += new OnClientReceivedDelegate(client_OnDataReceived);
client.Connect();
}
}
Please note that the OnClient* events are like private void client_OnDataReceived(object Sender, ClientReceivedArguments R) { UpdateTextBox(R.ReceivedData); }
So these events should write some text like "Connected" into the msg_log TextBox
PS. The txt object used to be a string variable but I change it since ParameterizedThreadStart only accepts objects as parameters as I know.
Any help would be greatly appreciated!
Thanks in advance,
George
EDIT: Edited the UpdateTextBox method as Abe Heidebrecht suggested.
There are a few things wrong with your Invoke calls.
You don't need to create an object array to pass the parameters.
Passing DispatcherPriority.Normal is redundant (Normal is default).
You aren't passing any parameters to the second Invoke method (which is probably where your error is happening).
It should look like this:
private void UpdateTextBox(object txt)
{
if (msg_log.Dispatcher.CheckAccess())
{
Dispatcher.Invoke(new UpdateText(UpdateTextBox), txt);
}
else
{
msg_log.Dispatcher.Invoke(new UpdateText(UpdateTextBox), txt);
}
}
EDIT For StackOverflowException
This will cause a StackOverflowException because you are calling your method in an infinite loop. This is happening because your method is simply calling itself over and over again.
What Dispatcher.Invoke does is invoke the passed delegate on the thread that owns the Dispatcher. Just because the msg_log may have a different dispatcher, when you were calling UpdateTextBox, you were passing a delegate to the current method, which causes the infinite loop.
What you really need to do is call a method on the msg_log object, like so:
private void UpdateTextBox(object txt)
{
if (msg_log.Dispatcher.CheckAccess())
{
if (txt != null)
msg_log.Text = txt.ToString();
}
else
{
msg_log.Dispatcher.Invoke(new UpdateText(UpdateTextBox), txt);
}
}

Categories