C# Using a only a couple buttons for many different methods? - c#

I'm trying to make a "Choose your own adventure game" in C#, and having a problem with the buttons. I have 6 buttons, and I want to reuse them for the different options on each "page" of the story. Program Window
As an example, I was using this for btn1.
void page1(){
txtStory.Text = "Can you sneak past the enemy?";
btn1.Click += (sender, args) => {
luckCheck();
if (lucky) {page2(); clearButtons();}
else {page3(); clearButtons();}
};
}
void luckCheck(){
int luckTest = gen.Next(1,12);
if (luckTest <= playerLuck) {lucky = true;}
else {lucky = false;}
}
void clearButtons(){
btnN.Text = "";
btnS.Text = "";
btnE.Text = "";
btnW.Text = "";
btn1.Text = "";
btn2.Text = "";
btn3.Text = "";
btn4.Text = "";
btn5.Text = "";
btn6.Text = "";
}
void page2(){
txtStory.Text = "Lucky!";
}
void page3(){
txtStory.Text = "Not Lucky!";
}
Even though it has moved on to another method, if clicked, btn1 still repeats the same command from page1. Is there a way to stop this from happening? Like clearing the memory for the button or something.

The problem you're having is that page1() is attaching an anonymous handler method, which is never detached, so each time you call it, you are just adding more and more handlers.
But this design is going to get out of hand very quickly once you start adding lots of pages.
Consider a different design, where you have a Page class to represent each page. That class would have the story as a property on it, and a collection of possible Choices (rather than hard-coding 6 of them). Choice is a class with a Description string property and an Action method to be executed if it is used.
Your application can hold the CurrentPage property, and your Choice's Action will need to be able to navigate by changing that page. Etc, etc...
Hope this helps.
P.S. extra tip. Rather than returning the result of the luckCheck method on a class field, consider redesigning it to return the result:
bool luckCheck(){
int luckTest = gen.Next(1,12);
return luckTest <= playerLuck;
}

