Remove the line below the selected item in a listbox - c#

When I add an item into a listbox I also add a new line because I want there to be a blank line between each item added. When I remove a selected item I also want to remove the blank line I added otherwise I will end up getting 2 blank lines between each item this is the problem I am having so I thought if I could delete the selected item as well as the blank line above and below the selected item this would work. Is there a better approach to this?
ListBox1.Items.Remove(ListBox1.SelectedItem);

I have typed the items and differentiate what is the blank item and what is the value item. At the time of deleting I have the reference of both. It worked fine, see if it helps.
Here's an example:
Form:
private void Form1_Load(object sender, EventArgs e)
{
Data data = new Data { description = "Test1" };
listBox1.Items.Add(data);
data.BlankLine = new BlankItem();
listBox1.Items.Add(data.BlankLine);
data = new Data { description = "Test2" };
listBox1.Items.Add(data);
data.BlankLine = new BlankItem();
listBox1.Items.Add(data.BlankLine);
data = new Data { description = "Test3" };
listBox1.Items.Add(data);
data.BlankLine = new BlankItem();
listBox1.Items.Add(data.BlankLine);
data = new Data { description = "Test4" };
listBox1.Items.Add(data);
data.BlankLine = new BlankItem();
listBox1.Items.Add(data.BlankLine);
}
Event to delete the item on click:
private void listBox1_Click(object sender, EventArgs e)
{
if((listBox1.SelectedItem != null && listBox1.SelectedItem.GetType() != typeof(BlankItem)))
{
Data item = (Data)listBox1.SelectedItem;
listBox1.Items.Remove(item);
listBox1.Items.Remove(item.BlankLine);
}
}
Object Data
public class Data
{
public string description { get; set; }
public BlankItem BlankLine { get; set; }
public override string ToString()
{
return description;
}
}
Object BlankItem
public class BlankItem
{
public override string ToString()
{
return Environment.NewLine;
}
}

I wanted to try to implement the above functionality, but using a data-bound Listbox such that I make changes to the underlying list instead of the Listbox. If possible, use BindingList<T> instead of List<T> because it implements additional functionality specific to data binding.
The core is still the same, as each item added must also be followed by adding a string.Empty item. The same for removal, when an item is removed
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
var list = new BindingList<string>();
list.Add("ABC");
list.Add(string.Empty);
list.Add("GHK");
list.Add(string.Empty);
list.Add("OPQ");
listBox1.DataSource = list;
var binding = listBox1.BindingContext[list] as CurrencyManager;
listBox1.KeyDown += (s, ev) =>
{
if (ev.KeyData == Keys.Delete)
{
if (listBox1.SelectedItem != null && !listBox1.SelectedItem.Equals(string.Empty))
{
int index = listBox1.SelectedIndex;
if (index >= 0)
{
list.RemoveAt(index);
if (index < list.Count && list[index].Equals(string.Empty))
{
list.RemoveAt(index);
}
binding.Refresh();
}
}
}
if (ev.KeyData == Keys.Insert)
{
int index = listBox1.SelectedIndex;
if (index==-1 || list[index] == string.Empty)
{
index++;
}
list.Insert(index, "NEW " + (index + 1).ToString());
list.Insert(index+1, string.Empty);
}
};
}
}
press the [DEL] key to remove an item, and the [INS] key to add an item.
But I am not happy with this solution. I think there is a way to create a class that implements IListSource that you directly add/remove items and it creates a list with blanks in between automatically for binding.

Related

CurrentPosition and current Index are out of sync when navigating items in System.Windows.Data.ListCollectionView

