DataGridViewComboBoxCell creates Null Reference Exception - c#

I'm new here to Stack Overflow so please excuse any improper form/etiquette. Thanks!
EDIT: I guess my questions is not about how to fix the NullReference Exception, but rather how to deal with it not correctly "Exiting" the combobox when I click outside of it.
I am having an issue with my DataGridViewComboBoxCell setup that I am using. First of all, I have a datagridview that contains 3 columns which, upon the user enabling editing, populates the cells with a DataGridViewComboBoxCell. In each row, These 3 cells are dependent on selected item in the previous cell(except for the first ComboBoxCell). The issue I am having is if I click on the first ComboBox and I let it show the drop down list but I don't actually select anything and I move to the next ComboBoxCell and try to click it to view its list of items it stops the program and creates an error for "NullReference Exception was unhandled". This exception comes up under "static void Main()" at Application.Run(new MainForm());
Code that handles changing ComboBox selected index which should auto populate the other ComboBoxes.
private void LoadRules_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
DataGridViewComboBoxEditingControl cbx = e.Control as DataGridViewComboBoxEditingControl;
if (cbx != null)
{
if (this.LoadRulesDataGridView.Columns[LoadRulesDataGridView.CurrentCell.ColumnIndex].Name.Equals("OEM"))
{
ComboBox cmbprocess = e.Control as ComboBox;
cmbprocess.SelectedIndexChanged += new EventHandler(OEMBox_SelectedIndexChanged);
cmbprocess.SelectedIndexChanged += new EventHandler(ModelBox_SelectedIndexChanged);
}
}
}
private void OEMBox_SelectedIndexChanged(object sender, EventArgs e)
{
NetMonDB.DBManager dbConn = new NetMonDB.DBManager(ConnStr, this.LogWarning, this.LogError);
ComboBox cmbprocess = (ComboBox)sender;
int row = this.LoadRulesDataGridView.CurrentCell.RowIndex;
string OEM = cmbprocess.SelectedItem.ToString();
this.RulesGridModels(row, cmbprocess, dbConn, OEM);//this method gets the required info from the database and loads it into the ComboBox
cmbprocess.SelectedIndexChanged -= new EventHandler(OEMBox_SelectedIndexChanged);
}
private void ModelBox_SelectedIndexChanged(object sender, EventArgs e)
{
NetMonDB.DBManager dbConn = new NetMonDB.DBManager(ConnStr, this.LogWarning, this.LogError);
ComboBox cmbprocess = (ComboBox)sender;
int row = this.LoadRulesDataGridView.CurrentCell.RowIndex;
string Model = cmbprocess.SelectedItem.ToString();
string OEM = cmbprocess.SelectedItem.ToString();
this.RulesGridOSVersions(row, cmbprocess, dbConn, OEM, Model);//this method gets the required info from the database and loads it into the ComboBox
cmbprocess.SelectedIndexChanged -= new EventHandler(ModelBox_SelectedIndexChanged);
}
Methods that update the ComboBoxes.
private void RulesGridModels(int r, ComboBox comboBox, NetMonDB.DBManager dbConn, string rowOEM)
{
//MessageBox.Show(this.LoadRulesDataGridView.Rows[0].Cells[4].Value.ToString());
DataGridViewComboBoxCell cbo = new DataGridViewComboBoxCell();
for (int i = 0; i < comboBox.Items.Count; i++)
{
cbo.Items.AddRange(comboBox.Items[i]);
}
try
{
cbo.Items.Clear();
cbo.Items.AddRange("");
if (this.LoadRulesDataGridView.Rows[r].Cells[4].Value == null)
this.LoadRulesDataGridView.Rows[r].Cells[4].Value = "";
NetMonDB.Phone OEMPhone = dbConn.getOEMId(rowOEM);
foreach (NetMonDB.Phone phone in dbConn.getModel(OEMPhone))
{
cbo.Items.Add(phone.Model);
}
this.LoadRulesDataGridView.Rows[r].Cells[5] = cbo;
}
catch (Exception e)
{
LogError("RulesGridModels", e.ToString());
}
}
private void RulesGridOSVersions(int r, ComboBox comboBox, NetMonDB.DBManager dbConn, string rowOEM, string rowModel)
{
DataGridViewComboBoxCell cbo = new DataGridViewComboBoxCell();
for (int i = 0; i < comboBox.Items.Count; i++)
{
cbo.Items.AddRange(comboBox.Items[i]);
}
try
{
cbo.Items.Clear();
cbo.Items.AddRange("");
NetMonDB.Phone CurrentPhone = dbConn.getOEMId(rowOEM);
CurrentPhone.Model = rowModel;
foreach (NetMonDB.Phone phone in dbConn.getOSVersion(CurrentPhone))
{
cbo.Items.Add(phone.OSVersion);
}
this.LoadRulesDataGridView.Rows[r].Cells[6] = cbo;
}
catch (Exception e)
{
LogError("RulesGridOSVersions", e.ToString());
}
}
Where the exception is caught.
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());//Crashes at this Point
}