Without going into details as to how the code above could be structured better, here is a solution using C# 7. Note how a local function is assigned to handle the event then unassigned - using Delegate Type Inference
void page1(){
txtStory.Text = "Can you sneak past the enemy?";
void Btn1Click(object s, EventArgs ev)
{
luckCheck();
if (lucky) {page2(); clearButtons(); btn1.Click -= Btn1Click; }
else {page3(); clearButtons();}
};
btn1.Click += Btn1Click;
void luckCheck(){
int luckTest = gen.Next(1,12);
if (luckTest <= playerLuck) {lucky = true;}
else {lucky = false;}
}
void clearButtons(){
btnN.Text = "";
btnS.Text = "";
btnE.Text = "";
btnW.Text = "";
btn1.Text = "";
btn2.Text = "";
btn3.Text = "";
btn4.Text = "";
btn5.Text = "";
btn6.Text = "";
}
void page2(){
txtStory.Text = "Lucky!";
}
void page3(){
txtStory.Text = "Not Lucky!";
}

Related

Making statements wait before being executed

i have this code, which has an animation and some other statements (they are like resetting the TextBoxs and other UI Controls).
what is happening is that the animation is starting with the statements at he same time, it looks weird that the users sees that the text in the TextBoxs suddenly disappers, so i want these other statements to wait some time (the animation duration) before being executed.
//this is an Animation that takes me back to the home view
switching = (Storyboard)this.FindResource("view_Copy1");
switching.Begin();
//I want these statments to wait until the animation is finished
st1.Children.Clear();
st2.Children.Clear();
st3.Children.Clear();
st4.Children.Clear();
name.Text = lname.Text = fname.Text = mname.Text = sex.Text =
bplace.Text = bday.Text = idcard.Text = socal.Text = region.Text = location.Text =
telephone.Text = mobile1.Text = mobile2.Text = email1.Text = email2.Text =
ctype.Text = cyear.Text = emergname1.Text = emergrelation1.Text = emergloc1.Text =
emergphone1.Text = emergname2.Text = emergrelation2.Text = emergloc2.Text = emergphone2.Text = "";
region.Items.Clear();
New.IsEnabled = true;
id = "";
bt_del.Visibility = Visibility.Collapsed;
There is a Completed event that you can use and on completion you execute your code.
switching.Begin();
switching.Completed += Storyboard_Completed;
...
private void Storyboard_Completed(object sender, EventArgs e)
{
// Your code to execute after the animation completed.
}

Virtual Keyboard test isn't working with my Keyboard Wedge

I've installed com0com so that I can write a NUnit test. Just in case there is a definition difference of Keyboard wedge a brief description of it is a piece of software that listens to a Serial communications device, reads any data sent to it (in my case formats it to ASCII data) then sends that to a Virtual keyboard. This code does work in production but we are required to now either document our code or have a unit test to prove how it is supposed to be used. so here is my test
[Test()]
public void WedgeSendsTextToVirtualKeyboardTest()
{
(var form = new Form())
using(var sp = new System.IO.Ports.SerialPort("COM"+COMB, 115200))
using (var wedge = new KeyboardWedgeConfiguration(WEDGE_KEY))
{
sp.Open();
TextBox tb = SetupForm(form);
TurnOnKeyboardWedge(wedge);
form.Activate();
form.Activated += (s, e) =>
{
tb.Focus();
};
while (!tb.Focused) { }
string str = "Hello World";
sp.Write(str);
//wait 1 second. This allows data to send, and pool in the wedge
//the minimum wait time is 200ms. the string then gets put into bytes
//and shipped off to a virtual keyboard where all the keys are pressed.
System.Threading.Thread.Sleep(1000);
Expect(tb.Text, Is.EqualTo(str));
}
}
private static TextBox SetupForm(Form form)
{
TextBox tb = new TextBox();
tb.Name = "tb";
tb.TabIndex = 0;
tb.AcceptsReturn = true;
tb.AcceptsTab = true;
tb.Dock = DockStyle.Fill;
form.Controls.Add(tb);
form.Show();
return tb;
}
private static void TurnOnKeyboardWedge(KeyboardWedgeConfiguration wedge)
{
wedge.Port = COMA;
wedge.PortForwardingEnabled = true;
wedge.Baud = 115200;
System.IO.Ports.SerialPort serialPort;
wedge.StartRerouting();
Assert.IsTrue(wedge.IsAlive(out serialPort));
Assert.IsNotNull(serialPort);
}
When the test runs, the form shows, no text is put in the textbox, then the test exits and the last assert fails (Expect(tb.Text, Is.EqualTo(str));) saying that tb.Text is string.Empty. I've tried a number of different tactics to get focus on that textbox (i'm assuming that is the problem atleast). At one time I made my sleep longer so that I had time to click on the textbox and type myself, and I couldn't click on the box (I'm assuming that is because of the sleep operation... which is also probably why my wedge can't type in there as well) so how can I fix this problem and make my test pass. Again this code does work in a production environment, so I am 100% convinced it is my test (and probably that sleep operation)
I was able to make my test pass with the help of this question stackoverflow question (thank you so much Patrick Quirk). It is actually a minor variation to it. I'm not even sure if my solution is 100% correct, but when the form pops up the text is entered and my test passes. The solution was two part system. First I had to make a class that extends Form override the Text property, and listen for the Activated and FormClosing Events. On Form Closing I would set my text, and on Activated I told my TextBox to have the focus.
private class WedgeForm : Form
{
public override string Text { get { return text; } set { text = value; } }
string text = string.Empty;
private TextBox tb;
public WedgeForm()
{
InitializeControls();
Activated += (s, e) => { tb.Focus(); };
FormClosing += (s, e) => { this.Text = tb.Text; };
}
private void InitializeControls()
{
tb = new TextBox();
tb.Name = "tb";
tb.TabIndex = 0;
tb.AcceptsReturn = true;
tb.AcceptsTab = true;
tb.Multiline = true;
tb.Dock = DockStyle.Fill;
this.Controls.Add(tb);
}
}
then using the InvokeEx method that was provided in the other qustion/answer My test easy to setup
[Test()]
public void WedgeSendsTextToVirtualKeyboardTest()
{
using (var form = new WedgeForm())
using (var wedge = new KeyboardWedgeConfiguration(WEDGE_KEY))
{
TurnOnKeyboardWedge(wedge);
string actual = MakeWedgeWriteHelloWorld(form, wedge); ;
string expected = "Hello World";
Expect(actual, Is.EqualTo(expected));
}
}
private static string MakeWedgeWriteHelloWorld(WedgeForm form, KeyboardWedgeConfiguration wedge)
{
var uiThread = new Thread(() => Application.Run(form));
uiThread.SetApartmentState(ApartmentState.STA);
uiThread.Start();
string actual = string.Empty;
var thread = new Thread
(
() => actual = InvokeEx<Form, string>(form, f => f.Text)
);
using (var sp = new System.IO.Ports.SerialPort("COM" + COMB, 115200))
{
sp.Open();
sp.Write("Hello World");
}
//wait 1 second. This allows data to send, and pool in the wedge
//the minimum wait time is 200ms. the string then gets put into bytes
//and shipped off to a virtual keyboard where all the keys are pressed.
Thread.Sleep(1000);
InvokeEx<Form>(form, f => f.Close());
thread.Start();
uiThread.Join();
thread.Join();
return actual;
}
One minor thing I have to remember when running this test is to not be clicking around because if that textbox loses focus I'm sunk. But the test is only 1second long.. I think I'll live.

