LINQ DataGridView Insertion Deletion not showed - winforms

I've a problem with my DataGrid bound to a LINQ Expression.
I've in fact two DataGrid bound to the same SQL table, using the same DataContext.
When I edit existing rows in one or the other grid, the changes are reflected to the other grid, and vice versa, as expected.
But when I insert a record or delete a record in one of the grid, changes are not reflected to the other grid. It is like new row are in a state where my LINQ expression does not take those type of changes.
I need to save the changes to the Database, before the other grid can "see" the changes.
Is there anyway for both LINQ result object can be notify of the Insert row/Delete row changes?
Here is my code:
public partial class Form1 : Form
{
GMR_DEVEntities CTX;
public Form1()
{
InitializeComponent();
CTX = new GMR_DEVEntities();
dataGridView1.AutoGenerateColumns = true;
dataGridView2.AutoGenerateColumns = true;
this.dataGridView1.Columns.Clear();
this.dataGridView2.Columns.Clear();
}
private void btnLoadGrid1_Click(object sender, EventArgs e)
{
var Data = from dd in CTX.tblConfigs select dd;
this.dataGridView1.DataSource = Data;
}
private void btnLoadGrid2_Click(object sender, EventArgs e)
{
var Data = from dd in CTX.tblConfigs select dd;
this.dataGridView2.DataSource = Data;
}
private void btnSave_Click(object sender, EventArgs e)
{
CTX.SaveChanges();
}
}

I've found a solution, that work good.
The first Grid (dtgGrid )is attach to a BindingSource, witch is attach to a LINQ query result. Then the second grid (dtgFilteredGrid), is attach to another BindindSource witch is the result of LINQ query that filter the first BindingSource. Then when the user edit dtgFilterGrid, update are manually done to keep the the first BindingSource up to date.

Related

DataGridView row formatting based on hidden column