I had the same problem in a situation like this, What I found as a solution of this problem is to use SelectionChangeCommitted instead of SelectedIndexChanged event for combobox.

Related

Create an EventHandler for ComboBox's that are inside a DataGridView, when ComboBox is created at Run time

I have a DataGridView called table_assets which is created in the designer.
In runtime, a DBAdapter is attached to the DataSource of table_assets, which populates table_assets with columns - one of the columns [Headertext] is: Owner.
The column Owner is a column of ComboBoxs.
A requirement of this program (along with the above) is that Items in the ComboBoxs that are inside the Column Owner, in all currently used Rows has to change from:
<client>
to
<client>
<spouse>
Joint
When the global Boolean spouseActive is false or true, respectably.
The challenge I am having is telling the program to change the Items. For the most part, I have been unable to add an event handler to the ComboBox's which as I understand it, is the only way to change the Items.
Here is my relevant code, although I do not think it would be of much use - it will crash in comboBoxColumn_AssetsOwner_Selected:
bool spouseActive;
public Client()
{
// table_assets
assetsAdapter = new DBAdapter(database.clientAssets);
assetsAdapter.ConstructAdaptor(null, " cID = " + clientID.ToString());
table_assets.DataSource = (DataTable)assetsAdapter.ReturnTable();
table_assets.CellClick += new DataGridViewCellEventHandler(comboBoxColumn_AssetsOwner_Selected);
}
private void comboBoxColumn_AssetsOwner_Selected(object sender, EventArgs e)
{
DataGridViewComboBoxCell cell = (DataGridViewComboBoxCell)sender;
if (spouseActive == true)
{
cell.Items.Add("<spouse>");
cell.Items.Add("Joint");
Debug.WriteLine("added");
}
else
{
cell.Items.Remove("<spouse>");
cell.Items.Remove("Joint");
Debug.WriteLine("removed");
}
}
Try using EditingControlShowing event for the DataGridView:
bool spouseActive;
public Client()
{
// table_assets
assetsAdapter = new DBAdapter(database.clientAssets);
assetsAdapter.ConstructAdaptor(null, " cID = " + clientID.ToString());
table_assets.DataSource = (DataTable)assetsAdapter.ReturnTable();
table_assets.EditingControlShowing += table_assets_EditingControlShowing;
}
private void table_assets_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
if (e.Control is ComboBox)
{
ComboBox cbMyComboBox = e.Control as ComboBox;
if (spouseActive == true)
{
cbMyComboBox.Items.Add("<spouse>");
cbMyComboBox.Items.Add("Joint");
Debug.WriteLine("added");
}
else
{
cbMyComboBox.Items.Remove("<spouse>");
cbMyComboBox.Items.Remove("Joint");
Debug.WriteLine("removed");
}
}
}

Property returns different value than what was set

