Unexpected behaviour with DataGridViewComboBoxColumn Value/Display members binded to DataTable from an Access DB - winforms

I'm doing something to be supposedly straight and simple, yet I cannot make it work. I've an Access DB (64 bits), which is accessed by an OleDB connection. If I bind the values of a DataGridViewComboBoxColumn to a DataTable's fields ([int]id and [string]name), the ComboBoxes are rendered correctly (it displays the "DisplayMember", and keeps the "ValueMember" as an internal value). Yet, as soon as I set the target field for storing it's value with "DataPropertyName", the ComboBox now renders the "ValueMember" - or rather the "DataPropertyName" takes priority [??] - (see the attached images).
The code used is shown in the following snipet:
private OleDbDataAdapter empleadosDA_local = new OleDbDataAdapter();
private OleDbDataAdapter activosDA_local = new OleDbDataAdapter();
private DBLocal dbConn_local;
internal DataSet catalogosDS, auxsDS;
internal BindingSource activosBind = new BindingSource();
......
private void BindControls() {
DataGridViewComboBoxColumn activos_responsable1DGComboBox;
activosBind.DataMember = "Activos";
activosBind.DataSource = catalogosDS;
this.Activos_dataGrid.AutoGenerateColumns = false;
// Create DGComboBox
activos_responsable1DGComboBox = new DataGridViewComboBoxColumn();
activos_responsable1DGComboBox.ValueMember = "id_empleado";
activos_responsable1DGComboBox.DisplayMember = "nombre";
activos_responsable1DGComboBox.DataSource = auxsDS.Tables["Responsables1"];
activos_responsable1DGComboBox.HeaderText = "Responsable1:";
// activos_responsable1DGComboBox.DataPropertyName = "idEmpleado1"; <--- this is the line that creates the unexpected behaviour
this.Activos_dataGrid.Columns.Insert(6, activos_responsable1DGComboBox);
this.Activos_dataGrid.Columns[6].Name = "Activos_dgResponsable1";
this.Activos_dataGrid.Columns["Activos_dgResponsable1"].Width = 195;
this.Activos_dataGrid.Columns["Activos_dgResponsable1"].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleLeft;
this.Activos_dataGrid.DataSource = activosBind;
I've done this in other Forms and it works fine using this same logic, but I just cannot find the reason why this fails. Any help will be greatly appreciated.
EDIT:
JohnG asked for the schemas that feeds the DataSource of the ComboBox and the target table where it's stored, so we can identify any data type inconsistencies:
ComboBox DataSource:
TargetTable which feeds the DataGridView:

So it results that the ComboBox's source table has an autoincrement field (the primary key), and this is forcibly a "number long integer", and I was using for storing it a table with a field defined as integer. Setting the target field to long int solved the issue.

Related

Can't prevent a binding error

I have a datatable in my dataset that has these fields:
and I have a wpf window that the user inputs data into controls corresponding to these fields. The controls are bound to a datarow as follows:
DataContext = myApp.Tables("VehicleExpenses").NewRow
When I load the form at run time I get this error:
System.InvalidCastException was unhandled by user code
HResult=-2147467262
Message=Conversion from type 'DBNull' to type 'Long' is not valid.
and this is the line that throws the error (this is generated code):
Return CType(Me(Me.tableVehicleExpenses.CategoryIDColumn),Long)
So the error is obvious but I have no idea how to prevent VB from pulling a Null value from the new row. I've tried writing a blocking converter and setting UpdateSourceTrigger = Explicit but neither solves the problem.
--UPDATE 1--
Do not get confused by Dataset and this model. This middle layer is for user interaction and validation purpose. Saving data to db is in 2 stages. You will still need the auto generated properties and methods for CRUD operations.
Imagine this possibility (this is c#)
public class Vehicle
{
public int VehId{get;set;}
public Vehicle(){}
public bool SaveToDb()
{
MyDataSet myDataSet = new MyDataSet();
myDataSet.VehicleExpenses newVehRow = myDataSet.VehicleExpenses.NewVehicleExpensesRow();
newVehRow.VehId = this.VehId;
myDataSet.VehicleExpenses.Rows.Add(newVehRow);
// Save the new row to the database
myDataSet.Update(this.myDataSet.VehicleExpenses);
}
}
Delete the row with that Null value in the table directly (if present).
The DataTable.NewRow creates a new DataRow with column defaults values (since nothing is passed). Not sure why it's not setting default value as 0 for the non-nullable column
Try this option instead of myApp.Tables("VehicleExpenses").NewRow if possible
'declare a VehicleExpenses in the form (you might have to create this class)
Dim blankMo As VehicleExpenses
' in Form_load() set this object with new VehicleExpenses and against Datacontext
blankMo = New VehicleExpenses()
DataContext = blankMo
You will need to add a separate save button and function to commit this model details to the database.
Let us know..

getting error when trying to read from DataGridView with event handler winform

I have a winform that has a gridview that I am applying data to via a Dataset. When the data binds, it calls the SelectionChanged event handler. I researched that and found a way around it by adding an if clause to see if the DGV has focus (all other resolutions did not work that I found). That part is working as planned. When I step through the program, the event handler tries to go through the code 3 times when it binds the data. The if clause stops it from reaching the code. My issue is after the data binds and I then choose a row in the DGV, the event handler then throws "An unhandled exception of type 'System.ArgumentOutOfRangeException' occurred in mscorlib.dll". When stepping through the code, the DGV is returning the proper row index to my 'int row' variable, but the code I use to get the row/cell info throws the error before it applies it to the 'loadtableID' variable. I need help. You can ignore the second DGV in the top. It takes the selected row's info and gets another DB table info. Also, if it helps, I did not apply a datasource to the program or create datasets for each individual dataset that is returned, I am using the system's generic Dataset when returning data.
private void gvMainSelectResults_SelectionChanged(object sender, EventArgs e)
{
if (gvMainSelectResults.Focused)
{
gvMainArchiveResults.DataSource = null; //second DGV that is populated later and everytime is cleared with a new selection
loadTableID = 0;
orgID = 0;
dbFileName = "";
sourceType = "";
int row = gvMainSelectResults.CurrentCell.RowIndex;
loadTableID = Convert.ToInt32(gvMainSelectResults.SelectedRows[row].Cells["LoadTableID"].Value); //this is where I get the error, even if the "int row" has the correct index number
orgID = Convert.ToInt32(gvMainSelectResults.SelectedRows[row].Cells["OrganizationID"].Value);
dbFileName = Convert.ToString(gvMainSelectResults.SelectedRows[row].Cells["FileName"].Value);
sourceType = Convert.ToString(gvMainSelectResults.SelectedRows[row].Cells["SourceType"].Value);
more code here...
You are using the RowIndex value to get your text from the SelectedRows collection.
But this collection contains only
Gets the collection of rows selected by the user.
This means that the collection contains only a subset of the rows present in your grid. When RowIndex is 2 and you have just one row in the SelectedRows collection you get the OutOfRange exception.
With the RowIndex value you should refer to the Rows collection instead
loadTableID = Convert.ToInt32(gvMainSelectResults.Rows[row].Cells["LoadTableID"].Value);
orgID = Convert.ToInt32(gvMainSelectResults.Rows[row].Cells["OrganizationID"].Value);
dbFileName = Convert.ToString(gvMainSelectResults.Rows[row].Cells["FileName"].Value);
sourceType = Convert.ToString(gvMainSelectResults.Rows[row].Cells["SourceType"].Value);

How Do I get a Bindingsource to see a record added with the TableAdapter.Update() method without using .fill?

I have a large dataset that load in with a fill method on page load.
Then, a record can be added to the dataset.
All of that works fine, but the only way that I can get the bindingsource to recognize the new record is to do a fill method. This also works but is a perfomance problem. Why does the binding source not see the new record in the dataset?
Mainform Code. Works Great.
DialogResult returnFormVal;
Schedulers.DataSets.SchedOneFetch.WOMainFetchRow newRow = schedOneFetch.WOMainFetch.NewWOMainFetchRow();
Schedulers.Forms.NewWorkOrder genReport = new Schedulers.Forms.NewWorkOrder(ref newRow);
Int32 picNumber;
returnFormVal = genReport.ShowDialog();
schedOneFetch.WOMainFetch.Rows.Add(newRow);
wOMainFetchBindingSource.EndEdit();
wOMainFetchTableAdapter.Adapter.Update(schedOneFetch.WOMainFetch);
Int32 passBackVal = newRow.DISID;
SubForm code. Also works great.
passBackRow.DISDueDate = monthCalendar1.SelectionStart;
passBackRow.DISID = 99999999;
if (ckbEqpt.Checked == true & lbProcNum.Items.Count > 0)
{
passBackRow.DISEquip = Convert.ToInt32(lbProcNum.SelectedValue.ToString());
}
else
{
passBackRow.DISEquip = 0;
}
passBackRow.DISLineNumber = Convert.ToInt32(lbLineName.SelectedValue.ToString());
passBackRow.DISManHours = Convert.ToInt32(nudEstTotTime.Value);
passBackRow.DISNumberAss = Convert.ToInt32(nudEstTM.Value);
passBackRow.DISOpenDate = DateTime.Now;
passBackRow.DISOriginator = userID.DBUserID;
passBackRow.DISRequestor = 0;
passBackRow.DISResponsible = Convert.ToInt32(lbRespons.SelectedValue.ToString());
passBackRow.DISType = Convert.ToInt32(lbType.SelectedValue.ToString());
passBackRow.DISWorkAccomp = "";
passBackRow.DISWorkRequired = rtbWorkReq.Text;
passBackRow.MLID = 0;
passBackRow.LIID = 0;
passBackVal = 0;
this.Close();
Return control to main form. The new record has been added to the database.
wOMainFetchBindingSource.Position = wOMainFetchBindingSource.Find("DISID", passBackVal);
DataRowView dtaRow = (DataRowView)wOMainFetchBindingSource.Current;
String woID = dtaRow["DISID"].ToString();
FAIL! The bindingsource wont find the the new record, returns a -1 on the find and defaults to the first record in the dataset.
If I put the .fill method in between the dialog and the main page then it all works fine, but takes a loooonnng time to do the fill... seven or eight seconds.
I guess my understanding of the binding source is disfunctional, I had assumed that if the underlying dataset was updated then the bindingsource would see it.
So, first if someone has a suggestion on how to refresh the binding source without the fill I would appreciate it, and if someone can explain why this works the way it does I might be able to find a workaround.
Thanks
My understanding of the Bindingsource was correct, it was seeing the new record added. The issue is that the Dataset gets its information from a view. The add new method only populates the fields in the base table. The other fields, the ones assembled by the View, arent populated until the tableadapter re-reads by using the fill method. I dont see a way around this other than filling each of the fields of the new record, big drawback is that whenever the view or any of the tables assembled in the view are changed you would have to make sure to change the code. I will instead reduce the number of records being loaded on each fill.

Entity Framework 4 Binding a related field to a DataGridView column

I have a DataGridView that is bound - via a binding source - to a list of entities:
VehicleRepository:
private IObjectSet<Vehicles> _objectSet;
public VehicleRepository(VPEntities context)
{
_context = context;
_objectSet = context.Vehicles;
}
List<Vehicle> IVehicleRepository.GetVehicles(Model model)
{
return _objectSet
.Where(e => e.ModelId == model.ModelId)
.ToList();
}
In my presenter
private List<Vehicle> _vehicles;
...
_vehicles = _vehicleRepository.GetVehicles(_model);
_screen.BindTo(_vehicles);
in my view
public void BindTo(List<Vehicle> vehicles)
{
_vehicles = vehicles;
if (_vehicles != null)
{
VehicleBindingSource.DataSource = _vehicles;
}
}
This works fine - my grid displays the data as it should. However, in the grid I am wanting to replace the ModelId column with a description field from the Model table. I've tried changing the binding for the column from ModelId to Model.ModelDescription but the column just appears blank.
I'm pretty sure that the data is being loaded, as I can see it when I debug, and when the same list is passed to a details screen I can successfully bind the related data to text fields and see the data.
Am I doing something obviously wrong?
It's a bit manual, but it 'works on my machine'.
Add a column to your DataGridView for the description field and then after you set your DataSource iterate through like so.
Dim row As Integer = 0
foreach (entity In (List<Entity>)MyBindingSource.DataSource)
{
string description = entity.Description;
MyDataGridView.Rows[row].Cells["MyDescriptionCell"].Value = description;
row ++;
}
You get a readonly view of your lookup. I make the new column readonly, but you could write something to handle the user changing the field if you wanted updates to run back to the server. Might be messy though.
The answer involves adding unbound read only columns and setting their value in the DataGridView's DataBindingComplete event
as described here
You can just add a column to your DataGridView, and in the DataPropertyName you must set the [entity].[Field name you need] in your case you could do: VehiclesType.Description
then you must add another binding source for the VehiclesTypes to the form, fill it using your context, and your good to go ;)

Refreshing BindingSource after insert (Linq To SQL)

I have a grid bound to a BindingSource which is bound to DataContext table, like this:
myBindingSource.DataSource = myDataContext.MyTable;
myGrid.DataSource = myBindingSource;
I couldn't refresh BindingSource after insert. This didn't work:
myDataContext.Refresh(RefreshMode.OverwriteCurrentValues, myBindingSource);
myBindingSource.ResetBinding(false);
Neither this:
myDataContext.Refresh(RefreshMode.OverwriteCurrentValues, myDataContext.MyTable);
myBindingSource.ResetBinding(false);
What should I do?
I have solved the problem but not in a way I wanted.
Turns out that DataContext and Linq To SQL is best for unit-of-work operations. Means you create a DataContext, get your job done, discard it. If you need another operation, create another one.
For this problem only thing I had to do was recreate my DataContext like this.dx = new MyDataContext();. If you don't do this you always get stale/cached data. From what I've read from various blog/forum posts that DataContext is lightweight and doing this A-OK. This was the only way I've found after searching for a day.
And finally one more working solution.
This solution works fine and do not require recreating DataContext.
You need to reset internal Table cache.
for this you need change private property cachedList of Table using reflection.
You can use following utility code:
public static class LinqDataTableExtension
{
public static void ResetTableCache(this ITable table)
{
table.InternalSetNonPublicFieldValue("cachedList", null);
}
public static void ResetTableCache(this IListSource source)
{
source.InternalSetNonPublicFieldValue("cachedList", null);
}
public static void InternalSetNonPublicFieldValue(this object entity, string propertyName, object value)
{
if (entity == null)
throw new ArgumentNullException("entity");
if(string.IsNullOrEmpty(propertyName))
throw new ArgumentNullException("propertyName");
var type = entity.GetType();
var prop = type.GetField(propertyName, BindingFlags.NonPublic | BindingFlags.Instance);
if (prop != null)
prop.SetValue(entity, value);
// add any exception code here if property was not found :)
}
}
using something like:
var dSource = Db.GetTable(...)
dSource.ResetTableCache();
You need to reset your BindingSource using something like:
_BindingSource.DataSource = new List();
_BindingSource.DataSource = dSource;
// hack - refresh binding list
Enjoy :)
Grid Data Source Referesh by new query instead just Contest.Table.
Simple Solution < But Working.
Whre is eg.
!!!!! Thanks - Problem Solved after no of days !!! but with so simple way ..
CrmDemoContext.CrmDemoDataContext Context = new CrmDemoContext.CrmDemoDataContext();
var query = from it in Context.Companies select it;
// initial connection
dataGridView1.DataSource = query;
after changes or add in data
Context.SubmitChanges();
//call here again
dataGridView1.DataSource = query;
I have the same problem. I was using a form to create rows in my table without saving the context each time. Luckily I had multiple forms doing this and one updated the grid properly and one didn't.
The only difference?
I bound one to the entity similarly (not using the bindingSource) to what you did:
myGrid.DataSource = myDataContext.MyTable;
The second I bound:
myGrid.DataSource = myDataContext.MyTable.ToList();
The second way worked.
I think you should also refresh/update datagrid. You need to force redraw of grid.
Not sure how you insert rows. I had same problem when used DataContext.InsertOnSubmit(row), but when I just inserted rows into BindingSource instead BindingSource.Insert(Bindingsource.Count, row)
and used DataContext only to DataContext.SubmitChanges() and DataContext.GetChangeSet(). BindingSource inserts rows into both grid and context.
the answer from Atomosk helped me to solve a similar problem -
thanks a lot Atomosk!
I updated my database by the following two lines of code, but the DataGridView did not show the changes (it did not add a new row):
this.dataContext.MyTable.InsertOnSubmit(newDataset);
this.dataContext.SubmitChanges();
Where this.dataContext.MyTable was set to the DataSource property of a BindingSource object, which was set to the DataSource property of a DataGridView object.
In code it does looks like this:
DataGridView dgv = new DataGridView();
BindingSource bs = new BindingSource();
bs.DataSource = this.dataContext.MyTable; // Table<T> object type
dgv.DataSource = bs;
Setting bs.DataSource equals null and after that back to this.dataContext.MyTable did not help to update the DataGridView either.
The only way to update the DataGridView with the new entry was a complete different approach by adding it to the BindingSource instead of the corresponding table of the DataContext, as Atomosk mentioned.
this.bs.Add(newDataset);
this.dataContext.SubmitChanges();
Without doing so bs.Count; returned a smaller number as this.dataContext.MyTable.Count();
This does not make sense and seems to be a bug in the binding model in my opinion.

Resources