How to print next line on Listbox - c#

I have been tearing my hair out with this problem, my ToString is repeating straight after each other rather than dropping to the next line.
as shown on this image, the second Event name should be on the line below.
//ToString method in Event Class
public override string ToString()
{
return "\nEvent name : " + m_evName + "\n Date :" + m_evDate + "\n";
}
//print method in class
public String PrintEvents()
{
StringBuilder retev = new StringBuilder("");
foreach (Event e in m_events)
{
retev.Append(e.ToString() + "\n");
}
return retev.ToString();
}
//Foreach that displays the text
private void cboListEv_SelectedIndexChanged(object sender, EventArgs e)
{
String SelectedVenue = cboListEv.Text;
List<Venue> found = plan.selectVen(SelectedVenue);
lstEvents.Items.Clear();
foreach (Venue v in found)
{
lstEvents.Items.Add(v.PrintEvents());
}
}

It's not possible to print multiple text lines per ListBox item with the standard ListBox. Try using a TextBox instead, with Multiline = true, and in read-only mode. It will achieve a similar effect.
Beyond that, you'll need to draw up your own custom ListBox control with customized item templates that support multiple lines of data.

ListBox items don't support multiline strings. However, you can make the ListBox's DrawMode OwnerDrawVariable, then hook up something like the following to its MeasureItem and DrawItem events:
internal int CountOccurrences(string haystack, string needle) {
int n = 0, pos = 0;
while((pos = haystack.IndexOf(needle, pos)) != -1) {
n++;
pos += needle.Length;
}
return n;
}
void ListBox1MeasureItem(object sender, MeasureItemEventArgs e)
{
e.ItemHeight = (int)((CountOccurrences(((ListBox)sender).Items[e.Index].ToString(), "\n") + 1) * ((ListBox)sender).Font.GetHeight() + 2);
}
void ListBox1DrawItem(object sender, DrawItemEventArgs e)
{
string text = ((ListBox)sender).Items[e.Index].ToString();
e.DrawBackground();
using(Brush b = new SolidBrush(e.ForeColor)) e.Graphics.DrawString(text, e.Font, b, new RectangleF(e.Bounds.Left, e.Bounds.Top, e.Bounds.Width, e.Bounds.Height));
e.DrawFocusRectangle();
}
-- will end up something like this:

If you provide the PrintEvents() method with the item collection of the ListBox you can have it add an item for every found event. Something like this:
//print method in class
public String PrintEvents(ObjectCollection items)
{
foreach (Event e in m_events)
items.Add(e.ToString());
}
//Foreach that displays the text
private void cboListEv_SelectedIndexChanged(object sender, EventArgs e)
{
String SelectedVenue = cboListEv.Text;
List<Venue> found = plan.selectVen(SelectedVenue);
lstEvents.Items.Clear();
foreach (Venue v in found)
v.PrintEvents(lstEvents.Items);
}

Add an Events property to the Venue class if not already there
public List<Event> Events
{
get { return m_events; }
}
Then add the items like this
foreach (Venue v in found) {
foreach (Event e in v.Events) {
lstEvents.Items.Add(e);
}
}

Related

How do I store text file into an array and then display line by line on demand (through key binding)

