Create or generate a control based on database results - c#

How do I create a control in a windows form application? I want to generate a textbox or a radio button when I select something from a Combobox. I basically want to query my database, and based on the values of the fields, I want to generate a textbox or a radio button. For example, if my query returns a value of "Textbox", I want to generate a textbox on the form in a specified location. How does one accomplish this? Please help.

The easiest way is to create the control manually in code and then add it to the Controls collection. Deciding which control to create depending on some input data (whether it's database query or a value selected from a combobox) is not much different then in any other case. Simple if will do the job. For instance:
private void AddControl(string control)
{
if (control == "Textbox")
{
TextBox tb = new TextBox();
tb.Location = new Point(100, 100);
this.Controls.Add(tb);
}
else if (control == "Radio")
{
RadioButton rb = new RadioButton();
rb.Location = new Point(200, 100);
this.Controls.Add(rb);
}
}
Of course, it's very naive version. But it's only a starting point. I leave to you adding more advanced logic (like dynamically adjusting location, setting up properties of the radio button or the textbox, relying on Type instead of on a simple string, etc.)
The assumption is that you retrieve a string value from the database. For example in form's constructor:
public Form1()
{
InitializeComponent();
string requestedControl = QueryDatabase();
AddControl(requestedControl);
}
I leave to you implementing the method to query the database.
In the question you also mentioned adding a control after selecting it in the combobox. In this case the only difference is that you rely on an event triggered after combobox's selection changed:
private void comboBox1_SelectedValueChanged(object sender, EventArgs e)
{
string res = this.comboBox1.SelectedItem.ToString();
this.AddControl(res);
}
Here you rely on SelectedValueChanged event. Of course, in this case your combobox has to be populated with expected values (here "Radio" and "Textbox"). Also, you have to attach the event handler to the specific event on the combobox. You can do that in designer or by adding in the constructor the following line:
combobox1.SelectedValueChanged += comboBox1_SelectedValueChanged;
Hope that clarifies the issue and sets up some starting point for you to continue from.

Related

WinForm - DataGridView when row change event

I currently have an event set for RowHeaderMouseClick when I click on the header, my textbox is populated with data from the DataGrid. I want to make my textbox populate when I select the row instead of the header. Ideally, I want to hide the header. What is the correct event/property that I need to set to achieve this?
Edit:
Attaching screenshot
You can make like this for any event
//Handle RowChanged.
table.RowChanged += table_RowChanged;
//RowChanged Event.
static void table_RowChanged(object sender, DataRowChangeEventArgs e)
{
Console.WriteLine("... Changed: " + (int)e.Row["Dosage"]);
}
You don't need to do it with events, as you seem to be saying that the textbox gets populated with an item from the row. In such a case you would have:
your datagridview bound to a bindingsource
your bindingsource bound to a datatable or other list that supports binding
your textbox's text property bound to the same bindingsource
Every time the user clicks a row in the grid (or uses the keyboard to move to another row) they are causing the Current property of the bindingsource to update. This in turn changes any of the textboxes that are bound to true same binding source (a textbox only shows the current row item to which it is bound)
For a quick demo of how this works, do these steps (apologies I can't make any screenshots - I'm on a cellphone) - skip any steps you've already done
add a DataSet type file to your project
open it and right click the surface, add a datatable and name it eg Person
right click it and add a couple of columns eg FirstName and LastName
save
switch to the form
open the Dat Sources window (view menu, other windows)
drag the Person node to the form, a datagridview appears as well as some other stuff (bindingsource) - look at the DataSource property of the grid
in the Data sources window again click the drop down button next to Person, change it to details
drag the person node to the form again, this time textboxes appear; take a look at their Text bindings in the (data bindings) section of their properties - they're bound to the same bindingsource as the grid is
run the project, type 4 names into the grid and then select different rows at random using the mouse; the textboxes update to stay current with the grid selection
If this isn't the way you've done things up to now you should consider making it the way; using the DataSet designer to create strongly typed datatables is an easy way to model the data aspects of your program and there is a lot of tooling set up to make life easier when you use them to make a data driven app. If you've been putting data directly into a datagridview it's something you should avoid going forward, and instead separate your concerns in a more mvc style pattern
If you want to remove or make the rowHeader invisible
You can SET an Event DataGridView CellClick and below is the CODE
//Im Assuming that we are getting the row VALUES
try{
int rowIndex = e.RowIndex; //getting the position of ROW in DGV when CLick
int columns = Columns.Count//numebr of Columns
for(int i=0; i< columns;i++)
{
//Here we can populate textBoxes and using FlowLayoutPanel
string values = dataGridView1.Rows[rowIndex].Columns[i].Value.toString();
//Creating new TextBox
TextBox txtBox = new TextBox();
txtBox.Text = values;
txtBox.Size = new Size(100,200);
flowLayoutPanel1.Controls.Add(txtBox);
}
}catch(Exception ee){}
OR If you want to pass the VALUES of the selected ROW you can do like this
textBox.Text = dataGridView1.Rows[rowIndex].Columns[0].Value.toString();//1st col
textBox1.Text = dataGridView1.Rows[rowIndex].Columns[1].Value.toString();//2nd col
First, right-click your dataGrid and select properties
Then change the SelectionMode to FullRowSelect if you like as shown below:
Next, In the datagrid event section double-click on SelectionChanged
and write code like this, you can use other events, although
// Just for example
private void dataGridView1_SelectionChanged(object sender, EventArgs e)
{
try
{
if (dataGridView1.CurrentRow != null && dataGridView1.CurrentRow.Index >= 0)
{
var row = dataGridView1.CurrentRow;
txtCode.Text = (row.Cells["code"].Value != null) ? row.Cells["code"].Value.ToString() : string.Empty;
txtFirstName.Text = (row.Cells["firstName"].Value != null) ? row.Cells["firstName"].Value.ToString() : string.Empty;
txtLastName.Text = (row.Cells["lastName"].Value != null) ? row.Cells["lastName"].Value.ToString() : string.Empty;
}
}
catch { }
}

Is there a way to dynamically generate code to interact with buttons in C#?

I'm trying to find a a way to be able to essentially dynamically generate code based on an input.
For example I could type something like:
int Number = 22;
Button<Number>.Text = "X";
So in this case it would set button22 to have its text be an "X".
And I could change it so that I could input, for example 24 into the program and it would then set button24 to be an "X", instead of setting up a bunch of if statements to cover every potential button press.
For further context I have a Grid of 64 buttons and I need to be able to edit them individually to show to the user which buttons have been pressed, it is possible to do it with a lot of if statements but I thought it might be worth trying to find a more elegant solution.
You could have a list of buttons:
private List<Button> _buttons = new List<Button>();
Populate it like this:
for (int i = 0; i < 10; i++)
{
var b = new Button();
b.Text = $"Button #{i}";
b.Click += HandleButtonClick;
}
And you could even set an event handler on one of its events which doesn't even need to use the list (the sender is the source of the event):
private void HandleButtonClick(object sender, EventArgs e)
{
(sender as Button).Text = "X";
}
Buttons have a Tag property that can be used to hold arbitrary data about a button, this is described for WinForms, WPF and UWP.
Simple usage that is similar to OP's requirement is demonstrated in this SO post
This situation is in a practical sense the very reason that .Tag exists at all in user interface controls pretty much from the birth of c#.
So you do not need to use a custom class for a button, just simply assign your value to the .Tag property on the Button class that you are creating programmatically:
in this example a list is used to create the buttons and separate the creation from the layout, it is not necessary to do this, but may be useful. Instead, you could assign this button to it's parent container and/or set the layout margins or coordinates without keeping a reference to the Button object at all.
If OP updates the post to include implementation examples, we can update this response with more specific and complete code.
private List<Button> _buttons = new List<Button>();
// ... iteration or switching logic
var nextButton = new Button
{
Text = "x",
Tag = 22
};
nextButton.Click += DynamicButton_Click;
_buttons.Add(nextButton);
// ... later push the buttons into the parent container or bind to the UI
Then the button click handler you can access this Tag property:
this is presented from WinForms, the only difference in UWP or WPF is the method signature, change EventArgs to RoutedEventArgs
private void DynamicButton_Click(object sender, EventArgs e)
{
if(int.TryParse((sender as Button).Tag?.ToString(), out int buttonValue))
{
// use buttonValue
Console.Out.WriteLine(buttonValue);
}
else
{
// Otherwise, sender was not a button, or the button did not have an integer tag value
// either way, handle that error state here...
}
}
Using these concepts, once the buttons are created, let's say in some simple grid alignment, you could allow the user to set this Tag value at runtime if you have a TextBox (or other) input field that can be accessed from the code.
I recommend that you use MVVM style bindings for this rather than directly referencing a TextBox control, but this is simply to demonstrate the point.
private void DynamicButton_Click(object sender, EventArgs e)
{
// assign the string value from the ButtonValueTextbox control to this button
string value = this.ButtonValueTextBox.Text;
if(sender is Button button)
{
button.Tag = value;
}
else
{
// Otherwise, sender was not a button
// handle the error state here if you need to...
}
}
Now that each button has a tag, you could easily add logic to maintain unique tag values by iterating through the other buttons and clearing the tag if it was previously assigned to a different button.
Maybe you could keep a List of Button References:
var myButtons = new List<Button>();
myButtons.Add(firstButton);
myButtons.Add(secondButton);
// ... etc
// ... then somewhere else
int number = 3;
myButtons[number].Text = "xxx";

C# DataGridView and Input Form

I have a DataGridView binded with data from a database. I need to create a Form, which has input fields for data from a single grid row.
The Form has 30+ input controls - TextBoxes, Checkboxes and NumericUpDowns.
For now I went with this approach:
Retrieve current row from the DataGridView and load values from the cells to class instance
Pass the instance to the form and manually fill the input controls
Update the database from the form, update the DataGridView
I want to improve some things:
Is there any way to quickly fill all input controls from a class instance?
Is there any way to determine which input controls have changed their values besides manually subscribing every control on an event handler?
Is there any way to improve this whole thing, e.g. do something more efficiently?
If you are already passing in a DataRow, then you could instead pass in the DataTable and something that identifies the row in that table. And maybe optionally an adapter, if you want to commit the changes immediately on form exit. Then you can create a DataView of that table. And bind each edit control to a field in that view. Something like this:
public partial class EditForm : Form
{
DataRow row = null;
DataView view;
SqlDataAdapter adapter;
public EditForm(SqlDataAdapter adapter, DataTable table, int rowId)
{
InitializeComponent();
this.adapter = adapter;
view = table.DefaultView;
view.RowFilter = $"ID = {rowId}";
if (view.Count == 0) throw new Exception("no such row");
DataRowView dvr = view[0];
row = dvr.Row;
datebox.DataBindings.Add(new Binding("Value", view, "DATE"));
stringbox.DataBindings.Add(new Binding("Text", view, "O_STRING"));
this.FormClosing += EditForm_FormClosing;
}
private void EditForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (row.RowState == DataRowState.Modified) adapter.Update(new DataRow[] { row });
}
}
The above assuming that your table has key column named ID and fields DATE and O_STRING.
This will save you the trouble of creating an intermediate custom class instance, based on that row, moving values to and from various objects and automatically sets the RowStatae in the original table.
Re: value changed indicators. Not sure if there is a really elegant way to do that. Firstly, if I had to, I would change the background (or foreground) color rather than font boldness. Setting font to bold would change the width of the content and that is usually quite annoying. Then, I would add handlers to the TextChanged events (or ValueChange events, for controls that are not text-based). You dont need to write custom handlers to each and every edit control - in the event handler, you get the object sender parameter that points to the control object. Then you can get the field name binded to that control with something like this:
private void stringbox_TextChanged(object sender, EventArgs e)
{
Control ctrl = (Control)sender;
string fieldName = ctrl.DataBindings[0].BindingMemberInfo.BindingMember;
if ((string)view[0].Row[fieldName] != ctrl.Text) ctrl.BackColor = Color.Pink;
}
That way you will only need to add a TextChanged handler once (per edit contol class), not one per every edit box you have.

