Await for the user to click on one control within multiple controls [duplicate] - c#

This question already has answers here:
Is it possible to await an event instead of another async method?
(11 answers)
Closed 2 years ago.
This question starts with the same words of a bunch of others... "I'm trying to do a card game in c#", i've seen a lot of similar questions, but no one is actually solving my problem.
I'll introduce my problem with a little scenario:
Player A plays card X with target B
Player B needs to choose a countercard Y or pass
Player B view contains his cards and each card is represented by a
picturebox.
To continue the game i need the controller of player B to await until he clicks on card
of type Y, or to pass the turn.
To me, the easiest way to do something like this is faking await using booleans. It's easy to record which was the last card played (E.g X) and then wait until the card.Click event is triggered on a card of type Y or if the pass button is pressed.
The problem with that is that it requires the use of a large number of boolean variables and if/else to handle those "events".
What would be a better implementation of something like this?
Edit.
GameController.cs
public void XCardPlayed(string target)
{
if (this.players[0].Username.Equals(target))
{
// I'm the target
lastCardPlayed = "X";
PlayYCard();
}
else
{
// display the card on the table
}
}
public void PlayYCard()
{
gameView.NotifyPlayYCard();
}
GameForm.cs
public void PlayCardFromHand(int index)
{
if (playY)
{
// Check if card at index is instance of Y
}
}
public void NotifyPlayYCard()
{
playY = true;
}
public void CardClick(object sender, EventArgs e)
{
PictureBox current = (PictureBox)sender;
PlayCardFromHand(pnl_cards.Controls.IndexOf(current));
}

In general these kinds of problems are solved by creating a statemachine, I.e. something that know what state the game is in, and depending on this state, how to handle the next input. There are several ways to program state machines, but one way is to use async/await to let the compiler do it.
You can wrap a button in a class that completes a task each time the button is pressed:
public class ButtonAwaiter
{
private readonly Button button;
private TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
public ButtonAwaiter(Button button)
{
this.button = button;
this.button.Click += OnClick;
}
private void OnClick(object sender, EventArgs e)
{
tcs.SetResult(true);
tcs = new TaskCompletionSource<bool>();
}
public Task GetTask() => tcs.Task;
}
Substitute 'bool' for whatever the button represents.
And await when the first of the buttons is pressed like:
public async Task DoGameLoop()
{
var b1 = new ButtonAwaiter(button1);
var b2 = new ButtonAwaiter(button2);
while (GameIsInProgress)
{
var pressed = await Task.WhenAny(new[] {b1.GetTask(), b2.GetTask()});
}
}
This method is especially nice when the user needs to follow some sequence of actions since it lets you write the code in a reasonably straightforward manner.

Related

How can I wait for another method to be triggered before continuing the current method while still displaying the UI

