Removing item from ListBox - unhandled exception in WPF? - c#

I have this code in my MainWindow.
The user enters Name, Phone and Email in the fields provided, selects Location and then the Name appears in the listbox, lstClients.
I'm trying to write a method to remove a selected name from the listbox.
namespace WpfApp_Employment_Help
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
// client list
List<Client> ClientList = new List<Client>();
public MainWindow()
{
InitializeComponent();
}
// method to select location via radio button
public string GetSelectedLocation()
{
string selected = string.Empty;
if (RBLocE.IsChecked == true) { selected = "Edinburgh"; }
else if (RBLocG.IsChecked == true) { selected = "Glasgow"; }
else if (RBLocO.IsChecked == true) { selected = "Other"; }
return selected;
}
// method to create a new client on click
private void newClient(object sender, RoutedEventArgs e)
{
Client c = new Client(boxClientName.Text, boxClientPhone.Text, boxClientEmail.Text, GetSelectedLocation());
boxClientName.Clear();
boxClientPhone.Clear();
boxClientEmail.Clear();
ClientList.Add(c);
lstClients.ItemsSource = null;
lstClients.ItemsSource = ClientList;
}
// method to id selected client
private void AssignID(object sender, RoutedEventArgs e)
{
Client c = lstClients.SelectedItem as Client;
if (c != null)
{
c.AssignID();
}
lstClients.ItemsSource = null;
lstClients.ItemsSource = ClientList;
}
// method to remove selected client
private void RemoveClient(object sender, RoutedEventArgs e)
{
lstClients.Items.Remove(lstClients.SelectedItem);
}
}
}
When I run this code, I get Unhandled Exception:
System.InvalidOperationException: 'Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead.'
how can I rewrite my RemoveClient method?
my code for the Client class is this:
public partial class Client
{
public string Name { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
public string Location { get; }
public bool IDed { get; private set; }
public Client(string n, string p, string e, string l)
{
Name = n;
Phone = p;
Email = e;
Location = l;
}
}
I have Visual Studio 2022 which has been recently updated.
I have also tried the following solution but it gives me another unhandled error?
It looks like I need to change List </string/> and string to something else. but what?
private void RemoveClient(object sender, EventArgs e)
{
if (lstClients.Items.Count >= 1)
{
if (lstClients.SelectedValue != null)
{
var items = (List<string>)lstClients.ItemsSource;
var item = (string)lstClients.SelectedValue;
lstClients.ItemsSource = null;
lstClients.Items.Clear();
items.Remove(item);
lstClients.ItemsSource = items;
}
}
else
{
System.Windows.Forms.MessageBox.Show("No ITEMS Found");
}
}
System.InvalidCastException: 'Unable to cast object of type 'System.Collections.Generic.List`1[WpfApp_Employment_Help.Client]' to type 'System.Collections.Generic.List`1[System.String]'.'

As the error message suggests, you can't modify the ItemsControl via the collection view returned from the ItemsControl.Items property.
WPF is generally designed to work on the data sources instead of handling the data related controls (data presentation). This way data and data presentation (GUI) are cleanly separated and code will become a lot simpler to write.
In case of the ListView (or ItemsControl in general), simply modify the source collection.
To improve performance, the source collection should be a INotifyCollectionChanged implementation, for example ObservableCollection<T>, especially when you expect to modify the source collection.
This makes invalidating the ItemsSource e.g. by assigning null, just to set it again redundant and significantly improves the performance.
public partial class MainWindow : Window
{
// client list
public ObservableCollection<Client> ClientList { get; } = new ObservableCollection<Client>();
// method to create a new client on click
private void newClient(object sender, RoutedEventArgs e)
{
Client c = new Client(boxClientName.Text, boxClientPhone.Text, boxClientEmail.Text, GetSelectedLocation());
boxClientName.Clear();
boxClientPhone.Clear();
boxClientEmail.Clear();
ClientList.Add(c);
// The following lines are no longer needed
// as the GUI is now notified about the collection changes (by the INotifyCollectionChanged collection)
//lstClients.ItemsSource = null;
//lstClients.ItemsSource = ClientList;
}
// method to id selected client
private void AssignID(object sender, RoutedEventArgs e)
{
Client c = lstClients.SelectedItem as Client;
// Same as the if-statement below
c?.AssignID();
//if (c != null)
//{
// c.AssignID();
//}
// The following lines are no longer needed
// as the GUI is now notified about the collection changes (by the INotifyCollectionChanged collection)
//lstClients.ItemsSource = null;
//lstClients.ItemsSource = ClientList;
}
// method to remove selected client
private void RemoveClient(object sender, RoutedEventArgs e)
{
var clientToRemove = lstClients.SelectedItem as Client;
this.ClientList.Remove(clientToRemove);
}
}

If you change the type of ClientList from List<Client> to ObservableCollection<Client>, you could simply remove the item directly from the source collection:
public partial class MainWindow : Window
{
ObservableCollection<Client> ClientList = new ObservableCollection<Client>();
public MainWindow()
{
InitializeComponent();
}
...
private void RemoveClient(object sender, RoutedEventArgs e)
{
ClientList.Remove(lstClients.SelectedItem as Client);
}
}

Related

Save a user's clicks in checkListBox

In my app, I have a few of checkListBoxes and I need to save clicks on this list. That means for the next time I don't need to click on them again and again.
Code:
public Form2()
{
InitializeComponent();
InitializeSecondForm();
}
private void InitializeSecondForm()
{
this.Height = Properties.Settings.Default.SecondFormHeight;
this.Width = Properties.Settings.Default.SecondFormWidth;
this.Location = Properties.Settings.Default.SecondFormLocation;
this.FormClosing += SecondFormClosingEventHandler;
this.StartPosition = FormStartPosition.Manual;
}
private void SecondFormClosingEventHandler(object sender, FormClosingEventArgs e)
{
Properties.Settings.Default.SecondFormHeight = this.Height;
Properties.Settings.Default.SecondFormWidth = this.Width;
Properties.Settings.Default.SecondFormLocation = this.Location;
Properties.Settings.Default.Save();
}
I tried to use this question for my answer, but it's not working: Save CheckedListBox Items to Settings
With a simple checkBox it's not a problem, but here we have a list.
Any idea how to do it?
Perhaps saving identifiers of each checked item to a json file.
In the following I did on CheckedListBox, for multiple CheckedListBox controls you need to adjust the code to use one json file with a modified structure to account for multiple CheckedListBox control or one json file per CheckedListBox.
Example, load items into the following class
public class Product
{
public int ProductID { get; set; }
public string ProductName { get; set; }
public override string ToString()
{
return ProductName;
}
}
Use the following class for read/write to a json file, in this case using json.net but will work also with system.text.json.
public class JsonOperations
{
/// <summary>
/// In your app you need to setup a different file name for each CheckedListBox
/// </summary>
public static string FileName =>
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Checked.json");
/// <summary>
/// Save only checked products
/// </summary>
/// <param name="list"></param>
public static void Save(List<ProductItem> list)
{
string json = JsonConvert.SerializeObject(list, Formatting.Indented);
File.WriteAllText(FileName, json);
}
/// <summary>
/// Read back if file exists
/// </summary>
/// <returns></returns>
public static List<ProductItem> Read()
{
List<ProductItem> list = new List<ProductItem>();
if (File.Exists(FileName))
{
list = JsonConvert.DeserializeObject<List<ProductItem>>(File.ReadAllText(FileName));
}
return list;
}
}
The following classes provide methods to get checked items set checked items from json mentioned above.
public static class CheckedListBoxExtensions
{
public static List<ProductItem> IndexList(this CheckedListBox sender)
{
return
(
from item in sender.Items.Cast<Product>()
.Select(
(data, index) =>
new ProductItem()
{
ProductID = data.ProductID,
Index = index
}
)
.Where((x) => sender.GetItemChecked(x.Index))
select item
).ToList();
}
public static void SetChecked(this CheckedListBox sender, int identifier, bool checkedState = true)
{
var result = sender.Items.Cast<Product>()
.Select((item, index) => new CheckItem
{
Product = item,
Index = index
})
.FirstOrDefault(#this => #this.Product.ProductID == identifier);
if (result != null)
{
sender.SetItemChecked(result.Index, checkedState);
}
}
}
public class CheckItem
{
public Product Product { get; set; }
public int Index { get; set; }
}
Form code would resemble the following to read checked items on form shown event and save checked item on form closing event.
public partial class SaveItemsForm : Form
{
private List<Product> _products = new List<Product>();
public SaveItemsForm()
{
InitializeComponent();
Shown += OnShown;
Closing += OnClosing;
}
private void OnClosing(object sender, CancelEventArgs e)
{
List<ProductItem> checkedItems = ProductCheckedListBox.IndexList();
if (checkedItems.Count > 0)
{
JsonOperations.Save(checkedItems);
}
else
{
JsonOperations.Save(new List<ProductItem>());
}
}
private void OnShown(object sender, EventArgs e)
{
_products = SqlServerOperations.ProductsByCategoryIdentifier(1);
ProductCheckedListBox.DataSource = _products;
/*
* Search for each product by id, if in the CheckedListBox check it
*/
var items = JsonOperations.Read();
if (items.Count >0 )
{
items.ForEach( x => ProductCheckedListBox.SetChecked(x.ProductID));
}
}
}
In such conditions, I prefer to have a simple database like SQLite.
Use this database to save the user profile. user profile data will store all such data. for example, the user and password can be saved into that to remember to the user for next time login, or all settings and configurations that user selected it during using the system.

When collection changed in ObservableCollection add new entries to ReactiveList

I have an ObservableCollection which getting filled from a TcpClient. When new data arrives (new Items are added), I want to create new Buttons inside an ItemsControl. It works the old way (with CollectionChanged) but I don't get it work with ReactiveUI.
I'm very new to ReactiveUI, and its quite hard for me to getting started. Could you may help me by putting me on the right path or maybe by providing some sample code?
The Idea:
public class ChooseMachineViewModel : ReactiveObject
{
public ReactiveList<Button> ButtonList { get; set; }
private Dictionary<ushort, Button> addressToButton;
//This one is normaly in another class and will be filled by a TcpClient
public readonly ObservableCollection<WWSS.Message.CUStatus> ControlUnitsStatus;
public ChooseMachineViewModel()
{
//TODO: Make this Reactive!
//The ButtonList for an ItemsControl
ButtonList = new ReactiveList<Button>();
//The Dictonary for addresses -> Button
addressToButton = new Dictionary<ushort, Button>();
//The ObservableCollection filled by a TCP Server
ControlUnitsStatus.CollectionChanged += ControlUnitsStatus_CollectionChanged;
}
private void ControlUnitsStatus_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (WWSS.Message.CUStatus stat in e.NewItems)
{
TryAddButton(stat);//When new Status arrive, try to create new button
}
}
if (e.OldItems != null)
{
foreach (WWSS.Message.CUStatus stat in e.OldItems)
{
TryRemoveButton(stat);//When Status removed, try to remove the button
}
}
}
private bool TryAddButton(WWSS.Message.CUStatus status)
{
if (!addressToButton.ContainsKey(status.Address))//if the Address is already in the dictonary don't create a button
{
var but = new Button { Content = status.Address.ToString() };
addressToButton.Add(status.Address, but);
ButtonList.Add(but);
return true;
}
return false;
}
private void TryRemoveButton(WWSS.Message.CUStatus status)
{
if (addressToButton.ContainsKey(status.Address))
{
ButtonList.Remove(addressToButton[status.Address]);
addressToButton.Remove(status.Address);
}
}
}
The trick was to use CreateDerivedCollection:
public class ChooseMachineViewModel : ReactiveObject
{
public IReactiveDerivedList<Button> ButtonList { get; set; }
public ChooseMachineViewModel(ObservableCollection<CUStatus> source)
{
addressToButton = new Dictionary<ushort, Button>();
ButtonList = ControlUnitsStatus.CreateDerivedCollection(status => new Button { Content = status.Address.ToString() },
status => !ButtonList.Any(button => button.Content.ToString().Equals(status.Address.ToString())));
}
}

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).