I found out that the CurrentPosition property and the IndexOf method in the ListCollectionView are out of sync when the NewItemPlaceholderPosition property = AtEnd;
When navigating to NewItemPlaceholder, the CurrentPosition property does not change although MoveCurrentTo returns true.
Here is test code:
public class Customer
{
public string CustomerID { get; set; }
public string CompanyName { get; set; }
}
private void ButtonTest_Click(object sender, RoutedEventArgs e)
{
List<Customer> list = new List<Customer>();
list.Add(new Customer() { CustomerID = "ALFKI", CompanyName = "Alfreds Futterkiste" });
list.Add(new Customer() { CustomerID = "ANATR", CompanyName = "Ana Trujillo Emparedados y helados" });
ListCollectionView collView = new ListCollectionView(list);
collView.NewItemPlaceholderPosition = System.ComponentModel.NewItemPlaceholderPosition.AtEnd;
Debug.WriteLine($" collView.Count = {collView.Count}");
foreach (var item in collView)
{
bool moved = collView.MoveCurrentTo(item);
int index = collView.IndexOf(item);
Debug.WriteLine($" moved = {moved}; CurrentPosition = {collView.CurrentPosition}; collView.IndexOf(item) = {index}");
}
}
Result is:
collView.Count = 3
moved = True; CurrentPosition = 0; collView.IndexOf(item) = 0
moved = True; CurrentPosition = 1; collView.IndexOf(item) = 1
moved = True; CurrentPosition = 1; collView.IndexOf(item) = 2
Last line - CurrentPosition = 1 but collView.IndexOf(item) = 2?
Can't figure out if this is a bug in the ListCollectionView or a feature.
But your collection only contains two items. The third is just a placeholder, which is not a real item and does not participate in collection view navigation.
If you would've consulted the docs, you would learn that:
"Methods that perform relative navigation, such as the MoveCurrentToNext, skip the NewItemPlaceholder."
and
"Methods that perform absolute navigation, such as MoveCurrentToPosition, do nothing if the NewItemPlaceholder would be the CurrentItem."
The last quote applies to your situation: since navigating to the next/last item would set the NewItemPlaceholder as CurrentItem, the operation MoveCurrentTo does nothing. It returns true as the operation has not failed by definition.
To make that selecting rows in the DataGrid will move the record pointer of the CollectionView to the SelectedItem you have to explicitly enable this behavior by setting IsSynchronizedWithCurrentItem to true:
<DataGrid IsSynchronizedWithCurrentItem="True" />
To detect if the currently selected item is the CollectionView.NewItemPlaceholder you can handle the DataGrid.SelectionChanged event:
private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Contains(CollectionView.NewItemPlaceholder))
{
// Clear TextBox e.g. by setting SelectedItem to null
(sender as Selector).SelectedItem = null;
}
}
The DataGrid uses the same logic internally to handle the blank line.
You can use this class to replace the default DataGrid. It exposes a CurrentActiveRow property. Everything else works as usual. If you want to edit the place holder row using an external TextBox, simply bind to the CurrentActiveRowProperty (which also reflects the SelectedItem property):
Usage Example
<TextBox Text="{Binding ElementName=DataGrid, Path=CurrentActiveRow.CustomerID, UpdateSourceTrigger=PropertyChanged}" />
<TextBoxEditDataGrid x:Name="DataGrid"
ItemsSource="{Binding Items}"
IsSynchronizedWithCurrentItem="True" />
TextBoxEditDataGrid.cs
public class TextBoxEditDataGrid : DataGrid
{
public object CurrentActiveRow
{
get { return (object)GetValue(CurrentActiveRowProperty); }
set { SetValue(CurrentActiveRowProperty, value); }
}
public static readonly DependencyProperty CurrentActiveRowProperty = DependencyProperty.Register(
"CurrentActiveRow",
typeof(object),
typeof(TextBoxEditDataGrid),
new PropertyMetadata(default));
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
if (e.AddedItems.Contains(CollectionView.NewItemPlaceholder)
&& this.HasItems
&& this.SelectedItems.Count == 1) // Only execute on single item select
{
var editableView = this.Items as IEditableCollectionView;
object newEditItem = editableView.AddNew();
editableView.CancelNew(); // Don't add here. Only use instance and add it on edit started
if (!TryLazyAddNewEditItem(newEditItem))
{
AddNewEditItem(newEditItem);
}
this.CurrentActiveRow = newEditItem;
}
else if (!ReferenceEquals(this.SelectedItem, this.CurrentActiveRow))
{
this.CurrentActiveRow = this.SelectedItem;
}
}
private bool TryLazyAddNewEditItem(object newEditItem)
{
if (newEditItem is not INotifyPropertyChanged propertyChangedSource)
{
return false;
}
PropertyChangedEventManager.AddHandler(propertyChangedSource, AddNewEditItem_OnPropertyChanged, string.Empty);
return true;
}
private void AddNewEditItem(object newEditItem)
{
InsertEditItem(newEditItem);
this.SelectedItem = newEditItem;
}
private void InsertEditItem(object newEditItem)
{
if (CollectionViewSource.GetDefaultView(this.ItemsSource) is ListCollectionView listView)
{
int insertIndex = listView.NewItemPlaceholderPosition == NewItemPlaceholderPosition.AtBeginning
? 0
: listView.Count - 1;
(listView.SourceCollection as IList).Insert(insertIndex, newEditItem);
}
else
{
var editableView = this.Items as IEditableCollectionViewAddNewItem;
editableView.AddNewItem(newEditItem);
editableView.CommitNew();
}
}
private void AddNewEditItem_OnPropertyChanged(object sender, EventArgs e)
{
PropertyChangedEventManager.RemoveHandler(sender as INotifyPropertyChanged, AddNewEditItem_OnPropertyChanged, string.Empty);
AddNewEditItem(sender);
}
}