Showing Form from a thread not rendering perfectly

I am developing a chat application using jabber-net opensource library..
my aim is to display a form (chat window ) when a message is coming.
But when I use this code, Form appears in the task bar,,, not perfectly rendered...
seems like this... More over I can see the form only when I mousehover the Icon on taskbar (Hail Windows 7)... Any form are like this...
Click here for Output Image
my code is this...
public jabber.client.JabberClient jabberClient1;
jabberClient1.User = UserName;
jabberClient1.Password = Password;
jabberClient1.Resource = resource;
jabberClient1.AutoRoster = true;
jabberClient1.OnMessage += new MessageHandler(jabberClient1_OnMessage);
private void jabberClient1_OnMessage(object sender, jabber.protocol.client.Message msg)
{
try
{
chatWindow chw = new chatWindow();
chw.Left = 0;
chw.Top = 0;
chw.TopMost = true;
//chw.LoadChat(msg.From.User, msg.From.Bare, "0");
//chw.SetMessage(msg);
chw.Show();
}
}
you have to use chw.ShowDialog()
or use if invokerequired
private delegate void dlgInvokeRequired();
public void InvokeMethode()
{
if (this.InvokeRequired == true)
{
dlgInvokeRequired d = new dlgInvokeRequired(InvokeMethode);
this.Invoke(d);
} else
{
chatWindow chw = new chatWindow();
chw.Left = 0;
chw.Top = 0;
chw.TopMost = true;
//chw.LoadChat(msg.From.User, msg.From.Bare, "0");
//chw.SetMessage(msg);
chw.Show();
}
}
I have solved it myself...
I have to use
JabberClient1.InvokeControl = FormInstance;
and, the FormInstance should be shown Before the chat window appears....
ie, It can be the contact window (Roster)....

Changing an Object's properties in another class?

I'm trying to change an Object's properties from another class, like so.
abilities.cs (class)
public static void hideAllButtons()
{
frmFight fight = new frmFight();
fight.btnAbility1.Visible = false;
fight.btnAbility2.Visible = false;
fight.btnAbility3.Visible = false;
fight.btnAbility4.Visible = false;
fight.btnAbility5.Visible = false;
fight.btnAbility6.Visible = false;
vars.buttonsVisible = false;
}
I'm trying to use the method from the previous class to change the object's properties in the following Form;
frmFight.cs (form)
private void btnAbility1_Click(object sender, EventArgs e)
{
abilities.hideAllButtons();
btnAbilities.Enabled = false;
}
I've tried everything in my knowledge and understanding, and a lot of looking up on the internet. I've tried making the objects static, public, creating the object within the class. But nothing works. Usually I get StackOverFlow errors.
I'm pretty new to OOP, too, but I'm not an idiot so don't think to go too 'lightly' on me with a possible fix, or cause, of my problem - if you understand, that is.
Method hideAllButtons is static so should have a Form argument.
public static void hideAllButtons(frmFight fight)
{
fight.btnAbility1.Visible = false;
fight.btnAbility2.Visible = false;
fight.btnAbility3.Visible = false;
fight.btnAbility4.Visible = false;
fight.btnAbility5.Visible = false;
fight.btnAbility6.Visible = false;
//vars.buttonsVisible = false; // What about this???
}
and call this method in click handler,
hideAllButtons(this);
This really wont work because your controls is on your frmFight.
You need to do is place
private void hideAllButtons()
{
btnAbility1.Visible = false;
btnAbility2.Visible = false;
btnAbility3.Visible = false;
btnAbility4.Visible = false;
btnAbility5.Visible = false;
btnAbility6.Visible = false;
vars.buttonsVisible = false;
}
on your frmFight.cs (form) as private method and just call it on your button click to make it simpler.
private void btnAbility1_Click(object sender, EventArgs e)
{
hideAllButtons();
btnAbilities.Enabled = false;
}
Regards

