Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations strongm on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Grid column, textbox Method will not fire 1

Status
Not open for further replies.

Colin Burton

Programmer
Sep 28, 2021
37
GB
Hi all. I have been trying to resolve this for a couple of days

I have a grid, with 11 columns each with a text box, only one of which is enabled, on the form is a "save Changes" button" which my users want disabled until a change in the grid is made (to the enabled text box)

so, I thought I would out that in the keypress or interactive change. however, that never fires.

I believe the issue is that at times I refresh the grid recordsource (as there are also selection criteria on the form) and this does a SQL select to the backend SQL Database tables to get the matching data and display in the grid

my google search point towards using bindevents - i tried that but with no joy

After each time setting the recordsource i execute
BINDEVENT (THISFORM.Grid1.Column11.Text1, "KeyPress", THISFORM, "myKeyPress")

And the form.myKeyPress is simply
ThisForm.formContainer1.CmdSave.Enabled = .T.

I also considered using Filters after an initial query, but the number of records in the SQL Database table really says that is a bad idea

Can anyone help point me in the right direction

TIA

Colin


 
When you reconstruct the grid, you lose any code you had at designtime. The better ideaa is to keep the recordsource. Ideally a view you can requery or a cusor you can then ZAP (watch out for SAFETY and dont ZAP something you don't want to ZAP) and then append a new result. That new result can be queried into a temp cursor.

So that's one way to solve that.

The other thing is with a BINDEVENTS solution rather bind InteractiveChange than KeyPress.

And last not least working with buffering, GETNEXTMODIFIED(0,"alias") will be >0 if there is a change in the alias, so that's a way to detect whether there is something to save. So one other simple solution is to create a timer that checks this and sets the cmdSave.Enabled accordingly.

Chriss
 
Colin Burton said:
my users want disabled until a change in the grid is made

My users always wanted an autosave, especially the close button should also save. Not only give notice there is something to save or aks whether changes should be discarded, simply save. But that's your users choice. But perhaps it's worth considering to them what are more viable options. An automatic save is easily done as you can use the GETNEXTMODIFIED to find out which aliases have changes.

And independtly from what you decide about the save button - the right choice of detecting changes is not by waiting for control usage, in your special case there only is one column of the grid that's editable, but think more general of an edit form, you don't want to have code in any control that en/disables a save button. That's not even a good solution if you put this in controlclasses.

A general pattern for any signal - not only modified records - would be settnig a flag property - say "form.datachanged" or "form.languagesetting" or whatever, and having an assign method that knows what to do when this flag changes.

Chriss
 
Chris, Thans very much for your swift response.

I tried setting the bind events on Interactivechange, but that unfortunatly did not work either

I also then tried your GETNEXTMODIFIED(0,"alias") ( changing alias to my cursor name of CurReturns - also did not have any effect

But it did point me in the right direction

in my cursor i store the original and current value of this field in question, so in my timer i perform this ...


SELECT curreturns
lnRecord = RECNO()
llChanged = .f.
ThisForm.formContainer1.CmdSave.Enabled = .F.
SCAN
IF ALLTRIM(Mersonarecd) <> ALLTRIM(OrigMersonaRecd)
llChanged = .T.
Endif
endscan
IF llChanged
ThisForm.formContainer1.CmdSave.Enabled = .T.
Endif
GO lnRecord

And that then nicely switched between the enabled and disabled states of the save changes command button
(timer fires every 10 milliseconds) so it is pretty much instant.

i'll keep an eye on performance when in use and vary timer as required if my cursor has too many records for the scan

I guess i could also index on the two fields and do a seek for where they are different

only actual useage will give me the way to go here




 
A user won't notice 100 ms. only 10% events. Don't make this too tight, even 250ms is good.

As said GetNextModified has buffering as requirement.

I wonder what's not working with bindevents, might come up with an example on how to work with it later, not much time on hands right now.

Chriss
 
Just an alternative that does what your code does shorter:

Code:
SELECT curreturns
lnRecord = RECNO()
Locate FOR Mersonarecd <> OrigMersonaRecd
ThisForm.formContainer1.CmdSave.Enabled = Found()
GO lnRecord

Innstead of SCAN all, you could SCAN FOR, but then it's enough one record changed, so a LOCATE is all you need
There's no eason to ALLTRIM, when both fields are same char size, then a normal comparison will be sufficient, no matter if EXACT is on or off.
For sake of clear intention you could store FOUND() into a variable changed. Because finding a difference means finding a change or having a change.

Chriss
 
Chris, yes i did some performance teting and changed it to exactly that !
 
NOw Im back and have a bit time.

That check could be made even one step better in not needing to move the reordpoiinter, by using INDEXSEEK() with the optional parameter to not move the record pointer. That just needs a very special indexx you can check, namely an index on the expression Mersonarecd <> OrigMersonaRecd. And it's questionable to establish such an index.

Wh is record pointer movement such an issue, that you could think about avoiding it, even for a small price? Bacuse - again- of buffering. Leaving the current rrecord in row buffering mode will save it, and that can cause probems, if its not yet eady to save, for example a foreign key does ot point to a record and triggers or table rules can be violated.

INDEXSEEK() is worth considering to check if something that needs to be unique is already in the data, in a list of categories, for example. Then you can reject a record without needing to wait for the index violation to occur if you try to save, simply by INDEXSEEK of an enterred category you can tell the user it already exists instead of seeing "too late" when rying to save it. That would also make use of that special feature of INDEXSEEK to rally just seek in the index without also locating the record fitting that index key.

I'll now make up a Bindevents example that would work on the basis of the grid textbox interactivechange. Maby your problem is you do this in init, and don't realize when the object is removed and then rebuilt due to the sql query reconstructing the grid, the binding also is gone, so you need bindevents after yyou change or refresh/reset the recordsourcee because you effectivelyy have a CoumnX.Texxt1 control and the old binding is gone.

Chriss
 
Here's a demo of bindevents working:

Code:
PUBLIC oform1

oform1=NEWOBJECT("form1")
oform1.Show
RETURN


	**************************************************
*-- Form:         form1 (c:\programming\tek-tips\editgrid.scx)
*-- ParentClass:  form
*-- BaseClass:    form
*-- Time Stamp:   11/25/22 06:20:07 PM
*
DEFINE CLASS form1 AS form

	Top = 0
	Left = 0
	Height = 439
	Width = 303
	DoCreate = .T.
	Caption = "Form1"
	categoryid = 0
	Name = "Form1"


	ADD OBJECT cboCategories AS combobox WITH ;
		BoundColumn = 2, ;
		ColumnWidths = "100,0", ;
		RowSourceType = 6, ;
		RowSource = "Categories.categoryname,categoryid", ;
		ControlSource = "Thisform.CategoryID", ;
		Height = 24, ;
		Left = 144, ;
		Style = 2, ;
		Top = 12, ;
		Width = 100, ;
		Name = "cboCategories"


	ADD OBJECT label1 AS label WITH ;
		BackStyle = 0, ;
		Caption = "Product categories", ;
		Height = 17, ;
		Left = 12, ;
		Top = 17, ;
		Width = 132, ;
		Name = "Label1"


	ADD OBJECT cmdsave AS commandbutton WITH ;
		Top = 384, ;
		Left = 192, ;
		Height = 27, ;
		Width = 84, ;
		Caption = "Save", ;
		Enabled = .F., ;
		Name = "cmdSave"


	ADD OBJECT grdProducts AS grid WITH ;
		ColumnCount = 2, ;
		Height = 312, ;
		Left = 12, ;
		Panel = 1, ;
		RecordSource = "curGrid", ;
		RecordSourceType = 1, ;
		Top = 60, ;
		Width = 264, ;
		Name = "grdProducts", ;
		Column1.ControlSource = "curGrid.productname", ;
		Column1.Width = 115, ;
		Column1.ReadOnly = .T., ;
		Column1.Visible = .T., ;
		Column1.BackColor = RGB(224,224,224), ;
		Column1.Name = "Column1", ;
		Column2.ControlSource = "curGrid.unitprice", ;
		Column2.Visible = .T., ;
		Column2.Name = "Column2"

	PROCEDURE datachanged
		thisform.cmdSave.Enabled = .t.
	ENDPROC


	PROCEDURE Init
		BindEvent(Thisform.grdProducts.Column2.Text1,"InteractiveChange",Thisform,"DataChanged")
	ENDPROC


	PROCEDURE Load
		Close DATABASES all
		Open Database (Home()+"Samples\Northwind\Northwind.dbc")
		Use northwind!products In 0 
		Use northwind!categories In 0

		Select productname, unitprice from products where .f. into Cursor curGrid readwrite
	ENDPROC


	PROCEDURE cbocategories.Valid
		Select productname, unitprice from products where categoryid = Thisform.categoryid into Cursor curTemp nofilter

		Select curGrid
		Set Safety off
		Zap
		Set Safety on
		Append From Dbf("curTemp")
		Go top
		Use in curTemp

		thisform.grdProducts.SetFocus()
	ENDPROC


	PROCEDURE cmdsave.Click
		MessageBox("saving")
		This.Enabled =.f.
	ENDPROC


*!*		PROCEDURE grdProducts.Column2.Text1.When
*!*			Return .f.
*!*		ENDPROC


ENDDEFINE
*
*-- EndDefine: form1
**************************************************

The essential part is this grid does not reconstruct, as the grid cursor used will be zapped and refilled instead of being reconstructted by a query, which in turn reconstructs the grid. Grid reconstruction means tearing down all inner object, i.e. the columns and interior objects and rebuilding them. This is done in the brief moment the SQL closes the previous result and creates the new on. And that happens, even if you use the same cursorname.

If instead you have a view and requeryy it this won't happpen, as the view cursor persists and is refilled just like this demo code zaps the grid cursor and appends the sql result into it.

If the grid reconstructs, especially as it first is torn down, the binding you do initially ends, bindevents is automatically unbinding if an invollved object of the event is destroeyd. And though there will again be a column11,tet1 box, it''s a new one. The initial binding is not revived when the same object name gets into existence again, the binding is to an ibject not to the object name.


Chriss
 
Sorry i havent responded before, but was not at work. back this morning so i am going to look into your binding solution

As said initially the recordsource is refreshed at times, and when i tried my original bindevents, it was done after every r3eset of the grid.recordsource so my understanding is that it "should" have worked !

Colin
 
This works, too.

Code:
PUBLIC oform1

oform1=NEWOBJECT("form1")
oform1.Show
RETURN


	**************************************************
*-- Form:         form1 (c:\programming\tek-tips\editgrid.scx)
*-- ParentClass:  form
*-- BaseClass:    form
*-- Time Stamp:   11/28/22 12:28:06 PM
*
DEFINE CLASS form1 AS form


	Top = 0
	Left = 0
	Height = 439
	Width = 303
	DoCreate = .T.
	Caption = "Form1"
	categoryid = 0
	Name = "Form1"


	ADD OBJECT cbocategories AS combobox WITH ;
		BoundColumn = 2, ;
		ColumnWidths = "100,0", ;
		RowSourceType = 6, ;
		RowSource = "Categories.categoryname,categoryid", ;
		ControlSource = "Thisform.CategoryID", ;
		Height = 24, ;
		Left = 144, ;
		Style = 2, ;
		Top = 12, ;
		Width = 100, ;
		Name = "cboCategories"


	ADD OBJECT label1 AS label WITH ;
		BackStyle = 0, ;
		Caption = "Product categories", ;
		Height = 17, ;
		Left = 12, ;
		Top = 17, ;
		Width = 132, ;
		Name = "Label1"


	ADD OBJECT cmdsave AS commandbutton WITH ;
		Top = 384, ;
		Left = 192, ;
		Height = 27, ;
		Width = 84, ;
		Caption = "Save", ;
		Enabled = .F., ;
		Name = "cmdSave"


	ADD OBJECT grdproducts AS grid WITH ;
		ColumnCount = 2, ;
		Height = 312, ;
		Left = 12, ;
		Panel = 1, ;
		RecordSource = "curGrid", ;
		RecordSourceType = 1, ;
		Top = 60, ;
		Width = 264, ;
		Name = "grdProducts", ;
		Column1.ControlSource = "curGrid.productname", ;
		Column1.Width = 115, ;
		Column1.ReadOnly = .T., ;
		Column1.Visible = .T., ;
		Column1.BackColor = RGB(224,224,224), ;
		Column1.Name = "Column1", ;
		Column2.ControlSource = "curGrid.unitprice", ;
		Column2.Visible = .T., ;
		Column2.Name = "Column2"


	PROCEDURE datachanged
		thisform.cmdSave.Enabled = .t.
	ENDPROC


	PROCEDURE Load
		Close DATABASES all
		Open Database (Home()+"Samples\Northwind\Northwind.dbc")
		Use northwind!products In 0 
		Use northwind!categories In 0

		Select productname, unitprice from products where .f. into Cursor curGrid readwrite
	ENDPROC


	PROCEDURE cbocategories.Valid
		With Thisform.grdProducts
		    
           * avoid partial grid deconstruction (non-ideal method)
		   .RecordSource=""
		   Select productname, unitprice From products Where categoryid = Thisform.categoryid Into Cursor curGrid Readwrite
		   .RecordSource="curGrid"

           * "Safe Select" technique - keep curGrid and ZAP/APPEND
		   *!*	Select curGrid
		   *!*	Set Safety off
		   *!*	Zap
		   *!*	Set Safety on
		   *!*	Append From Dbf("curTemp")
		   *!*	Go top
		   *!*	Use in curTemp
		   
		   * Bindevent after each refresh of recordsource
		   Bindevent(.Column2.Text1,"Interactivechange",Thisform,"datachanged")
		   .SetFocus()
		Endwith
	ENDPROC


	PROCEDURE cmdsave.Click
		MessageBox("saving")
		This.Enabled =.f.
	ENDPROC


ENDDEFINE
*
*-- EndDefine: form1
**************************************************

Chriss
 
Hi,

... or if you don't want to work with BINDEVENT you may add to your column a UDF TextBox which in its InterActiveChange triggers the state of the save button (see modified code from Chriss below)

Code:
PUBLIC oform1

oform1=NEWOBJECT("form1")
oform1.Show


RETURN


	**************************************************
*-- Form:         form1 (c:\programming\tek-tips\editgrid.scx)
*-- ParentClass:  form
*-- BaseClass:    form
*-- Time Stamp:   11/28/22 12:28:06 PM
*
DEFINE CLASS form1 AS form


	Top = 0
	Left = 0
	Height = 439
	Width = 303
	DoCreate = .T.
	Caption = "Form1"
	categoryid = 0
	Name = "Form1"


	ADD OBJECT cbocategories AS combobox WITH ;
		BoundColumn = 2, ;
		ColumnWidths = "100,0", ;
		RowSourceType = 6, ;
		RowSource = "Categories.categoryname,categoryid", ;
		ControlSource = "Thisform.CategoryID", ;
		Height = 24, ;
		Left = 144, ;
		Style = 2, ;
		Top = 12, ;
		Width = 100, ;
		Name = "cboCategories"


	ADD OBJECT label1 AS label WITH ;
		BackStyle = 0, ;
		Caption = "Product categories", ;
		Height = 17, ;
		Left = 12, ;
		Top = 17, ;
		Width = 132, ;
		Name = "Label1"


	ADD OBJECT cmdsave AS commandbutton WITH ;
		Top = 384, ;
		Left = 192, ;
		Height = 27, ;
		Width = 84, ;
		Caption = "Save", ;
		Enabled = .F., ;
		Name = "cmdSave"


	ADD OBJECT grdproducts AS grid WITH ;
		ColumnCount = 2, ;
		Height = 312, ;
		Left = 12, ;
		Panel = 1, ;
		RecordSource = "curGrid", ;
		RecordSourceType = 1, ;
		Top = 60, ;
		Width = 264, ;
		Name = "grdProducts", ;
		Column1.ControlSource = "curGrid.productname", ;
		Column1.Width = 115, ;
		Column1.ReadOnly = .T., ;
		Column1.Visible = .T., ;
		Column1.BackColor = RGB(224,224,224), ;
		Column1.Name = "Column1"
		
			PROCEDURE grdProducts.Init()
				WITH this.Column2
					.NewObject("txtPrice","txtIACTBox")
					.CurrentControl = "txtPrice"
					.txtPrice.Visible = .T.
					.Sparse = .F.
					.ControlSource = "curGrid.unitprice"
				ENDWITH 
			ENDPROC
			
*!*		PROCEDURE datachanged
*!*			thisform.cmdSave.Enabled = .t.
*!*		ENDPROC

	PROCEDURE Load
		Close DATABASES all
		Open Database (Home()+"Samples\Northwind\Northwind.dbc")
		Use northwind!products In 0 
		Use northwind!categories In 0

		Select productname, unitprice from products where .f. into Cursor curGrid readwrite
	ENDPROC


	PROCEDURE cbocategories.Valid
		With Thisform.grdProducts
		    
*!*	avoid partial grid deconstruction (non-ideal method)

		   .RecordSource=""
		   
		   Select productname, unitprice From products Where categoryid = Thisform.categoryid Into Cursor curGrid Readwrite
		   
		   .RecordSource="curGrid"

*!*	"Safe Select" technique - keep curGrid and ZAP/APPEND
		   *!*	Select curGrid
		   *!*	Set Safety off
		   *!*	Zap
		   *!*	Set Safety on
		   *!*	Append From Dbf("curTemp")
		   *!*	Go top
		   *!*	Use in curTemp
		   
*!*	Bindevent after each refresh of recordsource
*!*			   Bindevent(.Column2.Text1,"Interactivechange",Thisform,"datachanged")

		   .SetFocus()
		Endwith
	ENDPROC


	PROCEDURE cmdsave.Click
		MessageBox("saving")
		This.Enabled =.f.
	ENDPROC


ENDDEFINE
*
*-- EndDefine: form1
************************************************** 

DEFINE CLASS txtIACTBox as TextBox
	PROCEDURE InteractiveChange()
		ThisForm.cmdSave.Enabled = .T.
	ENDPROC
ENDDEFINE 

*************************************************

hth

MarK
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top