I'm a teacher with a very limited programming background struggling with C# to make some videos for my students who are pretty much anxious and depressed during the pandemic. I'm using GTA5 as my platform because it is easy to customize, has amazing locations, hundreds of potential characters, and supports voice as well (I'm using Amazon Polly). Needlessly to say I'm stripping out all the violence and swearing.
The roadblock I'm hitting, and there isn't any support to speak of on any GTA forums or Mods forums, is how to display lines of text on the screen on demand. I've managed to do this hardcoded, but I would prefer to read this from a text file and display line by line, not by a timer, but on demand with a single key binding, ideally with the ability to go back and forth (but line by line).
A friend of mine who works for a AAA gaming company, doesn't know the GTA5 environment but said the solution would be to read the lines into an array. Unfortunately I don't have that programming knowledge, so I'm stuck at this point. Here is my code. Right now it will only display the last line of a test text file. Code is put together from Microsoft documentation and random GTA forum posts. Again, I can do this manually through multiple hardcoded lines of text, each with a key bind, but that's totally impractical. Need text file, one keybinding and a way go line by line (and ideally backwards)
using System;
using System.Drawing;
using System.Windows.Forms;
using GTA;
using GTA.Math;
using GTA.Native;
namespace TextDrawing
{
public class TextDraw : Script
{
public static string TextString { get; set; }
public TextDraw()
{
Tick += onTick;
Interval = 0;
KeyDown += Basics_KeyDown;
}
private void onTick(object sender, EventArgs e)
{
if (Game.IsLoading) return;
DrawText();
}
private void DrawText()
{
var pos = new Point(100, 100);
// TextString = "Default Start Screen goes here if you want it on load";
var Text4Screen = new GTA.UI.TextElement(TextString, pos, 0.35f, Color.White, GTA.UI.Font.ChaletLondon, GTA.UI.Alignment.Left, true, false, 1000); //last parameter is wrap width
Text4Screen.Enabled = true;
Text4Screen.Draw();
}
private void Basics_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.NumPad7) //code replace code in Basics_Tick
{
string[] lines = System.IO.File.ReadAllLines
(#"C:\Users\UserName\test.txt");
foreach (string line in lines)
{
TextString = line; //"Lesson 1 Topics Today. \n\n Part 1. Drawing Text on Screen \n\n Part 2. Customizations" + TextString2;
DrawText();
}
}
}
}
}
There are many ways to do this. But a few key code changes should make it work.
Create two class-level variables, an array to keep all the read files and an int index to keep track of your current index.
Create a function to read and invoke that in the constructor (ideally it should not be read in the constructor but rather triggered by external user action. But let's keep it simple for now).
In the key press event, update the string and increase the index. Your code is currently not working because you are iterating through the entire array and assigning text to a single variable. So only the last value is remains on screen.
public class TextDraw : Script
{
public static string TextString { get; set; }
public string[] AllLines { get; set; }
public int currentIndex = 0;
public TextDraw()
{
Tick += onTick;
Interval = 0;
KeyDown += Basics_KeyDown;
ReadAllLines(); ///Ideally not here. But should work still.
}
private void ReadAllLines()
{
AllLines = System.IO.File.ReadAllLines (#"C:\Users\UserName\test.txt");
}
private void onTick(object sender, EventArgs e)
{
if (Game.IsLoading) return;
DrawText();
}
private void DrawText()
{
var pos = new Point(100, 100);
// TextString = "Default Start Screen goes here if you want it on load";
var Text4Screen = new GTA.UI.TextElement(TextString, pos, 0.35f, Color.White, GTA.UI.Font.ChaletLondon, GTA.UI.Alignment.Left, true, false, 1000); //last parameter is wrap width
Text4Screen.Enabled = true;
Text4Screen.Draw();
}
private void Basics_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.NumPad7) //code replace code in Basics_Tick
{
if (currentIndex >= 0 && currentIndex < AllLines.Length)
{
TextString = AllLines[currentIndex];
}
else
{
TextString = "The End";
}
currentIndex++;
DrawText();
}
}
}
I'm not familiar with GTA scripting, but maybe create a member variable: int index = 0; Then use that to figure out which line to print in DrawText():
namespace TextDrawing
{
public class TextDraw : Script
{
// The lines of the text file
string [] lines;
// The index of the next line to print
int index = 0;
public TextDraw()
{
Tick += onTick;
Interval = 0;
KeyDown += Basics_KeyDown;
// Read the file here
lines = System.IO.File.ReadAllLines(#"C:\Users\UserName\test.txt");
}
private void onTick(object sender, EventArgs e)
{
if (0 == lines.Length || Game.IsLoading) return;
DrawText();
}
private void DrawText()
{
var pos = new Point(100, 100);
// Get next line
string textString = lines[index];
var Text4Screen = new GTA.UI.TextElement(textString, pos, 0.35f, Color.White, GTA.UI.Font.ChaletLondon, GTA.UI.Alignment.Left, true, false, 1000);
Text4Screen.Enabled = true;
Text4Screen.Draw();
}
private void Basics_KeyDown(object sender, KeyEventArgs e)
{
// When Keys.NumPad7 is pressed, show next line
if (e.KeyCode == Keys.NumPad7)
{
if (index < lines.Length - 1) index++;
}
// When Keys.NumPad8 is pressed, show previous line
else if (e.KeyCode == Keys.NumPad8)
{
if (index > 0) index--;
}
}
}
}
Updated to use onTick and go backwards. Apparently the onTick is needed to continually redraw the text. Being unfamiliar with the GTA API, I did not realize that.

Connecting a picture box being pressed to a certain letter

I have questions in my program that are jumbled up words and the user tries to un-jumble them. I want them to do this by them clicking multiple picture which have certain letters on it to form the word but I don't know how to connect the picture I have to a certain letter (e.g. Say I have a picture of the letter "a" how do I connect it so that the program knows when that picture is pressed the letter a is pressed).
This is the coding I have so far (to randomize the question and link the answers to it).
*Also I'm very new to coding so any suggestions please can you show what exactly to add/change to my code.
public partial class Level1 : Form
{
Random rnd = new Random(); //sets the random variable
List<string> strStrings = new List<string>() { "ZUZB", "HXAO", "MXAE", "KYCU", "CWEH", "PHIC", "HOCP", "SXIA", "ISHF", "KOJE" };//displays wprds om a list
Dictionary<string, string> dictStrings = new Dictionary<string, string>() //dictionary containing the word as the key, and the answer as the value in the key/value pair.
{
{ "ZUZB", "BUZZ" },
{ "HXAO", "HOAX" },
{ "MXAE", "EXAM" },
{ "KYCU", "YUCK" },
{ "CWEH", "CHEW" },
{ "PHIC", "CHIP" },
{ "HOCP", "CHOP" },
{ "SXIA", "AXIS" },
{ "ISHF", "FISH" },
{"KOJE", "JOKE" }
};
public Level1()
{
InitializeComponent();
}
int skip; //declares the skip variable
int score; //declares the score variable
int question; //decalres the question variable
private void nextButton_Click(object sender, EventArgs e)
{
if (strStrings.Count > 0)
{
string rndWord = strStrings[rnd.Next(0, strStrings.Count())];
lbljumble.Text = rndWord;
strStrings.Remove(rndWord);
}
else
{
lbljumble.Text = "No more questions!";
}
answerLabel.Text = ""; //randomly displays questions in the label until there are no more questions left to ask
score += 10; //add 10 to score and display in label
lblscore.Text = Convert.ToString(score);
question += 1; //add one to question number and display in label
lblqnum.Text = Convert.ToString(question);
tmrtime.Interval = (tmrtime.Interval) - 100; //amount of time taken after each question
}
private void answerButton_Click(object sender, EventArgs e)
{
string answer = (dictStrings.TryGetValue(lbljumble.Text, out answer)) ? answer : "";
answerLabel.Text = answer; //displays answer in label after the answer button is pressed to display the corresponding answer to the question
}
private void btnskip_Click(object sender, EventArgs e)
{
skip = 1; //skip equals 1
if (skip == 1) //of skip equals one
{
skip--; //take one from skip
lblskip.Text = " remaining: no"; //display that no skips are available
}
else
{
lblskip.Text = "No skips remaining"; //display no skips remaining
}
}
}
}
If I understand your question correctly you would like to know what letter was selected/clicked by the user.
You can dynamically create picture boxes, assign the .Name of the picture box to the value you would like the picture box to have then subscribe to the Click event for each picture box.
In the click event check the name of the sending picture box object sender. (you could alternatively use the pictureBx.Tag if you don't want to use the pictureBx.Name )
Here is an example form.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
lblAnswer.Text = "";
DrawLeters();
}
string checkAnswer = "check";
void DrawLeters()
{
this.SuspendLayout();
string[] alphabet = "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z".Split(",");
var pictureLocation = new Point(0, 0);
for (int i = 0; i < alphabet.Length; i++)
{
//Create a picture box
PictureBox pictureBx = new PictureBox();
//set the default location of the box (0,0) in this scenario
pictureBx.Location = pictureLocation;
//make the box 16 by 16
pictureBx.Size = new Size(16, 16);
//set the name of the box to that of the letter it represents
pictureBx.Name = alphabet[i];
//assign a click event to the box
pictureBx.Click += PictureBx_Click;
//now create the image that will fill the box
Image img = new Bitmap(16, 16);
using (Graphics graph = Graphics.FromImage(img))
{
graph.Clear(Color.White);
Brush textBrush = new SolidBrush(Color.Black);
graph.DrawString(pictureBx.Name, this.Font, textBrush, 0, 0);
graph.Save();
}
//assign the image to the box
pictureBx.Image = img;
//add the box to the form
this.Controls.Add(pictureBx);
//change the location for the next box
pictureLocation.X += 17;
if (i % 10 == 0 && i > 0)
{
pictureLocation.Y += 17;
pictureLocation.X = 0;
}
}
this.ResumeLayout(false);
this.PerformLayout();
}
private void PictureBx_Click(object sender, EventArgs e)
{ // assign the clicked value to the answer label
if (sender is PictureBox pbx)
lblAnswer.Text += pbx.Name;
}
private void checkAnswerBtn_Click(object sender, EventArgs e)
{
//check the answer
if (lblAnswer.Text == checkAnswer)
lblFeedback.Text = "Correct!!";
else
lblFeedback.Text = "NO!!";
}
private void clearBtn_Click(object sender, EventArgs e)
{
//clear the answer label
lblAnswer.Text = "";
}
}
and the result is this.
i'm just checking that the answer is check you would use your own logic to determine the correct answer.
You can add some picture-boxes and name them as Letter_a_pb, Letter_b_pb etc., create a Click event, and then create an if statement to check which pictureBox is clicked and if it equal to the letter 'a'.
For instance:
string question = "a";
private void Letter_a_pb_Click(object sender, EventArgs e)
{
if (question == "a")
MessageBox.Show("Excellent!");
else
MessageBox.Show("Try Again!");
}
Let me know if this is what you've searched for.