Is there any way to set the style of a row based on some value from a column that is not visible to the user? The grid contains a couple of rows and I want some rows to be colored in red if they were deleted. I have a hidden column that stores true if the columns were deleted, false otherwise. I've tried CellFormatting but since my column is not visible, e.ColumnIndex never has the correct value for my hidden column.
Any help will be greatly appreciated.
Edit:
Below is an image of what I am trying to accomplish. You can see that the second row has the text red which is due to the values in a column that the user cannot see in the datagrid. This grid should be colored like this when the user see the form for the first time too (on load).
Instead of CellFormatting, try CellValueChanged for unbound data or DataBindingComplete for a bound data set. For example, let's say that you are "deleting/undeleting" a row using the following Button.Click event:
private void Button1_Click(object sender, EventArgs e)
{
bool value = (bool)dataGridView1.CurrentRow.Cells["Deleted"].Value;
dataGridView1.CurrentRow.Cells["Deleted"].Value = !value;
// For bound data (like a DataTable) add the following line:
// ((DataTable)dataGridView1.DataSource).AcceptChanges();
}
Unbound Data
Changing the rows "deleted" column value in this way will trigger the following event handler. Therefore, you can color your row based on that column's value of True or False:
private void DataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex == dataGridView1.Columns["Deleted"].Index)
{
dataGridView1.Rows[e.RowIndex].DefaultCellStyle.ForeColor = (bool)dataGridView1[e.ColumnIndex, e.RowIndex].Value ? Color.Red : Color.Black;
}
}
Bound Data
For bound data, such as from a DataTable, handling the DataBindingComplete event will be enough. This event will trigger when the binding is first set as well as after changes - such as the changes from the Button1.Click event. Here, you'll loop through the rows and set the desired style according to the hidden column's value. (Note the additional change to the Button1_Click event handler for a grid with a DataTable source. This is needed to give an immediate style change - otherwise it won't happen until you navigate to a different row.)
private void DataGridView1_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
{
foreach (DataGridViewRow row in dataGridView1.Rows)
{
row.DefaultSCellStyle.ForeColor = (bool)row.Cells["Deleted"].Value ? Color.Red : Color.Black;
}
}
Based on my understanding, you want to get a column's value when the column is a invisible column in DataGridView.
Is it right? Please correct me if I'm wrong.
private void button1_Click(object sender, EventArgs e)
{
dataGridView1.DataSource = CreateDataTable();
dataGridView1.Columns["ID"].Visible = false; // Set the ID column invisible.
MessageBox.Show(dataGridView1.Rows[2].Cells["ID"].Value.ToString()); // Get the ID column value.
}

ComboBox not displaying items in BindingSource

I am binding a ComboBox BindingSource and then adding items to the BindingSource. Unfortunately the items don't seem to added to the ComboBox.Items property. What am I missing in the following code?
BindingSource bindingSource;
private List<string> tables;
private void button1_Click(object sender, EventArgs e)
{
string newItemText = "item" + tables.Count;
tables.Add(newItemText); // comboBox1.Items.Count does not increase
}
private void Form1_Load(object sender, EventArgs e)
{
tables = new List<string>();
bindingSource = new BindingSource();
bindingSource.DataSource = tables;
comboBox1.DataSource = bindingSource;
}
List<T> doesn't support change notifications. If you are not going to modify the list after setting the DataSource, List<T> is fine, When you need to view your updates to list immediately use BindingList<T> or ObservableCollection<T> instead.
private BindingList<string> tables = new BindingList<string>();
and you don't need tables = new List<string>(); line in Form1_Load method, you can initialize it at the time of declaration itself.

Editing and commiting DataGridView cell changes (bound to a List of Objects)

I have a Windows Forms application which contains a DataGridView inside it. The DataGridView gets populated from a DataSource which is a list of MyClass. MyClass contains a set of properties and a constructor like so:
public class MyClass
{
public PropertyA{get;set};
public ProppertyB{get;set;}
public ProppertyC{get;Set}
}
Then in the Main form I have a method which returns a List myCollection and a button which populates the DataGridView with "myCollection" like so:
private void btlLoadDataInDataGrid_Click(object sender, EventArgs e)
{
var headers = GetAllHeaders();//GetAllheaders returns a List<MyClass>
dataGridView1.DataSource = headers;
}
What I get is a DataGridView with the columns from the original MyClass.
Now I want to be able to edit the data in the DataGridView and commit this change to the actual list of MyClass properties. Can somebody please advice what would be a best approach to do this?
When you edit your grid whose DataSource is set to a List of objects and change any cell's value, the underlying data in the row is automatically updated. In your case the underlying row data is of type MyClass.
At any point of time you can get the updated row data, using the DataGridView.DataBoundItem property.
Sample code:
foreach(DataGridViewRow item in dataGridView1.Rows)
{
MyClass rowData = item.DataBoundItem as MyClass;
if(rowData != null)
{
// Do your stuff
}
}
The data commit logic is different when your grid is populated from a database. You have to write code to commit the changed data to the database.

Data binding across several windows with WPF / Entity Framework? Pass context around or not?

Most WPF/EF tutorials only cover databinding in one window. However, in reality data gets displayed across many windows. You often display a record in the first window and dig deeper in related details in the next windows.
So, this also is the case in my scenario. Here you can see my data structure and the ui. Actually I am not dealing with Customers and Invoices, but the structure is the same. (My concrete questions are at the very end.)
In the InvoicesWindow I can select an Invoice and press "Show Invoice". That opens a CustomerWindow displaying Customer details and his invoices. The right invoice is pre-selected. To each Invoice displayed in the CustomerWindow I can add Items or edit them. This is done in a seperated window called "ItemWindow". Editing the DataGrids is not an option. They are set to ReadOnly.
Here is the code of the wpf-window classes (I only have done displaying data yet, not saving):
Invoices Window:
public partial class InvoicesWindow : Window
{
private MyEntities context = new MyEntities();
public InvoicesWindow ()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
CollectionViewSource invoicesViewSource = (CollectionViewSource)FindResource("invoicesViewSource");
invoicesViewSource.Source = context.Invoices;
}
private void ShowInvoice_Click(object sender, RoutedEventArgs e)
{
Invoice selectedInvoice = (Invoice)InvoicesDataGrid.SelectedItem;
var customerWindow = new CustomerWindow(selectedInvoice);
customerWindow.ShowDialog();
}
}
Customer Window:
public partial class CustomerWindow : Window
{
private MyEntities context = new MyEntities();
private Invoice selectedInvoice;
public CustomerWindow()
{
InitializeComponent();
}
public CustomerWindow (Invoice selectedInvoice)
{
InitializeComponent();
this.selectedInvoice = selectedInvoice;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
//Set the data
CollectionViewSource customerViewSource = (CollectionViewSource)FindResource("customerViewSource ");
customerViewSource.Source = context.Customers.Where(p => p.id == selectedInvoice.Customer.id);
//Select the right invoice
CollectionViewSource customerInvoicesViewSource = (CollectionViewSource)FindResource("customerInvoicesViewSource ");
customerInvoicesViewSource.Items.MoveCurrentTo(((ObjectSet<Invoice>)customerInvoicesViewSource.Source).Where(p => p.id == selectedInvoice.id).SingleOrDefault());
}
private void EditItem_Click(object sender, RoutedEventArgs e)
{
Item selectedItem = (Item)ItemsDataGrid.SelectedItem;
var itemWindow = new ItemWindow((IQueryable<Customer>)(customerViewSource.Source),selectedInvoice,selectedItem);
itemWindow.ShowDialog();
}
}
Item window:
public partial class ItemWindow : Window
{
private Invoice _selectedInvoice;
private Invoice _selectedItem;
private IQueryable<Customer> _customers;
public ItemWindo()
{
InitializeComponent();
}
public ItemWindow(IQueryable<Customer> customers, Invoice selectedInvoice, Item selectedItem)
{
InitializeComponent();
this._customers = customers;
this._selectedInvoice = selectedInvoice;
this._selectedItem = selectedItem;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
//Set the data
CollectionViewSource customerViewSource = (CollectionViewSource)FindResource("customerViewSource");
invoicesViewSource.Source = _customers;
//Select the right invoice
CollectionViewSource customerInvoicesViewSource = (CollectionViewSource)FindResource("customerInvoicesViewSource ");
customerInvoicesViewSource.Items.MoveCurrentTo(_selectedInvoice);
//Select the right item
CollectionViewSource customerInvoicesItemsViewSource = (CollectionViewSource)FindResource("customerInvoicesItems");
customerInvoicesItems.Items.MoveCurrentTo(_selectedItem);
}
}
I wrote the code out of my mind. So, maybe some casts are missing and some methods are mis-spelled. I hope I got the right type with "ObjectSet", it also could be "ObjectCollection" or something like that.
The XAML was created widely with assistance of VS2010 like in this video: http://msdn.microsoft.com/de-de/data/ff806174.aspx
So, finally my questions ;)
Is the design of binding I applied correct?
In CustomerWindow I create a new context.
Between CustomerWindow and ItemWindow I just pass the data of the same context and select the current item manually.
In CustomerWindow I use a ObjectSet (or ObjectCollection, I'm not sure about the type anymore) with a single entry as Source for the customersCollectionViewSource. This works fine. However, there is no need for a collection, because I only edit a single Customer. I did not manage to set a single Customer as Source. I didn't know how to adjust the view source which was generated by VS2010.
I haven't done saving yet. But I think I am going to run into problems due to my design between CustomerWindow and ItemWindow. Maybe you can give me some advice here.
When the "Apply"-Button in ItemWindow gets pressed, the Item data should be updated in DB. But not the Customer- and Invoices-related data in the CustomerWindow underneath.
The DataGrid of Items in CustomerWindow should get updated, when closing the ItemWindow. But not the rest of the fields in the CustomerWindow, since here could have been data changed before opening the ItemWindow.
The only solution for me to overcome that "synchronisation problem": The User is forced to press "Apply" in the CustomerWindow before he can press the "New Item" or "Edit Item", if there have been any changes. (Kinda like the "window resolution control" of windows 7 when working with two monitors) But this is not too user friendly.
A cleaner design would be to use the MVVM design pattern.
Inject the view model into the window's context and bind the view model to either a collection of entities or a single entity, bind in the xaml to properties in the view model(s) and use commands implemented in the view model for actions e.g. add new, delete.
The windows shouldn't be aware of the context.
If you have a list view model + window and a details window (preferably with a view model), then the list view model should pass the selected item to the details view model (or window) as the context.
If the windows are not open at the same time or do not have related objects, then their views models should not share a database context, otherwise, in order for the changes to be reflected easily between the windows, they will have to share the database context.

Winforms, Combobox, Databinding... allow user to type in a value not found in the DataSource

Just like the title says... I have a Winforms application with a databound dropdown. I want the user to have the convenience to pick from a bunch of predefined values, but also the ability to type in his own value
If I just enable databinding and set dropdown type to anything but DropDownList, it allows me to enter anything I want, but does not persist it to the objects...
Seems like a simple problem to solve... help?
I've added an event handler on ComboBox.Leave this code would add the newly typed in string in the combobox to the underlying list(countries) as well as refresh the combobox binding to it.
Limitations
You'd have to handle the addition of new element based on the type of datasource you have.
The List.Contains is case sensitive you might want to keep all the strings in one case. And convert the user entered value to that case before deciding to add it to the datasource.
Here you go, modify the comboBox1_Leave eventhandler according to your datatypes and datasource.
public partial class Form1 : Form
{
private List<string> countries;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
countries = new List<string>();
countries.Add("Australia");
countries.Add("Belgium");
countries.Add("Canada");
comboBox1.DataSource = countries;
}
private void comboBox1_Leave(object sender, EventArgs e)
{
ComboBox combo = (sender as ComboBox);
CurrencyManager cm = (combo.BindingContext[combo.DataSource] as CurrencyManager);
if (!cm.List.Contains(combo.Text))
{
cm.List.Add(combo.Text);
cm.EndCurrentEdit();
cm.Refresh();
cm.Position = cm.Count - 1;
}
}
}

Resources