I have in my Grid three columns each one with a LookUpEditor inside. Each one displays different data without any problem.
I have set the property GridView.OptionsBehavior.AutoSelectAllIneditor to true to display an empty row in the bottom to add new data to the grid.
My problem is that when I display the LookUpEditor in my first column and select one item and then move to the next column to select another, data my first column does not maintain it's value, and the same with the other column.
Here is the code I used to set my DataSource in the grid and in the LookUpEdit Repository:
gc_Operaciones.DataSource = _parametroEnsayoProductoList;
repositoryItemLookUpEdit1.DataSource = NBibliography.GetAllElab().Select(p => p.NombreProd).ToList();
repositoryItemLookUpEdit2.DataSource = NBibliography.GetAllTypeEnsayo().Select(p => p.Descripcion).ToList();
repositoryItemLookUpEdit3.DataSource = NBibliography.GetAllEnsayo().Select(p => p.Nombre).ToList();
The reason for this is that the references to your DataSources are being used between the multiple LookUpEdits. This has happened to me before and the best way to avoid this situation is to make sure that the data set that is being passed into the LookUpEdit's DataSource is a new object for each of the LookUpEdits. If you have references to the same data within the multiple LookUpEdits DataSources than the EditValues of the LookUpEdits will change even when you manipulate different LookUpEdits that you have not selected.
Without your code it is difficult for me to code the solution to your issue, but I always just take the data that I need and make sure that I am creating a new copy of the object that I bind to each LookUpEdit's DataSource. This way each LookUpEdit has it's own set of data and when the data is manipulated it will not interfere with the other LookUpEdits data references.
Related
I have an input enabled DataGridView bound to a BindingSource which has a SortableBindingList as DataSource.
There is no direct connection to the database. The list is read once from the database beforehand. After the user is done with editing he can choose either to save the changed data to the database or not.
The list has 2 fields:
"Type" (enum)
"Path" (string)
As I want to use a ComboBox for the user to select the "Type" I add an additional column "TypeCbx" which is bound to the enum values. Initially, all "Type" values from the list are copied to the column "TypeCbx" and for changes, the value of "TypeCbx" is copied back to "Type" in the event ...CellEndEdit().
Furthermore, I have 2 button columns included:
"Browse" button: opens a FolderDialog for the user in order to adjust the actual row and set the column "Path" and (if it is a new row/entry) a default value to "Type"
"Remove" button: removes the row/entry from the DGV
This worked as long as I used an unbound DataTable in which I had the following code for the button "Browse":
private void dgvPaths_OpenFolderClick(DataGridView sender, DataGridViewCellEventArgs e) {
string newSelectedPath = Helper.FileBrowserDialog("Select folder", LastSelectedPath);
if (newSelectedPath != null) {
LastSelectedPath = Helper.CleanPath(newSelectedPath);
if (dgvPaths.Rows[e.RowIndex].IsNewRow) {
// --- variante old: unbound datatable ----------------------------------------
DataGridViewRow row = (DataGridViewRow)dgvPaths.Rows[0].Clone();
row.Cells[dgvPaths.Columns["Path"].Index].Value = LastSelectedPath;
row.Cells[dgvPaths.Columns["Type"].Index].Value = LibraryPathType.Movies;
row.Cells[dgvPaths.Columns["TypeCbx"].Index].Value = LibraryPathType.Movies;
row.Cells[dgvPaths.Columns["TypeCbx"].Index].ReadOnly = false;
dgvPaths.Rows.Add(row);
dgvPaths_CellValueChanged(sender, e);
} else if (dgvPaths.Rows[e.RowIndex].Cells["Path"].Value == null || LastSelectedPath != dgvPaths.Rows[e.RowIndex].Cells["Path"].Value.ToString()) {
dgvPaths.Rows[e.RowIndex].Cells["Path"].Value = LastSelectedPath;
dgvPaths.Rows[e.RowIndex].Cells["TypeCbx"].ReadOnly = false;
dgvPaths_CellValueChanged(sender, e);
}
}
}
Now, with the bound DataSource, the line dgvPaths.Rows.Add(row); no longer works. So I adjusted the code as follows:
// --- variante new 1: bound list, working on dgv -----------------------------
dgvPaths.Rows[e.RowIndex].Cells["Path"].Value = LastSelectedPath;
dgvPaths.Rows[e.RowIndex].Cells["Type"].Value = LibraryPathType.Movies;
dgvPaths.Rows[e.RowIndex].Cells["TypeCbx"].Value = LibraryPathType.Movies;
dgvPaths.Rows[e.RowIndex].Cells["TypeCbx"].ReadOnly = false;
dgvPaths_CellValueChanged(sender, e);
Issue 1:
Now, the data is written into the row of the DataGridView but the DataGridView does not interpret it as an input and therefore it is not really added - it still is a "new Row" waiting for input. I need to manually go into the Path Column of the row and press a key in order that the DataGridView accepts it as a valid entry and shows a new "new Row" line.
=> How can I inform the DataGridView that programmatically entered data should be handled like a user input?
Issue 2:
Furthermore, when I manually enter an entry in the DataGridView and click in the "Path" column between the added line and the new "new Row" line, a first chance exception is thrown.
=> What is the reason for the first chance exception?
Then I've read that you should not manipulate the DataGridView but instead the BindingSouce or BindingSource.DataSource, which I tried with by changing the code to this:
// --- variante new 2: bound list, working on datasource ----------------------
Library.Current.AddDirtyPath(LibraryPathType.Movies, LastSelectedPath);
Issue 3:
Hereby, I also get a first chance exception when this entry is added to the source list.
=> What is the reason for the first chance exception?
What is the correct approach here?
=> Do I need to manipulate the rows of the DataGridView or the entries of the BindingSource or the entries of the BindingSource.DataSource?
Issue 4:
The bound DataGridView threw another exception when loading the DataSource and there is no enum value for "0" (I guess for the "new Row" line). Therefore, I needed to add a dummy enum value which is set to "0" to my enum value list which I need to skip again for the actual ComboBox selection values. It works but it messes up the code.
=> Is it possible to avoid this dummy value, at all?
--- U P D A T E ---
After reading Caius recommendation, I have decided to update this question as I was able to follow the SBL approach and reduce the issues.
The correct approach here is to use a main "storage" SBL for all working data (which is initially filled by reading the DB) and create a filtered SBL out of it which is used as DS for the DGV where the user can work on and use sort and filter methods. By adding/updating/removing data, you have to ensure that all adjustments stay synchronous in the "storage" SBL. Then, when you want to update the DB, you use the 'storage' SBL.
Thereby only 1 issue is left: if you want to add a new line with a button column function inside the DGV itself. It fails as the state of the actual new line is changed while you are calling the add function from inside the DGV. There are 2 ways to "fix" this:
a) You need to completely(!) Clear() the SBL that is used as DS for the DGV and add the new line afterwards (according with all other existing ones from the "storage" list) back to this SBL. Thereby the state of the new line is also changed but as it is completely removed the state is also cleared. After this you need to Refresh() the DGV. Hereby, you will lose focus of the actual cell.
b) You use a hidden button outside of the DGV by btnAdd.PerformClick() which calls the add function in which you do not need to Clear() the SBL that is bound to the DGV. This seems strange but it works (whyever) and you keep the focus on the "new line" row (not on the added one).
All other functions like updating and removing an existing DGV line can be called by additional button columns within the DGV itself without issues.
The enum issue is not an issue as it is common to use a dummy zero value for empty entries. If I find out, how to get rid of the dummy value, I will update this question accordingly.
In order to fill the combobox column without a split between display and value columns you only need to ensure that the DataPropertyName of the combobox column is set like the DB column name.
--- U P D A T E: 2 ---
I have created a detailed video (tutorial) about my approach:
https://www.youtube.com/watch?v=W_afaNf7nz8
From 1:31:20 I show the difference between adding a new line by an external and an internal button and also the strange behaviour of using the method directly (=> error) or triggering an external button which uses the same method (=> no error).
What is the correct approach here?
You have a datagridview
You bind its DataSource to a datatable
You have a datagridviewcombobox
You bind it's DataSource to a completely different datatable
You tell the combo which columns, from its own datatable, are to be used for display, value and which column in the table that the grid is bound to, shall be updated/used for deciding which value to Show
var gdt = new DataTable();
gdt.Columns.Add("FileType", typeof(FileType));//enum
gdt.Columns.Add("Path");
gdt.Rows.Add(FileType.Text, "c:\my.txt");
var fdt = new DataTable();
fdt.Columns.Add("Val", typeof(FileType));//enum
fdt.Columns.Add("Disp");
foreach(FileType t in Enum.GetValues<FileType>())
{
fdt.Rows.Add(t, t.ToString());
}
//now wire it up
datagridviewWhatever.DataSource = gdt; //makes columns
var c = new DataGridViewComboBoxColumn();
c.DisplayMember = "Disp"; //name of column in fdt to use for show
c.ValueMember = "Val"; //name of column in fdt to use for value during lookup/set operations
c.DataPropertyName = "FileType"; //name of column in gdt that this combo shall show/set
c.DataSource = fdt; //set DataSource last (performance reasons)
Combo will now, for each row, get the value in gdt.FileType (eg FileType.Text), lookup the value in its own table fdt.Val, use the related value in fdt.Disp (eg "Text") to show in the list. If the user chooses a new list item (eg "Excel") it goes the other way, get the relevant Val (eg FileType.Excel) chosen and push it into gdt.FileType
I've never actually done it with enum typed values; can't see why it wouldn't work but if it gives trouble it might be simpler to switch to using ints instead of FileTypes - make all your typeof() calls typeof(int) and cast the FileType t to int in the foreach when adding it to the row collection.
Finally, when you're down with this as a concept I recommend throwing it all away and doing it using the visual designer - it will make a much better, nicer, easier to use job of it:
add a DataSet type file to your project
open it
if there is a database somewhere backing all this, right click on the design surface and choose Add TableAdapter, enter the connection string, choose a query, that returns rows, put SELECT * FROM MovieFiles WHERE ID = #id or whatever, call it FillById/GetDataById, finish
if there isn't a database backing all this just right click the surface, add a datatable called MovieFiles, right click it and add columns like FileType (set type to int in property grid), Path etc
right click a blank space and add another datatable called FileTypes, put a string column called Disp and an int column called Val (sounding familiar?)
save and switch to Forms designer. Open Data Sources tool panel (View menu.. other windows submenu)
Drag the MovieFiles node out of data sources and into the form. A datagridview appears as well as a bunch of stuff in the bottom tray. The grid is already correctly binded to the DataSet that contains the MovieFiles table. You can see all the code VS is writing for you in the Forms.Designer. It looks a lot like the code I put manually above
Edit the columns of the datagridview, change the file type column to be a combobox type, set the DataSource to the FileTypes table, DisplayMember to Disp, ValueMemberto Val. Check the DataPropertyName is still FileType in movies - this should also be familiar as it's the visual version of the code above)
the only thing left to do is put some code in the constructor that fills the FileTypes table from the enum (or to be honest, just make a table in the DB called FileTypes, have an int and a string column, add a tableadapter, select them.. that way you don't have to recompile the whole program when you add a file type)
Why do I advocate using these visually designed datatables rather than code ones? Because they're so much nicer in every single way. They have logical methods and named properties for columns, they just work with LINQ and they behave like .net classes that are collections of POCOs not 2D arrays of object that are indexed by string and need casting to make them useful. DataTable are awful to work with in comparison. They wrap around basic DataTable and do expose them but follow a rule that if you've exposed it, you've probably gone wrong
//no; using .Tables puts you back in "world of pain"
var p = (string)datasetX.Tables["MovieTypes"].Rows.Cast<DataRow>().First()["Path"];
//yes - using named property for MovieTypes is good, LINQ works, Path is a string property etc
var p = datasetX.MovieFiles.First().Path;
I have a datagridview that has a list of objects built from a DB bound to it. The default sorting of a datagridview wasnt working and I find out that its because a list doesn't support sorting.
I really don't care what kind of collection I use as long as I can
A: sort the list by column on header click
B: search the list by a specific column
C: Get the object back when the user selects a row.
I have C working fine with a list but A and B wont work.
private BindingSource rollingStockSource = new BindingSource();
dgvInventoryList.AutoGenerateColumns = false;
List<RollingStock> rollingStockList =RollingStockDAO.GetRollingStocks();
rollingStockSource.DataSource = rollingStockList;
dgvInventoryList.DataSource = rollingStockSource;
This is how im setting up my datagridview now. Im setting my columns and tying them to object attributes in the form editor rather than in the code with the DataPropertyName field.
Is there no list that comes with sorting/searching by default?
I have built a program in WPF using Visual Basic that allows users to select from a series of items. The list of items may change as I add more items or remove older ones. Originally, I would just add and remove these items in the project code, and rebuild the executable.
Over time, however, the list of items grew and became tedious and impractical to maintain in the code. It occurred to me that it would be easier to store the items in a database table which the program can read, and the list can just be updated by adding and removing items from this database.
I have the database built, and I've added it as a Data Source. I have the data connection set up and everything is ready to go.
What I need to know is how to set the Content property of a label to a cell from a table in the database. I want to do this in the codebehind, not in markup (XAML).
My guess is that I will need to set up a sort of query with a for loop to find the cell I want.
The name of the database is ItemsDB. It contains a table called ItemsTable, and the fields in the table have a unique ID (key) with an AutoNumber data type. The column containing the data I want is named ItemLabel.
So, to sum it all up, I want a label to show the content from a single cell in the database, specified in the codebehind. Either C# or VB is fine.
EDIT
I guess I should've mentioned that the labels themselves actually function as buttons, and I don't really want to display a ListView if I don't have to. I know it's not helpful to not have any code posted, but I don't even know where to start, so there's no code to post.
Took a while longer than I expected, hope this is still useful. So, what you want to do is iterate through a collection and create a new Label for each row.
You will need a container for the Labels, I used WrapPanel, but you can use any Panel you want. I'm replacing your Data Source with a simple DataTable, you'll get the point from this:
foreach (DataRow row in YourItemsTable.Rows)
{
Label newLabel = new Label();
newLabel.Content = row["ItemLabel"].ToString();
// If you need some events, attach the handlers here
yourParentContainerPanel.Children.Add(newLabel);
}
This should be all you need.
So I have a TreeView in GTK# and I preload it with an empty TreeModelSort (no columns.)
At runtime I let the users select the columns they'd like to add and when the button is clicked I create a new ListStore with these columns, from that I create a TreeModelFilter and TreeModelSort. I then append the values to that TreeModelSort by using reflection to pick up correct values based on columns. That all works fine and I end up with a TreeModelSort with the columns and data I am interested in.
The problem arises when I try to replace the old TreeView.Model with the new one I've just created. There are no exceptions and it replaces it fine but the new columns and data don't show up in the table.
Any ideas on what I could be missing?
Is what I'm trying to allowed or do I need to remove the TreeView from the screen and replace it with a new one?
Edit: Or even if I could have all columns in the list store but only show a few selected ones in the TreeView, is that possible? All the tutorials I've found have exact one to one mapping of columns between ListStore and TreeView. Any ideas?
(no experience with GTK#, but lots of experience with gtk in other languages).
It seems to me you might be mixing views and models here. To make columns visible in a view, you not only need to have them in your model (as you are doing), but you also need to add cell rendered to your view, binding them with specific columns in your model.
I'll show some pseudo-code below, and let you convert to gtk#:
create a new GtkTreeViewColumn
create a new GtkCellRendererText.
add it to the column with a call to gtk_tree_view_column_pack_start
add attribute to the renderer, for instance to tell it which column of your model it should render. For instance:
gtk_cell_layout_add_attribute(column, renderer, "text", 0)
to show the first column of your model (interpreted as text)
In fact, I would not implement customizable columns the way you do. Instead, I would have, from the start, a model that contains all possible columns and their values. Then when the user choses which columns to display, I would add (or remove) cell renderers and/or GtkTreeViewColumns from the view.
One of the benefits is that you could have several views of the same model, each displaying a different set of columns.
I use a DataGridView to view and edit data in my PostgreSQL database, which works fine. I want to make it a little more user-friendly by choosing the right Control to input data. Specifically, I want to create a ComboBox in a column to let the user search and select a value fast.
To do so, I think I need the EditingControlShowing event to fill the combobox. However, the column I get is of type DataGridViewTextBoxColumn, so the corresponding e.Control is a TextBox instead of a ComboBox.
I never initialise those columns, because they come from views in the database. How can I cast/initialise the column to DataGridViewComboBoxColumn ?
This is how I populate my DataGridView:
dgView.DataSource = getView();
getView() returns a DataTable as can be gotten from NpgsqlDataAdaper.
Since you want to use something other than a DataGridViewTextBoxColumn you need to set AutoGenerateColumns to false and actually define the columns yourself. When defining them, choose a DataGridViewComboBox column for the appropriate fields.
This can be done in code, or in the designer, and based off your question I think you should be able to simply do it through the designer.
You should do it in 2 steps.
1. First of all ( as mentioned by Michael) you should set AutoGenerateColumns to false and then create the required columns using the datagrid view columns option (in properties)
2. Then you should loop through your records and assign appropriate values to appropriate columns.
In case you have hundreds of records that you are showing in grid view, than you should consider using server side paging.