c# textbox shows object name instead of value

I have a form written in c#, with various drop down lists, but I'm having problems with a listbox on the form. I need to populate a textbox with the values selected from the listbox when I double click on them. I've got the click event working, but the textbox will only populate with the object name, not the value from the listbox
i.e.
'System.Windows.Controls.SelectedItemCollection'
instead of the actual value.
Here is the entire code block I'm working on:
I should have just done this at the start - here is the complete code block I'm working on:
else if (theValue.FieldName.Equals("UIPathList", StringComparison.OrdinalIgnoreCase) == true)
{
int nRow = 14;
Button theUIPathOptionsButton = new Button();
TextBox theOldValueTextBox = AddLabelAndOldValue(theHelper, nRow, theValue);
theOldValueTextBox.Text = theValue.OldValue.Replace(",", "," + Environment.NewLine);
theUIPathOuterStackPanel = new StackPanel
{
Visibility = Visibility.Visible,
Orientation = Orientation.Vertical,
Background = new SolidColorBrush(Colors.White),
ClipToBounds = true,
};
theUIPathOptionsInnerStackPanel = new StackPanel
{
Visibility = Visibility.Visible,
Orientation = Orientation.Horizontal,
Background = new SolidColorBrush(Colors.White)
};
theUIPathOuterStackPanel.ClipToBounds = true;
TextBox theNewTextBox = new TextBox
{
TabIndex = nRow,
TextWrapping = TextWrapping.Wrap,
AcceptsReturn = true,
};
theNewTextBox.Clear();
theNewTextBox.MouseDoubleClick += MultiLineChildDatapointList_HandleMouseDoubleClick;
theNewTextBox.Focusable = true;
theNewTextBox.HorizontalAlignment = HorizontalAlignment.Stretch;
theNewTextBox.Width = 365;
theNewTextBox.PreviewKeyDown += theGetMetadataHelper.Preview_KeyDown_IsMultilineText;
theNewTextBox.Tag = theValue;
ListBox theUIPathOptionslistBox = new ListBox();
theUIPathOptionslistBox.Items.Add("RuntimeDefaults");
theUIPathOptionslistBox.Items.Add("CommonSettings");
theUIPathOptionslistBox.Items.Add(InputDatapointManager.CONST_CHANGE_RECORD_CHANGES_CLEAR_VALUE);
theUIPathOptionslistBox.TabIndex = nRow;
theUIPathOptionslistBox.SelectionMode = SelectionMode.Multiple;
theUIPathOptionslistBox.ClipToBounds = true;
theUIPathOptionslistBox.Focusable = true;
theUIPathOptionslistBox.Visibility = Visibility.Hidden;
theUIPathOptionslistBox.Height = 34;
theUIPathOptionsInnerStackPanel.Children.Add(theNewTextBox);
theUIPathOptionsInnerStackPanel.Children.Add(theUIPathOptionsButton);
theUIPathOuterStackPanel.Children.Add(theUIPathOptionsInnerStackPanel);
theUIPathOuterStackPanel.Children.Add(theUIPathOptionslistBox);
void button1_click(object sender, EventArgs e)
{
theUIPathOptionslistBox.Visibility = Visibility.Visible;
}
void button1_doubleclick(object sender, EventArgs e)
{
theNewTextBox.Text = theUIPathOptionslistBox.SelectedItem.ToString();
}
theUIPathOptionsButton.Click += button1_click;
theUIPathOptionslistBox.MouseDoubleClick += button1_doubleclick;
Grid.SetColumn(theUIPathOuterStackPanel, 4);
Grid.SetRow(theUIPathOuterStackPanel, nRow);
theDataGrid.Children.Add(theUIPathOuterStackPanel);
theEditControlList.Add(theNewTextBox);
}
This was (possibly) already answered here : Getting value of selected item in list box as string
string myItem = listBox1.GetItemText(listBox1.SelectedItem);
Then you just have to add your item to the textbox :
textBox1.Text = myItem;
If you don't want to create a new string variable, then this one is working too :
textBox1.Text = listBox1.SelectedItem.ToString();
ListBox, Item is a collection of objects, not strings, so you must let it know how to convert it to string, otherwise it will use its defualt .ToString() function that obviously the object currently in your items not giving the desired result.
Imagine items are oftype following class:
class SomeClass
{
public int Id;
public string Name;
}
You may do one of these three:
1.set the DisplayMember of your ListBox to Name
2.add override method to your class so that it overrides its .ToString() and return its Name property:
class SomeClass
{
public int Id;
public string Name;
public override string ToString()
{
return Name;
}
}
3.Just cast it to its real type and get the property you want:
SomeClass selected = (SomeClass)ListBox.SelectedItem;
TextBox1.Text = selected.Name;
This is because the TextBox will use the ToString() method on what it is bound to, which by default will return the class name.
You solutions are to either override the ToString() method of the class to return the value you want or set the text property of the TextBox to the text value you want rather then the object.
The solution I finally got to was as follows:
void theUIPathOptionslistBox_SelectedIndexChanged(object sender,
SelectionChangedEventArgs e)
{
theNewTextBox.Clear();
foreach (object selectedItem in theUIPathOptionslistBox.SelectedItems)
{
theNewTextBox.AppendText(selectedItem.ToString() + Environment.NewLine);
}
}
theUIPathOptionslistBox.SelectionChanged +=
theUIPathOptionslistBox_SelectedIndexChanged;

