add/remove List<> items selected in a ListBox - c#

I have two Lists, the firs (right) represents a list of all cars and the secound (left) a list of cars for sale.
There are two ListBoxes at my control, each of them will display one of the cars list. I want to click a button (<<) and the selected car at the ListBox 1 will be added to the ListBox 1 list.
The two lists are created outside the control class, so I need to bind then to the ListBoxes. I have tried to use DataSource but I can not use Add Remove from Items if I set it. Which is the best way to do that?
Thanks.
OBS: Ive changed to a ListBox.
The solution that I'm using is: Very very ugly solution... :/
public IList<Item> ItensToMaintaim
{
get { return (IList<Item>)this.itensToMainTainList.DataSource; }
set
{
//Need to set null to refresh
this.itensToMainTainList.DataSource = null;
this.itensToMainTainList.DataSource = value;
this.itensToMainTainList.DisplayMember = "Name";
this.itensToMainTainList.ValueMember = "Name";
}
}
public IList<Item> Itens
{
get { return (IList<Item>)this.itensList.DataSource; }
set
{
//Need to set null to refresh
this.itensList.DataSource = null;
this.itensList.DataSource = value;
this.itensList.DisplayMember = "Name";
this.itensList.ValueMember = "Name";
}
}
private void removeItem_Click(object sender, EventArgs e)
{
if (this.itensToMainTainList.SelectedItem != null)
{
this.itens2.Remove((Item)this.itensToMainTainList.SelectedItem);
this.ItensToMaintaim = this.itens2;
if (this.itensToMainTainList.SelectedIndex < 0)
{
this.itensToMainTainList.SelectedIndex = this.itens2.Count - 1;
}
}
}
private void addItem_Click(object sender, EventArgs e)
{
if (this.itensList.SelectedItem != null)
{
bool contains = false;
contains = this.itens2.Contains(this.itensList.SelectedItem);
if (!contains)
{
this.itens2.Add((Item)this.itensList.SelectedItem);
this.ItensToMaintaim = this.itens2;
}
if (this.itensList.SelectedIndex < this.itens1.Count - 1)
{
this.itensList.SelectedIndex++;
}
}
}

Once you set the DataSource, you can not add items to that collection.
"Items collection cannot be modified when the DataSource property is set."
But you can do it with some workarounds
1) Save to database and load it again with new values and bind it
OR
2) Get the exising data source of the Listbox and store it in a varaiable and add a new item (which is created from the selected item) and then re bind it again
Example ( the classes are specific to my need, you can customize based on your class structure)
//Take the existing
List<MailerKit> objExisting = (List<MailerKit>)comboBox1.DataSource;
//Add the new one
objExisting.Add(new MailerKit { KitName = comboBox1.SelectedText, ID = Convert.ToInt32(comboBox1.SelectedValue) });
//Rebind again
comboBox1.DataSource = objExisting;
comboBox1.DisplayMember = "KitName";
comboBox1.ValueMember = "ID";

You can create your custom events for adding and removing items in the lists. Handle these events in your control class where you have defined the comboboxes to add or remove their items.

Bind lists with datasource, as you did. Then remove and add items to lists, not to combobox itselves.

Related

Resort DataGridView after adding to the bound list