I am trying to write a chess program in C# Windows Forms and I am writing a method GetMove() in this HumanPlayer class I have, which will return the Move from a player input of two clicks on separate squares on the board UI.
Could I have some help / advice on what I should use to implement this or if I am misunderstanding something else, explain that to me.
I've tried to add code snippets, but please let me know if I've done them wrong.
class HumanPlayer : Player
{
private Coords _selected;
public HumanPlayer(PieceColour colour) : base(colour)
{
_selected = new Coords();
}
public override ChessMove GetMove(Board board)
{
Coords Start = new Coords();
board.RaiseSquareClicked += ReceiveStartSquareClickInfo;
// Want to wait until that function is triggered by the event until continuing
Start = _selected;
board.RaiseSquareClicked -= ReceiveStartSquareClickInfo;
Coords End = new Coords();
board.RaiseSquareClicked += ReceiveEndSquareClickInfo;
// Want to wait until that function is triggered by the event until continuing
End = _selected;
board.RaiseSquareClicked -= ReceiveEndSquareClickInfo;
return new ChessMove(Start, End);
}
public void ReceiveStartSquareClickInfo(object sender, SquareClickedEventArgs e)
{
_selected = e.Square.Coords;
}
public void ReceiveEndSquareClickInfo(object sender, SquareClickedEventArgs e)
{
_selected = e.Square.Coords;
}
}
One thing I tried was using AutoResetEvent and WaitOne() and Set(), but this caused the UI to stop displaying.
I also tried to understand and use await and async, but I just confused myself and overcomplicated it and didn't get anywhere with it. So it might just be that I need someone to explain it to me.
This code that doesn't work might help someone understand what I misunderstand about asynchronous functions etc.
public async void Play()
{
_currentPlayer = _players[0];
_currentTurn = 1;
while (!GameOver())
{
ChessMove move= await _currentPlayer.GetMove(_board);
if (_currentPlayer == _players[1])
{
_currentTurn += 1;
_currentPlayer = _players[0];
}
else
{
_currentPlayer = _players[1];
}
}
}
class HumanPlayer : Player
{
private Coords _selected;
private TaskCompletionSource<bool> _squareClicked;
public HumanPlayer(PieceColour colour) : base(colour)
{
_selected = new Coords();
}
public override async Task<ChessMove> GetMove(Board board)
{
Coords Start = new Coords();
_squareClicked = new TaskCompletionSource<bool>();
board.RaiseSquareClicked += ReceiveStartSquareClickInfo;
_squareClicked.Task.Wait();
Start = _selected;
board.RaiseSquareClicked -= ReceiveStartSquareClickInfo;
Coords End = new Coords();
_squareClicked = new TaskCompletionSource<bool>();
board.RaiseSquareClicked += ReceiveEndSquareClickInfo;
_squareClicked.Task.Wait();
End = _selected;
board.RaiseSquareClicked -= ReceiveEndSquareClickInfo;
return new ChessMove(Start, End);
}
public async void ReceiveStartSquareClickInfo(object sender, SquareClickedEventArgs e)
{
_squareClicked.SetResult(true);
_selected = e.Square.Coords;
}
public async void ReceiveEndSquareClickInfo(object sender, SquareClickedEventArgs e)
{
_squareClicked.SetResult(true);
_selected = e.Square.Coords;
}
}
I've kinda been hesitant / nervous to post this question because I don't want people to get annoyed at me for posting a "duplicate question". Even though I've looked through several of the questions, it is confusing and frustrating not knowing whether the solution just doesn't apply to my situation or if I've added it in wrong. I'm sure I could find my solution answered in another question, but I feel it would take me a lot longer to find it and understand it than posting my own question.
Sorry if I've posted this question wrong or not followed the guidelines, this is my first post here.
If I've done anything wrong in formatting / communicating through this post, let me know and I'll try to fix it.
Good question, from what I can understand, your wanting to wait on a response from another method before executing yours.
Using async/await is the best option for this, if your getting confused at that, there are some tutorials and such you can follow
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/await
https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/
https://dotnettutorials.net/lesson/async-and-await-operator-in-csharp/
TaskCompletionSource is a class in C# that enables creating a Task object which can be manually completed with a result or exception.
class Example
{
TaskCompletionSource<string> taskCompletionSource = new();
public async Task DoStuff()
{
// Wait for the result of the TaskCompletionSource
var result = await taskCompletionSource.Task;
Console.WriteLine(result);
}
public void SetResult(){
taskCompletionSource.SetResult("Hello World!");
}
}
In this example, calling to DoStuff method will wait until the SetResult method is called, which will then set the result variable to "Hello World!"
Your post states that you want to wait for another method to be triggered before continuing and then describes three "states of play" so my first suggestion is to identify in code exactly the things we need to wait for in the chess game loop.
enum StateOfPlay
{
PlayerChooseFrom,
PlayerChooseTo,
OpponentTurn,
}
Game Loop
The goal is to run a loop that cycles these three states continuously, waiting at each step. However, the main Form is always running its own Message Loop to detect mouse clicks and key presses and it's important not to block that loop with our own.
The await keyword causes a waiting method to return immediately which allows the UI loop to keep running. But when "something happens" that we're waiting for, the execution of this method will resume on the next line after the await. A semaphore object says when to stop or go and is initialized here in the waiting state.
SemaphoreSlim _semaphoreClick= new SemaphoreSlim(0, 1);
When the game board is clicked during the players turn then the Release() method will be called on the semaphore, allowing things to resume. In terms of the specific question that you asked, this code snippet shows how to use the await keyword in your chess game loop.
private async Task playGameAsync(PlayerColor playerColor)
{
StateOfPlay =
playerColor.Equals(PlayerColor.White) ?
StateOfPlay.PlayerChooseFrom :
StateOfPlay.OpponentTurn;
while(!_checkmate)
{
switch (StateOfPlay)
{
case StateOfPlay.PlayerChooseFrom:
await _semaphoreClick.WaitAsync();
StateOfPlay = StateOfPlay.PlayerChooseTo;
break;
case StateOfPlay.PlayerChooseTo:
await _semaphoreClick.WaitAsync();
StateOfPlay = StateOfPlay.OpponentTurn;
break;
case StateOfPlay.OpponentTurn:
await opponentMove();
StateOfPlay = StateOfPlay.PlayerChooseFrom;
break;
}
}
}
Player's turn
Here we have to wait for each square to get clicked. A straightforward way to do this is with a SemaphoreSlim object and call Release() when the game board is clicked during the player's turn.
Square _playerFrom, _playerTo, _opponentFrom, _opponentTo;
private void onSquareClicked(object sender, EventArgs e)
{
if (sender is Square square)
{
switch (StateOfPlay)
{
case StateOfPlay.OpponentTurn:
// Disabled for opponent turn
return;
case StateOfPlay.PlayerChooseFrom:
_playerFrom = square;
Text = $"Player {_playerFrom.Notation} : _";
break;
case StateOfPlay.PlayerChooseTo:
_playerTo = square;
Text = $"Player {_playerFrom.Notation} : {_playerTo.Notation}";
richTextBox.SelectionColor = Color.DarkGreen;
richTextBox.AppendText($"{_playerFrom.Notation} : {_playerTo.Notation}{Environment.NewLine}");
break;
}
_semaphoreClick.Release();
}
}
Opponents turn
This simulates a computer opponent processing an algorithm to determine its next move.
private async Task opponentMove()
{
Text = "Opponent thinking";
for (int i = 0; i < _rando.Next(5, 10); i++)
{
Text += ".";
await Task.Delay(1000);
}
string opponentMove = "xx : xx";
Text = $"Opponent Moved {opponentMove}";
richTextBox.SelectionColor = Color.DarkBlue;
richTextBox.AppendText($"{opponentMove}{Environment.NewLine}");
}
It might be helpful to look at another answer I wrote that describes how to create the game board with a TableLayoutPanel and how to interact with mouse to determine the square that's being clicked on.

