How to get Combobox.Datasource to a Dictionary? - c#

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"));

Related

How to add a dictionary to a ContextMenuStrip

I am currently trying to add to a context menu strip a dictionary of status values. The main issue I am having is how to pass in the Key value of the selected dictionary item to a click event.
Dictionary<int, string> statusList = getStatusList();
if (statusList.Count > 0)
{
Dictionary<int, ToolStripItem> statusMenu = new Dictionary<int, ToolStripItem>();
foreach (var keyValuePair in statusList)
{
statusMenu.Add(keyValuePair.Key, new ToolStripMenuItem() { Text = keyValuePair.Value.ToString(), Image = Resources.Refresh });
//statusMenu[statusMenu.Count - 1, statusMenu].Click += new EventHandler(MenuOption_Click_Handler); This is where I am confused
}
datagridview1.ContextMenuStrip.Items.Add(new ToolStripMenuItem("Set Status to", Resources.Refresh, statusMenu.Values.ToArray()));
}
I can easily get the array of the dictionary values. I am just wondering if its possible upon clicking a status in the contextmenu to pass the Key to a save method?
So, for example say statusList contains the following values:
{[1, Status1]}
{[2, Status2]}
{[5, Status3]}
So if I load the contextmenu, if I click Status3, I need to pass to the ClickEvent that Status Key 5 was clicked. Right now its only detecting the Value (Status3). Any help is much appreciated!
Why don't you rename your tooltip control? The id is the key of Dictionary, so it should be distinct. Anyway, any control must have name.
This is an idea. You name the control with the prefix ToolStrip(or whatever you like) + Key and get it later. Like:
var newItem= new ToolStripMenuItem() {
Text = keyValuePair.Value.ToString(),
Image = Resources.Refresh,
Name="ToolStrip" + keyValuePair.Key };
newItem.Click +=new EventHandler(MenuOption_Click_Handler);
statusMenu.Add(keyValuePair.Key,newItem);
In MenuOption_Click_Handler method:
public void MenuOption_Click_Handler(object sender,EventArgs e)
{
var id = Convert.ToInt32(((ToolStripMenuItem)sender).Name.Substring(0,9));
}

Access data in datagridview from a class

I've read a lot of topics on this issue but I'm not finding an answer. I'm fairly new to this so please bear with me.
I'm trying to pass values from datagridview to a list. And then in a new class I want to make som methods accessing that list. Trouble is that when I pass the datagridview it returns it without content and values which means I can't do anything with it.
The code under ////TESTING//// works like I want. I create an instance of the specified list and it's counting the amount of rows properly, see screenshot.
public List<vertEl> getVertList = new List<vertEl>();
//Opens the file dialog and assigns file path to Textbox
OpenFileDialog browseButton = new OpenFileDialog();
private void browse_Click(object sender, EventArgs e)
{
browseButton.Filter = "Excel Files |*.xlsx;*.xls;*.xlsm;*.csv";
if (browseButton.ShowDialog() == DialogResult.OK)
{
//SOME CODE TO GET DATA FROM EXCEL AND SOME METHODS TO CALCULATE
//VALUES TO PASS TO THE TAB VERTIKALELEMENTER TAB IN MY DATAGRIDVIEW
//VERTIKALELEMENTER IS vertElementerDgv IN MY CODE
////TESTING////
GetVertElementasList TEST = new GetVertElementasList();
getVertList = TEST.vertList(vertElementerDgv);
MessageBox.Show(getVertList.Count.ToString());
}
else return;
}
I now want to do this in a seperate class and call a method from that class to do the same but when I try that with code underneath I do not get the same count as when I have the code in form1 (public partial class BridgeGeometry). It return count of 0. The method foo() is assigned to the button 1 in the form.
class GetKoord
{
public GetVertElementasList getList = new GetVertElementasList();
BridgGeometry obj = new BridgGeometry();
public void foo()
{
var TEST = getList.vertList(obj.vertElementerDgv);
//var TEST = obj.getVertList;
MessageBox.Show(TEST.Count.ToString());
}
}
I also tried to get the values directly from the datagridview but there's nothing in it when I access it from a class which is not the form1/BridgeGeometry class.
Form - screenshot
You could run a loop and store the information with selected rows into a public var with something like this:
string itemOne = dataGridView1.SelectedRows[0].Cells[1].Value + string.Empty;
string itemTwo= dataGridView1.SelectedRows[0].Cells[2].Value + string.Empty;
string itemThree = dgMasterGridBun.SelectedRows[0].Cells[3].Value + string.Empty;
Variables
public var varItemOne = itemOne;
public var varItemTwo = itemTwo;
public var varItemThree = itemThree;
Based on the link I managed to get this working. Probably not the best solution, but a working one. I tried to wrap my head around databinding, listbinding etc. but since the class with the input values are a messy one I gave that up for now. The datagriview input values are a little from lists and some from other datagridview.
MSDN-forum: Accessing Form1 controls from a different class
Explanations are given in the link so I'll just provide how I did it in my program.
If my GetKoord class are like this:
public class GetKoord
{
private BridgGeometry bridgeGeometry;
public GetKoord(BridgGeometry form1)
{
bridgeGeometry = form1;
}
public List<vertElementerParam> getListvertElementer(List<vertElementerParam> theList)
{
//var vertElementerDgv = bridgeGeometry.vertElementerDgv;
GetVertElementasList getVertElementasList = new GetVertElementasList();
List<vertElementerParam> MyDgvListList = new List<vertElementerParam>();
MyDgvListList = getVertElementasList.vertList(bridgeGeometry.vertElementerDgv);
//MessageBox.Show(MyDgvListList.Count.ToString());
theList = MyDgvListList;
return theList;
}
}
then I can get the list in Button1_Click like this, check the screenshot in the first post:
public List<vertElementerParam> getVertList = new List<vertElementerParam>();
private void button1_Click(object sender, EventArgs e)
{
GetKoord getKoord = new GetKoord(this);
List<vertElementerParam> testList = new List<vertElementerParam>();
testList = getKoord.getListvertElementer(getVertList);
MessageBox.Show(testList.Count.ToString());
}

