I have an application with a datagrid view
This datagrid is filled by code, not binded to a datasource.
All cells are editable
when a user edits a value in a cell, then i need to perform an action
Private Sub dgView_CellValidated(sender As Object, e As DataGridViewCellEventArgs)
Handles dgView.CellValidated
Dim nColumnIndex As Integer
DIm nRowIndex as Integer
If sender.iscurrentRowDirty Then
nRowIndex = e.RowIndex
nColumnIndex = e.ColumnIndex
Call UpdateRowData(nRowIndex, nColumnIndex)
End If
End Sub
The Function UpdateRowData is called when user edits en then leave the cell, but if he goes to a cell in the same row. Then this routine will be called again when the user again goes to another cell without any editing
I want this function only called once
How are you going to know if the cell’s value had “actually” changed from its original value? And what is this “action” that is taken if the cell “was” changed? Is what I am getting at… is that it is unclear “what” this “action” is doing? If it is an extensive process that may take some time and you want to avoid this “action” if the cells value doesn’t “actually” change. Then you will need to know if a cells value actually DID change. And this will require a different event and some additional code implementation on your part.
On the other hand, if the “action” is simply to update something with the new value, then it may take MORE execution steps to actually “check” if the value has changed as opposed to simply overwriting the same values. So, if the “action” is trivial, I wouldn’t bother “checking” if the values have changed.
However, if you DID need to make sure that the user “actually” did change the cells value… Then below is a crude, yet simple, solution for this.
One possible issue is if the user clicked into a cell and edited it by “actually” typing some characters, but, after the user finishes typing characters they had ended up simply re-typing what the original value was… then “technically” from the grids perspective… THAT cells value DID change. In other words, We will need to somehow get the original value of the cell “BEFORE” the user starts to edit the cell. Then we could “compare” the new value with the original value when the user tries to leave the cell.
Fortunately, there is a grid event that should make this fairly trivial to implement. The event I suggest using is the grids CurrentCellDirtyStateChanged event. This event will actually fire TWICE. The event will fire once when the cell goes into “edit” mode. And it will fire again when the user ends the same cells edit mode.
Therefore, we can take advantage of this, however, we will need to know that WHEN the event fires… is it fired when the user “enters” the cells edit mode… OR … is the event fired because the user is “ending” the cells edit mode. I do not think the event keeps track of this internally so we need to create our own “mechanism” to be able to distinguish when the event is fired on the “begin” edit and the “end” edit.
One possible solution to make this work is to create two global variables… one a bool variable called FirstIn and a string variable called OriginalValue… When the event fires the first time to “begin” the cells edit mode, we can check the FirstIn variable. If it is true then we know the cell is “beginning” the edit of the cell… this is where we would capture the cells current value and set the OrignalValue string variable so we can “check” it against the final cells value when the user ends the cells edit mode.
Capturing the current cells value and setting FirstIn to false is all we need to do. We now wait until the user ends the cells edit mode and the event fires for the second time. When it fires the second time… again we check the FirstIn variable and in this case it is false which means the cells is ending its edit mode. Here we could check the OriginalValue with the cells current value and do your “action” if they are different. And finally setting FirstIn to true to start the whole process over.
Below is an example of what is described above.
bool FirstIn = true;
string OriginalValue;
private void dataGridView1_CurrentCellDirtyStateChanged(object sender, EventArgs e) {
if (FirstIn) {
FirstIn = false;
OriginalValue = dataGridView1.CurrentCell.Value.ToString();
}
else {
string NewValue = dataGridView1.CurrentCell.Value.ToString();
FirstIn = true;
if (OriginalValue.Equals(NewValue)) {
MessageBox.Show("NO changes were made in the cell edit");
}
else {
MessageBox.Show("YES changes were made in the cell edit");
}
}
}
Sorry but I wrote this in C#, below is a VB version.
Dim FirstIn As Boolean = True
Dim OriginalValue As String
Private Sub DataGridView1_CurrentCellDirtyStateChanged(sender As Object, e As EventArgs) Handles DataGridView1.CurrentCellDirtyStateChanged
If (FirstIn) Then
FirstIn = False
OriginalValue = DataGridView1.CurrentCell.Value.ToString()
Else
Dim NewValue As String = DataGridView1.CurrentCell.Value.ToString()
FirstIn = True
If (OriginalValue.Equals(NewValue)) Then
MessageBox.Show("NO changes were made in the cell edit")
Else
MessageBox.Show("YES changes were made in the cell edit")
End If
End If
End Sub
Related
My TableView is populated with data from a list of objects. The first column is a Boolean value.
Instead of displaying True or False in the cell, I would like to display an image if True and leave the cell empty if it's False.
This is how I populate the TableView:
colStarred.setCellValueFactory(new PropertyValueFactory<>("starred"));
colDate.setCellValueFactory(new PropertyValueFactory<>("date"));
colTime.setCellValueFactory(new PropertyValueFactory<>("time"));
I know that I need to use a custom TableCell and a CellValueFactory but I'm having a hard time wrapping my head around the documentation (I have not used Java factories in the past).
My research has lead to several answers regarding similar situations, but they all seem to deal with just displaying an image in the cell. I have been unable to find a solution for checking a boolean to determine whether an image should be displayed or not.
How do I check the starredProperty of my objects and show an image if it is True?
Thank you for all the help everyone has provided me in the past!
I'll assume the column to be a TableColumn<MyItemClass, Boolean>.
You simply create TableCells that adjust their look according to the item that gets passed to the updateItem method.
In this case we'll use a ImageView as graphic of the cell.
The following images are displayed depending on the item of the cell:
no image if the cell is empty or contains null
imageTrue if the item is true
imageFalse otherwise
You may of course use imageFalse = null for an empty cell when the item is false.
final Image imageTrue = ...
final Image imageFalse = ...
// set cellfactory
colStarred.setCellFactory(col -> new TableCell<MyItemClass, Boolean>() {
private final ImageView imageView = new ImageView();
{
// initialize ImageView + set as graphic
imageView.setFitWidth(20);
imageView.setFitHeight(20);
setGraphic(imageView);
}
#Override
protected void updateItem(Boolean item, boolean empty) {
if (empty || item == null) {
// no image for empty cells
imageView.setImage(null);
} else {
// set image for non-empty cell
imageView.setImage(item ? imageTrue : imageFalse);
}
}
});
What happens when the program is displayed is this:
The TableView creates cells needed to display the items using the cellfactories.
The TableView assigns the items to the cells. These items may be changed multiple times. Cells may also become empty after being filled. When this happens the updateItem methods of the TableCells are called.
I have a local sub that allows the user to move a row of a datagridview, triggered by a button click. The sub works fine in debugger but when it exits control is transfered to the calling form, i.e. the current form is closed. This also happens when no row is moved, i.e. when one of the abort conditions on entrance are met. Simply: exiting this sub will close the form!?!
Private Sub btnMove_Click(sender As Object, e As EventArgs) Handles btnMove.Click
Dim rowToGo As DataGridViewRow
Dim rtgIndex As Integer = 0
If (dgvAuftrag.RowCount <= 1) or (dgvAuftrag.CurrentRow Is Nothing) Then
Beep()
Exit Sub
End If
rowToGo = dgvAuftrag.CurrentRow
rtgIndex = rowToGo.Index + 1
If (rtgIndex >= dgvAuftrag.RowCount) Then rtgIndex = 0
Try
dgvAuftrag.Rows.Remove(rowToGo)
dgvAuftrag.Rows.Insert(rtgIndex, rowToGo)
Catch ex As Exception
IssueErrorMessage(ex)
End Try
End Sub
All other local subs and functions work normal, just this one behaves strange. Any ideas how to fix/avoid this bug?
This is not a solution of the problem but a functioning workaround based on the sugestion of Hans. I have introduced a global boolean var named OKtoExit which is intialized to false.
private OKtoExit as boolean = false
Then I have a new FormClose event handler that checks that var. If OKtoExit is false then e.Cancel = true and the handler exits. The regular Exit functions (Save and Quit) set OKtoExit to true, any other code leaves the values unchanged.
Private Sub Current_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
If Not exitOK Then
e.Cancel = True
Exit Sub
End If
End Sub
As I said, this is just a workaround that has the same effect as a normal functioning VB-Code. I would appreciate if somebody could present a real solution!
After many months I discovered the true reason for the problem and I must give all credits to Hans Passant: I had a button on one of the first forms that had the Dialog Result property set to Cancel. This was a really beautyful button so many other buttons in the appliaction were a copy of this first button where I just modified the label. Thus they all led to the unwanted behavior that a form was closed as soon as a user clicked one of them no matter what the label said... After months I discovered that just by chance. Thanks to Hans again, I obviously overlooked his last hint "And look at the button's DialogResult property."!
I have a DataGridView with 3 columns.
I need the first two columns remain constant, These columns always have to be the same and the user will not be able to enter a different value to these.
The values for the two first columns of the DGV are in textBox1 and textBox2.
I need that when the user is going to add a new row in the DGV the first two columns automatically fill with these constant values and set the focus to the third column.
Thanks in advance
If you don't want the user to edit the first two columns, just set their ReadOnly property to true:
this.dataGridView1.Columns[0].ReadOnly = true;
As for your other criteria, first set the following property to limit control of when a new row is added (ex. click of a button):
this.dataGridView1.AllowUserToAddRows = false;
Then create your own method to add new rows when needed:
private void AddNewRow()
{
DataGridViewRow row = new DataGridViewRow();
row.CreateCells(this.dataGridView1);
row.Cells[0].Value = this.textBox1.Text;
row.Cells[1].Value = this.textBox2.Text;
this.dataGridView1.CurrentCell = row.Cells[2];
this.dataGridView1.BeginEdit(true);
}
Technically, you could do this when AllowUserToAddRows == true by handling the DataGridView.RowsAdded event and the above code slightly modified, but you'll get an annoying behavior where as soon as you enter one character into the new row, 3rd column, another new row is added and the editing cell loses focus (probably before you actually entered anything useful).
This shouldn't be this hard... But it's late.
I am working on a simple form, and trying to delete a record from a connected DataSource while using a TableAdapter. Here is the SQL for the TableAdapter;
DELETE FROM Main WHERE (ID = ?) AND (tbl_Job_Name = ?)
Main is the table name, only two fields.
I am populating a ComboBox with this data, and I am using a Button to call the Delete() action like this;
Private Sub btnDeleteJob_Click(sender As Object, e As System.EventArgs) Handles btnDeleteJob.Click
Dim deleteJobAdapter As New DCGDataSetTableAdapters.MainTableAdapter
deleteJobAdapter.DeleteQuery(ComboBox2.SelectedIndex, ComboBox2.SelectedText)
End Sub
When I break the code I can see the ID value, but the SelectedText field is blank, and of course when it runs through the record is not deleted. I would ideally like to just pass the ID of the selected record in the ComboBox to delete the record. What am I missing?
Try,
deleteJobAdapter.DeleteQuery(ComboBox2.SelectedIndex, ComboBox2.Text)
SelectedText property:
Gets or sets the text that is selected in the editable portion of a ComboBox.
Text property:
Gets or sets the text associated with this control.
You can use the SelectedText property to retrieve or change the currently selected text in a ComboBox control. However, you should be aware that the selection can change automatically because of user interaction. For example, if you retrieve the SelectedText value in a button Click event handler, the value will be an empty string. This is because the selection is automatically cleared when the input focus moves from the combo box to the button.
When the combo box loses focus, the selection point moves to the beginning of the text and any selected text becomes unselected. In this case, getting the SelectedText property retrieves an empty string, and setting the SelectedText property adds the specified value to the beginning of the text.
When I enter the code;
deleteJobAdapter.DeleteQuery(ComboBox2.SelectedIndex, ComboBox2.Text)
My break point shows the correct text from the record, but I did notice that the ID value is incorrect, the SelectedIndex returns a sequential number of the record itself, starting with "0". So it looks like SelectedIndex does not return the actual ID value...
And back to the original issue, the record selected is still not deleted.
This is what I ended up using;
Dim delJobID = ComboBox2.SelectedValue
Dim delJobRowAdpt As New DCGDataSetTableAdapters.MainTableAdapter
Dim delJobRow As DCGDataSet.MainRow
Dim intDelete As Integer
delJobRow = DCGDataSet.Main.FindByID(delJobID)
delJobRow.Delete()
I am using data validation rules on a Google Spreadsheet.
In my scenario, I need users to entry only valid values. I use the 'Reject input' to force them to write only validated content.
However, the 'Reject input' option works for manually entried data only, but it does not work if the user pastes content into the cell from a different source (e.g. a MS Excel document). In that case, a warning is still shown but the invalid value is written.
In other words, I need the 'Reject input' option to work also with pasted content.
OR... another approach would be to programmatically check the validity of the value according the Datavalidation rule for that cell.
Any ideas?
Thank you in advance.
I had a little play with this.
I had inconsistent behavior from google.
On occasion when I ctrl-c and ctrl-p, the target cell lost its data validation!
To do this programmatically
Write myfunction(e)
Set it to run when the spreadsheet is edited, do this by Resources>Current Project's Triggers
Query e to see what has happened.
Use the following to gather parameters
var sheet = e.source.getActiveSheet();
var sheetname = sheet.getSheetName();
var a_range = sheet.getActiveRange();
var activecell = e.source.getActiveCell();
var col = activecell.getColumn();
var row = activecell.getRow();
You may wish to check a_range to make sure they have not copied and pasted multiple cells.
Find out if the edit happened in an area that you have data validated;
if (sheetname == "mySheet") {
// checking they have not just cleared the cell
if (col == # && activecell.getValue() != "") {
THIS IS WHERE YOU CHECK THE activecell.getValue() AGAINST YOUR DATA VALIDATION
AND USE
activecell.setValue("") ;
to clear the cell if you want to reject the value
}
}
The obvious problem with this is that it is essentially repeating programmatically what the data validation should already be doing.
So you have to keep two sets of validation rules synchronized. You could just delete the in sheet data validation but I find that useful for providing the user feedback. Also is the data validation you are using provides content it is practical to leave it in place.
It would be great if there was a way of detecting that ctrl-p had been used or one of the paste-special options and only run the script in those cases. I would really like to know the answer to that. Can't find anything to help you there.
Note also that if someone inserts a row, this will not trigger any data validation and the onEdit() trigger will not detect it. It only works when the sheet is edited and by this I think it means there is a content change.
onChange() should detect insertion, it is described as;
Specifies a trigger that will fire when the spreadsheet's content or
structure is changed.
I am posting another answer because this is a programmatic solution.
It has a lot of problems and is pretty slow but I am demonstrating the process not the efficiency of the code.
It is slow. It will be possible to make this run faster.
It assumes that a single cell is pasted.
It does not cater for inserting of rows or columns.
This is what I noticed
The onEdit(event) has certain properties that are accessible. I could not be sure I got a full listing and one would be appreciated. Look at the Spreadsheet Edit Events section here.
The property of interest is "e.value".
I noticed that if you typed into a cell e.value = "value types" but if you pasted or Paste->special then e.value = undefined. This is also true for if you delete a cell content, I am not sure of other cases.
This is a solution
Here is a spreadsheet and script that detects if the user has typed, pasted or deleted into a specific cell. It also detects a user select from Data validation.
Type, paste or delete into the gold cell C3 or select the dropdown green cell C4.
You will need to request access, if you can't wait just copy & paste the code, set a trigger and play with it.
Example
Code
Set the trigger onEdit() to call this or rename it to onEdit(event)
You can attach it to a blank sheet and it will write to cells(5,3) and (6,3).
function detectPaste(event) {
var sheet = event.source.getActiveSheet();
var input_type =" ";
if (event.value == undefined) { // paste has occured
var activecell = event.source.getActiveCell();
if (activecell.getValue() == "") { // either delete or paste of empty cell
sheet.getRange(5,3).setValue("What a good User you are!");
sheet.getRange(6,3).setValue(event.value);
input_type = "delete"
}
else {
activecell.setValue("");
sheet.getRange(5,3).setValue("You pasted so I removed it");
sheet.getRange(6,3).setValue(event.value);
input_type = "paste";
}
}
else { // valid input
sheet.getRange(5,3).setValue("What a good User you are!");
sheet.getRange(6,3).setValue(event.value);
input_type = "type or select";
}
SpreadsheetApp.getActiveSpreadsheet().toast('Input type = ' + input_type, 'User Input detected ', 3);
}