binding to a combobox getting different results when using using SelectedValue

I have bound to my combobox this simple class:
public class Company
{
public Guid CorporationId { set; get; }
public Guid TokenId { set; get; }
public string Name { set; get; }
}
And this is my binding:
private void FillCompaniesComboBox()
{
_doneLoadingComboBox = false;
comboBox_Companies.Items.Clear();
if (CurrentSettings.AllCompanies.Count == 0)
{
return;
}
bindingSource1.DataSource = CurrentSettings.AllCompanies;
comboBox_Companies.DataSource = bindingSource1.DataSource;
comboBox_Companies.DisplayMember = "Name";
comboBox_Companies.ValueMember = "CorporationId";
comboBox_Companies.SelectedIndex = 1;
_doneLoadingComboBox = true;
}
When I attempt to get the value of the selected item, I'm getting different results. Here is the code I am using to get my value:
private void comboBox_Companies_SelectedIndexChanged(object sender, EventArgs e)
{
if (!_doneLoadingComboBox && comboBox_Companies.SelectedIndex == -1)
{
return;
}
var value = (Company)comboBox_Companies.SelectedValue;
Console.WriteLine("Value: " + value.CorporationId);
}
Here is what is happening:
This one works at intended:
And this is were it is causing an issue:
Am I not retrieving the data correctly? I need the Company information that it is bound to.
Okay so here's what you need to do...
Assuming that your CurrentSettings.AllCompanies is an IList<Company> that you've already populated with data, here's what your code should look like:
public class ComboBoxItem {
// your class
private Company Comp;
}
private readonly BindingSource _bsSelectedCompany = new BindingSource();
private readonly ComboBoxItem _comboBoxItem = new ComboBoxItem();
// your main form method
public MainForm() {
// initialization code...
InitializeComponent();
// prevents errors in case your data binding objects are empty
ResetComboBox(comboBox1);
comboBox1.DataBindings.Add(new Binding(
"SelectedItem",
_bsSelectedCompany,
"Comp",
false,
DataSourceUpdateMode.OnPropertyChanged
));
comboBox1.DataSource = CurrentSettings.AllCompanies;
comboBox1.DisplayMember = "Name";
}
// simple method for resetting a given combo box to a default state
private static void ResetComboBox(ComboBox comboBox) {
comboBox.Items.Clear();
comboBox.Items.Add("Select a method...");
comboBox.SelectedItem = comboBox.Items[0];
}
By doing this, you're able to just use _comboBoxItem to safely get the information about your selected item without having to potentially Invoke it (in the case of accessing it on a separate thread).

Set ComboBox Text on Basis of SelectedIndex