Updating a Label with DataBinding

After my balance label is initially bound to a number, changing the datasource again doesn't update the value again.
I want to update the a Windows Form Label automatically after the database object is changed and I re-pull it into the constructorData.BankAccount.
public class ConstructorData
{
public Client Client { get; set; }
public BankAccount BankAccount { get; set; }
}
private void frmTransaction_Load(object sender, EventArgs e)
{
// Pretend we populated constructor data already
// This line of code is working
bankAccountBindingSource.DataSource = constructorData.BankAccount;
}
private void lnkProcess_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
constructorData.BankAccount = db.BankAccounts.Where(x => x.BankAccountId == constructorData.BankAccount.BankAccountId).SingleOrDefault();
// What do I do here
// Doesn't work
bankAccountBindingSource.EndEdit();
bankAccountBindingSource.ResetBindings(false);
}
Auto generated code:
//
// lblAccountBalance
//
this.lblAccountBalance.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.lblAccountBalance.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.bankAccountBindingSource, "Balance", true));
this.lblAccountBalance.Location = new System.Drawing.Point(482, 71);
this.lblAccountBalance.Name = "lblAccountBalance";
this.lblAccountBalance.Size = new System.Drawing.Size(196, 23);
this.lblAccountBalance.TabIndex = 7;
this.lblAccountBalance.Text = "label1";
Since here (inside the form load):
bankAccountBindingSource.DataSource = constructorData.BankAccount;
you bind directly to the BankAccount instance, even implementing INotifyPropertyChanged in ConstructorData class (as suggested in the comments) will not help.
With that design, anytime you assign a new BankAccount instance to the ConstructorData.BankAccount property (as in the shown code), you need also to set it as DataSource of the BindingSource used:
constructorData.BankAccount = db.BankAccounts.Where(x => x.BankAccountId == constructorData.BankAccount.BankAccountId).SingleOrDefault();
// What do I do here
bankAccountBindingSource.DataSource = constructorData.BankAccount;
Without Implementing INotifyPropertyChanged Ivan's answer is exactly what you need.
The reason is because you put an object in DataSource of binding source this way: BindingSource.DataSource = constructorData.BankAccount, so it uses the object which is in BankAccount property as data source. If you change the value of constructorData.BankAccount, you disd't changed the data source of BindingSource and it will contain the previous object. For example take a look at this code:
var a = new MyClass("1"); // ← constructorData.BankAccount = something;
var b = a; // ← bindingSource.DataSource = constructorData.BankAccount.
a = new MyClass("2"); // ← constructorData.BankAccount = something else;
What should contain b now? Do you expect b contains MyClass("1")? Surely no.
For more information take a look at this post:
Data Binding doesn't work when I assign a new object instance to the bound variable
Can I use INotifyPropertyChanged to solve the problem?
If you implement INotifyPropertyChanged in ConstructorData and change bindings this way, yes:
bankAccountBindingSource.DataSource = constructorData;
//...
this.lblAccountBalance.DataBindings.Add(new System.Windows.Forms.Binding("Text",
this.bankAccountBindingSource, "BankAccount.Balance", true));

