I have a block of code that repeats using a "for" loop, and each loop constructs a form to display some text. Some thing like the shorthand code below.
Main()
{
For (int x: x<=20; x++)
{
createform(string[x]);
}
}
So for each loop a different string is passed to a method that will construct a form as below.
createform void (string input_)
{
...
code to build form and add a button "cancelbutton"
form.text = intput_
....
form.cancelbutton.Click += // and I want this to cause the original loop to end....
}
No I know I could use the button to make int x greater than 20 and that would end the loop, but I don't actually know what the max value will be as this is dynamic. Again I could work this out and do he same thing but it seems a bit "messy".
Is there a neater way to cause the button click to exit the loop. How about if the Createform method is in a separate class to main, does that make any difference?
If your loop runs from 0 to 20 (or even over 9000) the user won't be able to click the cancel button in time. In fact, since it is all on one thread, the loop will finish before the UI responds to the click, but maybe I have misunderstood. Could you just have a boolean flag which you check each time you enter the loop and set it to false once the user clicks the button?
Just add a variable and code the click event:
static bool clicked;
Main()
{
clicked = false;
For (int x: (x<=20) && !clicked; x++)
{
createform(string[x]);
}
}
public static void Click_Detector(object sender, EventArgs e) {
clicked = true;
}
Your routine would need to do something like this:
createform void (string input_)
{
...
code to build form and add a button "cancelbutton"
form.text = intput_
....
form.cancelbutton.Click += MainClass.Click_Detector;
}
You shouldn't be using a loop to create each form. Assuming you have a submit button. Every time the user clicks submit, you should explicitly show the next form. That way, if a user clicks cancel, you don't have to worry about the rest of the forms.
The first solution that comes to my mind is: return value of the method True/False and in foreach check for its return value, if False=> break.
Your psuedo code isn't indicating how you are showing the form in CreateForm(). Is this going to be a modal or non modal? It would have to be modal, otherwise, you'd just keep creating forms until the cows come home. Remember, adding a handler to the click button doesn't actually execute the handler method until the click button is clicked.
Presumably your click event handler could set the value of x to be greater than 20, but since you said you're not sure what the maximum is, you could have a do loop governed by a boolean flag, and your createform() could set the state of the flag.
I'd recommend rethinking your problem space, as this overall approach seems really convoluted. I don't understand your UI, but it seems like your intent is to have the user control the exit of an infinite loop, like those old RPGs that ask you "Do you want to go on an adventure Yes/No", and it would continue asking you until you hit "Yes". These are pointless and confusing.
Add a boolean to the class and have the click event set the boolean value. Then you can break from the loop. However, if your real world use is the question above I would agree with the answer given by mikerobi.
One good solution would be to use the CancelAsync() method of the BackgroundWorker class: http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.cancelasync.aspx
If you don't want to work with a separate thread to improve the performance, that I don't recommend, is to set a class level boolean value when the button clicked, then the for loop checks that boolean value in each next process.
Related
I am trying to detect if the user did not click directly on the slider but somewhere on the track bar where it makes the slider value jump by LargeChange property. I am trying to get the value the slider was on before the jump occurred. So for example, if the slider is on the value 40 and the user makes it jump to 140, I need to know the initial value before it was changed.
I know I can use variables to keep track of the previous values, but I have a lot of TrackBars so I will end up having a lot of variables which I am trying to avoid.
Is there some kind of event for the TrackBar I can use to detect the jump or get the initial value before it has been changed by the user? Right now, I'm using the MouseDown event, but that gives me the value of wherever I click on the TrackBar instead of where it was at.
Talking about this TrackBar (the slider being the thing you can move left and right):
A solution that jumps to mind is to use a dictionary. You don't want to have many variables, but a dictionary is only one variable. And I don't believe you having so many trackbars that the memory for the dictionary should be any problem.
So declare a member in your Form class like
private readonly Dictionary<TrackBar, int> trackBarValue = new Dictionary<TrackBar, int>();
And in the handler for the ValueChanged event of the TrackBars you can do
private void TrackBarsValueChanged(object sender, EventArgs e)
{
TrackBar clickedBar = sender as TrackBar;
if (clickedBar == null) return;
if (!trackBarValues.ContainsKey(clickedBar))
trackBarValues[clickedBar] = clickedBar.Value;
if (Math.Abs(trackBarValues[clickedBar] - clickedBar.Value) > clickedBar.LargeChange)
{
// yes, it was a big change
}
// store the current value
trackBarValues[clickedBar] = clickedBar.Value;
}
I don't know if you currently have a different handler for each single trackbar. Since that would mean to repeat that code in every handler, it is possibly a good idea to add this single handler to the events of all trackbars.
See here for how to execute code when a variable changes.
Try something like that, but have the code set something like OldVar equal to TrackVar2, TrackVar2 being the value of TrackBar1 (your actual trackbar int) but only update it after OldVar has been updated first.
This should give you what you want, there is likely a way better solution but this should work.
I am a beginner to the OOP and the C#.
I am working on a quiz game using the Windows Forms.
My problem is related to two classes, the form and the game logic.
I have a basic UI with classic Froms controls. Take a look.
The thing I want to achieve is, when a player presses any answer button, it will higlight that pressed button by red or green color, depending on if it is right or wrong answer. After changing the color I want the program to wait for a while and then go to the next question.
Probelm is, that I don´t know how to achieve this correctly. I don´t know how to work with threads and how exactly the Form app works related to threads. Should I use a thread sleep or a timer or a async?
I will show you the method in game logic class which should handle this.
public static void Play(char answer) //Method gets a char representing a palyer answer
{
if (_rightAnswer == answer) //If the answer is true, the button should become green
{
Program.MainWindow.ChangeBtnColor(answer, System.Drawing.Color.LightGreen);
_score++;
}
else //Otherwise the button becomes Red
{
Program.MainWindow.ChangeBtnColor(answer, System.Drawing.Color.Red);
}
//SLEEP HERE
if (!(_currentIndex < _maxIndex)) //If it is the last question, show game over
{
Program.MainWindow.DisplayGameOver(_score);
}
else //If it is not the last question, load next question and dispaly it and finally change the button color to default
{
_currentIndex++;
_currentQuestion = Database.ListOfQuestions.ElementAt(_currentIndex);
_rightAnswer = _currentQuestion.RightAnswer;
Program.MainWindow.DisplayStats(_score, _currentIndex + 1, _maxIndex + 1);
Program.MainWindow.DisplayQuestion(_currentQuestion.Text);
Program.MainWindow.DisplayChoices(_currentQuestion.Choices);
}
Program.MainWindow.ChangeBtnColor(answer, System.Drawing.SystemColors.ControlLight);
}
I don´t want to completely block the UI but also I don´t want users to make other events by pressing other buttons during the pause. Because it will result in improper run of app.
If the program is really simple and you do not want to implement Threads I would suggest using Timer. Just start your Timer when clicking answer button. Your timer should contain function which would stop itself after some time and do other actions needed (e.g. pick another question).
Once the user has selected an answer you can disable all the buttons so they can't press anything else.
Then start a timer so you don't block the UI. The timer is basically a thread but handles all the threading for you so you don't have to worry about that aspect.
When the timer reaches the desired delay stop it and fire an event to select the next question.
At //SLEEP HERE add this line of code
Timer timer = new Timer(new TimerCallback(timerCb), null, 2000, 0);
The 2000 is milliseconds and is the wait time, timerCb is a call back method.
Also under that disable all your buttons so new events wont be generated.
private void timerCb(object state)
{
Dispatcher.Invoke(() =>
{
label1.Content = "Foo!";
});
}
You can do whatever you want in the callback, however if you do something that would change anything in the UI, you need to use the Dispatcher like I have changing the label content.
Suspending execution in a GUI scenario is very easy thanks to await:
await Task.Delay(2000);
This does not block the UI.
You should research what await does and how to use it. If you have never heard about it and are programming WinForms you are doing something wrong.
No timers or threads are needed. No callbacks, no Invoke.
I googled a few things before posting, but I couldn't find anything like this. Basically, I want to take text from a textbox, save as a variable (say history1) to then be able to call that in the future to display the text. I can do that, but what I'm stuck with is that I want 3 variables (history1, history2 and history3, for example) and each time the button is pressed the string is moved to the next variable.
For example, the button is pressed, the text is saved as variable history1. The text is changed and the button is pressed again, the text from history1 is moved to variable history2, and the new text is saved as history1. This would only need to work for 3 instances though, not infinitely, so when text is stored in history3 and the button is pressed the text is just overwritten.
The way I had thought of approaching this was:
string history1;
string history2;
string history3;
for (int i = 1; i < 4; i++)
{
history1 = txtOutput.Text;
btnToFile_Click()
{
history2=history1;
btnToFile_Click()
{
history3=history2;
}
}
}
However, this isn't going to work because the btnToFile_Click doesn't take any arguements. Is there an easier way to go about this or just a way to fix the method not taking arguements ?
Thanks in advance!
Make sure that you delcare history1, history2, and history3 on the form level (not inside any method).
Then, have the following code inside the handler of the click event of the button:
history3 = history2;
history2 = history1;
history1 = txtOutput.Text;
You don't need to call the btnToFile_Click() method multiple times in your loop, just move the text from end textbox to another in reverse order. Nor do you need a loop because you only have three textboxes.
Why reverse order? So you move the value to the next textbox before it is overwritten by the new value.
So:
history3 = history2;
history2 = history1;
history1 = txtOutput.Text;
btnToFile_Click() is a Click event handler for btnToFile (a button). You're not supposed to call that method yourself, it's called by the UI framework (say WPF or WinForms etc.). By the way, it does receive a parameter, then event source (since you can assign the same event handler to multiple buttons and do something based on which one sent the event)
You can try saving in a string array and move the strings within it when you call the button clicked event
I'm currently using a text box to filter some entries. I have the display updating on the text box TextChanged event, so the user isn't hitting enter or pressing a button to begin filtering. I want to use an AutoCompleteStringCollection to remember entries typed into the text box; however, if I save every string from the text box when the TextChanged event is fired then it will store all the substrings of each filter term.
So for instance, if I typed the string "test" it would display:
"t"
"te"
"tes"
"test"
as recommended strings. I just want the last string added to the AutoCompleteStringCollection.
I've thought about two separate methods I could implement.
1) I could create a Task that waits "x" amount of time after the last TextChanged event before it adds the string to the AutoCompleteStringCollection. If I did this I would have to use a cancellationToken to cancel the Task every time the textChanged event fired. This is slightly more complicated because I'm using .NET 4.0.
2) I could also search through the AutoCompleteStringCollection every time a string is added and remove all substrings (that start at the beginning of the word). This may backfire if the user types in a more specific filter, but still wants to store the shorter one.
Is there a better way to go about doing this? Which method would you recommend?
There are two things to be aware of when trying to dynamically fill the AutoCompleteStringCollection. First is Microsoft's Resolution to the issue:
Do not modify the AutoComplete candidate list dynamically during key events. (MSDN)
Having said that, I was able to figure out a way to dynamically add elements to the list.
I ended up opting for a modified version of the Task implementation. Instead of using a CancellationToken and TokenSource I used a bool. My code ended up looking roughly like this:
private void AddSearchToDropDown ()
{
Task.Factory.StartNew (() =>
{
if (CanAdd && filterTxtBox.Text.Length > 2)
{
CanAdd = false;
Thread.Sleep (4000);
this.Invoke(new Action(() =>
{
filterTxtBox.AutoCompleteMode = AutoCompleteMode.None;
m_suggestedTests.Add (filterTxtBox.Text);
filterTxtBox.AutoCompleteMode = AutoCompleteMode.Suggest;
CanAdd = true;
}));
}
});
}
You'll also want code in your textChanged event handler that will set the bool to false whenever they begin typing in the textbox. That way you don't add the first entry 4 seconds after the first text changed event.
Second thing to be aware of is that there was a Violation Exception if I used AutoCompleteMode.SuggestAppend or Append.
While this isn't a complete answer I hope that it helps anyone that manages to find this question.
Is there a way to make a control dependent on another control? I have a combo box and a button, and I need the button to be enabled if and only if there is an item selected in the combo box.
I know I can set the Enabled property of the button inside the SelectedIndexChanged callback, but then it will require some code, and besides there's an issue with what initial state the button would have. So I'm looking for something that wouldn't require manually handing events, is this possible?
Thanks!
No, there is no way in winforms to do this without code. What I usually do is to collect all such state-setting code into one specific method:
private void SetControlStates()
{
theButton.Enabled = theComboBox.SelectedIndex >= 0;
// code for other controls follow here
}
Then I trigger this method from all over the place, as soon as there is an interaction that may lead to the state changing (including the last thing I do when the form has finished loading; that takes care of initial state). If you want to avoid unnecessary assignments, just add code to check the value first:
private void SetControlStates()
{
bool buttonEnabled = theComboBox.SelectedIndex >= 0;
if (theButton.Enabled != buttonEnabled) theButton.Enabled = buttonEnabled;
// code for other controls follow here
}