Make a method wait for right click to go to next step (C# form) - c#

well, I'm writing a bot that will use certain coordinates on screen and then will simulate 15 clicks on them (every click with different coordinates). I already made it work with coordinates I entered manually on the code but now I need a way to record those coordinates. What i wanted to do is: the users press a button, then the program shows a messagebox saying "right click the main menu", the user right clicks that and those coordinates will be recorded on an array, then the program will show a second messagebox asking to right click the next button and so... My problem is that I don't know how to make the method wait for the user to right click to continue.
I tested my program by making an event that would trigger everytime I right click and show the coordinates in a messagebox, using a UserActivityHook class with contains the event OnMouseActivity:
UserActivityHook actHook;
void MainFormLoad(object sender, System.EventArgs e)
{
actHook = new UserActivityHook();
// crate an instance with global hooks
// hang on events
actHook.OnMouseActivity+=new MouseEventHandler(MouseMoved);
}
public void MouseMoved(object sender, MouseEventArgs e)
{
if (e.Clicks > 0)
{
if (e.Button.Equals(MouseButtons.Right))
{
MessageBox.Show("X:" + e.X + " Y:" + e.Y);
}
}
}
I've trying to do something like:
private void button1_Click(object sender, EventArgs e)
{
RecordMacro(cords, 1);
}
public void RecordMacro(int coordinates[][], int slotnumber){
MessageBox.show("Right click main menu");
//saves coordinates on [0][0] and [0][1]
WaitForRightClickAndSaveCords(coordinates[][]);
MessageBox.show("Right click resupply button");
//saves coordinates on [1][0] and [1][1]
WaitForRightClickAndSaveCords(coordinates[][]);
...
}
I'm still a newbie and this is my first question in StackOverflow (I usually find an answer browsing here and don't have the need to ask myself) so I'll gladly accept any critics.

This is easiest to implement using C# 5.0's asynchrony model. We'll start out by creating a method that will generate a Task that will be completed when your conditions are met. It will do this by creating a TaskCompletionSource, adding a handler to the event, and marking the task as completed in the handler. Throw in some boilerplate code to make sure the handler is removed when done, return the Task from the completion source, and we're set:
public static Task<Point> WhenRightClicked(this UserActivityHook hook)
{
var tcs = new TaskCompletionSource<Point>();
MouseEventHandler handler = null;
handler = (s, e) =>
{
if (e.Clicks > 0 && e.Button == MouseButtons.Right)
{
tcs.TrySetResult(new Point(e.X, e.Y));
hook.OnMouseActivity -= handler;
}
};
hook.OnMouseActivity += handler;
return tcs.Task;
}
Now you can write:
public async void RecordMacro(int[][] coordinates, int slotnumber)
{
MessageBox.Show("Right click main menu");
Point mainMenuPosition = await actHook.WhenRightClicked();
MessageBox.Show("Right click resupply button");
Point resupplyButtonPosition = await actHook.WhenRightClicked();
}

There are a myriad number of ways to make this work, none of which you should remotely do. The reason is, that assuming you managed to stop execution of the thread with WaitForRightClick, you would be blocking the UI thread!
By doing that, you prevent the user from being able to click on the element you want (among lots of other reasons to never block the UI thread).
You could thread it or use asynchornous methods, as Servy suggests. This blocks the method (or executes it asynchronously) without blocking the UI thread itself.
While more complex, you could also queue up a bunch of object representing a "ClickTarget". Then, you would listen on the right-click event and record the associated coordinates with the current ClickTarget, dequeue to get the next instruction, and so on.
The complete code would be too long for StackOverflow, but to give you some ideas:
public class ClickTarget
{
Point Coordinates {get; set;}
String TargetName {get; set;}
}
Queue<ClickTarget> clickTargets;
//Obviously you instantiate/populate this somewhere
private void onRightClick(...)
{
ClickTarget target = clickTargets.Dequeue();
target.Coordinates = clickLocation;
MessageBox.Show("Please click on " + clickTargets.Peek().TargetName);
}

Related

Need to click button twice to have an effect