I have a DataGridView that is bound to a BindingSource that is bound to a BindingList. I can sort the DataGridView by clicking on a column header, as expected. But when I drop an item onto the DataGridView, it appears at the bottom of the list rather than in its correct sorted position. I found code in a StackOverflow grid to check the grid's SortOrder and SortedColumn properties, and if they are set, then to resort the grid. However, when I put a breakpoint in the DragDrop handler, I find that SortOrder is set to None and SortedColumn is null.
Here's the DragDrop handler:
private void dgvCoils_DragDrop(object sender, DragEventArgs e)
{
int? coilStack = m_draggedCoil.Stack;
m_currentCharge.RemoveCoil(m_draggedCoil);
if (coilStack.HasValue)
{
DisplayStack(coilStack.Value);
}
if (!m_inventoryList.Any(coil => coil.Coil_id == m_draggedCoil.Coil_id))
{
m_inventoryList.Add(m_draggedCoil);
if (dgvCoils.SortOrder != SortOrder.None && dgvCoils.SortedColumn != null)
{
ListSortDirection dir = ListSortDirection.Ascending;
if (dgvCoils.SortOrder == SortOrder.Descending) dir = ListSortDirection.Descending;
dgvCoils.Sort(dgvCoils.SortedColumn, dir);
}
}
}
And here is the code where I create the list and bind the source to it:
InventorySet coilSet = new InventorySet(m_db);
coilSet.FilterOnField(coilSet.m_archived, 0);
coilSet.Open();
// coilBindingSource.DataSource = coilSet.UnderlyingTable;
while (!coilSet.IsEOF())
{
m_inventoryList.Add(coilSet.ExportData());
coilSet.MoveNext();
}
coilBindingSource.DataSource = m_inventoryList;
coilBindingSource.ResetBindings(false);
dgvCoils.Refresh();
I am not setting any Sort columns in my BindingSource object. Do I need to?
And do I need to use a BindingSource if the underlying list in a BindingList?
Edit:
In looking back through my source code, I remembered that I had to add code to my ColumnHeaderMouseClick event handler to get sorting to work. Here is that handler:
private void dgvCoils_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
string strColumnName = dgvCoils.Columns[e.ColumnIndex].DataPropertyName;
SortOrder strSortOrder = GetSortOrder(dgvCoils, e.ColumnIndex);
if (strSortOrder == SortOrder.Ascending)
{
m_inventoryList = new BindingList<InventorySetData>(m_inventoryList.OrderBy(x => typeof(InventorySetData).GetProperty(strColumnName).GetValue(x, null)).ToList());
}
else
{
m_inventoryList = new BindingList<InventorySetData>(m_inventoryList.OrderByDescending(x => typeof(InventorySetData).GetProperty(strColumnName).GetValue(x, null)).ToList());
}
dgvCoils.DataSource = m_inventoryList;
dgvCoils.Columns[e.ColumnIndex].HeaderCell.SortGlyphDirection = strSortOrder;
}
I am guessing that I am doing too much programmatically, and not letting the DataGridView do what it was designed to do.

Set datasource of particular cell's combobox based on another column of particular row

private void dgGrid_CellListSelect(object sender, CellEventArgs e)
{
if (e.Cell.Column.Key == "ColumnA")
{
UltraGridRow selectedItem = ((UltraCombo)e.Cell.EditorControlResolved).SelectedRow;
if (selectedItem != null)
{
//Option A
cmbColumnB.DataSource = GetUISender<someBF>().RetrieveData(dataset).dataTable;
cmbColumnB.DataBind();
//Option B
//((UltraCombo)e.Cell.Row.Cells["ChipSetID"].EditorControlResolved).DataSource = GetUISender<someBF>().RetrieveData(dataset).dataTable;
}
}
}
There is a button that allow the datagrid to add new row.
This datagrid have 2 columns and both columns are UltraCombo. ColumnB combobox's dataSource will based on ColumnA. Based on the above code, it works if the datagrid only have 1 row, but once user add another row, both row's ColumnB will be sharing the same DataSource.
How to make sure ColumnB's DataSource stay independently without affecting other rows? It's very obvious that this happened because every row are sharing the same component which is cmbColumnB but I'm not sure on how to remove the reference
I've found the solution which is everytime create a new UltraCombo and bind it to the particular cell's EditorControl
private void dgGrid_CellListSelect(object sender, CellEventArgs e)
{
if (e.Cell.Column.Key == "ColumnA")
{
UltraGridRow selectedItem = ((UltraCombo)e.Cell.EditorControlResolved).SelectedRow;
if (selectedItem != null)
{
UltraCombo cmbValue = new UltraCombo();
cmbValue.LimitToList = true;
cmbValue.DropDownStyle = UltraComboStyle.DropDownList;
cmbValue.DataSource = GetUISender<someBF>().RetrieveData(dataset).dataTable;
cmbValue.ValueMember = someDS.someDT.someColumnIDColumn.ColumnName;
cmbValue.DisplayMember = someDS.someDT.someColumnDescriptionColumn.ColumnName;
cmbValue.BindingContext = someDg.BindingContext;
cmbValue.DataBind();
e.Cell.Row.Cells["ColumnB"].EditorControl = cmbValue;
e.Cell.Row.Cells["ColumnB"].Style = Infragistics.Win.UltraWinGrid.ColumnStyle.DropDownList;
}
}
}

C# binding to multiple controls - > selection changes at every control