I am trying to set the Text property of ComboBox on the basis of SelectedIndex but the problem is Text is becoming String.Empty after changing the Index of Combobox.
Each Item in ComboBox correspond to a string in DataTable having 2 columns Name, Description
What i need is when users select's a Name (Index Changes) when i want to show the Description of that in ComboBox
What i have tried :
private void tbTag_SelectionChangeCommitted(object sender, EventArgs e)
{
// get the data for the selected index
TagRecord tag = tbTag.SelectedItem as TagRecord;
// after getting the data reset the index
tbTag.SelectedIndex = -1;
// after resetting the index, change the text
tbTag.Text = tag.TagData;
}
How i have populated the Combobox
//load the tag list
DataTable tags = TagManager.Tags;
foreach (DataRow row in tags.Rows)
{
TagRecord tag = new TagRecord((string)row["name"], (string)row["tag"]);
tbTag.Items.Add(tag);
}
Helper Class Used :
private class TagRecord
{
public TagRecord(string tagName, string tagData)
{
this.TagName = tagName;
this.TagData = tagData;
}
public string TagName { get; set; }
public string TagData { get; set; }
public override string ToString()
{
return TagName;
}
}
I think that happens because -1 index in ComboBox means that no item was selected (msdn) and you are trying to change text of it. I would create one more element (at index 0) and make it change text depending on selection:
bool newTagCreated = false;
private void tbTag_SelectionChangeCommitted(object sender, EventArgs e)
{
TagRecord tag = tbTag.SelectedItem as TagRecord;
TagRecord newtag = null;
if (!newTagCreated)
{
newtag = new TagRecord(tag.TagData, tag.TagName); //here we change what is going to be displayed
tbTag.Items.Insert(0, newtag);
newTagCreated = true;
}
else
{
newtag = tbTag.Items[0] as TagRecord;
newtag.TagName = tag.TagData;
}
tbTag.SelectedIndex = 0;
}
Found a solution.
private void tbTag_SelectedIndexChanged(object sender, EventArgs e)
{
TagRecord tag = tbTag.SelectedItem as TagRecord;
BeginInvoke(new Action(() => tbTag.Text = tag.TagData));
}

Filtering items in a Listview

I am trying to filter items in a ListView by using a TextBox.
I've managed to make something, but it can only delete items from my listview, not bring them back. Here is a little example of my code:
private void textBox1_TextChanged(object sender, EventArgs e)
{
string value = textBox1.Text.ToLower();
for (int i = listView1.Items.Count - 1; -1 < i; i--)
{
if
(listView1.Items[i].Text.ToLower().StartsWith(value) == false)
{
listView1.Items[i].Remove();
}
}
}
Does anybody has an idea on how to retrieve the deleted items? I can't seem to figure it out >:...
check below sample app
using System;
using System.Windows.Forms;
using System.Collections.Generic;
using System.Linq;
public partial class Form1 : Form
{
// keep list of listview items
List<Data> Items = new List<Data>();
public Form1()
{
InitializeComponent();
// get initial data
Items = new List<Data>(){
new Data(){ Id =1, Name ="A"},
new Data(){ Id =2, Name ="B"},
new Data(){ Id =3, Name ="C"}
};
// adding initial data
listView1.Items.AddRange(Items.Select(c => new ListViewItem(c.Name)).ToArray());
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
listView1.Items.Clear(); // clear list items before adding
// filter the items match with search key and add result to list view
listView1.Items.AddRange(Items.Where(i=>string.IsNullOrEmpty(textBox1.Text)||i.Name.StartsWith(textBox1.Text))
.Select(c => new ListViewItem(c.Name)).ToArray());
}
}
class Data
{
public int Id { get; set; }
public string Name { get; set; }
}
You can change your logic and first search the items to delete, them delete they.
IList<Object> itemsToDelete = new List<Object>( listView1.find(delegate(string text){
return !text.ToLower().StartsWith(value);
}));
listView1.Remove(itemsToDelete);
return itemsToDelete;
But you have to return another list. When you delete the items form the original list, you cant recovery It. You have to store it in another list.
listBox1.Items.AddRange(listBox1.Items.Cast<String> ().Where(X=>X.StartsWith(textBox1.Text)).ToArray());
Link
Works the same way with ListView.
Hope it helps
After trying to find a solution and giving up on searching I created this simple method and it worked for me.
public static void FilterList(ListView list, string text)
{
if (text.Length > 0)
{
foreach (ListViewItem item in list.Items)
{
if (!item.ToString().ToLower().Contains(text.ToLower()))
{
list.Items.Remove(item);
}
}
}
else
{
UpdateList(list);
}
}

