C# moving the last remaining item in a list box - c#

This is a Windows Forms Application that connects to a server, gets the tables and converts them to a drop create SQL script. There are two list boxes, lbSource where the initial list of tables is loaded and lbTarget that contains the tables that the user moved there to export.
The problem that occurs is, if I move the last remaining item (selIndex = 0), there is no item left to select (lbTarget.SetSelected(selIndex, true);) selIndex gets a value, but the list box is empty (OutOfRange).
I need the code at (selIndex >= 0) to select the next item if it's top of the list (also selIndex = 0).
So what I tried to do, was to check if (listBox.Items.Count == 0), but that does not seem to work.
This is the code of what happens if you click the button to move (a) certain item(s) from one list box to the other. Same code for source to target.
private void cmdTargetToSource_Click(object sender, EventArgs e)
{
//move more than one item
lbTarget.SelectionMode = SelectionMode.MultiSimple;
//sorting the lists
lbSource.Sorted = true;
lbTarget.Sorted = true;
//save the selectedIndex
int selIndex = lbTarget.SelectedIndex;
if (lbTarget.SelectedIndex == -1)
{
return;
}
//moving last entry in listBox sets selIndex higher than lbTarget.Items.Count
if (selIndex < lbTarget.Items.Count)
{
selIndex = selIndex - 1;
}
if (selIndex == -1)
{
selIndex = selIndex + 1;
}
//If there are no items left do nothing
if (lbTarget.Items.Count == 0)
{
return;
}
if (selIndex == lbTarget.Items.Count -2)
{
selIndex = selIndex - 1;
}
MoveListBoxItems(lbTarget, lbSource);
//select next item
if (selIndex >= 0)
{
lbTarget.SetSelected(selIndex, true);
}
//selectionmode back to single selection
lbTarget.SelectionMode = SelectionMode.One;
}

Let see: when you click on cmdTargetToSource you want to move all the selected items from ListBox lbSource to ListBox lbTarget, right?
In that case, first extract the method:
private static void MoveSelectedItems(ListBox source, ListBox target) {
if (null == source)
throw new ArgumentNullException("source");
else if (null == target)
throw new ArgumentNullException("target");
// Indice to move (Linq)
var indice = source.SelectedIndices.OfType<int>().OrderByDescending(item => item);
// Place to move (at the end of target listbox, if target is not sorted)
int place = target.Items.Count;
// we don't need repaint source as well as target on moving each item
// which cause blinking, so let's stop updating them for the entire operation
source.BeginUpdate();
target.BeginUpdate();
Boolean sorted = target.Sorted;
try {
// switch sorting off so sorting would not change indice
// of items inserted
target.Sorted = false;
// Move items from source to target
foreach (var index in indice) {
target.Items.Insert(place, source.Items[index]);
target.SelectedIndices.Add(place);
source.Items.RemoveAt(index);
}
}
finally {
target.Sorted = true;
target.EndUpdate();
source.EndUpdate();
}
}
and then call it
private void cmdTargetToSource_Click(object sender, EventArgs e) {
MoveSelectedItems(lbSource, lbTarget);
}
with the method extracted you can easily implement a reverse move if you want:
private void cmdBackTargetToSource_Click(object sender, EventArgs e) {
// Just swap source and target:
MoveSelectedItems(lbTarget, lbSource);
}

Related

efficient way of selecting DataGrid rows between two indexes

I have modified c# wpf datagrid PreviewMouseLeftButtonDown default behavior so that rows get select/deselect by clicking instead of default ctrl+click to deselect and all selected rows getting deselected after clicking on rows without pressing ctrl.
All is fine on this part.
Then I started to add shift+click functionality to my modified function.
using indexes of last row selected and current selected row , i will get two indexes.
I want to select all rows between this two indexes.
So far i came this solution.
private void ItemsGrid_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (ItemsControl.ContainerFromElement((DataGrid)sender, e.OriginalSource as DependencyObject) is DataGridRow row)
{
e.Handled = true;
if (Keyboard.Modifiers == ModifierKeys.Shift && ItemsGrid.SelectedItems.Count > 0)
{
int start = ItemsGrid.Items.IndexOf((DataRowView)ItemsGrid.SelectedItems[^1]);
int end = row.GetIndex();
if (start == end)
{
if (row.IsSelected)
{
row.IsSelected = false;
}
else
{
row.IsSelected = true;
}
}
else
{
if (start > end)
{
end = start;
start = row.GetIndex();
end--;
}
else
{
start++;
}
for (int i = start; i <= end; i++)
{
ItemsGrid.SelectedItems.Add(ItemsGrid.Items[i]);
}
}
}
else
{
if (row.IsSelected)
{
row.IsSelected = false;
}
else
{
row.IsSelected = true;
}
}
}
base.OnPreviewMouseLeftButtonDown(e);
}
And everything just works fine.
The problem is after trying to test this method against default selection system of datagrids I find this method is taking longer time if too many rows need to get selected.
I tried to replace for loop with linq operation but it seems linq cant run on Datagrid.Items
Is there any faster way of doing this ?
Thanks