C# Thread dataGridView and Loading

Im trying to load a dataGridView which takes some time, so ive come up with an idea of hiding the datagridview and put an image over the top which says Loading... when finished, the image goes away and the datagrid reappears. Ive tried to do this using threading but having no luck.
Can somebody tell me if i am approaching this in the right way?
Label loadingText = new Label();
PictureBox loadingPic = new PictureBox();
private void TelephoneDirectory_Load(object sender, EventArgs e)
{
dataGridView1.Visible = false;
Thread i = new Thread(LoadImage);
i.Start();
Thread t = new Thread(LoadData);
t.Start();
}
void LoadImage()
{
if (this.InvokeRequired)
{
this.Invoke(new MethodInvoker(LoadImage));
}
else
{
loadingPic.Image = Properties.Resources.ReportServer;
loadingPic.Location = new Point(0, 0);
loadingPic.Name = "loadingPic";
loadingPic.Dock = DockStyle.Fill;
loadingPic.SizeMode = PictureBoxSizeMode.CenterImage;
loadingText.Text = "Loading, please wait...";
loadingText.Name = "loadingText";
loadingText.TextAlign = ContentAlignment.MiddleCenter;
loadingText.Size = new Size(this.Size.Width, 30);
loadingText.Font = new System.Drawing.Font("Segoe UI", 9);
loadingText.Location = new Point(0, (this.Size.Height / 2 + 10));
this.Controls.AddRange(new Control[] { loadingPic, loadingText });
loadingText.BringToFront();
}
}
private void LoadData()
{
if (dataGridView1.InvokeRequired)
{
dataGridView1.Invoke(new MethodInvoker(this.LoadData));
}
else
{
DirectorySearcher sea = null;
DirectoryEntry dir = null;
DirectoryEntry d = null;
string domainname = System.DirectoryServices.ActiveDirectory.Domain.GetComputerDomain().Name;
domainname = domainname.Replace(".", ",DC=");
try
{
dir = new DirectoryEntry();
dir.Path = "LDAP://DC=" + domainname;
sea = new DirectorySearcher(dir);
sea.Filter = "(&(objectClass=user)(extensionAttribute1=1)(telephoneNumber=*))";
sea.PageSize = 2000;
SearchResultCollection res = sea.FindAll();
foreach (SearchResult result in res)
{
//DO ALL MY STUFF
dataGridView1.Rows.Add(row);
}
}
catch { }
LoadDataComplete();
}
}
void LoadDataComplete()
{
PictureBox loadingGraphic = this.Controls["loadingPic"] as PictureBox;
Label LoadingLabel = this.Controls["loadingText"] as Label;
DataGridView dataGrid = this.Controls["dataGridView1"] as DataGridView;
dataGrid.Visible = true;
LoadingLabel.Visible = false;
LoadingLabel.Dispose();
loadingGraphic.Visible = false;
loadingGraphic.Dispose();
dataGridView1.Sort(dataGridView1.Columns[0], ListSortDirection.Ascending);
dataGridView1.ClearSelection();
}
Spawning threads for this might not be the best idea, you could use ThreadPool or BackgroundWorker
Loading image should be fast enough that you could just do it synchronously, so make sure you actually need to do some operation on the other thread before you actually need it.
Ask yourself questions like: what if actually my image will load later then my dataTable? ("but it loads faster every time I checked" is not an valid argument when talking about threads)
At the beginning of your methods you are using .Invoke which basically means "wait for UI thread and invoke my code on it synchronously" which bombards your whole idea.
Try something like this:
Load image synchronously
Use ThreadPool to load your DataTable in it, but without using .Invoke
When it's loaded and you need to interact with UI -> then put your code in .Invoke()
Pseudocode coud look like this:
private void TelephoneDirectory_Load(object sender, EventArgs e)
{
dataGridView1.Visible = false;
LoadImage();
ThreadPool.QueueUserWorkItem(new WaitCallback(o => LoadData()));
}
void LoadData()
{
//...Do loading
//but don't add rows to dataGridView
if (dataGridView1.InvokeRequired)
{
//Invoke only the ui-interaction code
dataGridView1.Invoke(new MethodInvoker(this.LoadDataComplete));
}
}
void LoadDataComplete()
{
foreach (SearchResult result in res)
{
//DO ALL MY STUFF
//If do all my stuff is compute intensive and doesn't require UI,
//put it before Invoke() (like here)
dataGridView1.Rows.Add(row);
}
//Rest of code
}

Categories

Resources