Need to implement "Scan" method in dll (non blocking)

Sorry for the title, i didn't find it easy to resume.
My issue is that I need to implement a c# dll that implements a 'scan' method, but this scan, when invoked, must not block the main thread of the application using the dll. Moreover, it is a duty that after the scan resolves it rises an Event.
So my issue (in the deep) is that i'm not so experienced at c#, and after very hard investigation i've come up with some solutions but i'm not very sure if they are the "right" procedures.
In the dll i've come up with:
public class Reader
{
public delegate void ReaderEventHandler(Object sender, AlertEventArgs e);
public void Scan(String ReaderName)
{
AlertEventArgs alertEventArgs = new AlertEventArgs();
alertEventArgs.uuiData = null;
//Code with blocking scan function here
if (ScanFinnished)
{
alertEventArgs.uuiData = "Scan Finnished!";
}
alertEventArgs.cardStateData = readerState[0].eventState;
ReaderEvent(new object(), alertEventArgs);
}
public event ReaderEventHandler ReaderEvent;
}
public class AlertEventArgs : EventArgs
{
#region AlertEventArgs Properties
private string _uui = null;
private uint cardState = 0;
#endregion
#region Get/Set Properties
public string uuiData
{
get { return _uui; }
set { _uui = value; }
}
public uint cardStateData
{
get { return cardState; }
set { cardState = value; }
}
#endregion
}
While in the main app I do:
Reader reader;
Task polling;
String SelectedReader = "Some_Reader";
private void bButton_Click(object sender, EventArgs e)
{
reader = new Reader();
reader.ReaderEvent += new Reader.ReaderEventHandler(reader_EventChanged);
polling = Task.Factory.StartNew(() => reader.Scan(SelectedReader));
}
void reader_EventChanged(object sender, AlertEventArgs e)
{
MessageBox.Show(e.uuiData + " Estado: " + e.cardStateData.ToString("X"));
reader.Dispose();
}
So here, it works fine but i don't know if it's the proper way, in addition i'm not able to handle possible Exceptions generated in the dll.
Also tried to use async/await but found it difficult and as I understand it's just a simpler workaround Tasks.
What are the inconvinients of this solution? how can i capture Exceptions (are they in other threads and that's why i cant try/catch them)? Possible concept faults?
When your class sends events, the sender usually is that class, this. Having new object() as sender makes absolutely no sense. Even null would be better but... just use this.
You shouldn't directly raise events as it might result in race conditions. Might not happen easily in your case but it's just a good guideline to follow. So instead of calling ReaderEvent(new object(), alertEventArgs); call RaiseReaderEvent(alertEventArgs); and create method for it.
For example:
private void RaiseReaderEvent(AlertEventArgs args)
{
var myEvent = ReaderEvent; // This prevents race conditions
if (myEvent != null) // remember to check that someone actually subscribes your event
myEvent(this, args); // Sender should be *this*, not some "new object()".
}
Though I personally like a bit more generic approach:
private void Raise<T>(EventHandler<T> oEvent, T args) where T : EventArgs
{
var eventInstance = oEvent;
if (eventInstance != null)
eventInstance(this, args);
}
Which can then be used to raise all events in same class like this:
Raise(ReaderEvent, alertEventArgs);
Since your scan should be non-blocking, you could use tasks, async/await or threads for example. You have chosen Tasks which is perfectly fine.
In every case you must understand that when you are not blocking your application, your application's main thread continues going like a train. Once you jump out of that train, you can't return. You probably should declare a new event "ErrorEvent" that is raised if your scan-procedure catches an exception. Your main application can then subscribe to that event as well, but you still must realize that those events are not (necessarily) coming from the main thread. When not, you won't be able to interact with your GUI directly (I'm assuming you have one due to button click handler). If you are using WinForms, you'll have to invoke all GUI changes when required.
So your UI-thread safe event handler should be something like this:
void reader_EventChanged(object sender, AlertEventArgs e)
{
if (InvokeRequired) // This true for others than UI Thread.
{
Invoke((MethodInvoker)delegate
{
Text = "My new title!";
});
}
else
Text = "My new title!";
}
In WPF there's Dispather that handles similar invoking.

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

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);
}