Feeding a override value from OnSelectionChangeCommitted DataGridViewComboBoxEditingControl to column object

So I have this and I know it is wrong:
protected override void OnSelectionChangeCommitted(EventArgs e)
{
if (SelectedIndex == 0)
{
GENIO_Viewer.FullColourPaletteForm dlgColour = new GENIO_Viewer.FullColourPaletteForm();
if(dlgColour.ShowDialog() == DialogResult.OK)
{
bool bFound = false;
for(int i = 1; i < Items.Count; i++)
{
ComboboxColourItem ocbItem = (ComboboxColourItem)Items[i];
if(ocbItem.Index == dlgColour.iSelectedColour)
{
SelectedIndex = i;
bFound = true;
break;
// We can just select this one
}
}
if(!bFound)
{
// Add it
ComboboxColourItem ocbItem = ComboboxColourItem.Create((ushort)dlgColour.iSelectedColour);
Items.Add(ocbItem);
SelectedIndex = Items.Count - 1;
}
}
}
base.OnSelectionChangeCommitted(e);
}
This handler is part of my DataGridViewComboBoxEditingControl. But it is the wrong place to add new Items.
I can't workout how to get access to the owning Column as that is where I need to add the Item, otherwise I get exceptions.
I have looked here: https://msdn.microsoft.com/en-us/library/system.windows.forms.datagridviewcomboboxeditingcontrol(v=vs.110).aspx
But i can't see a property I can use to get the column object.
How do we do this from the editing control?
Further explaination:
The list combo items are added by the "column" object. Thus we have a set of 15 colours to choose from. Now I have added a genric colour tot he top of the list.
So, the user invokes the edit, droplist displays, and they pick item 0. We intercept this with the aforementioned handler. Since they picked item 0, we show a popup dialogue to let them choose a different colour.
When they have chosen, we must now either find it or add it to the the mandatory list of items for the column. Make sense now?
I tried to use the DataGridView Notify object but for some reason it is not showing in the list of available functions.
I don't use a DataSource. I populate like this in the columns constructor:
private void InitialiseComboItems()
{
List<ushort> listColours = new List<ushort>();
listColours.Add(0);
listColours.Add(1);
listColours.Add(2);
listColours.Add(3);
listColours.Add(4);
listColours.Add(5);
listColours.Add(6);
listColours.Add(7);
listColours.Add(8);
listColours.Add(9);
listColours.Add(250);
listColours.Add(251);
listColours.Add(252);
listColours.Add(253);
listColours.Add(254);
listColours.Add(255);
this.Items.Clear();
foreach (ushort iColourIndex in listColours)
this.Items.Add(ComboboxColourItem.Create(iColourIndex));
}
I also have a helper method:
public ComboboxColourItem InsertColour(ushort iColourIndex)
{
ComboboxColourItem ocbItem = ComboboxColourItem.Create(iColourIndex);
bool bAppend = true;
if (Items.Count > 16)
{
// There are other colours, need to find right index
for(int i = 16; i < Items.Count; i++)
{
if(ocbItem.Index < ((ComboboxColourItem)Items[i]).Index)
{
bAppend = false;
Items.Insert(i, ocbItem);
break;
}
}
}
if (bAppend)
Items.Add(ocbItem);
return ocbItem;
}
You can use EditingControlDataGridView to find the DataGridView which owns the editing control. Then you can use CurrentCell property of grid to find the current cell and using ColumnIndex you will find the column index. Then using Columns collection, you can get the column at that index:
var c = this.EditingControlDataGridView
.Columns[this.EditingControlDataGridView.CurrentCell.ColumnIndex]
as DataGridViewComboBoxColumn;
if (c != null)
c.Items.Add("Something");

What control should I use for multiple selection of item in winforms C#?

