I have a Form with a DataGridView which DataSource is a BindingSource to a table. This view will have a single row selection and a button to delete, edit the current selected row in a popup Form and a insert button that will use the same Form as well.
My question is how can I sync the pop Form with the current row?
I tryied to use the RowStateChanged event to get and store the current selected Row to be used in the Form but I coudnt. After the event I get the row that was selected before.
Other thing I dont understand yet in C# how to have a single recordSet and know wich is the current record even if its a new being inserted in a way that once in the Form all data being entered will show up at the same time in the DataGridView.
You don't have to sync the form with the current row. That's what a BindingSource is for.
When you do simple binding to a BindingSource, then every time its current item changes, the bound controls get updated, and every time the values in the bound controls change, the underlying values in the bound item get updated. When you do complex binding (i.e. the bound control displays the BindingSource's list, not just the current item), changing Position on the BindingSource will also change the current position in the bound control, and vice versa. So in this case, you want to bind the controls on your second form using simple binding, and the DataGridView on your first using complex binding.
The only unusual thing you need to do is make sure that both forms are using the same BindingSource. When you do that, clicking on a new row in the DataGridView updates the Position on the BindingSource, and the BindingSource pushes the values from the current bound item out to all of the simply-bound controls that are bound to it.
This is easily accomplished. Assuming that Form1 is the form with the DataGridView, and Form2 is the one with the simply-bound controls, do this:
In Form1:
private BindingSource Source = new BindingSource();
Form1_Load(object sender, EventArgs e)
{
// set Source's DataSource to your DataTable here.
mainDataGridView.DataSource = source;
// create DataGridView columns and their bindings here.
Form2 f2 = new Form2();
f2.TopMost = true;
f2.Source = Source;
f2.Show();
}
In Form2:
public BindingSource Source { get; set; }
public void Form2_Load(object sender, EventArgs e)
{
idTextBox.DataBindings.Add("Text", Source, "id");
descriptionTextBox.DataBindings.Add("Text", Source, "description")
}
You can easily keep it in sync, but not using BindingSource.
Windows Forms data binding is built on top of a few interfaces the most important are:
IList and IBindingList. The first one is just responsible for providing access to elements by their index in the list(to make it simple) the second one actually is more complicated.
IBindingList - (which is implemented by BindingSource) has methods to support:
change notification
adding new "empty" element
sorting
searching
The one that is important for you is of course change notification. Unfortunately BindingSource doesn't implement that bit of code. You may do 2 things here - either implement your version of BindingSource with change notification or try to mess with DGV and textboxes/comboboxes events to update the data.
Personally I've done the first one(I can share the code I have).
"Other thing I dont understand yet in C# how to have a single recordSet and know wich is the current record even if its a new being inserted in a way that once in the Form all data being entered will show up at the same time in the DataGridView."
Every form has a BindingContext object that keeps CurrencyManagers - one for each DataSource. That way every control bound to the same data source knows which record is current. Actually what BindingNavigator does is messing with the apropriate CurrencyManager and calling its methods. (I have no idea why it requires BindingSource instead of in the minimum IList or for full functionality IBindingList)
Related
I have a DataGridView which is filled by a BindingSource. Everytime that the user modifys a row and he/she moves to another row (Everytime the row modified lose the focus) it triggers the event RowValidated.
In this event, the elements of the DataGridView.CurrentRow are modified in the SQLServer with a stored procedure.
When the BindingSource have a filter active this approach doesn't. Because if is modified the cell of the column which is filtered, the row dissapers (Because of the filter) and my method catch the DataGridView.CurrentRow as the next one.
I cannot use the method TableAdapter.Update() because the DataSet is a join of multiples tables and it doesn't autogenerates.
Is there any event that is triggerred before the effects of the filter?
I would accept as answer any different approach.
Edit: When a cell is modified, is saved in a List the ID value of that row. The code in RowValidated is encapsulated in
if(ListOfIds.Contains(DataGridView.CurrentRow.Cells["ID"])){StoredProcedure();}
It is seldom a good idea to read and write the values of the cells yourself. Using DataBinding is usually an easier method.
It seems that you already use a BindingSource, but for some reason you read the changed row from the displayed data, not from the original BindingSource.
I'm not sure if it is wise to update your DataBase as soon as the operator moves to a new row. What if he doesn't know the value to fill in, and wants to scroll down to check another row? Consider to change the interface such that the operator can indicate that he finished editing all data. For instance a button. But this is a bit outside your question.
If you put your data in a BindingList, and assign this data to the DataSource of your DataGridView, then every change of the data in the DataGridView is automatically updated in the BindingList. Use event BindingList.ListChanged to get notified, or use the above mentioned button before processing the changed data.
BindingList<Customer> DisplayedCustomers {get; } = new BindingList<Customer>();
public MyForm()
{
this.InitializeComponents();
this.DataGridView1.DataSource = this.DisplayedCustomers();
this.BindingListView.ListChanged += BindingListChanged;
}
An empty DataGridView is shown. To fill it with Customers:
private InitializeDataGridView()
{
IEnumerable<Customer> customers = this.QueryCustomers();
foreach (var customer in Customers)
this.Customers.Add(customer);
}
This will automatically show all Customers in your DataGridView. Alas you will get the event once per customer. If you don't want that, consider to create a new BindingList and assign that to the DataSource of the DataGridView. Don't forget to unsubscribe the old BindingList and to subscribe to the event of the new BindingList
private void BindingListChanged(object sender, ListChangedEventArgs e)
{
BindingList<Customer> bindingList = (BindingList<Customer>)sender;
// use e.ListChangedType to detect what has changed:
// item added / removed / moved to a new location / value changed?
switch (e.ListChangedType)
{
case ListChangedType.ItemChanged:
var changedCustomer = bindingList[e.NewIndex];
ProcessChangedCustomer(changeCustomer);
break;
case ...
}
}
The advantage of using the BindingList instead of fiddling with the values of the cells of the DataGridView, is that you decouple the data from how this data is communicated with the operator. This makes it easier to reuse (show it in a ComboBox instead of a DataGridView? Go ahead, changes are minimal), easier to unit test: BindingList also works if there is no form; and therefore easier to maintain and change.
One final advice: to facilitate filtering on properties (show only the Customers that live in New York), and to make sorting easy, consider to use nuget package Equin.ApplicationFramework.BindingListView
List<Customer> customers = GetCustomers();
BindingListView<Customer> view = new BindingListView<Customer>(customers);
dataGridView1.DataSource = view;
And Bingo: free sorting by mouse click the column header. It even remembers whether to sort ascending or descending and it shows the sorting glyph (the arrow that indicates the sorting direction).
Filtering:
void FilerCustomersByCity(string city)
{
view.ApplyFilter(delegate(Customer) customer => customer.City == city);
}
I have a ComboBox bound to a List via a DataSource. For some reason, when the datasource items change, the items in the combo box don't seem to automatically update. I can see in the debugger the datasource contains the correct items.
There are lots of answers on StackOverflow about this, but most are either unanswered, don't work for me, or require changing from using Lists to BindingLists which I cannot do this instance due to the volume of code which uses methods BindingLists don't have.
Surely there must be a simple way of just telling the ComboBox to refresh it's items? I can't believe this doesn't exist. I already have an event which fires when the Combo needs to be updated, but my code to update the values has no effect.
Combo declaration:
this.devicePortCombo.DataBindings.Add(
new System.Windows.Forms.Binding("SelectedValue",
this.deviceManagementModelBindingSource, "SelectedDevice", true,
DataSourceUpdateMode.OnPropertyChanged));
this.devicePortCombo.DataSource = this.availableDevicesBindingSource;
Code to update the combobox:
private void Instance_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "AvailableDevices")
{
// Rebind dropdown when available device list changes.
this.Invoke((MethodInvoker)delegate
{
devicePortCombo.DataSource = AvailableDevicesList;
devicePortCombo.DataBindings[0].ReadValue();
devicePortCombo.Refresh();
});
}
}
You are not binding the DataGridview's DataSource to same BindingSource object in your case this.availableDevicesBindingSource which bound first time. but later you are binding to different object AvailableDevicesList. again you are using another binding source for SelectedValue i.e this.deviceManagementModelBindingSource.
use one BindingSource only, may solve your issue
I'm using a winform DataGridView for data entry. The problem is that when my user has entered a row but not clicked off it, that row isn't getting saved to the grid's datasource (a datatable in memory). So, when my user submits the form, I want to detect if the DataGridview has focus, and simulate a keypress (tab?) or set the focus to a different control or do some other operation to save the current row without changing the data.
DataGridView.ContainsFocus is always returning false, as is DataGridView.Focused. Is there another property i should be using? How should I save this row of data?
EDIT: See this question Trouble using DataGridViewComboboxColumn for input for how I'm creating the DataTable and binding it to the datagridview. And datagridview not allowing user to delete row to see another issue I had with this same datagrid.
Windows forms controls can be connected directly to data sources, but are designed to be used with a BindingSource control. This is used to marshall input from form controls to and from a data source.
It is a component which you can drag onto your form in the designer, and then you can set it as the data source for your datagridview control.
Once it is on the form, you can use the designer to set it as the datasource for your datagridview. Then you can use an event handler, for example, the form load event handler to provide a datatable as the data source for the binding source.
The binding source provides you with much finer control over how and when data is transferred from the form controls to the underlying data sources. Simply using the Binding Source may fix your problem. If not, then calling the EndEdit method on the binding source before attempting to save the data should cause any outstanding edits to be written to the datatable.
For a simple example, create a new Windows forms project. To the form, add a datagridview called "datagridview1", a BindingSource with the name peopleBinding source, and a command button called saveButton. Set the datasource for gridview1 to be peopleBindingSource.
Add event handlers for form.Load and saveButton.Click as follows:
public DataTable GetData()
{
DataTable t = new DataTable();
t.Columns.Add("FirstName", typeof(string));
t.Columns.Add("LastName", typeof(string));
t.Rows.Add("Joe","Bloggs");
t.Rows.Add("Fred","Bloggs");
return t;
}
private void Form1_Load(object sender, EventArgs e)
{
dataGridView1.AutoGenerateColumns = true;
DataTable people = GetData();
peopleBindingSource.DataSource = people;
}
private void SaveButton_Click(object sender, EventArgs e)
{
DataTable t = peopleBindingSource.DataSource as DataTable;
}
Run the application, and you should find that if you inspect the data table in SaveButton_Click that any changes you have made to the data are persisted.
If you prefer Visual Studio to write all the code for you:
Add a new Data source using the "Add data source..." wizard.
From the Data sources window, drag a table onto your form.
Visual studio will add the necessary DataGridView, BindingSource, etc and wire them all up for you. You can then inspect the code to see how it all fits together.
I have a winform with a combo box thats filled from a query in the database. If I add a field to the database, the new field won't show up in the form until I close it and reopen it.
I was able to put in a MessageBox.Show() and once that popped up, I closed it, and saw the new data in the combo box.
EDIT:
Let me clarify a bit. I have a dropdown combo box, and that is populated by a table adapter. I just did the databinding with the GUI so I'm not sure how it works.
What I want is, I want the new data that I enter to be refreshed when I come back to it. I have a seperate window to manage the data, and then I close it I want the combo box to be updated with what I just saved.
Is this possible? I tried to do it on form load but that doesn't work either, I think because the form is already loaded.
The Refresh method is not for that. What you want to achieve is refreshing the data binding. This would be sth like this:
cb.DataBindings[0].ReadValue();
Another way is using a data source that supports change notification. Such a data source fires ListChanged event with appropriate parameters to trigger update of controls that are bound to it.
you have to fill your table adapter from dataset again by
youTableAdapter.Fill(yourdataSet.tablename);
then you have to reassign datasource
this.combobox.DataSource = this.yourBindingSource;
in the end you may refresh your combobox
combobox.Refresh();
It depends on what your datagrid is bound to. Repopulating the datasource (like a DataTable or a customized BindingList) should automatically repopulate the grid, assuming the data source's ListChanged event is properly fired.
If I understand this correctly, one thing you could do is use a ListChanged event like what was mentioned but it seems as if that didn't work.
TableAdapters aren't really a table in the standard sense but a temporary storage area.
Look in your Form1_Load function (or whatever you named your form, just using the default) and look for a tableadapter.fill method. this.comboboxTableAdapter.Fill(yourdataset name). This is what actually fills your dataset.
Create a function that fills those datasets (if you have more than one) and then call that function on a ListChanged event, or even on the Activate event of the form. This way when you go into that child form and change the data, when you come back to main form the data will be there.
I hope this helps, and good luck with your project.
Here is what worked for me. When I refreshed the secondary dataset (first line), the combo box didn't originally pick up the new values, so I reassigned the dataTable as the DataSource (third line).
boundDataSet = proxy.ReadResources();
DataGridViewComboBoxColumn nameColumn = dataGrid.Columns["Name"] as DataGridViewComboBoxColumn;
nameColumn.DataSource = boundDataSet.Table;
cmbguest.DataSource = null;
loadguestDetails();
make the dataset null and rebind the combobox then u can refresh the data driven combobox in winforms
Child Form inner;
private void UpdateAccount_FormClosed(object sender, FormClosedEventArgs e)
{
ParentForm parentForm= (ParentForm )Application.OpenForms["ParentFormName"];
parentForm.GetAccounts();
}
Parent Form inner;
public void GetAccounts()
{
AccountData lastSelectedItem = (AccountData)cbAccounts.SelectedItem;
cbAccounts.Items.Clear();
List<AccountData> accountDatas = AccountXML.ReadAccountXML();
if (accountDatas != null)
foreach (var item in accountDatas)
{
cbAccounts.Items.Add(item);
}
if(lastSelectedItem != null)
{
cbAccounts.SelectedText = lastSelectedItem.AccountName;
}
}
I want to cache a DataGridView row between 'refreshes' i.e. between a Rows.Clear() and a Columns.Clear(). However it seems that calling the Clear() methods does not unbind the data from the DataGridView instance, An example,
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
DataGridViewRow cachedRow = new DataGridViewRow();
private void button1_Click(object sender, EventArgs e)
{
this.dataGridView1.Rows.Clear();
this.dataGridView1.Columns.Clear();
DataGridViewColumn aColumn = new DataGridViewTextBoxColumn();
this.dataGridView1.Columns.Add(aColumn);
this.dataGridView1.Rows.Add(cachedRow);
}
}
This is done on a Form containing a DataGridView and a Button. Clicking the button twice gives the "Row provided already belongs to a DataGridView" error.
There has been some discussion online about this that suggests that it may be a bug, however this was around 2004.
Once a row is part of a gridview, you can't re-add it. The row itself keeps track of what DataGridView it is in. I would suggest making a copy of the cached row and adding the copy to the view. Since you make a new copy each time it won't be in the view. Alternatively, you can go through and remove only those rows that have not been cached from the view, leaving the cached rows behind so that you don't need to re-add it.
Clone it to a DataRow() and then DataTable.ImportRow to the originating DataTable.
I'm not sure why you'd want this behviour? You should only remove the rows in the grid that you want to remove.
You should look into implementing ObservableCollection and a Binding component. This way, if an item is removed from your object model, then it is automatically removed from the grid. This saves you having to perform what sounds like manual data binding and avoids this problem altogether.
If you're using DataSet or typed DataSets, then the observable functionality is already implemented for you - you just need to bind the datatable to the grid. If you have a table object in memory, you'll notice that you can load another one, then use the DataTable.Merge function to combine the results.