How do I properly implement a model-view-presenter with passive in C# Winforms?

I'm studying design patterns right now, I'm fairly new to this model-view-presenter, although I have already experience in asp.net mvc I'm trying to do an implementation of mvp in winforms.
The string in the textbox will be sorted with an algorithm based on the combobox. Right now when I click the button it throws a null reference exception
Here is the UI:
Here are my classes and codes:
class FormPresenter
{
private ISortingView _view;
private string _algorithm;
private StringToSortModel sortMe = new StringToSortModel();
public FormPresenter(ISortingView view)
{
_view = view;
_view.sortTheString += view_sortString;
sortMe.sortThis = view.stringToSort;
_algorithm = _view.algorithm;
//Algorithm = view.stringToSort;
//sortingform.sortTheString += (obj
}
private void view_sortString(object sender, EventArgs e)
{
SortContext context = new SortContext();
_view.sortedText = context.Sort(sortMe.sortThis.ToCharArray());
}
}
interface ISortingView
{
event EventHandler sortTheString;
string stringToSort { get; }
string algorithm { get; }
string sortedText { get; set; }
}
public partial class SortingForm : Form, ISortingView
{
public SortingForm()
{
InitializeComponent();
comboBox1.Items.Add("Bubble Sort");
comboBox1.Items.Add("Insertion Sort");
comboBox1.SelectedItem = "Bubble Sort";
textBox1.Text = "Emiri";
}
public event EventHandler sortTheString;
public string algorithm { get { return comboBox1.SelectedItem.ToString(); } }
public string stringToSort { get { return textBox1.Text; } }
public string sortedText { get { return label2.Text; } set { label2.Text = value; } }
private void Form1_Load(object sender, EventArgs e)
{
}
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
//char[] x = textBox1.Text.ToCharArray();
//SortContext con = new SortContext();
//con.SetSortStrategy(new InsertionSort());
//label2.Text = con.Sort(x);
//if(sortString != null)
//{
//this prodcues a null exception error
sortTheString(sender, e);
//}
}
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var mainForm = new SortingForm();
var presenter = new FormPresenter(mainForm);
Application.Run(new SortingForm());
}
}
I have not included the codes for the model and the classes the contains the sorting functions to keep this post short. The problem I have is that when button is clicked it throws a null reference exception error, something that I have been stuck on for hours already.
Sir/Ma'am your answers would be of great help. Thank you++
Your null is coming from this line
sortTheString(sender, e);
because you are not using the same form instance in your Presenter. Change to this in your main...
Application.Run(mainForm);
The event handler does not have any subscribers (because of the Application.Run(new SortingForm()); C# will treat that as null rather than an empty subscriber list.
ISortingView mainForm = new SortingForm();
var presenter = new FormPresenter(mainForm);
Application.Run(mainForm as Form);

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