I am working on a Visual Studio extension and my current goal is to set up a menu item in the Tools menu. When clicked on this menu item will open a WinForms window containing a ListView, 3 textboxes, and a button. The idea is when you click on one of the rows in the ListView the data from that row will be populated in the textboxes so that you can update it. If you click the button a new row is added and the textboxes are cleared. However, I'm having an issue with getting the index of the row that I've selected.
private int _index;
private void newSourceBtn_Click(object sender, EventArgs e)
{
// Add new row to the ListView
ListViewItem row = new ListViewItem();
row.SubItems.Add("new");
row.SubItems.Add(String.Empty);
row.SubItems.Add(String.Empty);
remoteSourceListView.Items.Add(row);
int index = remoteSourceListView.Items.Count - 1;
remoteSourceListView.Items[index].Selected = true;
newSourceAdded = true;
sourceNameTextBox.Clear();
sourceUrlTextBox.Clear();
}
public void SourceName_TextChanged(object sender, EventArgs e)
{
remoteSourceListView.Items[IndexSelected].SubItems[1].Text = sourceNameTextBox.Text;
}
public void SourceURL_TextChanged(object sender, EventArgs e)
{
string url = sourceUrlTextBox.Text;
if ((url.StartsWith("http")) || (url.StartsWith("https")) || (url.StartsWith("git")))
{
sourceBranchTextBox.Enabled = true;
}
remoteSourceListView.Items[IndexSelected].SubItems[2].Text = url;
}
public void SourceBranch_TextChanged(object sender, EventArgs e)
{
}
public void SourcesListView_SelectedIndexChanged(object sender, EventArgs e)
{
ListView.SelectedListViewItemCollection selectedRows = remoteSourceListView.SelectedItems;
foreach (ListViewItem row in selectedRows)
{
sourceNameTextBox.Text = row.SubItems[1].Text;
sourceUrlTextBox.Text = row.SubItems[2].Text;
IndexSelected = row.Index;
if (row.SubItems[3].Text != "")
{
sourceBranchTextBox.Enabled = true;
sourceBranchTextBox.Text = row.SubItems[3].Text;
}
}
}
public int IndexSelected
{
get { return _index; }
set { _index = value; }
}
This code shows the button click event which adds the new row to the ListView, the text changed events for each of the textboxes which updates the row in the ListView (sorta), and the selected index changed event for the ListView which is where I'm getting the index of the row that was just selected. While debugging, I noticed that when I click on a row I'm getting the correct index in the selected index changed event; however, when I call IndexSelected from either of the text changed events it is always giving me a different index.
Any suggestions?
From the code posted I can't find any reason that explain the behavior documented.
A possible reason could be the insertion/deletion of new/existing ListViewItem in a position before the saved RowIndex.
However another approach is possible. Instead of keeping the RowIndex you could try to set a global property to the ListViewItem selected and reuse this instance when you need to set its subitems.
In this way you avoid problems if the number of ListViewItems change and some item is inserted/removed before the saved RowIndex. However a safeguard against a null value should be provided.
private ListViewItem CurrentItemSelected {get;set;}
......
public void SourcesListView_SelectedIndexChanged(object sender, EventArgs e)
{
ListView.SelectedListViewItemCollection selectedRows = remoteSourceListView.SelectedItems;
foreach (ListViewItem row in selectedRows)
{
sourceNameTextBox.Text = row.SubItems[1].Text;
sourceUrlTextBox.Text = row.SubItems[2].Text;
CurrentItemSelected = row;
if (row.SubItems[3].Text != "")
{
sourceBranchTextBox.Enabled = true;
sourceBranchTextBox.Text = row.SubItems[3].Text;
}
}
}
public void SourceName_TextChanged(object sender, EventArgs e)
{
if(CurrentItemSelected != null)
CurrentItemSelected.SubItems[1].Text = sourceNameTextBox.Text;
}
However, I am a bit perplexed by your code. Do you have the property MultiSelect set to true? Because if it is set to false then your code doesn't need to loop.
public void SourcesListView_SelectedIndexChanged(object sender, EventArgs e)
{
if(remoteSourceListView.SelectedItems.Count > 0)
{
// With MultiSelect = false; there is only one selected item.
CurrentItemSelected = remoteSourceListView.SelectedItems[0];
sourceNameTextBox.Text = CurrentItemSelected.SubItems[1].Text;
sourceUrlTextBox.Text = CurrentItemSelected.SubItems[2].Text;
if (CurrentItemSelected.SubItems[3].Text != "")
{
sourceBranchTextBox.Enabled = true;
sourceBranchTextBox.Text = CurrentItemSelected.SubItems[3].Text;
}
}
}

Get values of dynamically created controls (comboboxes)

I've got panel on which by default are two comboboxes and one "+" button which creates two new combo boxes bellow the first one, I can create multiple (n) rows with two combo boxes and everything is working, I just can't figure out how to get values of those boxes?
Here's code for creating (adding) controls
private void btnCreateFilter_Click(object sender, EventArgs e)
{
y += comboBoxHeight;
ComboBox cb = new ComboBox();
cb.Location = new Point(x, y);
cb.Size = new Size(121, 21);
panelFiltri.Controls.Add(cb);
yDrugi += comboBoxHeight;
ComboBox cbSql = new ComboBox();
cbSql.Location = new Point(xDrugi, yDrugi);
cbSql.Size = new Size(121, 21);
panelFiltri.Controls.Add(cbSql);
btnCancel.Location = new Point(btnCancel.Location.X, btnCancel.Location.Y + 25);
btnSaveFilter.Location = new Point(btnSaveFilter.Location.X, btnSaveFilter.Location.Y + 25);
}
And here's code where I'm lost:
private void btnSaveFilter_Click(object sender, EventArgs e)
{
int i;
foreach (Control s in panelFiltri.Controls)
{
//GOT LOST
}
}
You can get the text in the ComboBox as
private void btnSaveFilter_Click(object sender, EventArgs e)
{
foreach (Control control in panelFiltri.Controls)
{
if (control is ComboBox)
{
string valueInComboBox = control.Text;
// Do something with this value
}
}
}
I don't really know what you're trying to achieve... Maybe this will help you along...
private void btnSaveFilter_Click(object sender, EventArgs e)
{
foreach (ComboBox comboBox in panelFiltri.Controls)
{
var itemCollection = comboBox.Items;
int itemCount = itemCollection.Count; // which is 0 in your case
}
}