ontextchanged async sql stored proc call

I have a winform application and on the main form I placed a textbox.
What I want is that on the OnTextChanged event I need to query sql (stored proc) table in order to bring a list of matches with the character typed. For example if I type letter A then automaticly I should go and search in the database for names starting with letter "A", if I then type letter "L" then I should go and search for names starting with "AL" and so on.
The thing is that If I the user type fast then It should cancel any async process that is in progress and keep only the task for the last letter typed.
Any clue on how to achieve these?
The thing is that If I the user type fast then It should cancel any async process
It shouldn't need to cancel anything - the trick is not to start anything until you think the user has stopped typing.
I wrote something a bit similar, whereby when the user typed I had a thread start a new method, the method waited for a (configurable) time before executing. Another keystroke would call the method again and reset the wait period - effectively if someone continued to type the task wouldn't execute; as soon as they stopped it would (after a 200 ms pause for example).
I would implement something similar if I were you, as it avoids going to SQL when you don't have to
updated - by way of a simple example, a new project with a new form and default text box should allow the following code to function for your needs (as a very simple starting point)
public partial class Form1 : Form
{
private bool _waiting;
private bool _keyPressed;
private const int TypingDelay = 220; // ms used for delay between keystrokes to initiate search
public Form1()
{
InitializeComponent();
}
private void WaitWhileUserTyping()
{
var keepWaiting = true;
while (keepWaiting)
{
_keyPressed = false;
Thread.Sleep(TypingDelay);
keepWaiting = _keyPressed;
}
Invoke((MethodInvoker)(ExecuteSearch));
_waiting = false;
}
private void ExecuteSearch()
{
Thread.Sleep(200); // do lookup
// show search results...
MessageBox.Show("Search complete");
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
if (_waiting)
{
_keyPressed = true;
return;
}
_waiting = true;
// kick off a thread to do the search if nothing happens after x ms
ThreadPool.QueueUserWorkItem(_ => WaitWhileUserTyping());
}
}

Playing multiple sounds using muliple buttons

How to make multiple buttons play different sounds one at a time and have a common stop button using Windows Phone XNA framework? When a sound is played, it should play looped until one hits the stop button or hits another button.
The way I used SoundEffect with CreateInstance, it looped and played fine but when a second button is clicked, the second sound starts playing along with the first. Also need help with creating the common stop button. Thanks a lot in advance.
I was trying something like below for each button.
private void button2_Click(object sender, RoutedEventArgs e)
{
var stream = TitleContainer.OpenStream("Sounds/A3.wav");
var effect = SoundEffect.FromStream(stream);
SoundEffectInstance instance = effect.CreateInstance();
instance.IsLooped = true;
instance.Play();
But since the instance created isn't on a program-wide level, I'm having trouble creating a common stop button.
I'm a beginner in programming. Thank you for your understanding.
You can add a member variable to your class and some helper methods:
public class YourClass
{
private SoundEffectInstance currentSoundEffect = null;
private void StopCurrentSoundEffect()
{
this.currentSoundEffect.Stop();
this.currentSoundEffect = null;
}
private void PlaySoundEffect(string fileName)
{
this.StopCurrentSoundEffect();
using (var stream = TitleContainer.OpenStream("Sounds/A3.wav"))
{
var soundEffect = SoundEffect.FromStream(stream);
this.currentSoundEffect = soundEffect.CreateInstance();
this.currentSoundEffect.IsLooped = true;
this.currentSoundEffect.Play();
}
}
}
Now, each of your event handlers can just call this.PlaySoundEffect with the desired file name.

Categories