I have a problem with binding to multiple controls. For example I have different controls: 1 listbox, 2 comboboxes, 1 datagridview and all of them have a binding to the same list or iobservablecollection (and except the datagridview display the same property). Everything works fine. But, if I change the selection of one control, the selection of all the other controls change to the same item. Or in other words, if I change the selection of a combobox to item Number 5, the other combobox changes it's selection to Number 5 as well and the listbox changes it's selection also to item Number 5. Does anybody know how to avoid this?
edit:
Here's sample code that leads to the same behavior:
private List TestList;
private void Form1_Load(object sender, EventArgs e)
{
TestList = new List<foo>();
for (int i = 0; i <= 5; i++)
{
foo f = new foo();
f.test = "Test_" + i;
TestList.Add(f);
}
comboBox1.DataSource = TestList;
comboBox1.DisplayMember = "test";
comboBox2.DataSource = TestList;
comboBox2.DisplayMember = "test";
}
class foo
{
private string _test;
public string test
{
get
{
return _test;
}
set
{
_test = value;
}
}
}
If I change the ite of combobox1 the item of combobox2 changes automatically to the same item.
picture: 1
I would like to see your controls but your datasources for both
comboBox1.DataSource = TestList; and comboBox2.DataSource = TestList; are the same. So when you change something in one its going to affect the other.
I found at least a temporary solution that does not show this behavior.
from:
combobox1.DataSource = TestList;
to:
combobox1.DataSource = Testlist.ToList();
For all who have the same problem, please have a look at these links:
https://blogs.msdn.microsoft.com/bethmassi/2007/09/19/binding-multiple-combo-boxes-to-the-same-data-source/
and
One DataSource for multiple controls

C# Adding Filter to combobox dropdown list

Need some help with adding filter to my ComboBox drop down list(windows Forms Visual studio 2015)
The drop down is populated as per below:
public ReconciliationReport()
{
InitializeComponent();
AppDomain.CurrentDomain.AssemblyResolve += FindDLL;
this.sRootDirectory = Properties.Resources.sRootDirectory;
string[] arrProjectList = Directory.GetDirectories(sRootDirectory).Select(Directory => Path.GetFileName(Directory)).ToArray();
Array.Sort(arrProjectList);
int iProjectCount = arrProjectList.Length;
this.DropDownListSize = iProjectCount;
for (int i = 0; i < iProjectCount; i++)
{
SelectJobDropdown.Items.Add(arrProjectList[i]);
}
}
This gives me a nice drop down list of all current directories.
Now, I need to add a filer to show only items which contain a text typed into the ComboBoxitself regardless if the dropdown list itself is open or not.
I have disabled both AutoCompleteMode and AutoCompleteSource as it was not working as expected with the opened droped down list. It was opening additonal list on top the existing one but I could only select from the dropdown under it. See print screen below:
The list on top is inactive and I cannot select the text but also does not give an option to display substrings.
Only have one even for the box itself which is
private void SelectJobDropdown_SelectedIndexChanged(object sender, EventArgs e)
{
//Plenty of code here
}
Can someone point in the right direction how to filter the list as I type within the box itself.
Please NOTE I have been using C# for only 3 weeks so might get confused with some of the terminology or other aspects of this language etc.
I would suggest to use 2 Lists. 1 for the original values
List<string> arrProjectList;
public ReconciliationReport()
{
InitializeComponent();
AppDomain.CurrentDomain.AssemblyResolve += FindDLL;
this.sRootDirectory = Properties.Resources.sRootDirectory;
arrProjectList = Directory.GetDirectories(sRootDirectory).Select(Directory => Path.GetFileName(Directory)).ToList();
arrProjectList.Sort();
// then just bind it to the DataSource of the ComboBox
SelectJobDropdown.DataSource = arrProjectList;
// don't select automatically the first item
SelectJobDropdown.SelectedIndex = -1;
}
and 1 for the filtered values. In this example I use a TextBox to catch the filter text. In the TextChanged event take the filter text and pull out only those values from the original arrProjectList List. You would need an extra option at the end to reset the binding to the old list if the filter is empty.
private void textBox1_TextChanged(object sender, EventArgs e)
{
string filter_param = textBox1.Text;
List<string> filteredItems = arrProjectList.FindAll(x => x.Contains(filter_param));
// another variant for filtering using StartsWith:
// List<string> filteredItems = arrProjectList.FindAll(x => x.StartsWith(filter_param));
comboBox1.DataSource = filteredItems;
// if all values removed, bind the original full list again
if (String.IsNullOrWhiteSpace(textBox1.Text))
{
comboBox1.DataSource = arrProjectList;
}
// this line will make sure, that the ComboBox opens itself to show the filtered results
}
EDIT
I found a Solution for typing the filter into the ComboBox directly. The filtering is the same procedure, but using the TextUpdate event it is necessary to unselect the SelectedIndex which is automatically set to the first element after the binding. Then I guess you want to proceed to write your filter (more than just one letter), write the filter back into the ComboBox.Text property and set the cursor position to the end:
private void comboBox1_TextUpdate(object sender, EventArgs e)
{
string filter_param = comboBox1.Text;
List<string> filteredItems = arrProjectList.FindAll(x => x.Contains(filter_param));
// another variant for filtering using StartsWith:
// List<string> filteredItems = arrProjectList.FindAll(x => x.StartsWith(filter_param));
comboBox1.DataSource = filteredItems;
if (String.IsNullOrWhiteSpace(filter_param))
{
comboBox1.DataSource = arrProjectList;
}
comboBox1.DroppedDown = true;
// this will ensure that the drop down is as long as the list
comboBox1.IntegralHeight = true;
// remove automatically selected first item
comboBox1.SelectedIndex = -1;
comboBox1.Text = filter_param;
// set the position of the cursor
comboBox1.SelectionStart = filter_param.Length;
comboBox1.SelectionLength = 0;
}
Et voilĂ  automatic filtering with a nice display and arrow selection afterwards.
EDIT 2
for case insensitive search you can use this:
List<string> filteredItems = arrProjectList.FindAll(x => x.ToLower().Contains(filter_param.ToLower()));
NOTE:
After the opening of the dropdownlist the cursor will disappear. To prevent this the Cursor.Current has to be set to Cursor.Defualt
comboBox1.DroppedDown = true;
Cursor.Current = Cursors.Default;
In case you are using a Dictionary as data source, the following can be useful.
I created a static function in the form to be able to reuse it as I have several ComboBox instances for which I wanted to have the same behavior.
Simply call the function from the TextUpdate event passing the control name and the source dictionary.
private static void FilterComboBox(ComboBox combo, Dictionary<int, string> dataSource)
{
var filter = combo.Text;
if (string.IsNullOrWhiteSpace(filter))
return;
var filteredItems = dataSource.Where(kv => kv.Value.Contains(filter)).ToDictionary(k => k.Key, k => k.Value);
combo.DisplayMember = "Value";
combo.ValueMember = "Key";
combo.DataSource = new BindingSource(filteredItems, null);
// this will ensure that the drop down is as long as the list
combo.IntegralHeight = false;
combo.IntegralHeight = true;
combo.DroppedDown = true;
// remove automatically selected first item
combo.SelectedIndex = -1;
combo.Text = filter;
// set the position of the cursor
combo.SelectionStart = filter.Length;
combo.SelectionLength = 0;
}