Get name of control when mouse hovers over it C#

I am adding ComboBoxes dynamically at runtime as shown below.
The problem that I am having is that i do not know which of the comboboxes the user is using.
For eg. The user decides to add 5 comboBoxes to the form, and then goes to the first comboBox,and selects a value, I need to retrieve the value of that comboBox.
What the below code is doing - My approach
I am adding a comboBox to a FlowlayoutPanel and the retrieve its name based on the mouse co-ordinates.... this by the way is not working... and I have no idea what to do.
Any help is greatly appreciated.
public partial class Form1 : Form
{
int count = 0;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
count += 1;
ComboBox cb = new ComboBox();
cb.Name = count.ToString();
cb.MouseHover += new EventHandler(doStuff);
Label lb = new Label();
lb.Text = count.ToString();
flowLayoutPanel1.Controls.Add(cb);
flowLayoutPanel1.Controls.Add(lb);
}
public void doStuff(object sender, EventArgs e)
{
label1.Text = flowLayoutPanel1.GetChildAtPoint(Cursor.Position).Name;
}
}
}
You could try:
cb.SelectionChangeCommitted += selectionChangedHandler
...
void selectionChangedHandler(object sender, EventArgs e) {
ComboBox cb = (ComboBox)sender;
label1.Text = cb.Name;
// Do whatever else is needed with the combo box
}
The SelectionChangeCommitted event is "raised only when the user changes the combo box selection", which sounds like what you're after.
The combobox that raised the event in your doStuff-eventhandler is in the sender-parameter. Try casting it to a checkbox likte this:
ComboBox boxThatRaisedTheEvent = (ComboBox)sender;
string text = ((ComboBox)this.GetChildAtPoint(pt)).Text;
public void DoStuff(object sender, EventArgs e)
{
var comboBox = sender as ComboBox;
var name = (comboBox != null ? comboBox.Name : null);
}
this code casts the 'sender' parameter to a ComboBox object and if the cast is done correctly assings the ComboBox name to the string 'name', otherwise 'name' is null.
Tip: The C# coding style suggests that method names should start with capitalized letter.
You can try something like:
flowLayoutPanel1.Controls.OfType<ComboBox>().FirstOrDefault(cb => cb.Name.Equals(NAME_OF_COMBOBOX))
Or better:
ComboBox box = (ComboBox)sender;

C# combobox options dependent on another combobox

I am working on a program in which a combobox's options are dependent on another combobox's selected option. The selected item from the first combobox chooses which options are in the second combobox. Does anyone know how to do this?
This is the button that adds the information to the first combobox
try
{
CustomerAccount aCustomerAccount = new CustomerAccount(txtAccountNumber.Text, txtCustomerName.Text,
txtCustomerAddress.Text, txtPhoneNumber.Text);
account.Add(aCustomerAccount);
cboClients.Items.Add(aCustomerAccount.GetCustomerName());
ClearText();
}
catch (Exception)
{
MessageBox.Show("Make sure every text box is filled in!", "Error", MessageBoxButtons.OK);
}
And here is the selectedIndex for the first combobox.
private void cboClients_SelectedIndexChanged(object sender, EventArgs e)
{
CustomerAccount custAccount = account[cboClients.SelectedIndex] as CustomerAccount;
if (custAccount != null)
{
txtAccountNumberTab2.Text = custAccount.GetAccountNumber();
txtCustomerNameTab2.Text = custAccount.GetCustomerName();
txtCustomerAddressTab2.Text = custAccount.GetCustomerAddress();
txtCustomerPhoneNumberTab2.Text = custAccount.GetCustomerPhoneNo();
}
}
Add a SelectedIndexChanged event handler for the first ComboBox. Use it to clear the content of the second ComboBox and populate it with the related items:
public Form1()
{
InitializeComponent();
for(int i = 0; i < 10; i++) {
comboBox1.Items.Add(String.Format("Item {0}", i.ToString()));
}
comboBox1.SelectedIndex = 0;
}
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
comboBox2.Items.Clear();
for (int i = 0; i < 5; i++)
{
comboBox2.Items.Add(String.Format("Item_{0}_{1}",
comboBox1.SelectedItem, i.ToString()));
}
comboBox2.SelectedIndex = 0;
}

Categories