CheckedListBox with search function not checking correctly

I'm having strange issues with the check box control in C# .Net
My code below shows all logic that is required - _itemsChecked is a private dictionary containing all of the _fixtures and whether they are true or false (checked or un checked)
What I want is to be able to search my check list whilst retaining those which have been checked previously. If a checked item is included in the search results I want it to be checked.
The code nearly works! But for some reason boxes are randomly checked here and there, and it appears to work through debug but when the screen returns to the control it then hasn't worked.
Sure I'm missing something very simple.
My logic is:
DataSource includes those which match the typed search query,
Iterate through this list and check if the Guid is true in the dictionary.
If it is true then we set it as checked.
Hope I have provided adequate information.
Many thanks in advance.
private void searchTextBox_KeyUp(object sender, EventArgs e)
{
lst.DataSource = _fixtures
.OrderBy(f =>
f.Description)
.Where(f =>
f.Description.ToLower().Contains(searchFixturesTextBox.Text.ToLower()))
.ToList();
lst.DisplayMember = "Description";
for (var i = 0; i < lst.Items.Count; i++)
if(_itemsChecked.Contains(new KeyValuePair<Guid, bool>(((Fixture)lst.Items[i]).Guid, true)))
lst.SetItemChecked(i, true);
}
void lst_ItemCheck(object sender, ItemCheckEventArgs e)
{
var selectedItem = ((ListBox) sender).SelectedItem as Fixture;
if (selectedFixtureItem != null)
_itemsChecked[selectedItem.Guid] = e.CurrentValue == CheckState.Unchecked;
}
So I put this together from a few examples I found. The majority of the work came from How do I make a ListBox refresh its item text?
public class Employee
{
public string Name { get; set; }
public int Id { get; set; }
public bool IsChecked { get; set; }
public override string ToString()
{
return Name;
}
}
public partial class Form1 : Form
{
// Keep a bindable list of employees
private BindingList<Employee> _employees;
public Form1()
{
InitializeComponent();
// Load some fake employees on load
this.Load += new EventHandler(Form1_Load);
// Click once to trigger checkbox changes
checkedListBox1.CheckOnClick = true;
// Look for item check change events (to update there check property)
checkedListBox1.ItemCheck +=
new ItemCheckEventHandler(CheckedListBox_ItemCheck);
}
// Load some fake data
private void Form1_Load(object sender, EventArgs e)
{
_employees = new BindingList<Employee>();
for (int i = 0; i < 10; i++)
{
_employees.Add(new Employee()
{ Id = i, Name = "Employee " + i.ToString() });
}
// Display member doesnt seem to work, so using ToString override instead
//checkedListBox1.DisplayMember = "Name";
//checkedListBox1.ValueMember = "Name";
checkedListBox1.DataSource = _employees;
// Another example databind to show selection changes
txtId.DataBindings.Add("Text", _employees, "Id");
txtName.DataBindings.Add("Text", _employees, "Name");
}
// Item check changed, update the Employee IsChecked property
private void CheckedListBox_ItemCheck(object sender, ItemCheckEventArgs e)
{
CheckedListBox clb = sender as CheckedListBox;
if (clb != null)
{
Employee checked_employee = clb.Items[e.Index] as Employee;
if (checked_employee != null)
{
checked_employee.IsChecked = (e.NewValue == CheckState.Checked);
}
}
}
// Just a simple test that removes an item from the list, rebinds it
// and updates the selected values
private void btnChangeList_Click(object sender, EventArgs e)
{
_employees.RemoveAt(1);
checkedListBox1.DataSource = _employees;
for (var i = 0; i < checkedListBox1.Items.Count; i++)
{
Employee employee_to_check = checkedListBox1.Items[i] as Employee;
if (employee_to_check != null)
{
checkedListBox1.SetItemChecked(i, employee_to_check.IsChecked);
}
}
}
}

Categories