I am new to C# so please fogive my newbie question.
I created a dictionary of controls from a Windows form called dictControls. I then populated it with all text box and combobox controls and values from the form:
Dictionary<Control, string> dictFormControls = new Dictionary<Control, string>();
foreach (Control c in Controls)
{
if (c is ComboBox)
{
dictFormControls.Add(c, ((ComboBox)c).SelectedValue.ToString());
}
if (c is TextBox)
{
dictFormControls.Add(c, ((TextBox)c).Text);
}
if (c is MaskedTextBox)
{
dictFormControls.Add(c, ((MaskedTextBox)c).Text);
}
}
if (discNumber <= Convert.ToInt32(numDiscs))
{
frmAddVideo frm = new frmAddVideo(numDiscs, discNumber, videoID, sequenceID, dictFormControls);
frm.Show();
this.Close();
}
I want the dictionary basically look something like this:
Key ------------ Value
"txtName" ----- "Test"
"txtYear" ------ "1980"
I am passing this back into the same form (frmAddVideo):
public frmAddVideo(string numDiscs, int discNumber, string videoID, string sequenceID, Dictionary<Control, string> dict)
{
this.numDiscs = numDiscs;
this.discNumber = discNumber;
this.videoID = videoID;
this.sequenceID = sequenceID;
InitializeComponent();
//This is where I want to parse out the Dictionary and populate the form values
foreach (KeyValuePair<Control, string> item in dict)
{
**Basically, I am looking for a way to take **
**item(Key)**
**and do something like item(Key).Value = item(Value);**
**so it would be the same as writing**
**txtName.Text= "1980";**
**cbxLocID.Value = 1;**
}
}
I am looking for a way to take key and turn it into the control name, then add ".Text" or ".Value" to it and then set the value to item(value) as I explained in the code above.
Is this possible? I tried researching this, but I have yet to put 2 and 2 together.
You may just store the set of controls you work with in your dictionary:
class ControlBoundValueDescription
{
private Control _control;
public ControlBoundValueDescription(Control control)
{
_control = control;
}
public string Value
{
get
{
if(_control is ...) return ...
...
}
set
{
if(_control is ...) ((Xxx)_control).Yyy = value;
...
}
}
}
...
Dictionary<string, ControlBoundValueDescription> dictControls =
new Dictionary<string, ControlBoundValueDescription>();
...
// defining mappings (you may also want to populate it automatically,
// by iterating over all the controls you have on your form)
dictControls["UserName"] = new ControlBoundValueDescription(tbUserName);
dictControls["Group"] = new ControlBoundValueDescription(cbxGroup);
...
// working with controls using previously defined mappings
dictControls["UserName"].Value = "guest"; // probably, text box
dictControls["Group"].Value = "Guest Users"; // probably, combo
But the whole idea seems to be bad design. You should probably clarify the problem you're trying to solve.
If I understand your question, you can use Find()
((TextBox)myForm.Controls.Find(Key, true)).Text = Value;
((CheckBox)myForm.Controls.Find(Key, true)).Checked = Boolean.Parse(Value);
Related
I have a ToolStripMenuItem called myMenu. How can I access this like so:
/* Normally, I would do: */
this.myMenu... etc.
/* But how do I access it like this: */
String name = myMenu;
this.name...
This is because I am dynamically generating ToolStripMenuItems from an XML file and need to reference MenuItems by their dynamically generated names.
Use the Control.ControlCollection.Find method.
Try this:
this.Controls.Find()
string name = "the_name_you_know";
Control ctn = this.Controls[name];
ctn.Text = "Example...";
Assuming you have the menuStrip object and the menu is only one level deep, use:
ToolStripMenuItem item = menuStrip.Items
.OfType<ToolStripMenuItem>()
.SelectMany(it => it.DropDownItems.OfType<ToolStripMenuItem>())
.SingleOrDefault(n => n.Name == "MyMenu");
For deeper menu levels add more SelectMany operators in the statement.
if you want to search all menu items in the strip then use
ToolStripMenuItem item = menuStrip.Items
.Find("MyMenu",true)
.OfType<ToolStripMenuItem>()
.Single();
However, make sure each menu has a different name to avoid exception thrown by key duplicates.
To avoid exceptions you could use FirstOrDefault instead of SingleOrDefault / Single, or just return a sequence if you might have Name duplicates.
Control GetControlByName(string Name)
{
foreach(Control c in this.Controls)
if(c.Name == Name)
return c;
return null;
}
Disregard this, I reinvent wheels.
Using the same approach of Philip Wallace, we can do like this:
public Control GetControlByName(Control ParentCntl, string NameToSearch)
{
if (ParentCntl.Name == NameToSearch)
return ParentCntl;
foreach (Control ChildCntl in ParentCntl.Controls)
{
Control ResultCntl = GetControlByName(ChildCntl, NameToSearch);
if (ResultCntl != null)
return ResultCntl;
}
return null;
}
Example:
public void doSomething()
{
TextBox myTextBox = (TextBox) this.GetControlByName(this, "mytextboxname");
myTextBox.Text = "Hello!";
}
I hope it help! :)
this.Controls.Find(name, searchAllChildren) doesn't find ToolStripItem because ToolStripItem is not a Control
using SWF = System.Windows.Forms;
using NUF = NUnit.Framework;
namespace workshop.findControlTest {
[NUF.TestFixture]
public class FormTest {
[NUF.Test]public void Find_menu() {
// == prepare ==
var fileTool = new SWF.ToolStripMenuItem();
fileTool.Name = "fileTool";
fileTool.Text = "File";
var menuStrip = new SWF.MenuStrip();
menuStrip.Items.Add(fileTool);
var form = new SWF.Form();
form.Controls.Add(menuStrip);
// == execute ==
var ctrl = form.Controls.Find("fileTool", true);
// == not found! ==
NUF.Assert.That(ctrl.Length, NUF.Is.EqualTo(0));
}
}
}
One of the best way is a single row of code like this:
In this example we search all PictureBox by name in a form
PictureBox[] picSample =
(PictureBox)this.Controls.Find(PIC_SAMPLE_NAME, true);
Most important is the second paramenter of find.
if you are certain that the control name exists you can directly use it:
PictureBox picSample =
(PictureBox)this.Controls.Find(PIC_SAMPLE_NAME, true)[0];
You can use find function in your Form class. If you want to cast (Label) ,(TextView) ... etc, in this way you can use special features of objects. It will be return Label object.
(Label)this.Controls.Find(name,true)[0];
name: item name of searched item in the form
true: Search all Children boolean value
this.Controls["name"];
This is the actual code that is ran:
public virtual Control this[string key]
{
get
{
if (!string.IsNullOrEmpty(key))
{
int index = this.IndexOfKey(key);
if (this.IsValidIndex(index))
{
return this[index];
}
}
return null;
}
}
vs:
public Control[] Find(string key, bool searchAllChildren)
{
if (string.IsNullOrEmpty(key))
{
throw new ArgumentNullException("key", SR.GetString("FindKeyMayNotBeEmptyOrNull"));
}
ArrayList list = this.FindInternal(key, searchAllChildren, this, new ArrayList());
Control[] array = new Control[list.Count];
list.CopyTo(array, 0);
return array;
}
private ArrayList FindInternal(string key, bool searchAllChildren, Control.ControlCollection controlsToLookIn, ArrayList foundControls)
{
if ((controlsToLookIn == null) || (foundControls == null))
{
return null;
}
try
{
for (int i = 0; i < controlsToLookIn.Count; i++)
{
if ((controlsToLookIn[i] != null) && WindowsFormsUtils.SafeCompareStrings(controlsToLookIn[i].Name, key, true))
{
foundControls.Add(controlsToLookIn[i]);
}
}
if (!searchAllChildren)
{
return foundControls;
}
for (int j = 0; j < controlsToLookIn.Count; j++)
{
if (((controlsToLookIn[j] != null) && (controlsToLookIn[j].Controls != null)) && (controlsToLookIn[j].Controls.Count > 0))
{
foundControls = this.FindInternal(key, searchAllChildren, controlsToLookIn[j].Controls, foundControls);
}
}
}
catch (Exception exception)
{
if (ClientUtils.IsSecurityOrCriticalException(exception))
{
throw;
}
}
return foundControls;
}
Assuming you have Windows.Form Form1 as the parent form which owns the menu you've created. One of the form's attributes is named .Menu. If the menu was created programmatically, it should be the same, and it would be recognized as a menu and placed in the Menu attribute of the Form.
In this case, I had a main menu called File. A sub menu, called a MenuItem under File contained the tag Open and was named menu_File_Open. The following worked. Assuming you
// So you don't have to fully reference the objects.
using System.Windows.Forms;
// More stuff before the real code line, but irrelevant to this discussion.
MenuItem my_menuItem = (MenuItem)Form1.Menu.MenuItems["menu_File_Open"];
// Now you can do what you like with my_menuItem;
Since you're generating them dynamically, keep a map between a string and the menu item, that will allow fast retrieval.
// in class scope
private readonly Dictionary<string, ToolStripMenuItem> _menuItemsByName = new Dictionary<string, ToolStripMenuItem>();
// in your method creating items
ToolStripMenuItem createdItem = ...
_menuItemsByName.Add("<name here>", createdItem);
// to access it
ToolStripMenuItem menuItem = _menuItemsByName["<name here>"];
Have a look at the ToolStrip.Items collection. It even has a find method available.
You can do the following:
private ToolStripMenuItem getToolStripMenuItemByName(string nameParam)
{
foreach (Control ctn in this.Controls)
{
if (ctn is ToolStripMenuItem)
{
if (ctn.Name = nameParam)
{
return ctn;
}
}
}
return null;
}
A simple solution would be to iterate through the Controls list in a foreach loop. Something like this:
foreach (Control child in Controls)
{
// Code that executes for each control.
}
So now you have your iterator, child, which is of type Control. Now do what you will with that, personally I found this in a project I did a while ago in which it added an event for this control, like this:
child.MouseDown += new MouseEventHandler(dragDown);
I have a situation where I need to validate if multiple checkboxes are checked in WPF application and run code block accordingly. The only thing that changes with all the iterations of the code block is the file name. Sample code looks like this
if(checkbox1.IsChecked == true)
{
Code(fileName1);
}
if(checkbox2.IsChecked == true)
{
Code(fileName2);
}
if(checkbox3.IsChecked == true)
{
Code(fileName3);
}
It all looks redundant. And I cannot create a method and pass the file name and checkBox name to it as the checkBox will be passed as a string and IsChecked property will be invalid for a string. Any way to overcome this and make the code look neat ?
If you have to manually link up the checkboxes to strings the same way you have it now, and are just looking for shorter code, you can wrap up the checkboxes into a dictionary, then loop through them:
var filesByCheckbox = new Dictionary<CheckBox, string> {
{ checkbox1, filename1 },
{ checkbox2, filename2 },
{ checkbox3, filename3 }
};
foreach (var kvp in filesByCheckbox)
{
if (kvp.Key.IsChecked)
{
Code(kvp.Value);
}
}
This doesn't really improve anything, but it just makes it a little clearer which checkbox is attached to which file so adding to the list is only one line instead of 3.
I'd probably go with Tanner's answer if it works for you.
Can you change the form to use a subclass of CheckBox? If so, you could do this:
class MyCheckBox : CheckBox
{
public string FileName { get; set; }
public void MyMethod()
{
if (IsChecked)
{
(do something with FileName);
}
}
}
Then set the value of FileName in the form designer. Finally, replace the code from your question with something like this:
checkbox1.MyMethod();
checkbox2.MyMethod();
checkbox3.MyMethod();
You need to somehow map your checkboxes to the filenames. An easy way would be to use the Tag property:
checkbox1.Tag = filename1;
checkbox2.Tag = filename2;
checkbox3.Tag = filename3;
Then put yout checkboxes into an array and process that:
var checkboxes = new [] { checkbox1, checkbox2, checkbox3 };
foreach (var checkbox in checkboxes.Where(cb => cb.IsChecked))
{
Code((string)checkbox.Tag);
}
Following is my code which is binding a List() to a ComboBox control. I tried to add few items to the CombBox control using Insert() method which is not allowed as it has been assigned to a datasource. So, how can I get the datasouce back to a new variable (say, var colours2) from cmbColour.DataSource which is returning an object. Thanx !
var colours= new Dictionary<string, string>
{
{"1x","Green"},
{"2x","Red"},
{"3y","Blue"},
{"4y","Black"}
}.ToList();
cmbColour.ValueMember = "Key";
cmbColour.DisplayMember = "Value";
cmbColour.DataSource = colours;
var colours2 = //how can I get the DataSource back
The following code will return a new dictionary containing the same data you bound to the combo box.
var list = (List<KeyValuePair<String, String>>)cmbColor.DataSource;
var dictionary = list.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
The property DataSource will return the same instance you assigned but because it is typed Object you have to cast it back to the actual type before you can access any members.
But why don't you just keep the original dictionary? And it is definitely supported to modify a list bound to a data source - that is the whole point of data binding.
I guess my answer does not really solve your actual problem, only what you think what your problem is. Maybe you can give some additional information about what you are trying to achieve and I or someone else will be able to help you with your underlying problem.
UPDATE
This should work for your scenario - I stick with the user example.
public class User
{
public String Id { get; set; }
public String Name { get; set; }
}
And the code for the form.
public partial class MainForm : Form
{
private readonly BindingList<User> recentlyAddedUsers = new BindingList<User>();
private void MainFormLoad(Object sender, EventArgs e)
{
this.comboBoxRecentlyAddedUsers.DataSource = this.recentlyAddedUsers;
this.comboBoxRecentlyAddedUsers.ValueMember = "Id";
this.comboBoxRecentlyAddedUsers.DisplayMember = "Name";
var recentlyAddedUsersFromService = this.GetRecentlyAddedUsers();
foreach (var user in recentlyAddedUsersFromService)
{
this.recentlyAddedUsers.Add(user);
}
}
private void ButtonAddNewUserClick(Object sender, EventArgs e)
{
var newUser = new User();
newUser.Id = this.textBoxUserId.Text;
newUser.Name = this.textBoxUserName.Text;
this.SaveNewUser(newUser);
this.recentlyAddedUsers.RemoveAt(0);
this.recentlyAddedUsers.Insert(newUser);
}
private List<User> GetRecentlyAddedUsers()
{
// Get a list of recently added users from the database.
}
private void SaveNewUser(User user)
{
// Save the new user to the database.
}
}
Note the usage of BindingList<T> - this will notify the combo box about any changes to the list. A simple List<T> would also work but then you have to explicitly tell the combo box to refresh the data binding.
Since DataSource is typed as an object there's not a clean way to extract the data from it. If you know the underlying type of the data source (meaning you set it somewhere else so you KNOW what type it is) you can just cast it and add items to the collection.
Be aware that the object will be a REFERENCE not a COPY of the original object, meaning if you set the data source to a dictionary initially then try to extract it to add items you're adding items to the SAME dictionary.
So in your case:
var colours2 = cmbColour.DataSource as List<KeyValuePair<string, string>>;
// can add items to colours2 here (but it is the same instance as colours)
var coloursList = cmbColour.DataSource as List<KeyValuePair<string, string>>;
var colours2 = coloursList.ToDictionary(x => x.Key, x => x.Value);
You can get the data back by using following cast.
namespace WebApplication2
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
var colours = new Dictionary<string, string>
{
{"1x", "Green"},
{"2x", "Red"},
{"3y", "Blue"},
{"4y", "Black"}
}.ToList();
cmbColour.DataSource = colours;
List<KeyValuePair<string, string>> l1 = new List<KeyValuePair<string, string>>();
l1 = (List<KeyValuePair<string, string>>)cmbColour.DataSource;
}
}
}
List<KeyValuePair<String, String>> c = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("m", "n"),
new KeyValuePair<string, string>("k", "c")
};
comboBox1.DataSource = c;
c.Add(new KeyValuePair<string, string>("new","new value"));
or
var list = (List<KeyValuePair<String, String>>)comboBox1.DataSource;
list.Add(new KeyValuePair<string, string>("new", "new value"));
I have a game I'm making in .NET at the moment. Right now I have something like this for determining which textbox belongs to which player:
switch(player.ID) {
case 1: storeRandPlayerInfo = textbox1.Text;
break;
case 2: storeRandPlayerInfo = textbox2.Text;
break;
case 3: storeRandPlayerInfo = textbox3.Text;
break;
}
My question is, is there a way to do something like Windows.Textbox["textbox"+player.ID].Text; Know what i mean? I can't find anything online so I assume it's not possible but I just wanted to know.
Surely there is a way. One of them is:
// define and init
TextBox[] playerBoxes = new TextBox[] { textBox1, textBox2, textBox3 };
// use
storeRandPlayerInfo = playerBoxes[player.ID - 1];
You can do something like this
private TextBox GetPlayerTextBox(int playerId)
{
string textBoxName = string.Format("TextBox{0}", playerId);
return this.Controls.OfType<TextBox>().Where(t => t.Name == textBoxName).Single();
}
You can just use the indexing of "this.Controls" (point to note, "this.Controls" will only contain the top level controls on the form, controls within a groupBox for instance will be in a collection in "this.groupBox1.Controls", so index into that collection rather than "this.Controls")
private TextBox getPlayersBox(int player)
{
string expected = "textBox" + player.ToString();
if (this.Controls.ContainsKey(expected))
return this.Controls[expected] as TextBox;
else
return null;
}
Personally I would use a dictionary; you are describing a mapping, and that's what dictionaries do best. You can do it either with your ID, or the objects themselves:
// using ID as the key
private readonly Dictionary<int, TextBox> mPlayerTextBoxes;
// ...or using object as the key
private readonly Dictionary<Player, TextBox> mPlayerTextBoxes;
// in form constructor, after InitializeComponent call:
// using ID as the key
mPlayerTextBoxes = new Dictionary<int, TextBox>
{
{ player1.ID, textbox1 },
{ player2.ID, textbox2 },
{ player3.ID, textbox3 }
};
// using object as the key
mPlayerTextBoxes = new Dictionary<Player, TextBox>
{
{ player1, textbox1 },
{ player2, textbox2 },
{ player3, textbox3 }
};
// then when you want a textbox, given a player:
// using ID
TextBox textBox = mPlayerTextBoxes[player.ID];
// using object
TextBox textBox = mPlayerTextBoxes[player];
i wish to create a form at runtime that will read the columns for any datasource and create fields based on the columns and datatype just like a datagridviews insert line
Best regards,
Mark
What you are doing sounds a lot like how PropertyGrid already works, which is essentially:
foreach(PropertyDescriptor prop in TypeDescriptor.GetProperties(obj)) {
object val = prop.GetValue(obj);
string s = prop.Converter.ConvertToString(val);
Control cont = // TODO: create some control and set x/y
cont.Text = s;
this.Controls.Add(cont);
}
To avoid lots of work with alignment, using Dock to set the positions might help:
using(Form form = new Form())
using (PropertyGrid grid = new PropertyGrid())
{
form.Text = obj.ToString(); // why not...
grid.Dock = DockStyle.Fill;
form.Controls.Add(grid);
grid.SelectedObject = obj;
form.ShowDialog(this);
}
I wonder if it is easier to use PropertyGrid in simple circumstances, though. Or there are some 3rd-party versions that work similarly.
Ok so heres what i came up with!
public partial class Form2 : Form
{
private Boolean isBrowsable(PropertyInfo info)
{
return info.GetCustomAttributes(typeof(BrowsableAttribute), false).Length>-1;
}
public Form2()
{
InitializeComponent();
}
public Form2(Boolean showCheckBoxes)
{
InitializeComponent();
_showCheckBoxes = true;
}
private Boolean _showCheckBoxes;
private Object _reflection;
private TableLayoutPanel _table = new TableLayoutPanel{Dock=DockStyle.Fill, CellBorderStyle = TableLayoutPanelCellBorderStyle.Single};
public Object SelectedObject
{
get
{
return _reflection;
}
set
{
//clear all controls from the table
_table.Controls.Clear();
foreach (var property in _reflection.GetType().GetProperties())
{
if (isBrowsable(property))
{
if ((property.PropertyType == typeof(int)) || (property.PropertyType == typeof(string)))
{
var textField = new TextBox { Dock = DockStyle.Fill, AutoSize = true };
textField.DataBindings.Add("Text", _reflection, property.Name);
_table.Controls.Add(textField, 2, _table.RowCount += 1);
var propertyLabel = new Label
{
Text = property.Name,
Dock = DockStyle.Fill,
TextAlign = ContentAlignment.MiddleLeft
};
_table.Controls.Add(propertyLabel, 1, _table.RowCount);
if (_showCheckBoxes)
{
var checkBox = new CheckBox
{
AutoSize = true,
Name = property.Name,
Dock = DockStyle.Left,
CheckAlign = ContentAlignment.TopLeft
};
_table.Controls.Add(checkBox, 0, _table.RowCount);
}
}
}
}
//add one extra row to finish alignment
var panel = new Panel { AutoSize = true };
_table.Controls.Add(panel, 2, _table.RowCount += 1);
_table.Controls.Add(panel, 1, _table.RowCount);
if (_showCheckBoxes)
{
_table.Controls.Add(panel, 0, _table.RowCount);
}
Controls.Add(_table);
if (!Controls.Contains(_table))
Controls.Add(_table);
}
}
public Boolean Execute(Object reflection)
{
SelectedObject = reflection;
return ShowDialog() == DialogResult.OK;
}
}
thanks all!
I don't fully understand your question. Is it correct that you want to create a Windows form which provides input fields (textboxes, checkboxes, etc.) for all fields/properties of an object that you feed to the form as its DataSource?
You might have to use reflection for this (see the System.Reflection namespace). For example, to get a list of all properties:
using System.Reflection;
....
public object DataSource;
...
Debug.Assert( DataSource != null );
var properties = DataSource.GetType().GetProperties();
You would then instantiate one input control per property:
foreach ( var property in properties )
{
// extract some information about each property:
string propertyName = property.Name;
Type propertyType = property.PropertyType;
bool propertyReadOnly = !property.CanWrite;
// create input controls based on this information:
// ...
}
However, it might be fairly tricky to reliably map property types to the correct input control; for example, what are you going to do when you encounter a property with some unknown class as its type, or when a property is a collection of values? You might have to create a sub-form inside your form in some cases; in other cases, a listbox might be enough.
I've recently built a sample project that uses the Dynamic Data assemblies of ASP.NET to do just this for a WPF grid, but I'm sure you could adapt the concept to WinForms. Dynamic Data provides much richer metadata than just reflection or the database, but it does require an Entity Data Model, or a LINQ to SQL data model.
basically, all you need is a reference to System.Web.DymamicData, and maybe you can find something useful in my class:
public class DynamicDataGridBuilder<TContext, TEntity> where TEntity : EntityObject
{
readonly MetaModel model = new MetaModel();
public DynamicDataGridBuilder()
{
model.RegisterContext(typeof(TContext), new ContextConfiguration { ScaffoldAllTables = true });
}
public void BuildColumns(DataGrid targetGrid)
{
MetaTable metaTable = model.GetTable(typeof(TEntity));
// Decision whether to auto-generated columns still rests with the caller.
targetGrid.Columns.Clear();
foreach (var metaColumn in metaTable.Columns.Where(x => x.GetType().Name == "MetaColumn" && x.Scaffold))
{
switch (metaColumn.ColumnType.Name)
{
case "Boolean":
targetGrid.Columns.Add(new DataGridCheckBoxColumn { Binding = new Binding(metaColumn.Name), Header = metaColumn.DisplayName });
break;
default:
targetGrid.Columns.Add(new DynamicDataGridTextColumn { MetaColumn = metaColumn, Binding = new Binding(metaColumn.Name), Header = metaColumn.DisplayName });
break;
}
}
}
}
TContext is the type of your object model, and TEntity the type of the entity / class in that model your want to generate controls for.
Use control data binding. It will do all the work for you.