Generic.List: How to set/get position/currentitem for DataGridView?

I'm currenty working on a DataGridView-extentsion with custom DataSource handling. If I bind a list to two normal System.Windows.Forms.DataGridView and select an item in dataGridView1, dataGridView2 automatically also sets the position to the item.
If I assign a BindingSource I can handle the PositionChanged event, but a Generic.List doesn't have a CurrencyManager, so how does the dataGridView2 know the new position?
You want to get the current position of some DataGridView (having the as list as DataSource) from the List?
Then the answer is: you cannot. The list knows nothing of the connected view - shown element included (of course)
Alternative to get the info from the DataGridView:
subscribe to the SelectionChanged event of the DataGridView and set the index of the second accordingly - for both you should be able to use the CurrentCell-property
You cannot do such things as you described in your comments below without knowing something of the DataGridView.
It's a different design - you could implement your own "ShowableList" or something and try creating your own DataGridView that shows the indicated item from your ShowableList and sets the ShownIndex in there too - but you have to do this yourself.
Finally i found the answer: BindingContext!
A simple example:
public class ModifiedCollection : BindingSource {
BindingSource Source {get;set;}
BindingManagerBase bmb;
Control Parent;
public ModifiedCollection(object Source, Control Parent) {
if ((this.Source = Source as BindingSource) == null) {
this.Source = new BindingSource();
this.Source.DataSource = Source;
}
this.Source.ListChanged += new ListChangedEventHandler(Source_ListChanged);
this.Parent = Parent;
this.Parent.BindingContextChanged += new EventHandler(Parent_BindingContextChanged);
}
void Parent_BindingContextChanged(object sender, EventArgs e) {
if (bmb != null) {
bmb.PositionChanged -= bmb_PositionChanged;
}
if (Parent.FindForm().BindingContext.Contains(this.Source.DataSource)) {
bmb = Parent.BindingContext[this.Source.DataSource];
if (bmb != null) {
bmb.PositionChanged += new EventHandler(bmb_PositionChanged);
}
}
}
}

Categories

Resources