Fill a combo box by selecting item in ListView - c#

Im trying to fill combobox from one of two arraylist, on changing selection in listview with method listView1_SelectedIndexChanged.
The problem is it works fine the first time, but the second time I get the following error: "Object reference not set to an instance of an object."
The error is probably happening here: string pr = listView1.FocusedItem.Text;
Please help.
private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
ArrayList Profesor1 = new ArrayList();
Profesor1.Add("Kolegij 1 profesor 1");
Profesor1.Add("Kolegij 2 profesor 1");
ArrayList Profesor2 = new ArrayList();
Profesor2.Add("Kolegij 1 profesor 2");
Profesor2.Add("Kolegij 2 profesor 2");
string pr = listView1.FocusedItem.Text; //posible prob
switch (pr)
{
case "Profesor 1": comboBox1.DataSource = Profesor1;
break;
case "Profesor 2": comboBox1.DataSource = Profesor2;
break;
}
}

Is there something else going on in your example affecting the listView1 item? I've mocked up a small clone of your question and I can't seem to replicate the error:
I'm using your code for the event handler, and I'm populating the listView1 thusly:
listView1.Items.Add("Profesor 1");
listView1.Items.Add("Profesor 2");
It sounds like you're causing the listView1_SelectedIndexChanged handler to be fired in the background.
As a slight aside, your array lists aren't going to change, you probably don't want to keep your ArrayList creation in your event handler and keep recreating them each time (they don't seem to have any dynamic information in). particularly as that handler will often be called twice once, for the deselection, and again for a selection.
On that last point, it's also worth having a look at the ItemSelectionChanged event, which could be used to help your initial problem, as it comes with ListViewItemSelectionChangedEventArgs which contain the property IsSelected, which you could use to verify that you have an object selected.
e.g:
private void listView1_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
{
if (e.IsSelected)
{
// Your code here.
// e.Item...
}
}

Related

checking items in a list and take a decision

I want to build a program in Windows forms where the user can create a pizza by pressing buttons.
My problem is that when the user presses an ingredient more than once, the list will just increment. I tried various methods but they don't seem to work.
I have seen a solution using a for loop checking individual items in the list however I will have to implement that 19 times which is not really efficient (once for every button)
string check = "Thin Base";
if (My_Pizza.Contains(check))
{
My_Pizza.Items.Remove("ThinBase");
My_Pizza.Items.Add("Thin Base");
}
You have "ThinBase" and "Thin Base". Not the same thing.
Also, if an item is already in the list, you don't need to do anything. Simply invert your check
string check = "Thin Base";
if (!My_Pizza.Items.Contains(check))
{
My_Pizza.Items.Add(check);
}
General idea is not to hardcode string values, but create reusable method that will do what you need: check if certain value is already in the list and if it not, add this item to list. This will help you to avoid duplicate code.
In the button event handler you simply call this method and provide string value as parameter. I'm not sure how exactly you handle button clicks, but I would suggest creating single reusable method once again and acquire string value from button.Text property.
Here is code sample for you to demonstrate the idea.
private void OnButtonClick(object sender, EventArgs e)
{
Button clickedButton = (Button) sender;
if (clickedButton != null)
{
string buttonContent = clickedButton.Text;
CheckAndAdd(buttonContent);
}
}
private void CheckAndAdd(string valueToCheck)
{
if (!My_Pizza.Items.Contains(valueToCheck))
{
My_Pizza.Items.Add(valueToCheck);
}
}

Winforms Datagridview can't be refreshed from a delegate

I'm trying to load data from file to list and show that data immediately on Winforms' Datagridview. For that I've made the reading in another thread using Backgroundworker. The problem is, it only updates once and I can't get it to show more data. Not only that, when clicked, it tries to access element with -1 index, which of course doesn't exist, resulting in a crash.
Usually, from what I've seen, simply adding again same data to data source dataGridView1.DataSource = samelist; should work, but not in this case.
BackgroundWorker's work
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
//lotsofCode...
while (readData != null)
{
fooLists.Add(readData);
//someCalculations...
worker.ReportProgress();
}
}
BackgroundWorker's progressChanged
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.Invoke((MethodInvoker)delegate { UpdateGridView(); });
}
UpdateGridView Method
private void UpdateGridView()
{
if (fooLists.GetListById(1).calculatedList != null)
dataGridView1.DataSource = fooLists.GetListById(1).calculatedList;
}
Later on I've read some threads on stack, where one suggested using BindingSource as a "middleman", so now I have dataGridView1.DataSource = MyBindingSource; in the component initialization and tab1source.DataSource = fooLists.GetListById(1).calculatedList; instead of dataGridView1.DataSource. It certainly helped, as the list is now clickable as it should be, but still there are only few records on a list.
None of dataGridView1.Refresh(), dataGridView1.RefreshEdit() or dataGridView1.Update() helped, though made the list loading slightly fancier (probably due to the delay they introduced :) ).
I tried making some "protections" (semaphores, so the delegate isn't called again, while working; try-catches, though no exceptions are thrown there; data clearing before re-writing...) but the "better version" worked as poor as this one and it only darkened the code.
Am I missing a way to update the Datagridview control? Thanks in advance.
Although you didn't write it, but I think the reason that the items that you add to your dataSource are added to a collection that does not implement interface IBindingList. You probably use a simple list to hold your read data.
If your 'DataSourceimplements this interface, then after adding an item to your collection an event is raised. The class that holds theDataSource, whether it is aDataGridViewor aBindingSource` get notified about the changes in the list and update their contents accordingly.
Your solution would be to store your elements in an object of class System.ComponentModel.BindingList<T>.
Suppose the items you want to show are of class MyReadData
class MyForm : Form
{
public MyForm()
{
InitializeComponents();
this.myReadItems = new BindingList<MyReadData>();
this.MyBindingSource.DataSource = this.myReadItems;
// if not already done in InitializeComponents
this.MyDataGridView.DataSource = this.MyBindingSource;
}
private readonly BindingList<MyReadData> myReadItems;
// whenever needed, start the BackGroundWorker.
private void OnButtonReadFile_Click(object send, EventArgs e)
{
// create and start the backgroundworker
BackGroundWorkdr worker = ...
MyBackGroundWorkerParams params = ...
worker.RunWorkerAsync(params);
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
// I am certain the sender is my BackGroundWorker:
BackgroundWorker worker = (BackGroundWorker)sender;
MyBackGroundWorkerParams params = (MyBackGroundWorkerParams)e.Argument;
// do some work using the params
while (readData != null)
{
// some data read.
// dont't add the data to the list, just report the data that must been added to the list:
// someCalculations...
int percentProgress = ...
MyReadData dataToAddToGrid = ...
worker.ReportProgress(percentProgress, dataToAddToGrid);
}
private void bw_progressChanged(object sender, ProgressChangedEventArgs e)
{
// no need to call invoke, this is already the context of your forms thread
Debug.Assert(!This.InvokeReguired);
MyReadData dataToAdddToGrid = (MyReadData)e.UserState;
this.myReadItems.Add(dataToAddToGrid);
}
}
The main difference is that you should not let your BackgroundWorker to add data to the list of displayed data. The task of the BackGroundWorker is to read the data and to report to everyone who is interested what data has been read.
As it is the task of MyForm to display the read data, let MyForm decide which read data to display and in what format. This enhances reusage of both MyForm and MyBackGroundWorker: MyForm could display that that has been fetched in a different way, and MyBackGroundWorker could be used to inform others than MyForm to notify about read data.
Furthermore the display context of the progress changed event handler is the context of 'MyForm`, so an invoke is not needed.
You could also assign the IBindingList directly to the DataGridView, so without the use of a BindingSource. The only reason to keep a BindingSource is if you want access to the Current item, or if you want the freedom to fill your DataGridView with other items than the contents of your BindingList.
Finally: the most important part of the solution was that the items were added to an IBindingList.
System.Components.BindingList<T> is a class with limited functionality. If you want to order the rows in your DataGridView, or only show items that match some predicate, or combine items from several sources into one DataGridView, consider using Equin.ApplicationFramework.BindingListView
using Equin.ApplicationFramework;
public MyForm()
{
InitializeComponents();
this.myReadItems = new BindingListView<MyReadData>(this.components);
this.MyBindingSource.DataSource = this.myReadItems;
this.MyDataGridView.DataSource = this.MyBindingSource;
}
private readonly BindingListView<MyReadData> myReadItems;
private void bw_progressChanged(object sender, ProgressChangedEventArgs e)
{
MyReadData dataToAdddToGrid = (MyReadData)e.UserState;
this.myReadItems.Add(dataToAddToGrid);
// finished updating the list, DataGridView can be updated:
this.myReadItems.Refresh();
// this Refresh function allows you to change several items in the list
// without unnecessary intermediate updates of your BindingSource and DataGridView
}
Presto, that is all: free sorting or your columns by clicking on the column header. Consider examining their example project to see how filtering works and how to use several sources.
// Show only Brummies
this.myReadData.ApplyFilter(person => person.Address.City == "Birmingham");
// Remove the filter, show everyone again
this.myReadData.RemoveFilter();

DataBinding() not working with Distinct() (Entity Framework)

I'd like to create an application that reads data from a database and then shows it through a UI. The user then can add/delete/update fields and save it to the DB, pretty standard, right?
I have two tables: Motors and Measures. Motors table has a lot of fields, one of them is "company". Of course, there can be several motors from the same company, so I would like to filter those companies and get only the distinct ones in a comboBox.
I'm still playing around with the language and VS, so I've made a simple version of the UI where the user can add a new motor, in fact, the user can add the company field, because I'm trying to add a new company and see if it updates automatically in the comboBox.
For this purpose, I'm using Entity Framework and this tutorial from msdn for data binding:
https://msdn.microsoft.com/en-us/data/jj682076.aspx
The problem is that when I add a new motor (with a new company), it doesn't update if I filter the distinct ones, I mean, the following code does work and automatically updates the comboBox with all the companies:
private void MainForm_Load(object sender, EventArgs e)
{
_context = new MotorsContext();
_context.Motors.Load();
this.companyBindingSource.DataSource = _context.companies.ToBindingList();
companyBindingSource.ListChanged += CompanyBindingSource_ListChanged;
}
And the following doesn't:
private void MainForm_Load(object sender, EventArgs e)
{
_context = new MotorsContext();
_context.Motors.Load();
this.companyBindingSource.DataSource = _context.Motors.Local.ToBindingList().Select(x => x.company).Distinct();
companyBindingSource.ListChanged += CompanyBindingSource_ListChanged;
}
I've created a ListChanged method to see when the software does detect that the list has, indeed, changed. In the first code it does trigger, but it doesn't in the second. Maybe the observer isn't detecting the change in the list when I add a filter?
private void CompanyBindingSource_ListChanged(object sender, ListChangedEventArgs e)
{
MessageBox.Show("List changed!");
}
And finally, the add motor button:
private void button1_Click_1(object sender, EventArgs e)
{
if (!string.IsNullOrWhiteSpace(textBox1.Text))
{
Motor m = new Motor();
m.company = textBox1.Text;
_context.Motors.Add(m);
_context.SaveChanges();
MessageBox.Show($"New motor, id: {m.motorID}");
}
}
With the first implementation, the comboBox does update and shows every company (for every motor):
Push add button -> "List changed!" popup -> "New motor: id" popup
With the filter:
Push add button -> "New motor: id" popup
In fact the motor adds, but it doesn't show until the restart of the program.
Any idea will be much appreciated. I hope I've explained myself well.
The following line in the second example break the binding:
_context.Motors.Local.ToBindingList().Select(x => x.company).Distinct();
The reason is that the result of .Select(x => x.company).Distinct() is not a BindingList<Motor>, but a simple IEnumerable<string>
Use the following replacement:
var _companies = _context.Motors.Select(x => x.company).Distinct().ToList();
this.companyBindingSource.DataSource = _companies;
This line
this.companyBindingSource.DataSource = _context.Motors.Local.ToBindingList().Select(x => x.company).Distinct();
Returns IEnumerable<T>. In your case you want it to be list, so add .ToList();
this.companyBindingSource.DataSource = _context.Motors.Local.ToBindingList().Select(x => x.company).Distinct().ToList();

Accessing a dynamic RadioButtonList array object

I've created an array of RadioButtonList class, but apparently can't seem to access it or use the answer retrieved from it. I always get the exception: Object reference not set to an instance of an object
static int jimmy = 0;
protected void Button5_Click(object sender, EventArgs e)
{
int sizeOfPain = GlobalVariables.sympLCWR1Pain.Count;
RadioButtonList[] RBLPain = new RadioButtonList[sizeOfPain];
Label1.Visible = false;
RadioButtonList1.Visible = false;
Label[] Labella = new Label[sizeOfPain];
if (jimmy < sizeOfPain)
{
Labella[jimmy] = new Label();
RBLPain[jimmy] = new RadioButtonList();
Labella[jimmy].Text = GlobalVariables.sympLCWR1Pain[jimmy];
RBLPain[jimmy].Items.Add("Yes");
RBLPain[jimmy].Items.Add("No");
Panel1.Controls.Add(Labella[jimmy]);
Panel1.Controls.Add(RBLPain[jimmy]);
if (RBLPain[jimmy].SelectedIndex == 0)
{
GlobalVariables.sympLCWR1Yes.Add(GlobalVariables.sympLCWR1Pain[jimmy]);
}
}
else
{
Label2.Text = "YOUS DONE!";
Label3.Text = GlobalVariables.sympLCWR1Yes[0];
Button5.Visible = false;
}
jimmy++;
}
i get the exception at the if condition. Any help would be appreciated thanks :)
What that error means is that you are trying to access something that hasn't yet been instantiated. In your updated code, I see you have the following within your click event handler:
RadioButtonList[] RBLPain = new RadioButtonList[sizeOfPain];
Label[] Labella = new Label[sizeOfPain];
This means that every time the click event is handled, you are redeclaring the RBLPain and Labella arrays. Also, when execution leaves leaves the handler, the variables fall out of scope, so you will not be able to use them in other functions, or use the changes made within the handler from one call to the next. I don't know what the rest of your code is doing, but despite the seemingly unnecessary arrays, execution should survive your click event.
In your original post you were trying to access the SelectedItem.Text property of the RBLPain[jimmy]. In this revision you are checking the SelectedIndex instead. When SelectedIndex is -1, SelectedItem will be null, perhaps this led to your original problem. Regardless of what is changed on your form, because you are creating a new RadioButtonList during every click event, you are not working with the values from your form - SelectedIndex will always be -1 from what I can see.
I dont understand why you checking the condition .If you are creating rbl on buttonclick first item should always get selected. Anyway use RBLPain[jimmy].SelectedIndex=0;before if condition.

Assigning a value to a global variable from inside of event handler?

I'm working on a program but an issue i was faced to keep me worried.I'm kind of novice and i'm building this program for a competition.The code where the problem lies is like following :
class Blabla : Usercontrol
{
public List<string> mainList;
public Blabla()
{
mainList = new List<string>();
something.DownloadStringCompleted += new DownloadStringCompletedEventHandler(xx_DownloadStringCompleted);
}
void xx_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
List<string> abc = SomeMethod(e.Result);
mainList = abc;
}
}
I try it.Even though "abc" variable has the value i want , mainList remains empty.I don't know why and how to make it work.That's why i need some hand.Thank you.
Variable abc has the value you want UNTIL you get out of your event handler, probably, when it gets deleted because it uses e.Result directly.
Familiarize yourself with .Clone() method and IClonable interface, and try creating a COPY of the list that is in question, not the reference.
If abc is a list, mainList will be set to the same list. You don't have to clone the list, it should stay active because there is a reference to it, and therefore it doesn't get garbage collected.
When you said that mainList was empty, did you look at it in the debugger immediately after setting it in the xx_DownloadStringCompleted method? Or are you looking at it somewhere else in your program?
I would guess that this is a threading issue. Does your event handler get called from a different thread? If so, you would need to add some synchronization logic in order to guarantee that mainList is available to your other thread.

Categories

Resources