The application is a machine control, so it needs access to ui to show status etc. (I know, goes against the recommendation to separate UI and work code, but it is what it is, at least for now). The issue boils down to this: When one button event handler is not finished, another button needs to be clicked twice. First click gives the focus to the button, next click fires the event.
Here is the issue simplified to extreme. There are two buttons and a label. Stop button needs two clicks to stop the machine:
bool Stop = true;
private void Start_button_Click(object sender, EventArgs e)
{
RunMachine();
}
private void Stop_button_Click(object sender, EventArgs e)
{
Stop = true;
}
private void RunMachine()
{
Stop = false;
Status_label.Text = "Running";
do
{
Application.DoEvents();
Thread.Sleep(50);
}
while (!Stop);
Status_label.Text = "Stopped";
}
How can I make the button to react to the first click?
DoEvents() is bad. Don't use it.
If you have to use it (e.g. as workaround), then you are adding technical debt and likely to pay in the future, similar to your case.
A better approach is to run work inside the task and use cancellation token, but in your case the minimum modification required is this (add async modifier to a method):
while (!Stop)
{
await Task.Delay(50);
// or
await Task.Run(() => Thread.Sleep(50));
}
The UI should be responsive now.
The latter is simulating synchronous code, put it instead of Sleep, don't forget to invoke if there you have to modify UI.
Thank you! I wasn't aware of the implications of Doevents, and using async and await is just as simple. I added a counter to show myself that the toy example is doing what I think it is. To make the answer complete and to help other noobs like me that might search answers for the same issue, here is the full example again. This works as wanted (stops with one click) and doesn't leave the RunMachine() running if the main form is closed without clicking stop. (My real application has enough code in the form closing event to prevent that, but I certainly wasn't aware of the trap.)
bool Stop = true;
private async void Start_button_Click(object sender, EventArgs e)
{
await RunMachine();
}
private void Stop_button_Click(object sender, EventArgs e)
{
Stop = true;
}
internal async Task RunMachine()
{
Status_label.Text = "started";
Stop = false;
int i=0;
do
{
await Task.Delay(500);
Status_label.Text = i.ToString();
i++;
} while (!Stop);
Status_label.Text = "Stopped";
}

create and move a point on canvas at once

I got a Problem with creating and moving a point (an ellipse) in WPF MVVM.
Right now i have a RelayCommand which calls my create point handler in my vm which creates a command and executes it:
private void CreatePointHandler(MouseEventArgs e)
{
AddConnectionPointCommand addConnectionPointCommand = new AddConnectionPointCommand(this, e);
PetriNetViewModel.ExecuteCommand(addConnectionPointCommand);
}
Furthermore for an already existing point, I got a Move handler aswell (in another vm tho):
public void MovePointHandler(ConnectionPoint pointMoved, Point oldLocation, Point newLocation)
{
Vector move = new Vector(newLocation.X - oldLocation.X, newLocation.Y - oldLocation.Y);
PetriNetViewModel.ExecuteCommand(new MoveDragCanvasElementsCommand(new ConnectionPoint[] { pointMoved }, move));
}
Adding and moving a point afterwards is just working as expected.
Now i want to give the user the possibility to add and move a point in one step. In my CreatePointHandler i can figure out if the left mouse buttin it still pressed like this:
if (e.LeftButton == MouseButtonState.Pressed) {
}
but how would I move the point now? The MovePointHandler is called by an event in the codebehind (I know this shouldnt be done in mvvm, but my collegues and I think it's ok if you don't have too much code in it), which is also passing an ElementsMovedEventArgs which I dont have here.
thanks for your help in advance.
It's hard to say without seeing your codebehind that calls these handlers.
I would have thought you should have a concept of a SelectedPoint, at which you can always check if there is an intended drag happening when the mouse is moved.
i.e.
private ConnectionPoint SelectedPoint { get; set; }
private void OnMouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
// dragging something.. check if there is a point selected
if (SelectedPoint != null)
{
_viewModel.MovePointHandler(SelectedPoint, _oldLocation, _newLocation);
}
}
}
Then, as part of your CreatePointHandler, you immediately set the newly created instance to the SelectedPoint, until a MouseUp is detected.
private void ExecuteAddConnectionPointCommand()
{
// standard logic...
ConnectionPoint addedPoint = ...
SelectedPoint = addedPoint;
}
The specifics of the implementation will likely change depending on your architecture, but hopefully you get the point.