I am really confused on thinking which control to use for my purpose.
I am having list of items say item1 to item10. User can select 4 or 5 items in any order.
Now user selected items has to be separated in same order.
For example, if the user selected the items in following order, item4, item8, item3 and item2.
I want it in the same order. item4,item8,item3,item2.
How do I achieve this in winforms control?
It is not a very nice solution but I wrote it as you asked.
Set the SelectionMode of your ListBox to MultiExtended or MultiSimple as you need.
Then write this code in SelectedIndexChanged event of your ListBox:
List<string> orderedSelection = new List<string>();
bool flag = true;
private void listBox3_SelectedIndexChanged(object sender, EventArgs e)
{
if (flag)
{
flag = false;
var list1 = listBox3.SelectedItems.Cast<string>().ToList();
if (listBox3.SelectedItems.Count > orderedSelection.Count)
{
orderedSelection.Add(list1.Except(orderedSelection).First());
}
else if (listBox3.SelectedItems.Count < orderedSelection.Count)
{
orderedSelection.Remove(orderedSelection.Except(list1).First());
}
var list2 = listBox3.Items.Cast<string>().Except(list1).ToList();
listBox3.Items.Clear();
for (int i = 0; i < list1.Count; i++)
{
listBox3.Items.Add(list1[i]);
listBox3.SelectedIndex = i;
}
foreach (string s in list2)
{
listBox3.Items.Add(s);
}
flag = true;
}
}
When user selects an item, It comes to first of the list and the rest of the items comes next.
Also, there is an alternative way. You can use a CheckedListBox with two extra button for moving selected items up and down. So user can change the order of selected items.
This solution uses CheckListBox's ItemCheck event along with a private List to keep track of the click items order.
protected List<string> clickOrderList = new List<string>();
private void Form1_Load(object sender, EventArgs e)
{
// Populate the checked ListBox
this.checkedListBox1.Items.Add("Row1");
this.checkedListBox1.Items.Add("Row2");
this.checkedListBox1.Items.Add("Row3");
this.checkedListBox1.Items.Add("Row4");
}
private void checkedListBox1_ItemCheck(object sender, ItemCheckEventArgs e)
{
if (sender != null && e != null)
{
// Get the checkListBox selected time and it's CheckState
CheckedListBox checkListBox = (CheckedListBox)sender;
string selectedItem = checkListBox.SelectedItem.ToString();
// If curent value was checked, then remove from list
if (e.CurrentValue == CheckState.Checked &&
clickOrderList.Contains(selectedItem))
{
clickOrderList.Remove(selectedItem);
}
// else if new value is checked, then add to list
else if (e.NewValue == CheckState.Checked &&
!clickOrderList.Contains(selectedItem))
{
clickOrderList.Insert(0, selectedItem);
}
}
}
private void ShowClickOrderButton_Click(object sender, EventArgs e)
{
StringBuilder sb = new StringBuilder();
foreach (string s in clickOrderList)
{
sb.AppendLine(s);
}
MessageBox.Show(sb.ToString());
}

Determining the currently pointed to item in a list