How do I get the value of a dynamically generated control in C#?

I'm trying to get the value of items in a dynamically made combobox from a separate event. I need to put the value the user chose into a string, but I can't find a way to do it. Here's an example.
ComboBox player1Role = new ComboBox();
player1Role.Width = 100;
player1Role.Items.Add("Top");
player1Role.Items.Add("Jungle");
player1Role.Items.Add("Mid");
player1Role.Items.Add("Bot");
player1Role.Items.Add("Support");
player1Role.Items.Add("Fill");
player1Role.Location = new Point(200, 200);
And then in the following click event, I need to get the value they selected:
private void CreateParty_Click(object sender, EventArgs e)
{
ComboBox player1SelectedRole = (ComboBox)Controls["player1Role"];
string player1roleString = player1SelectedRole.Items[0].ToString();
MessageBox.Show(player1roleString);
}
That code just gives me an exception saying player1SelectedRole was empty. What am I doing wrong?
You're looking for the control by its name, but you didn't assign a name to the control.
player1Role.Name = "player1Role";
Also, I think player1SelectedRole.Items[0] is always going to select the first item regardless of which one is actually selected. Check out player1SelectedRole.SelectedItem instead.

c# listbox with control names to change properties [duplicate]

This question already has answers here:
Find control by name from Windows Forms controls
(3 answers)
Closed 8 years ago.
I am creating new controls and putting there names in a list box, how do i use the name selected in the list box to change the controls properties.
//creating the label
LabelNumber++;
label label=new Label();
label.BackColor=Color.Transparent;
label.Location=new System.Drawing.Point(1,
1);
label.Name="Label" + LabelNumber;
label.Text=LabelNumber.ToString();
label.Size=new System.Drawing.Size(20,
20);
label.TextAlign=ContentAlignment.MiddleCenter;
ProjectPanel.Controls.Add(label);
ControlBox1.Items.Add(label.Name);
The question suggests that the OP is using a ListBox, so my code makes that assumption.
Essentially what you need to do is as follows: Get the selected text from the ListBox, find the control that has the same Name (we will assume this is always unique), then change the properties of that control.
The following code will meet those requirements:
// Get the selected text from the ListBox.
string name = ControlBox1.GetItemText(ControlBox1.SelectedItem);
// Find the control that matches that Name. Assumes there is only ever 1 single match.
Control control = ProjectPanel.Controls.Find(name, true).FirstOrDefault();
// Set properties of the Control.
control.Name = "new name";
// If you know it's a Label, you can cast to Label and use Label specific properties.
Label label = control as Label;
label.Text = "some new text";
Yo can use the Label Name under ControlBox1_SelectedIndexChanged event and get the value of selected indexed Label Name.
You can create a simple 'listboxitem' structure and use it like this:
struct lbo
{
// make the structure immutable
public readonly Control ctl;
// a simple constructor
public lbo(Control ctl_) { ctl = ctl_; }
// make it show the Name in the ListBox
public override string ToString() { return ctl.Name; }
}
private void button1_Click(object sender, EventArgs e)
{
// add a control:
listBox1.Items.Add(new lbo(button1));
}
private void button2_Click(object sender, EventArgs e)
{
// to just change the _Name (or Text or other properties present in all Controls)
((lbo)listBox1.SelectedItem).ctl.Text = button2.Text;
// to use it as a certain Control you need to cast it to the correct control type!!
((Button)((lbo)listBox1.SelectedItem).ctl).FlatStyle = yourStyle;
// to make the cast safe you can use as
Button btn = ((lbo)listBox1.SelectedItem).ctl as Button;
if (btn != null) btn.FlatStyle = FlatStyle.Flat;
}
No checks here for the correct type or that you have selected an item..but you get the idea: put something more useful than a naked object or a mere string into the ListBox!
You could instead loop over all controls and compare the names but that's less efficient and actually not safe as the Name property is not guaranteed to be unique..

Categories