c# button click queueing

I have a button click event handler with a switch case in it that controls multiple buttons in one event handler.
I need to use a queue because while one button is clicked and doing some processing, second button click won't interfere with the first button click, but added to the queue. I don't want to use .enabled=false; because it'll discard the second click completely, and I'm currently editing someone's software at work so I don't want to break things that I don't know, so what are you suggesting?
The best idea, I think, is to create a producer/consumer queue.
Another question is explaining this technique.
Basically, the idea is to have a worker thread that will consume a queue to get the job to do, while other thread produce job by queuing operation in the queue.
I did succeed this with System.Collections.Queue
The code is :
private Queue<Button> Button_Queue = new Queue<Button>();
private bool isProcessing = false;
private void Button_Click((object sender, EventArgs e){
if(isProcessing){
Button_Queue.Enqueue(this);
}
else
{
isProcessing = true;
// code here
isProcessing = false;
while(Button_Queue.Count > 0){
Button_Queue.Dequeue().PerformClick();
}
}
of course mine is slightly different from this because I need to pass some variables and my click method is modified for this.
Dirty, but simple solution.
public partial class DataRefresh : Form //DataRefresh is just "some form"
{
...
...
public DateTime ClickTime; //Time when click is processed by system
public DateTime LastExecutionRunTime = DateTime.MinValue; //Time when the all the click code finish
private void buttonDataRefresh_Click(object sender, EventArgs e)
{
ClickTime = DateTime.Now;
if (ClickTime.Subtract(LastExecutionRunTime).TotalSeconds < 5 )
{
//It will keep returning - hopefully until all events in que are satisfied
return;
}
//Long running code
//Importing whole table from remote DB
...
...
//End of the Long running code
LastExecutionRunTime = DateTime.Now;
}
}

How to update a text box continously without halting the button in windows form application C#?

I need to update my text box continuously after clicking the button but the button should perform its remaining task as it is.
simple is that when click event is performed then Text box should not wait for the completion of click event but to start updating its text continuously.
sample code
using System.threading;
namespace name
{
public class sA
{
public void th()
{
textbox.invoke(new MethodInvoke(()=> textbox.AppendText("hello\n")));
}
private void Button1Click(object sender, EventArgs e)
{
thread cThread=new thread(th);
cThread.start();
while(true)
{
// do any thing
}
}
}
}
Important :: when it performs the event " Cthread.start();" text box should immediately start updating the text while the remaining functions of click event like "while loop" should perform in parallel.
IF this is inside Windows Forms.. then add Application.DoEvents(); anywhere in the loop
e.g.
private void Button1Click(object sender, EventArgs e)
{
thread cThread=new thread(th);
cThread.start();
while(true)
{
// do any thing
textbox.Invalidate();
Application.DoEvents(); // Releases the current thread back to windows form
// NOTE Thread sleep different in Application.DoEvents();
//Application.DoEvents() is available only in System.Windows.Forms
}
}
Hope this help you although late.. :)
Your while(true) block has to happen on another thread as well.
Right now its blocking the UI thread from performing any updates.
Method th() is running on a background thread but the call to Invoke can't run until the UI thread is available again.
If I understood your question correctly, you need to keep updating the TextBox's text while the button click procedure is running inside it's "while" loop. You didn't really specify where will the textbox be updated from, but I will assume that it is coming from the code inside your "while" loop.
As "akatakritos" has stated, your while loop inside the button click is the reason why your application is halting. That happens because the while loop is blocking the User Interface (UI) Thread.
What you should be doing is moving the code inside your "while" loop to run inside a different thread, and use the button click to start this new thread.
Here is a way to do this, maybe not the best, but it will do what you need:
Create a new class:
public class ClassWithYourCode
{
public TextBox TextBoxToUpdate { get; set; }
Action<string> updateTextBoxDelegate;
public ClassWithYourCode()
{ }
public void methodToExecute()
{
bool IsDone = false;
while (!IsDone)
{
// write your code here. When you need to update the
// textbox, call the function:
// updateTextBox("message you want to send");
// Below you can find some example code:
for (int i = 0; i < 10; i++)
{
Thread.Sleep(1000);
updateTextBox(string.Format("Iteration number: {0}", i));
}
// Don't forget to set "IsDone" to "true" so you can exit the while loop!
IsDone = true;
}
updateTextBox("End of method execution!");
}
private void updateTextBox(string MessageToShow)
{
if (TextBoxToUpdate.InvokeRequired)
{
updateTextBoxDelegate = msgToShow => updateTextBox(msgToShow);
TextBoxToUpdate.Invoke(updateTextBoxDelegate, MessageToShow);
}
else
{
TextBoxToUpdate.Text += string.Format("{0}{1}", MessageToShow, Environment.NewLine);
}
}
}
and, inside your button1_Click method, you can add the following code:
private void button1_Click(object sender, EventArgs e)
{
ClassWithYourCode myCode = new ClassWithYourCode();
myCode.TextBoxToUpdate = textBox1;
Thread thread = new Thread(myCode.methodToExecute);
thread.Start();
}
Now, your "while" loop is executing inside a new thread and, whenever you need to update the textbox, you do so from the UI thread, because you cannot update Windows Forms controls from a thread other than the UI thread.

C# loop while mousedown button pressed

I'd like to use loop while left mousebutton is pressed:
private void Loop_MouseDown(object sender, MouseEventArgs e)
{
while (e.Button==MouseButtons.Left)
{
//Loop
}
}
I can't use solution from this thread:
C# how to loop while mouse button is held down
because I'm sending via RS232 data and using timer with it's own interval doesn't work. Also any solution from this topic doesn't work for me.
It can't also work one like here:
if (e.Button == MouseButtons.Left)
{
//loop
}
This solution also doesn't work:
bool isLooping = false;
//on mouse down
private void myControl_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e) {
isLooping = true;
runLoop();
}
//on mouse up event
private void myControl_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e) {
isLooping = false;
}
//This is the main loop you care about. Put this in your application
//This should go in its own thread
void runLoop() {
while (isLooping) {
//do stuff
}
}
because calling runLoop would block the thread, and so the MouseUp event would never fire.
So how to make it work correctly?
Use a BackGroundWorker. Perfect for your problem.
Put the loop function in the worker and start / stop the worker on mouse events.
If using a timer won't work, you'll need to send the data on a different thread, and signal that thread from the MouseUp handler.
The correct way to do this would be to put the rs-232 send function into a separate thread so the UI will remain responsive, then you can start and stop it when the mouse events change.
This page might be useful:
http://www.yoda.arachsys.com/csharp/threads/winforms.shtml
These scenarios are very complicated to implement - see your handlers and boolean variables for storing the state.
I would suggest to use Reactive Extensions.
Edit:
It will probably be slightly over-engineered (I don't know if this is the only scenario Elfoc wants to implement). In Rx you can create observable sequence of events
var mouseDown = Observable.FromEvent<MouseButtonEventArgs>(source, "MouseDown");
var mouseUp = Observable.FromEvent<MouseButtonEventArgs>(image, "MouseUp");
var mouseMove = from evt in Observable.FromEvent<MouseEventArgs>(image, "MouseMove")
select evt.EventArgs.GetPosition(this);
use LINQ-to-Rx to query and filter the events
var leftMouseDown = from evt in mouseDown
where evt.LeftButton == MouseButtonState.Pressed
select evt;
and compose it using Rx operators - until any mouse up event is raised take all the positions while left mouse is down
var q = from position in leftMouseDown
from pos in mouseMove.Until(mouseUp)
select new { X = pos.X - imageOffset.X, Y = pos.Y - imageOffset.Y };
Finally, subscribe to the observable sequence of positions and do your stuff
q.Subsribe(value => { ... });
Slightly modified from the code here.

Categories