-2

I'm fairly new to C# but understand basic concepts.

I'm currently working on a Uni assignment where I have to have multiple textboxes be entered as a single entry in a listbox, then save all entries to a text file. I also need to be able to load the text file and add new entries to the list.

I've figured out how to save data to a .txt file, as well as reloading the .txt file back into the listbox using

if (File.Exists("PersonalFile.txt"))
{
 string[] line = File.ReadAllLines("PersonalFile.txt");
 lbxStaffDetails.ItemsSource = line;
}

However, doing it this way I can't add new entries to the listbox due to the data binding, I get this error message System.InvalidOperationException: 'Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead.'

Is there a way to remove the binding but keep the data in the listbox? Using lbxStaffDetails.ItemsSource = null; clears the listbox; or is there another way to read all lines of the .txt file to the listbox without using the file as a binding source?

Notes:

  • lbxStaffDetails is this listbox in question
  • PersonalFile.txt is the .txt holding the entries on new lines.
  • This is the first time I've bound data and files.

Edit:

Forgot to mention how I'm adding the data to the listbox so here's the code for that.

private void btnAddWaitingList_Click(object sender, RoutedEventArgs e)
{
 _EmployeeID = tbxEmployeeID.Text;
 _Name = tbxName.Text;
 _PayRate = tbxPayRate.Text;            
 _Email = tbxEmail.Text;

 string employeeDetails = _EmployeeID + "," + _Name + "," + _PayRate + "," + _Email;

 lbxStaffDetails.Items.Add(employeeDetails);
}

As the code fires and gets to the bottom line it throws the error mentioned above.

  • There's not enough detail in your question to provide a good answer. But your trouble seems to be rooted in the fact that you're treating your bound model properties incorrectly. Specifically, the _sole purpose_ of the view model containing properties for binding is that those properties reflect what the view should be presenting at any given time. There should be a collection (e.g. `ObservableCollection`) bound to the `ListBox.ItemsSource` (in XAML, not assigned in code-behind), and that collection should be populated by copying to it the data read from the file. ... – Peter Duniho Sep 05 '20 at 00:22
  • ... The `string[]` returned by `File.ReadAllLines()` is not itself the collection to use for the `ListBox`. Though alternatively, if you do want to use it like that, you should bind `ItemSource` to a `string[]` property in the view model, and update that property with the `string[]` read from the file when reading the file. Other changes to the presentation to the user should be handled similarly, always mediated through a view model, with the underlying "business logic" being handle separately from that view model. – Peter Duniho Sep 05 '20 at 00:22
  • Since you want to be able to add items to the `ListBox` one at a time as the user enters the data, you probably should go the `ObservableCollection` route. Adding to that collection will automatically update the `ListBox` if you've bound the collection to `ItemsSource` correctly. – Peter Duniho Sep 05 '20 at 00:25

1 Answers1

0

Don't confuse data binding with simple value assignment. Data binding is a different concept, where a target binds to a data source using a Binding. A Binding will monitor target and source and delegates changes from one to the other. It's a bi-directional dynamic data link.
You can setup a Binding in XAML or C# (see Data binding overview in WPF).

You are not binding the ListBox to a file. You have read contents of a file to an array of strings. This array is then assigned to the ListBox.ItemsSource property.

Since you have populated the ListBox using the ItemsSource property, you are not allowed to modify its items using the Items property (InvalidOperationException).
You have to either assign the modified collection again to ListBox.ItemsSource (which will cause the complete ListBox to create all items again, which is bad for the performance) or make use of ObservableCollection.

It's a special collection that allows to be observed (Observer pattern). The observer gets notified by the observed collection via an event that the collection has changed (add/move/remove). Every ItemsControl is able to listen to this event and will automatically update itself.

MainWindow.xaml.cs

partial class MainWindow : Window
{
  public ObservableCollection<string> StaffDetails { get; set; }

  public MainWindow()
  {
    InitializeComponent();

    // Set the DataContext to MainWindow for data binding (XAML version)
    this.DataContext = this;
  }

  private void ReadFile()
  {    
    if (!File.Exists("PersonalFile.txt"))
    {
      return;
    }
     
    string[] lines = File.ReadAllLines("PersonalFile.txt");
    
    // Create a new ObservableCollection and initialize it with the array
    this.StaffDetails = new ObservableCollection<string>(lines);

    // You write to the file using this same collection directly,
    // without accessing the ListBox
    File.WriteAllLines("PersonalFile.txt", this.StaffDetails);
    
    // Option 1: assignment (static data link)
    this.lbxStaffDetails.ItemsSource = this.StaffDetails;
    
    // Alternative option 2: C# data binding (dynamic data link)
    var binding = new Binding(nameof(this.StaffDetails)) { Source = this };
    this.lbxStaffDetails.SetBinding(ItemsControl.ItemsSourceProperty, binding);
    
    // Alternative option 3 (recommended): XAML data binding (dynamic data link). See MainWindow.xaml
  }

  private void btnAddWaitingList_Click(object sender, RoutedEventArgs e)
  {
    _EmployeeID = tbxEmployeeID.Text;
    _Name = tbxName.Text;
    _PayRate = tbxPayRate.Text;            
    _Email = tbxEmail.Text;
    
    var employeeDetails = $"{_EmployeeID},{_Name},{_PayRate},{_Email}";
    
    // Modify the ObservableCollection. 
    // Since ListBox is observing this collection, it will automatically update itself
    this.StaffDetails.Add(employeeDetails);
  }
}

MainWindow.xaml

<Window>

  <!-- Alternative option 3: XAML data binding (recommended) -->
  <ListBox x:Name="lbxStaffDetails" 
           ItemsSource="{Binding StaffDetails}" />
</Window>
BionicCode
  • 1
  • 4
  • 28
  • 44