At first, I want to talk a little about normal binding (with standard property such as Text of a textbox). The sample grid here has only 2 rows (for simplicity). Suppose I have a table of 2 rows (myDataTable) with 2 columns (ID and Name), a DataGridView (myGrid) and a TextBox (myTextBox). Here is the code for binding data:
myGrid.DataSource = myDataTable;
myTextBox.DataBindings.Add("Text", myDataTable, "Name");
After binding data, when the selection changes in the grid, the info is updated automatically to the control TextBox, for example, the 2 rows are:
ID | Name
1 .NET
2 Java
At first, the selection in grid is at index 0, the Text of myTextBox is ".NET", moving the selection to next position (at index 1), the Text of myTextBox is "Java", moving again and again, forward and backward, it works OK as I expect. But now I have a control with a custom property called List, this is type of List and is readonly. I want to bind it to a column of table (for example, Name), I do the same binding rule, however add a little custom Parse to format the correct string before updating to the datasource (myDataTable) because my custom property is type of List while my Name column is type of string, here is the binding code:
Binding bind = new Binding("List", myDataTable, "Name"){
ControlUpdateMode = ControlUpdateMode.Never //Because my List property is readonly
};
//formating string data before updating to the datasource
bind.Parse += (s,e) => {
List<string> data = (List<string>) e.Value;
if(data.Count == 0) e.Value = DBNull.Value;
else e.Value = string.Join(",",data.ToArray());//format as comma separated string
};
myCustomControl.DataBindings.Add(bind);
In this case, suppose myDataTable currently has no data at column Name like this:
ID | Name
1 <DBNull.Value> <--- current index
2 <DBNull.Value>
After running the demo, the current selection index in the grid is 0, I try changing the value of myCustomControl property List (the Items, not the reference), for example, update it like this:
myCustomControl.List.Add(".NET");
myCustomControl.List.Add("Java");
Then, moving the selection in grid to the next position (index 1), the value ".NET,Java" is updated to the datasource in row 0 at column Name, like this:
ID | Name
1 .NET,Java
2 <----- current index
Now if I move the selection back to index 0, the value at column Name in row 1 is also updated to ".NET,Java" like this:
ID | Name
1 .NET,Java <----- current index
2 .NET,Java
Which is not what I want. I mean the value should be updated via control myCustomControl. Here is what I want:
ID | Name
1 .NET,Java <----- current index
2
I can understand that, at the time moving back from index 1 to index 0, the value of List property is still a List with 2 items (".NET" and "Java") and so after the moving, this is updated to the cell at column Name in row 1. I'm finding how to reset that value of List property after it's updated to the cell at column Name in row 0 so that when the selection is at index 1, it's already empty. I'v tried changing the Parse event handler to the following but no good shake:
bind.Parse += (s,e) => {
List<string> data = (List<string>) e.Value;
if(data.Count == 0) e.Value = DBNull.Value;
else e.Value = string.Join(",",data.ToArray());//format as comma separated string
//I think at here, the value has been already updated to the datasource
//and I can perform the reset
myCustomControl.List.Clear();
};
But it seems to Clear before the value is updated to the datasource and so there is no value udpated to the datasource (instead of ".NET,Java", it's a DBNull.Value).
Then I have also tried this:
bind.BindingComplete += (s,e) => {
if(e.BindingCompleteContext == BindingCompleteContext.DataSourceUpdate)
myCustomControl.List.Clear();
};
I thought, it should check if the data is updated to the datasource, the List can be clear. I have also tried some flag to mark as true before clearing and reset it to false after clearing, use this flag to control the flowing in bind.Parse but it did nothing.
Do you have any idea to solve this problem? Your help would be highly appreciated! Thank you.
I've found the solution myself. In fact I can't reset the List anyway, this will update the underlying datasource when switching between rows in the grid. The key idea here is initially set DataSourceUpdateMode to DataSourceUpdateMode.Never, then whenever the List is about to change, turn the DataSourceUpdateMode to DataSourceUpdateMode.OnPropertyChanged. In the Parse event handler, after the parsing is done, reset DataSourceUpdateMode to DataSourceUpdateMode.Never. And that works greatly. The underlying datasource is updated only when user changing the control's value (List) by typing or selecting ,...
Here is all the code:
Binding bind = new Binding("List", myDataTable, "Name"){
ControlUpdateMode = ControlUpdateMode.Never, //Because my List property is readonly
DataSourceUpdateMode = DataSourceUpdateMode.Never//This will be turned on when preparing to change the List's value
};
//formating string data before updating to the datasource
bind.Parse += (s,e) => {
List<string> data = (List<string>) e.Value;
if(data.Count == 0) e.Value = DBNull.Value;
else e.Value = string.Join(",",data.ToArray());//format as comma separated string
//At here reset the DataSourceUpdateMode to Never
//We can also do this in BindingComplete event handler with BindingCompleteContext = BindingCompleteContext.DataSourceUpdate
myCustomControl.DataBindings[0].DataSourceUpdateMode = DataSourceUpdateMode.Never;
};
myCustomControl.DataBindings.Add(bind);
myCustomControl has a method to update/populate the new items for the List property called UpdateList(), we have to set DataSourceUpdateMode to OnPropetyChanged at the very beginning of the method like this:
public void UpdateList(){
if(DataBindings.Count > 0) DataBindings[0].DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged;
//The remaining code for populating/updating new items goes below
....
}
And that's all, very clean. Hope this will help someone who will encounter the same situation with me.Thank you!
Related
I have a DataGrid bound to an ObservableCollection of DataRowView.
When I do any operations such as Insert/Delete/Copy/Paste/Undo/Redo, I manipulate the underlying DataSource.
Ex: When I have to paste data into a set of cells, I get their row and column indexes (the cell the context menu points to) and edit the DataSource. I store their previous values and their indexes to revert back when I do an "Undo" operation.
When I filter the grid, the view changes where as the DataSource remains the same. So, when I do a paste operation, the indexes I get don't match with the ones in the DataSource. I end up pasting in the wrong cells.
How can I manipulate the DataSource i.e get the actual indexes/data to do all the operations after filter is applied?
Based on what I understood after reading your question. New record which appear after Item1 should apppear after Item1 in source collection too. This is not happening right now as index is differing which will of course differ.
As you have not posted code, so I have posted sample code below :
// new employee to add
Employee empNew = new Employee() { Name = "New1", Address = "NewAdd1" };
// get corresponding item in filtered view after which you want to insert
Employee emp = (Employee)DgrdEmployees.SelectedItem;
// get true collection which is datasource
ObservableCollection<Employee> sourceCol = (ObservableCollection<Employee>)(CollectionViewSource.GetDefaultView(DgrdEmployees.ItemsSource).SourceCollection);
// find index of selected item after which we are inserting/pasting
int trueIndex = sourceCol.IndexOf(emp);
// insert it at exact location in true unfiltered source collection
sourceCol.Insert(trueIndex + 1, empNew);
I've a little problem using a DataGridView with AllowUserToAddRows set to True that is binding to a list (BindingList<State>).
When adding a new row through the GUI, the row isn't added to the BindingList.
I've a entity which contains a collection with other entities. For example: Country contains States. So, the new row in datagridview isn't added in the StateCollection on country object.
That's is how I add the BindingList to the DataGridView:
var bl = new BindingList<State>(country.StatesCollection.ToList<State>());
grdData.DataSource = bl;
Properties:
EditMode is set to EditOnKeystrokeOrF2
If more data is needed, please ask me.
Following your code, I initialized context.MyEntities with 3 MyEntity objects.
After binding, the DataGridView shows the 3 rows as well as the expected * NewRow. Thus:
context.MyEntities.Length == 3 // .Count or .Length depending on data structure
((BindingList<MyEntity>)grdData.DataSource).Count == 3
grdData.Rows.Count == 4
After entering data into the NewRow within the GUI, the DataGridView now shows 4 rows as well as the expected * NewRow. Thus:
context.MyEntities.Length == 3
((BindingList<MyEntity>)grdData.DataSource).Count == 4
grdData.Rows.Count == 5
Adding an item to the DataGridView did indeed add the object to the BindingList source, but not to the actual context.MyEntities source, which I assume is where you are expecting to see this new item added.
The reason you are seeing this behavior is because of this line:
var bl = new BindingList<MyEntity>(context.MyEntities.ToList<MyEntity>());
You are declaring a new object for your binding. Even if context.MyEntities is also a BindingList<MyEntity> (I doubt since that would negate the reason for the above line of code), it will not be updated with the new entry. For the desired behavior, you have two options:
Change context.MyEntities type to BindingList<MyEntity>, then simply change your binding to:
grdData.DataSource = context.MyEntities;
Handle DataGridView.RowsAdded and manually update context.MyEntities by a) adding the new item or b) refreshing the original object:
private void grdData_RowsAdded(object sender, DataGridViewRowsAddedEventArgs e)
{
var source = ((BindingList<MyEntity>)grdData.DataSource);
context.MyEntities = // source converted to the correct Enumerable type
}
I'm trying to set DataSource to DataGridViewComboBoxColumn of my DataGridView. First I'm trying to bind a data source to my DataGridView, where bindingList is a List of my custom class Plugin with properties Name (string), Id (string) and Dependencies (List):
var bindingList = PluginsHandler.GetPlugins();
var source = new BindingSource(bindingList, null);
pluginsDataGridView.AutoGenerateColumns = false;
pluginsDataGridView.DataSource = source;
pluginsDataGridView.Columns["pluginName"].DataPropertyName = "Name";
pluginsDataGridView.Columns["pluginID"].DataPropertyName = "Id";
So I can set my first two columns, but now I want to bind data to a third column of type DataGridViewComboBoxColumn. I try to do it on DataBindingComplete event:
private void pluginsDataGridView_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
{
for (int i = 0; i < pluginsDataGridView.Rows.Count; i++)
{
var comboCell = (DataGridViewComboBoxCell) pluginsDataGridView.Rows[i].Cells["pluginDependencies"];
var entry = pluginsDataGridView.Rows[i].DataBoundItem as IPlugin;
comboCell.DataSource = entry.Dependencies;
}
}
Sadly comboBox is empty. Funny thing happens when I incorrectly put these lines after the first block of code I posted:
var dependenciesColumn = (DataGridViewComboBoxColumn) pluginsDataGridView.Columns["pluginDependencies"];
dependenciesColumn.DataPropertyName = "Dependencies";
Then binding seem to start to work, as I can see that there are some entries in comboboxes, but when I try to hover mouse on combobox, I am getting an error that says DataGridViewComboBoxCell value is not valid).
How can I make it work?
Instead of assigning each ComboCell a data Source, set the DataSource of the column. I assume the Dependencies property of PlugIn class is a List<string>.
pluginsDataGridView.Columns["pluginDependencies"].DataSource = //list of dependencies
You have to set the DataPropertyName of the Dependencies ComboBoxColumn to get the initial values. If you don't set it you won't see any value in the column when the application is loaded.
pluginsDataGridView.Columns["pluginDependencies"].DataPropertyName = "Dependencies"
Edit:
You have a list of dependencies for a plug-in. i.e, more than one value. Usually, to select one value from list of values, you associate list with ComboBoxColumn. Achieving your requirement of multiple values from a list using standard ComboBoxColumn is difficult. Write a custom CheckedComboBoxColumn where you can display and select multiple values.
I have this problem for days and can't find solution for it. I tried all possible solutions i found on internet, but seems like none suits this one.
Thing is that i added repository item to gridControls (i added it through designer, not through code). Then, in code i added data source to that repository lookUpEdit and i have items in dropDown in that column. But when i select item in repository and click on other cell, Selected item in repository is cleared and repository shows null value again...
Any ideas what i did wrong ?
EDIT:
Also, when i click on any cell in my grid, i have delay of second or two, and after that delay clicked cell is focused... Any solutions for all of this?
EDIT:
Don't know what code to show You, because I did all in devExpress designer. Here is part of the code where I set data source to repository item, and i will give You code from designer of that repository item.
private void ConfigureRepositoryItems()
{
BetService.SportManagerClient dbSportManager = new BetService.SportManagerClient();
BetService.BLOddsControlSettings[] oddsControlSettings = dbSportManager.GetOddsControlSettings("", "");
repositoryOddsControlSettings1.DataSource = oddsControlSettings;
}
And here is code from designer:
//
// repositoryOddsCalculationSettings1
//
this.repositoryOddsCalculationSettings1.AutoHeight = false;
this.repositoryOddsCalculationSettings1.Buttons.AddRange(new DevExpress.XtraEditors.Controls.EditorButton[] {
new DevExpress.XtraEditors.Controls.EditorButton(DevExpress.XtraEditors.Controls.ButtonPredefines.Combo)});
this.repositoryOddsCalculationSettings1.Columns.AddRange(new DevExpress.XtraEditors.Controls.LookUpColumnInfo[] {
new DevExpress.XtraEditors.Controls.LookUpColumnInfo("ID", "ID", 20, DevExpress.Utils.FormatType.None, "", false, DevExpress.Utils.HorzAlignment.Default),
new DevExpress.XtraEditors.Controls.LookUpColumnInfo("Name", "Name")});
this.repositoryOddsCalculationSettings1.DisplayMember = "Name";
this.repositoryOddsCalculationSettings1.Name = "repositoryOddsCalculationSettings1";
this.repositoryOddsCalculationSettings1.NullText = "Select Settings";
this.repositoryOddsCalculationSettings1.PopupSizeable = false;
this.repositoryOddsCalculationSettings1.ValueMember = "ID";
For starters check whether the column name in your Grid datasource and the column in your grid control match. The match is case sensitive so name and Name are not same and hence can cause this issue. Secondly make sure the Grid datasource column datatype matches the value type of the LookUpEdit. If the LookupEdit is returning int and the Grid datasource column datatype is string, this alone can cause lots of headaches.
I had a databound combobox in my windows form I populate it by a function deptload() IN FORM LOAD
public void DeptcomboLoad()
{
DataTable dt = depttrans.getDeptName();
Cmb_Department.DataSource = dt;
Cmb_Department.DisplayMember = "DepartmentName"; //CHAR
Cmb_Department.ValueMember = "DepartmentPK"; //INT
}
Now when an employee of a department (say accounts DepartmentName="Accounts " , DepartmentPK=23 ) login I want the ComboBox text to be selected as "acounts "
and when I go to get the selected value of the ComboBox I should get 23
I tried
Cmb_Department.selectedtext="Accounts"
Cmb_Department.Text="Accounts"
but its not giving the selected value
Can anyone give a suggestion
Instead of trying to put a value INTO the combobox, try to GET the SelectedItem like this:
string txt= Cmb_Department.SelectedItem.Text
or just:
string txt= Cmb_Department.SelectedText
To change selected value of the combobox you can use
SelectedItem property or SelectedIndex.
Index must be exact number in your data sourse, and Item must be exact object from datasource
You can get it to select the right item by issuing something like this:
Cmb_Department.SelectedValue = 23;
Where 23 comes from some other variable, maybe on another object, maybe from a local variable, whatever works in your case.
Now, to get the selected value you can use this statement:
var val = Cmb_Department.SelectedValue;
To get the selected text (which would be the text associated with the value):
var text = ((DataRow)Cmb_Department.SelectedItem)["DepartmentName"];
The reason I'm prescribing the aforementioned is because the SelectedText property is volatile, and the Text property doesn't always work based on how the DropDownStyle is set.
However, some would probably argue to get the same as the aforementioned you could issue this statement:
var text = Cmb_Department.Text;