I am working with lists. I have been able to determine the first and last position of items in my list. I am using getPostion and displaying item name through a Label. Three buttons in my form: ShowFirstItem ShowNextItem(not working) and ShowLastItem show the corresponding item in a label. I am having problems for my next item. I don't have a member that is holding the current fruit_tree. So I am not sure how to either add an int member or another fruit_tree member called current. How would I be able to find out next (item after first) and display result?
public class ListForTrees
{
public fruit_trees GetNextTree()
{
current = 0;
fruit_trees ft = first_tree;
int i = 0;
while (i != current)
{
ft = ft.next_tree;
i++;
}
return ft;
}
}
ListForTrees mainlist = new ListForTrees();
private void BtnGo_Click(object sender, EventArgs e)
{
fruit_trees[] ar_items = { new fruit_trees("cherry", 48, 12.95, 3),
new fruit_trees("pine", 36, 9.95, 8),
new fruit_trees("oak", 60, 14.95, 2),
new fruit_trees("peach", 54, 19.95, 3),
new fruit_trees("pear", 36, 11.85, 2),
new fruit_trees("apple", 62, 13.45, 5)
};
mainlist = new ListForTrees(ar_items);
fruit_trees current = mainlist.first_tree;
while (current != null)
{
TxtOutput.AppendText(current.ToString() + Environment.NewLine);
current = current.next_tree;
}
}
private void ShowFirstItem_Click_1(object sender, EventArgs e)
{
// Show First Item
labelSpecificTree.Text = mainlist.first_tree.GetTreeType;
}
private void ShowLastItem_Click(object sender, EventArgs e)
{
//Show Last Item
labelSpecificTree.Text = mainlist.last_tree.GetTreeType;
}
private void ShowNextItem_Click(object sender, EventArgs e)
{
//Show Next Item
fruit_trees obj = mainlist.GetNextTree();
if (obj == null)
{
labelSpecificTree.Text = "No more trees!";
}
else
{
mainlist.current++;
labelSpecificTree.Text = obj.next_tree.GetTreeType.ToString();
}
}
}
I would add
public int current;
to the listoftrees. in the constructor set it to 0;
Then create a method to return the current fruit_trees object
public fruit_trees getCurrent()
{
fruit_trees ft = first_tree;
int i = 0;
while(i != current)
{
ft = ft.next_tree;
i++;
}
return ft;
}
now this method returns the current and not the next object. So you have 2 options in the next button event.
the quesiton here is do you want to move to the next tree object every time the button is clicked?
if so increment the current property and then call getcurrent to display. If you want the current to be retained and not move (i.e. clicking next over and over will result in the same thing)
then display getcurrent().next_tree.tree_type with no increment to current.
I'm not sure if this solves Your issue, but You can add
public fruit_trees current_tree;
field in ListForTrees class.
Then, You can easily add method showing next and moving item pointer to the next item in the list. Remembering the current item allows You to avoid browsing the whole list each time You want next tree from the list.
The code can look like this:
public fruit_trees GetNextTree()
{
//save currently pointed tree
fruit_trees tree = this.current_tree;
if (tree != null)
{
//move current_tree to the next tree, to be returned next time
current_tree = current_tree.next_tree;
//return saved tree object
}
return tree;
}
In order to make it work, You should also remember to initiate the value of current_tree with the first tree added to the list. You can also write some kind of ResetNextTreePointer() method pointing it back to the first tree in the list.
Some more suggestions to the code:
I suppose it would be better to use properties for fields in your classes, so i.e. change
public fruit_trees first_tree;
into
private fruit_trees first_tree;
public fruit_trees First_tree { get { return first_tree; }
as it would disable any class outside your class to modify those fields explicitly.
This code:
if (count == 0)
{
...
}
else if (count != 0)
{
...
}
can be changed into:
if (count == 0)
{
...
}
else
{
...
}

How do I determine if multiple items are selected in a ListBox

I think it's obvious what I'm trying to do, but if you don't understand, please ask.
if (listBox1.SelectedIndex == 1 && 2)
{
label1.Text = "Sometext";
}
SelectedIndices is what you want if you have enabled multi-select. You can also check the size of the SelectedItems property.
The documentation for ListBox.SelectedIndex states:
For a standard ListBox, you can use this property to determine the index of the item that is selected in the ListBox. If the SelectionMode property of the ListBox is set to either SelectionMode.MultiSimple or SelectionMode.MultiExtended (which indicates a multiple-selection ListBox) and multiple items are selected in the list, this property can return the index to any selected item.
Try this
if( listBox1.SelectedItems.Count > 1 )
{
// multiple items are selected
}
if (listBox1.SelectedIndices.Count > 1) // I'd use to group all of your multi-selection cases
{
if (listBox1.SelectedIndices.Contains(1) && listBox1.SelectedIndices.Contains(2))
{
label1.Text = "Sometext";
}
}
Keep in mind that the control is 0 based so if you're trying to select the first two options, you'll want to check for 0 (item 1) and 1 (item 2).
edit: modified to handle the requirement listed in comments. Note, there's probably a better way and there may even be a method for this built in (never used the multi-selection list box). But I built a function to handle so you don't have to do it for every scenario.
The function that does the work:
private bool CasesFunction(ListBox lbItem, List<int> validIndices)
{
for (int index = 0; index < lbItem.Items.Count; index++)
{
if (lbItem.SelectedIndices.Contains(index) && !validIndices.Contains(index))
return false;
}
return true;
}
And how I used it:
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
if (listBox1.SelectedIndices.Count > 1)
{
List<int> MyCase = new List<int> { 0, 1 };
if (CasesFunction(listBox1, MyCase))
{
label1.Text = "Sometext";
return;
}
else
label1.Text = "";
MyCase = new List<int> { 1, 2 }; // can do other checks
if (CasesFunction(listBox1, MyCase))
{
label1.Text = "Sometext 2";
return;
}
else
label1.Text = "";
}
else
label1.Text = listBox1.SelectedIndex.ToString();
}

Categories

Resources