linq query and foreign used to create data

I got problem facing the problem that i am using the table, "locationstation" and create station and location inside and both the station and location were linked to locationstation by their primary key. I have already successfully showed their data in the combo box but the problem now is that i don't know how to select the data inside the combo box and save the data in the locationstation table.
private void btnCreate_Click(object sender, EventArgs e)
{
using (testEntities Setupctx = new testEntities())
{
//station selectStation = cbStation.SelectedItem as station;
//location selectLocation = cbLocation.SelectedItem as location;
string selectStation = cbStation.SelectedItem.ToString();
string selectLocation = cbLocation.SelectedItem.ToString();
locationstation creLS = new locationstation();
creLS.idStation = cbStation.SelectedItem.ToString();
selectLocation.Location1 = (string)cbLocation.SelectedItem;
Setupctx.locationstations.AddObject(selectStation);
//Setupctx.SaveChanges();
//cbStation.SelectedIndex = -1;
//cbLocation.SelectedIndex = -1;
MessageBox.Show("New Location Station Is Created");
}
}
I don't how to make it working but my codes that i'm trying is right here. Help will be greatly appreciated.
This is the code that i bind the station name and location name into the combo box.
private void Create_LS_Load(object sender, EventArgs e)
{
using (testEntities Setupctx = new testEntities())
{
var storeStation = (from SLS in Setupctx.locationstations
join station s in Setupctx.stations on SLS.idStation equals s.idstations
select s.Station1).Distinct().ToList();
foreach (var LocationStation in storeStation)
{
cbStation.Items.Add(LocationStation);
}
var storeLocation = (from SLS in Setupctx.locationstations
join location l in Setupctx.locations on SLS.idLocation equals l.idlocation
select l.Location1).Distinct().ToList();
foreach (var LocationStation1 in storeLocation)
{
cbLocation.Items.Add(LocationStation1);
}
}
}
Hi set all Navigation properties of selectStation equal to null before Adding the the object using
Setupctx.locationstations.AddObject(selectStation);
instead of
string selectStation = cbStation.SelectedItem.ToString();
use
LocationStation selectStation=(LocationStation)cbStation.SelectedItem;
and then extract values from selectStation or do what you want with it.
I hope this will help.
Station selectStation = (Station)cbStation.SelectedItem ; //cast here to your T
Location selectLocation = (Location)cbLocation.SelectedItem; //cast here to your T
locationstation creLS = new locationstation()
{
StationId=selectStation.Id ,
LocationId=selectLocation.Id
};
Setupctx.locationstations.AddObject(creLS);
Setupctx.SaveChanges();
However I can not imagine doing something like above on the combobox with Key/Value data.
because probably binding lookup types comboboxes to KeyValuePair by using .ToDictionary()
(where T is type of primary key) opens up easier code reuse to handle
selected items and bindings on any combobox in whole app by setting ValueMember = Key
and DisplayMember =Value (or whatever Key/Value properties are in your combo control) then you can do this for example:
long GetSelectedId(comboBox cbo)
{
long IdOut=-1;
if (cbo.SelectedItem==null)
return IdOut;
KeyValuePair<long, string> Item= (KeyValuePair<long, string>)cbo.SelectedItem;
IdOut = Item.Key;
return IdOut;
}

Populating Control values from a Dictionary?

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);

Categories