What i'm trying to do is select an item in my listview, and it works! That is it works once, the first time a select an item it go's well, the second time a get an argument out of range exception on features[0].SubItems[1].Text; on the zero.
this is what i have:
private void listViewFeatures_SelectedIndexChanged(object sender, EventArgs e)
{
ListView.SelectedListViewItemCollection features = listViewFeatures.SelectedItems;
string feature = features[0].SubItems[1].Text;
BL_AddReport addReport = new BL_AddReport(this.databaseConnectionString);
Dictionary<string, bool> pictures = addReport.GetpicturesFromFeature(feature);
foreach (KeyValuePair<string, bool> pic in pictures)
{
if (pic.Value) {
pictureBoxCar.Image = Image.FromFile(pic.Key);
}
else
{
pictureBoxEquip.Image = Image.FromFile(pic.Key);
}
}
}
Does anyone know what the problem is?
I'm betting you'd get this exception if you clicked off of the listview as well.
Remember that this event is for selection changes.. which may mean that something was selected and now nothing is. In fact, according to this an event is fired once for every thing that is selected. Take a look at that link for more information and designs around this problem if that is the case for you.
Otherwise just check to make sure that your "features" variable has anything inside of it before indexing into it
Related
I'm trying to create a simple listbox with ObjectListView (WinForm, C#). The goal is to have a single value (a double) and a check box.
I want to be able to edit the double value by Single Click, so here are the relevant lines of code from my MyWindow.Designer.cs file (i've left out the default values for efficiency):
this.olvDepths = new BrightIdeasSoftware.ObjectListView();
this.olvColumn1 = ((BrightIdeasSoftware.OLVColumn)(new BrightIdeasSoftware.OLVColumn()));
...
this.olvDepths.CellEditActivation = BrightIdeasSoftware.ObjectListView.CellEditActivateMode.SingleClick;
this.olvDepths.CheckBoxes = true;
this.olvDepths.CheckedAspectName = "IsDefault";
this.olvDepths.FullRowSelect = true;
//
// olvColumn1
//
this.olvColumn1.AspectName = "Depth";
this.olvColumn1.Text = "";
this.olvColumn1.IsEditable = true;
I then create a list of my class (ShieldingEntry) and use the olvDepths.SetObjects() with the list. My ShieldingEntry class looks like this:
public class ShieldingEntry
{
public double Depth { get; set; }
public bool IsDefault { get; set; }
}
However, when I click the field, it doesn't go into edit mode. I've also tried the DoubleClick, SingleClickAlways, and F2Only modes and they don't work either.
The Checkbox works fine.
************** I have additional information *********************
I've pulled and build the ObjectListView source, so I could step through it.
I put a breakpoint in the OLV StartCellEdit method and it gets called and appears to setup and select the control appropriately. It just never appears...
As I noted in the comments on the answer below, I've got this control on a tabbed dialog, and if I switch to another tab, then back, the control works fine.
What am I missing?
I've used ObjectListView before, and here is what I had to do:
Handle the CellEditStarting event. This event is raised when the cell goes into edit mode. Since OLV doesn't really have built-in editors, you have to make your own. Then handle the CellEditFinishing event to validate the data before putting it back into your model.
So first, handling the CellEditStarting event:
private void objlv_CellEditStarting(object sender, CellEditEventArgs e)
{
//e.Column.AspectName gives the model column name of the editing column
if (e.Column.AspectName == "DoubleValue")
{
NumericUpDown nud = new NumericUpDown();
nud.MinValue = 0.0;
nud.MaxValue = 1000.0;
nud.Value = (double)e.Value;
e.Control = nud;
}
}
This creates your editing control. If you want to make sure the size is right, you can set the size of the control (in this case a NumericUpDown) to the cell bounds using e.CellBounds from the event object.
This will show the editor when you click in the cell. Then you can handle the editor finished event to validate the data:
private void objlv_CellEditFinishing(object sender, CellEditEventArgs e)
{
if (e.Column.AspectName == "DoubleValue")
{
//Here you can verify data, if the data is wrong, call
if ((double)e.NewValue > 10000.0)
e.Cancel = true;
}
}
I don't think handling it is required, but its good practice to validate data from the user.
The editing control in the CellEditStarting event can be any control, even a user defined one. I've used a lot of user defined controls (like textboxes with browse buttons) in the cell editor.
[Edit]
I uploaded an example here dropbox link that seems to work. Might not be in the exact view as needed, but seems to do the job.
For anyone else with this problem. I had it specifically when trying to edit a 'null' value in a decimal? on the OLV on a tab page. Solution for me was to set UseCustomSelectionColors to 'False'. I didn't look elsewhere to see if it was reported as a bug. Seems like a bug.
I've been working on the past couple of days on a ListView based Music player using NAudio in C#. It's now time for me to start working on the forward/previous functions but I've come to a bit of a bump in the road. I need to select whatever the next item in the listView I have is. However, it is not selected by the user but instead is marked as now playing by a checkmark next to it with the default ListView checkboxes.
Here's what it looks like:
I've got a public string that's accessible by anything; it has the filename of the currently playing track in it. Whenever I click to play a track, I've got a foreach loop that loops through all of the items in this listView (I've got a second listview in another tab that has all the music I click to play from) and if the filename subitem and currentlyPlaying string match, then it checks it. If not, it unchecks.
I've got an event handler in my mainclass for when the playback stops on the track. What's going to go in there will be the logic for the next track. I've got a general idea of what to do but I'm not sure how to go about doing it
Get the index of the item with the check mark next to it
Get the item after it
Retrieve its fileName subitem
Play it
So what would be the way to go about doing this? I'm still a bit confused with listViews and such.
Update: Also, how possible is it to disable the user checking the check box, I've got it down for when it's a double click but what about when the user checks the checkbox themselves?
Update 2: Here's the eventhandler with some scratch code I was working on
public void waveOutDevice_PlaybackStopped(object sender, StoppedEventArgs e)
{
string fileName;
foreach (ListViewItem lvi in playListView.Items)
{
fileName = lvi.SubItems[1].Text;
if(lvi.Checked == true)
{
int finIndex;
lvi.Checked = false;
finIndex = lvi.Index;
//finIndex + 1;
}
}
}
I think you are just about there. all you need to do is something like this:
public void waveOutDevice_PlaybackStopped(object sender, StoppedEventArgs e)
{
string fileName;
foreach (ListViewItem lvi in playListView.Items)
{
fileName = lvi.SubItems[1].Text;
if(lvi.Checked == true)
{
int finIndex;
lvi.Checked = false;
finIndex = lvi.Index;
finIndex++;
if(finIndex < playListView.Count())
{
var nextGuy = playListView.Items[finIndex];
nextGuy.Checked = true;
//Play the file and what not.
}
}
}
}
I have a BindingList< KeyValuePair < string, string > > that is bound to a ComboBox control. Based on some conditions, the BindingList will be added a new KeyValuePair. Now, the Newly added item shows up at index 0 of the Combobox, instead of at the end.
While debugging, I found that the BindingList has got the right order. (i.e, the new KeyValuePair is appended)
Also, I check the SelectedValue of the ComboBox in it's SelectedIndexChanged handler and it seems to be not of the ListItem that got selected. Instead, it is that of the supposed ListItem, if the ComboBox had got the right order as in its DataSource, - the BindingList..
The code is a small part of a large project.. Plz let me know if the question is not clear. I can put the relevant parts of the code as per our context.
How could something like this happen? What can I do differently?
I have this class something like this.
public class DropdownEntity
{
//removed all except one members and properties
private string frontEndName
public string FrontEndName
{
get {return this.frontEndName; }
set {this.frontEndName= value; }
}
//One Constructor
public DropdownEntity(string _frontEndName)
{
this.FrontEndName = _frontEndName;
//Removed code which initializes several members...
}
//All methods removed..
public override string ToString()
{
return frontEndName;
}
}
In my windows form, I have a tab control with several tabs. In one of the tabs pages, I have a DataGridView. The user is supposed to edit the cells and click on a Next - button. Then, some processing will be done, and the TabControl will be navigated to the next tab page.
The next tab page has the combobox that has the problem I mentioned. This page also has a back button, which will take back.. the user can modify the gridview cells again.. and click on the next button. This is when the order gets messed up.
I am posting here the Click event handler of the Next Button.. Along with the class, with the rest of the code removed.
public partial class AddUpdateWizard : Form
{
//Removed all members..
BindingList<KeyValuePair<string, string>> DropdownsCollection;
Dictionary<string, DropdownEntity> DropdownsDict;
//Defined in a partial definition of the class..
DataGridView SPInsertGridView = new DataGridView();
ComboBox DropdownsCmbBox = new ComboBox();
Button NextBtn2 = new Button();
Button BackBtn3 = new Button();
//Of course these controls are added to one of the panels
public AddUpdateWizard(MainForm mainForm)
{
InitializeComponent();
DropdownsDict = new Dictionary<string, DropdownEntity>();
}
private void NextBtn2_Click(object sender, EventArgs e)
{
string sqlArgName;
string frontEndName;
string fieldType;
for (int i = 0; i < SPInsertGridView.Rows.Count; i++)
{
sqlArgName = "";
frontEndName = "";
fieldType = "";
sqlArgName = SPInsertGridView.Rows[i].Cells["InsertArgName"].Value.ToString().Trim();
if (SPInsertGridView.Rows[i].Cells["InsertArgFrontEndName"].Value != null)
{
frontEndName = SPInsertGridView.Rows[i].Cells["InsertArgFrontEndName"].Value.ToString().Trim();
}
if (SPInsertGridView.Rows[i].Cells["InsertArgFieldType"].Value != null)
{
fieldType = SPInsertGridView.Rows[i].Cells["InsertArgFieldType"].Value.ToString().Trim();
}
//I could have used an enum here, but this is better.. for many reasons.
if (fieldType == "DROPDOWN")
{
if (!DropdownsDict.ContainsKey(sqlArgName))
DropdownsDict.Add(sqlArgName, new DropdownEntity(frontEndName));
else
DropdownsDict[sqlArgName].FrontEndName = frontEndName;
}
else
{
if (fieldType == "NONE")
nonFieldCount++;
if (DropdownsDict.ContainsKey(sqlArgName))
{
DropdownsDict.Remove(sqlArgName);
}
}
}
//DropdownsCollection is a BindingList<KeyValuePair<string, string>>.
//key in the BindingList KeyValuePair will be that of the dictionary.
//The value will be from the ToString() function of the object in the Dictionary.
DropdownsCollection = new BindingList<KeyValuePair<string,string>>(DropdownsDict.Select(kvp => new KeyValuePair<string, string>(kvp.Key, kvp.Value.ToString())).ToList());
DropdownsCmbBox.DataSource = DropdownsCollection;
DropdownsCmbBox.DisplayMember = "Value";
DropdownsCmbBox.ValueMember = "Key";
//Go to the next tab
hiddenVirtualTabs1.SelectedIndex++;
}
private void BackBtn3_Click(object sender, EventArgs e)
{
hiddenVirtualTabs1.SelectedIndex--;
}
//On Selected Index Changed of the mentioned Combobox..
private void DropdownsCmbBox_SelectedIndexChanged(object sender, EventArgs e)
{
if (DropdownsCmbBox.SelectedValue != null)
{
if (DropdownsDict.ContainsKey((DropdownsCmbBox.SelectedValue.ToString())))
{
var dropdownEntity = DropdownsDict[DropdownsCmbBox.SelectedValue.ToString()];
DropdownEntityGB.Text = "Populate Dropdowns - " + dropdownEntity.ToString();
//Rest of the code here..
//I see that the Datasource of this ComboBox has got the items in the right order.
// The Combobox's SelectedValue is not that of the selected item. Very Strange behavior!!
}
}
}
}
The very first time the user clicks the Next Button, it's fine. But if he clicks the Back Button again and changes the Data Grid View cells.. The order will be gone.
I know, it can be frustrating to look at. It's a huge thing to ask for help. Any help would be greatly appreciated!
Please let me know if you need elaboration at any part.
Thanks a lot :)
I think you have two problems here.
First, if you want to retain the order of the items you should use an OrderedDictionary instead of a regular one. A normal collection will not retain the order of the items when you use Remove method. You can see more info about this related to List here.
You could use such dictionary like this:
DropDownDict = new OrderedDictionary();
// Add method will work as expected (as you have it now)
// Below you have to cast it before using Select
DropDownCollection = new BindingList<KeyValuePair<string, string>>(DropDownDict.Cast<DictionaryEntry>().Select(kvp => new KeyValuePair<string, string>(kvp.Key.ToString(), kvp.Value.ToString())).ToList());
The second problem could be that you change the display name (FrontEndName) of already existing items, but the key is preserved. When you add a new item, try to remove the old one that you're not using anymore and add a new item.
The Sorted Property of the Combobox is set to True! I didn't check that until now. I messed up. Terribly sorry for wasting your time Adrian. Thanks a lot for putting up with my mess here.. :)
I asked this question previously and did not get an answer but now I have more detail.
Basically I want to programatically display the column sort icon in a wpf datagrid column.
I have the following code to do this:
private void dtgMain_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
dtgMain.Columns[0].SortDirection = ListSortDirection.Ascending;
}
This seems to set the sort order of the column but when the grid is drawn the icon does not show.
When I add a message box into the method it works fine. My question is twofold. Why would the message box cause the method to work? And how can I get it to work without the use of a messagebox?
This is the method working with the messagebox in it:
private void dtgMain_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
MessageBox.Show("Here");
dtgMain.Columns[0].SortDirection = ListSortDirection.Ascending;
}
edit
Here is the method that is setting the datacontext of the datagrid
public void processLoad(string response)
{
XmlDataProvider provider = new XmlDataProvider();
if (provider != null)
{
System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
doc.LoadXml(response);
provider.Document = doc;
provider.XPath = "/moo/response/data/load/panel";
dtgMain.DataContext = provider;
}
}
Please let me know if you need anymore information.
OK, I suspect what is happening is that the data layout changes caused by the DataContext update are being completed after your call to set the direction arrow, and it is therefore being erased after you set it. Interestingly, in my case it failed to work even when I put the messagebox in, perhaps because that was hanging up the UI thread while it displayed.
Could you try replacing the line that sets the sort direction with a similar call put on the dispatcher queue:
dtgMain.Dispatcher.BeginInvoke(new Action(() =>
{
dtgMain.Columns[0].SortDirection = ListSortDirection.Ascending;
}), DispatcherPriority.ApplicationIdle);
and see if that works?
So I have two comboBoxes (comboBoxFromAccount and comboBoxToAccount). Each has the same datasource, which is AccountsList (a list of BankAccount objects that was passed from the parent form).
I would like to make it so that if an item is selected in one of the comboBoxes, it would no longer be selectable in the other. The way I'm trying to do this is by copying the list of BankAccounts from the comboBoxFromAccount to the comboBoxTo account, and removing the selected index of comboBoxFromAccount from the comboBoxToAccount.
I think I'm close, but what seems to happen is I have a blank comboBoxToAccount.
Here is my code:
private BankAccountCollection accountsListTransferTo = new BankAccountCollection();
// public property for passing collection data to the dialog
public BankAccountCollection AccountsList
{
get { return accountsListTransferTo; }
set { accountsListTransferTo = value; }
}
// Initial loading
private void TransferFundsDialog_Load(object sender, EventArgs e)
{
textBoxAmount.Text = String.Empty;
textBoxAmount.Select();
comboBoxFromAccount.DataSource = AccountsList;
accountsListTransferTo.AddRange(AccountsList); // Copy content
accountsListTransferTo.Remove(comboBoxFromAccount.SelectedItem as BankAccount); // Remove item
comboBoxToAccount.DataSource = accountsListTransferTo; // Data binding
}
private void comboBoxFromAccount_SelectedIndexChanged(object sender, EventArgs e)
{
accountsListTransferTo.Clear(); // Clear list, if you don't to it, AddRange will just add more items.
accountsListTransferTo.AddRange(AccountsList); // Copy ALL accounts
accountsListTransferTo.Remove(comboBoxFromAccount.SelectedItem as BankAccount); // Remove selected, so user cannot transfer to same account
// Refresh data binding
comboBoxToAccount.DataSource = null;
comboBoxToAccount.DataSource = accountsListTransferTo;
// Select very first item in "TO" combobox
comboBoxToAccount.SelectedIndex = 0;
}
Help would be appreciated.
Try removing the line
comboBoxToAccount.DataSource = null;
I have a vague recollection about comboboxes having problems with this.
Another possible problem that I can see is that you are using accountsListTransferTo both as your master collection and the one where you are removing the selected account from. Every time comboBoxFromAccount_SelectedIndexChanged is called one more account will disappear from the collection (and therefore from the available options in comboBoxToAccount).
I think I have seen comboboxes behave in a way where the SelectedIndexChanged (or a similar) event is triggered as new items are being added. If that is the case here it will explain the empty comboBoxToAccount, because comboBoxFromAccount_SelectedIndexChanged will run once for each bank account being added, essentially removing them from the master list and then rebinding the reduced list. You can easily verify this with a break point in your event handler.