C# saving colours of many things

I have managed to get a colour-picker on the go which works, but I want to be able to save the colours the user wants. I have made links to the colordialog for labels/comboboxes/other controls as seperate items which works, but I would like to know how to save them (maybe to a txt file or something) as when the program restarts, it reverts back to black and white.
Thing is, it's not just 1 or 2 colour changes, I have got 35 labels, 7 comboboxes, 4 radiobuttons, 5 tabpages, 22 textboxes (text & background of each), 10 buttons (text and background of each) and A LOT of numericupdowns (text and background of each) - I have just spent the last hour sorting each one with the colour-picker...I don't want to have to do something to each thing in turn if I can help it...is there a way, or am I going to have to go through them all :)
I would follow the process of saving the colors on form closing, loading the colors on the form load, then applying the default colors after they have loaded.
I store the settings into a global variable inside of the form so I can reference them by name.
Settings Variable:
public Dictionary<string, FieldColor> colorSettings = new Dictionary<string, FieldColor>();
I use this following structure because you currently have two colors, but you could add more properties should the need arise.
Data Structure:
public struct FieldColor
{
public Color BackColor;
public Color ForeColor;
public FieldColor(Color backColor, Color foreColor)
{
this.BackColor = backColor;
this.ForeColor = foreColor;
}
}
The save method iterates through each control, then iterates through it's children controls, and if the color isn't the form's default color it saves the line. This saves it as a csv.
Save:
public void SaveFieldColors(Control parent, StreamWriter colorWriter)
{
foreach (Control currentControl in parent.Controls)
{
if (currentControl.ForeColor != DefaultForeColor || currentControl.BackColor != DefaultBackColor)
{
//Save the file in your format
colorWriter.WriteLine(currentControl.Name + "," + currentControl.BackColor.ToArgb() + "," + currentControl.ForeColor.ToArgb());
}
ApplyDefaultColorToControls(currentControl);
}
}
To load the settings I just break each line into a entry into the colorSettings dictionary.
Load:
public void LoadFieldColors(StreamReader colorReader)
{
while (colorReader.EndOfStream == false)
{
//Read the file from your format
string line = colorReader.ReadLine();
string[] fieldData = line.Split(',');
colorSettings.Add(fieldData[0], new FieldColor(Color.FromArgb(int.Parse(fieldData[1])), Color.FromArgb(int.Parse(fieldData[2]))));
}
}
Here I iterate through each control and their children to apply the formatting if there's an entry for that control's name. If there isn't one I just set it to the default.
Apply the Formatting:
public void ApplyDefaultColorToControls(Control parent)
{
foreach (Control currentControl in parent.Controls)
{
if (colorSettings.ContainsKey(currentControl.Name) == true)
{
//Set the controls settings equal to their color in your stored dictionary
currentControl.BackColor = colorSettings[currentControl.Name].BackColor;
currentControl.ForeColor = colorSettings[currentControl.Name].ForeColor;
}
else
{
//Set the control settings equal to default settings
currentControl.BackColor = DefaultBackColor;
currentControl.ForeColor = DefaultForeColor;
}
ApplyDefaultColorToControls(currentControl);
}
}
Example Calls from the form events:
private void Form1_Load(object sender, EventArgs e)
{
using (StreamReader settingsReader = new StreamReader(#"C:\path\to\file\filename.ending"))
{
LoadFieldColors(settingsReader);
}
ApplyDefaultColorToControls(this);
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
using (StreamWriter settingsWriter = new StreamWriter(#"C:\path\to\file\filename.ending"))
{
SaveFieldColors(this, settingsWriter);
}
}
}
There are many ways to store data in c#, starting with a simple text file and ending in databases... this is too broad. However you choose to save the data, I suggest to start with a Dictonary<string, Touple<color, color>> where the string is the control name, one is the backColor and the other is the foreColor and serialize it into a file (xml or json, doesn't matter all that much).
When you load your form, use a recursive function on
this.Controls to change the colors for each control.
You can try this if it is for WinForms project.
Create class for your settings :
[Serializable]
sealed class Settings
{
internal Dictionary<string, Color> ForeColors { get; set; }
internal Dictionary<string, Color> BackColors { get; set; }
internal Settings()
{
ForeColors = new Dictionary<string, Color>();
BackColors = new Dictionary<string, Color>();
}
internal void Save(string fileName = #"settings.ini")
{
using (FileStream stream = File.Create(Directory.GetCurrentDirectory() + "\\" + fileName))
{
BinaryFormatter serializer = new BinaryFormatter();
serializer.Serialize(stream, this);
}
}
internal static Settings Load(string fileName = #"settings.ini")
{
if (!File.Exists(Directory.GetCurrentDirectory() + "\\" + fileName)) return null;
using (FileStream stream = File.OpenRead(Directory.GetCurrentDirectory() + "\\" + fileName))
{
BinaryFormatter serializer = new BinaryFormatter();
return serializer.Deserialize(stream) as Settings;
}
}
}
Now in your windows form subscribe to Form_Load Form_Closing events and add handlers:
private void Form1_Load(object sender, EventArgs e)
{
Settings s = Settings.Load();
if (s == null) return;
foreach (Control item in this.Controls)
{
item.ForeColor = s.ForeColors[item.Name];
item.BackColor = s.BackColors[item.Name];
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
Settings s = new Settings();
foreach (Control item in this.Controls)
{
s.ForeColors.Add(item.Name, item.ForeColor);
s.BackColors.Add(item.Name, item.BackColor);
}
s.Save();
}

How to find a programatically created button by text?

So my program is generating a bunch of buttons like so:
foreach (var subdir in dir.GetDirectories()) {
var path = subdir.Name;
var button = new Button {
Text = getFlavor(path) + "\t(" + path + ")",
Width = Width,
Height = 35,
Top = y
};
button.Click += buttonClick;
Controls.Add(button);
if (button.Text.Contains("Kittens")
i++;
}
I want to try something like this
if (i == 1) {
[Button.ThatContains("Kitten)].Click;
}
"ThatContains" is not a real method. How do I get references to buttons I've created programmatically ?
You could use OfType<Button> to find all buttons in the container control where you've added them(f.e. a Panel). Then a liitle bit LINQ power gives you the correct button(s):
var kittenButtons = panel.Controls.OfType<Button>()
.Where(btn => btn.Text.Contains("Kittens"));
foreach(Button btn in kittenButtons)
btn.PerformClick();
If you just want to click the first:
Button kittenButton = panel.Controls.OfType<Button>()
.FirstOrDefault(btn => btn.Text.Contains("Kittens"));
if(kittenButton != null)
kittenButton.PerformClick();
For what it's worth, here is also an extension method that returns controls recursively via deferred execution which allows to use only the first found Buttton or consume all down the road:
public static IEnumerable<T> GetChildControlsRecursive<T>(this Control root) where T : Control
{
if (root == null) throw new ArgumentNullException("root");
var stack = new Stack<Control>();
stack.Push(root);
while (stack.Count > 0)
{
Control parent = stack.Pop();
foreach (Control child in parent.Controls)
{
if (child is T)
yield return (T)child;
stack.Push(child);
}
}
yield break;
}
Now you can use similar code as above to get for example the first matching button or all:
var kittenButtons = this.GetChildControlsRecursive<Button>()
.Where(b => b.Text.Contains("Kittens"));
// search just until the first button is found
Button firstKittenButton = kittenButtons.FirstOrDefault();
if(firstKittenButton != null) firstKittenButton.PerformClick;
// loop all
foreach(Button btn in kittenButtons)
btn.PerformClick();
Either create a subclass of Button to store the information you want and instantiate that instead or use the Tag property
public class MyButton : Button
{
public int ButtonID { get; set; }
}
public class MyApplication
{
public void DoSomething()
{
int i; // todo: loop stuff
var button = new MyButton
{
Text = getFlavor(path) + "\t(" + path + ")",
Width = Width,
Height = 35,
Top = y,
ButtonID = i
};
}
}
Or why not cast the sender parameter of the button click event as a Button and check the text?
public class MyApplication
{
public void DoSomething()
{
var b = new Button();
b.Click += b_Click;
}
public void b_Click(object sender, EventArgs e)
{
Button b = (Button)sender;
switch (b.Text) {
case "Kittens":
return;
default:
return;
}
}
}
Something like this
var button = FirstOrDefault(y => y is Button && y.Text.Contains("Kittens"));
if(button != null)
button.PerformClick();
In order to get the references, you may need to what you would do with getting references of any other type - store them somewhere, which does not seem to be the case here at all. Normally, you would register your buttons for interaction from a user by attaching them to a Form. Assuming you're not doing this by the looks of your sample code, I'm going to recommend storing them into a Dictionary<string, Button>.
You could use a dictionary or you could use a simple recursive loop (in case you are sticking the buttons into different containers).
private bool ClickButton(string buttonName, Control control) {
if (control is Button && control.Text.Contains(buttonName) {
((Button)control)PerformClick();
return true;
}
if (control.HasChildren) {
foreach (Control childControl in control.Controls) {
if (ClickButton(buttonName, childControl)) {
return true;
}
}
}
return false;
}
Usage: ClickButton("Kittens", this);
Or you could use a dictionary, as some have suggested.
private Dictionary<string, Button> DynamicButtons = new Dictionary<string, Button>();
private void ClickDictionaryButton(string buttonName) {
var matches = DynamicButtons.Where(x => x.Key.Contains(buttonName));
foreach (var match in matches) {
match.Value.PerformClick();
}
}
Usage: ClickDictionaryButton("Kittens", this);

Categories