Link to home
Start Free TrialLog in
Avatar of cafulford
cafulfordFlag for United States of America

asked on

Visual Foxpro 6 Forms Question

Hi, I am not a developer but have some experience in COBOL (yes, I am aging myself) and I have been asked to add a new form to an existing FVP 6 application. The scenario is:

I have a dbf table (label.dbf) and I need to:
1) have the user enter a delivery #
2) display a few fields for that delivery # in read only mode
3) allow them to enter a Fedex tracking # in 1 of the existing fields in the table. That 1 Fedex # will need to be entered only once and will apply to all the records that apply to that delivery #.

I know this can't be done with the forms wizard and will require some programming. Can anyone lay out a step by step way that I can do this. The more detailed the better as I am totally in the dark with the language and approach.

Thank you in advance!
Avatar of Pavel Celba
Pavel Celba
Flag of Czechia image

You may look at "Creating a Form Manually" video from http://www.garfieldhudson.com/FreeVideos.aspx it will tell much more than any written instructions. Other videos are also helpful.

If you prefer written instructions the you may look at http://www.hentzenwerke.com.

And you could start with some FoxPro course or visit some FoxPro event near to your home (http://fox.wikis.com/wc.dll?Wiki~UpcomingEvents).
Avatar of jrbbldr
jrbbldr

"I know this can't be done with the forms wizard"

Even if it could be done with the Wizard, I'd recommend that you NOT do it that way.  

First, it totally eliminates your learning opportunity in how to bring a working form together through your own development.

Secondly it would result in a Form which has those classes, properties, & behavior which the Wizard wanted to give you, not necessarily those features and functionality that YOU wanted.

In your VFP6 development mode you will CREATE a FORM.
Rather than trying to lay out a step by  step approach, let me point you to a set of free tutorial videos which might be of some assistance to you.
         http://www.garfieldhudson.com/FreeVideos.aspx

I'd recommend that you look over:   Creating a Form Manually
and the various videos related to:    Building a Simple Application

If, after reviewing those videos, you have other specific questions please feel free to come back with questions on your specific topics.

Good Luck

It looks like Pavel (pcelba) hit his Submit button with some of the same advice as mine just before I did.    

Regardless - Good Luck
I would say to update Form Wizard output is possible and fast way but if the application exists already then you should use the same framework or same style at least.

The easiest way is to open some existing (and similar) form and save it under different name then you may start to change its code and input controls. You'll learn how app forms are built and the update should be like a game for you :-). The first step is to change the table name for which the form is designed and replace it by your own name. Or you should work on a separate copy of your database.

When you save some existing form under a new name then you may double click on it and the method code will appear. You should go throught this code and try to understand it first. You may preview all methods incorporated in the form and its controls when you click PgDn multiple times.

You may also look at parent classes code if any.
before starting with a form I typically design data first, the gui follows quite easily. You say the fedex tracking number will apply to all records of one deliver #, then a table design with two tables related 1:n would make more sense than one label.dbf.

Also, without the structure of that table there's not much to help about. You'll have some field where to store the deliver #, that would rather go into the same table as the fedex tracking #, as that seems to be two numbers for the same thing, a delivery.

Then you talk about "all the records belonging to that delivery", which may mean some other table or labels.dbf?

Aside of the data structure, all you seem to need is some textbox controls on a form, quite easy stuff.

Like pavel said, you should rather add to the project in it's style. There are two major file types for forms, in the project manager you find forms in the documents tab or you find form classes in the Classes tab within class libraries.

It depends on this if you rather create a new form or form class. you can always mix both styles, but you rather shouldn't. Roughly we talk about procedural vs oop programming.

Bye, Olaf.
Avatar of cafulford

ASKER

Thanks all, I will take your advice and keep this thread open in case I need to ask a specific question. My problem is, I need to get it done fast and I probably will not have to use VFP again for another 6 months which makes really learning it kind of tough.

Olaf, the data file already exists so there will not be any new data files. All the fields I need already exist in that 1 file. I just need to look up 1 field (delivery #) and be able to make an entry into another field already in the file called Fedex tracking # for all the records that have that same delivery #.

Charlie
That's poor design. You'd put a primary key to another primary key. If you have a delivery # in that table multiple times that may be okay, if it's a foreign key to a delivery parent table. But then that's where the fedex tracking number would belong, too.

The first steps you could do:
Open the project file of that application, there must be some pjx file. You'll have an overview of the project files in a project manager window. In the project manager activate the documents tab and click on the forms root node, then on Add at the right side of the project manager. You're in a new form. You could at first save it with CTRL+S to the same folder other forms of the project are, at least put it to the same folder or a subfolder of the pjx, don't save it to the home foxpro folder.

Once you named it and have it on disc you now can change it and CTRL+S every now and then.

Now in the form designer you see an empty form. First step is not to put controls to it, but to setup the data source, your label.dbf. Use the menu: View->Data Environment. Now in the Add Table Or View dialog find your label.dbf
Now choose some field of the table, eg the fedex tracking number field and drag the name to the form. Then you already got a form showing the tracking number of records. Drag some more fields to the form.

Finally add some buttons: a next button to skip forward and a prev button to skip back in the table. You get the button by choosing Tools menu, Toolbox menu item, then choose VFP base classes, command button and drag that on the form. double click on the button and you get into the code editor into the click event code of the button. In there write:

If !BOF("label")
   Skip -1 in label
EndIf

ThisForm.Refresh()

Thats the prev button, skipping back one record each time you click and refreshes the form with that record. Do a second button the same way, check for EOF instead of BOF and skip 1, you get a next button. Now you can already run the form and skip through the records.

Run the form by clicking the button [!] from the standard toolbar or CTRL+E.

Bye, Olaf.
If others now tell you there is a downside on using the data environment, but that's accepable in the first place.

The disadvantage is you're kind of hard wiring the file path of the label.dbf. You later may have difficulties, when the table is somewhere else for users than during development.

But you can merely set PATH to include the dbf path and then the data environment will also find the table again, if it's somewhere else in prodcution vs. development.

Bye, Olaf.
"That's poor design. You'd put a primary key to another primary key. If you have a delivery # in that table multiple times that may be okay, if it's a foreign key to a delivery parent table. But then that's where the fedex tracking number would belong, too."

rereading it...Let me put that clearer:

You'd typically have a head / parent table for all items of a delivery, that would be a table where the delivery ID or delivery # would be defined and that's where the corresponding fedex tracking number should go, only once, only there, it's part of the head data of the delivery then. Other tables with records related to the delivery will point back by the delivery ID only, you only have one foreign key in such tables, not many, so you wouldn't add the tracking number there.

Bye, Olaf.
Yes, I see why that is poor design but I am kind of stuck with making it work. I am looking at everything now. Hopefully I will be able to make some headway today. They are really pushing me to get this done...

Charlie
You can always create a new table, as simple as CREATE TABLE. Besides that you can always do an unbound control and set trackingnumber via update-sql: UPDATE label SET trackingnum = inputvalue WHERE deliverynumber = somedeliverynumber.

Bye, Olaf.
Thanks all, I have gone over some of the videos and have an idea on what I am trying to do but not quite sure how to get there.

Olaf, I think what I am trying to do may be a little different than what you described. I annotated a screen shot as to where I am with explanations that might help you understand.

Any guidance from here will be greatly appreciated!

Charlie
Fedex-Tracking---Layout.jpg
ASKER CERTIFIED SOLUTION
Avatar of Olaf Doschke
Olaf Doschke
Flag of Germany image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Hi Olaf,

This is what I designed today from your instructions and the videos I looked at. It was not already existing. The field names are actually from the table itself.

The grid will just be a read-only display of the records that were found to have the delivery # they entered in #1. I am just showing some of the more pertinent fields from the table to the user as reference only.

I did not see a reason to show the delivery # in the grid because it will be the same for all the records displayed in the grid.

I will try what you are saying for # 1, 3 and 4 but how would I take the delivery # entered by the user in #1 and lookup all records in that table (label.dbf) that match that delivery # and display the records as read-only in #2?

I am not at work but I will add a picture of the table when I get in tomorrow morning.

Thanks so much! I really, REALLY appreciate it.

Charlie
Olaf is not here but if you will read his posts once more then you'll find it :-)

The trick how to display records for entered delivery # is :

in the command button of (1) put
SELECT label
SET FILTER TO order_num=Thisform.txtDeliv_num.Value
Locate


The missing part is just "display the records as read-only in #" but it is also easy:

The Grid has ReadOnly property. Set it to .T.


Anyway, I have to say you are progressing!

Thanks, pcelba, for chiming in.

Yes, cafulford, I already gave you that answer and also showed how to put the tracking number into all these records. If this is not working, then most probably because you don't have set the grids recordsource to the label data.

What is the recordsource and recordsourcetype of the grid?

Another way of making the grid readonly is to have data in a readonly cursor, which means the alternative I already wrote about: A Parameterizable view, which is not made updatable. Setting grid to readonly is easier of course, the view could work better as filtering the table though in regard of the performace of the filtering.

Bye, Olaf.
Hi Olaf/pcelba, I know I am getting close but a bit frustrated I'm afraid. This object oriented stuff is completely foreign to me. Here is where I am at:

Using the #'s 1-4 from my original layout I designed:

For #1:
- I used the "Forms Controls" "Text Box" to create a field called txtDeliv_num
- I used the "Forms Controls" "Command" to create the command button and labeled it "Click to lookup
  records for this delivery #"
- I double clicked the command button and entered:
  SELECT label
  SET FILTER TO order_num=Thisform.txtDeliv_num.Value
  Locate

For #2:
- I have the grid set to read-only and the record source set to "label" (record source type = 1 - Alias ??)

When I run it, the grid pre-populates with all the records from the label.dbf table before I enter my delivery #. When I do enter the delivery # and click the "Click to lookup records for this delivery #" command button, nothing changes in the grid. The only thing I can see is in the bottom left it says "End of Locate scope". The read-only is working though.

I am trying to have the grid be blank until the user enters the delivery # and presses the command button, then it will only display those records that apply to that delivery #.

I figure I will try to get that working before I tackle #3 & #4.

I also attached a pic of the data.

Help!

Charlie
Labeldotdbf-data-sample.jpg
Fedex-Tracking---LayoutGrid.jpg
You are most certainly on the right track now.

In order to control how your Grid display appears when the form is first opened you will most likely want to put some code into the Form's INIT Method -- something like:

SELECT labelSET FILTER TO order_num > 100000000  && Something which does not existGO TOPThisForm.Grid1.Refresh

The when the user enters something into your Textbox txtDeliv_num you will want some code in the individual object's WHEN method - something like

SELECT LabelSET FILTER TO order_num = ThisForm.txtDeliv_num.ValueGO TOPThisForm.Grid1.Refresh

See how close that gets you to your goal.

Good Luck





Good advice from jrbbldr, additional to that do Thisform.Refresh() after Locate to refresh the grid after the filter is applied.

If you replace the grid by dragging&dropping the label.dbf from the dataenvironment of the form (or have you already created the grid that way) You also can make sure the recordsource and recorsourcetype is already set and the headers are set to field names, you can always remove columns from the grid afterwards by putting all the columns you want left and then reducing grid.columncount. Otherwise you may not get the fields you want in the columns you defined, setting recordsource and recordsourcetype only the grid displays all fields in the order they are in the table.

And yes, "alias" as recordsourcetype is okay, "table" would be perfect as you have a table as the recordsource.

Bye, Olaf.
Well, I getting closer to getting 1 and 2 done. I got rid of the "Click to lookup records for this delivery #" command button on # 1 and have the lookup go when they press enter instead of having to add another step of clicking the button.

Olaf/jrbbldr, I did the init for the form to something that does not exist and I added the select to the when event but when I run the form, the prompt does not appear anywhere on the screen (or in the enter delivery # where it should be). Everything is blank including the grid now but when I click on the enter delivery screen prompt, the grid fills in with all the records until I enter something, then it is fine.

Is there a way to have the prompt "live" and in that box when the form is run to start?

Thanks. 500 points is not nearly enough for the help and education I am getting.

Charlie
I have difficulties understanding what you mean by prompt. The cursor?
The first and active control on a form is defined by the tab order of the form. When editing the form choose View->Tab Order -> Assign by List and in the Tab ORder dialog click "by row". That should put the textbox #1 first.

You could also modify the code in the textbox when to go to an unexsiting delivery #, when the textbox value is empty, so users deleting the delivery # or not entering one will stay with an empty grid.

Bye, Olaf.
I am stumped. I have gone over everything multiple times, changed a couple of things I saw (order_num should really be deliv_num) and now I get a blank screen and when I enter a delivery #, nothing happens except the cursor moves to #3 and nothing shows up in grid.

After 1 1/2 days on a simple little application, I feel like pulling what ever little hair I have left out. I have attached the code and a sample database file. Can one of you look at it and tell me what I am doing wrong? I renamed all the files to .txt and before the dot put "_ext" so you know what to rename them as.

Thanks again...

Charlie
fedextracknbrs-SCT.zip
I know jrbbldr suggested the when event, but perhaps he meant the valid event. The when event occurs when you enter a control, not when you leave it.

I modified the code of the txt_Search_deliv.when() event and moved it to the valid() event. If you do that it works.


Select Label

If NOT Empty(ThisForm.txt_Search_deliv.value)
	Set Filter To deliv_num = AllTrim(ThisForm.txt_Search_deliv.value)
	OneDeliverySelected = "Y"
	Locate
Else
	Set Filter To deliv_num = "X"
	OneDeliverySelected = "N"
	Go Top
Endif
ThisForm.grdLabel.refresh()

Open in new window

Olaf - you are correct - OOPs.   Yes it should have been the Valid Method

cafulford - "I did the init for the form to something that does not exist"

The Form Grid needs to have its RecordSource defined and present BEFORE the INIT Method.    Based on the sequence of Form Method execution, I do this work in the Form's LOAD method.   If the Grid's RecordSource does not exist by this point in time, it will most often either crash the form entirely or it will dramatically mis-behave.

If the Grid's RecordSource exists by the end of the LOAD method execution, then the INIT method can do what it needs to do to 'initialize' the display in the Grid.   This work could have been done in the LOAD method but I often re-initialize my forms when one operation has been fully completed by issuing a ThisForm.Init -- restoring the form to its default values and the 'initialization' code being there makes it convenient.

Good Luck
"The Form Grid needs to have its RecordSource defined and present BEFORE the INIT Method."

Let me elaborate a bit about this a little bit...

In your Form's LOAD method you should have something like:

   USE Label IN 0

Or if you create the data table Label from a SQL Query of other tables

  SELECT <whatever fields>     FROM <whatever table or tables(s)>     WHERE <whatever critieria>     INTO TABLE Label

You will have already defined the Grid.RecordSource when you created the Form so that does not need to be done again, but by making the data table Label available in the Load method your Grid's RecordSource is present when it is displayed.

The INIT Method's setting the Filter on the Label data table is done merely to initialize how the table Label will appear in the Grid at the beginning.

Good Luck

The Form has Data environment already, so no need to change anything in Init or Load.

I would just recommend to change Grid.RecordSourceType to 1 - Alias.

BTW, the Grid object does not exist yet when Form.Load event fires.
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Nothing is easy I guess...jrbbbldr, I added the modified code to the forms INIT method and now I am getting a "Variable NLENDELIVNUM is not found" error when I run the form.

The exact code in the forms INIT method is:

* --- Initialize The Text Box Value To All Spaces ---
SELECT Label
nLenDelivNum = LEN(Label.Deliv_Num)
ThisForm.txt_Search_deliv.Value = SPACE(nLenDelivNum)

* --- Set Filter on Label To Some 'Impossible' Value ---
* --- So As To Initially Blank The Grid Display ---
SELECT Label
SET FILTER TO Deliv_num = REPLICATE('9',nLenDelivNum)
GO TOP
ThisForm.grdLabel.Refresh

I did some digging before I sent this to see if it was something simple and all I found was "Public variable" and "Private variable" that seem like they could define working variables and tried but that did nto work either...

I also added the "USE Label IN 0" on the forms LOAD method and when I ran it, it said the table was already in use so I removed that.

Thanks everyone again, it will be nice to stay employed but this is a challenging new concept for me...
Because the field is always 10 characters long, I changed it to:

* --- Set Filter on Label To Some 'Impossible' Value ---
* --- So As To Initially Blank The Grid Display ---
SELECT Label
SET FILTER TO Deliv_num = REPLICATE('9',10)
GO TOP
ThisForm.grdLabel.Refresh

so I think I am all set on this one...
The question is why didn't you just follow Olaf's instructions from answer ID:33631551 with my update from ID:33632180.  The result is correct and tested.

Jrbbldr is programming but not testing :-)   (and I am just lurking here)

OK, to be more constructive, the bug fix follows (the problem is nLenDelivNum variable scope in SET FILTER)
Modified code in Form INIT Method
  * --- Initialize The Text Box Value To All Spaces ---
  SELECT Label
  ThisForm.txt_Search_deliv.Value = SPACE(FSIZE("Deliv_Num", "Label"))

  * --- Set Filter on Label To Some 'Impossible' Value ---
  * --- So As To Initially Blank The Grid Display ---
  SELECT Label
  SET FILTER TO Deliv_num = REPLICATE('9',FSIZE("Deliv_Num", "Label"))
  GO TOP
  ThisForm.grdLabel.Refresh

Open in new window

Yes, the constant value 10 is also correct for the moment.
pcelba, I am definitely not trying to take anyone's input over another. You all have been absolutely great and helpful. What would take any of you 15 minutes to do has taken me 3 days to do but without your help would have taken me 2 weeks if I didn't quit first.

I apologize if I offended you or anyone else. I am just a little overwhelmed but trying to learn from all of you. I had gone through these 1 by 1 tinkering and testing and then tried each thing even though the previous worked. I am trying to not just get it to work but also figure out why from each post from all of you and his was the last one...
No, everything is OK. We are not feeling offended. Maybe I am too offensive sometimes.

You have great time to test what FoxPro can do and we have perfect picture how difficult is to learn FoxPro in its today's complexity.

And please do not award any point to me for this question because Olaf and jrbbldr did the job for you.

BTW, did you try to trace your form?  When you place SET STEP ON command to the form Init method then you may see what's happening inside. (Don't forget to remove this command before release.)
Thanks for the trace. I will try that.

One last question to you all, then I think I am finally done with this 1st one:

It all works including the update but what I think I would like to do is add a step 5 which would simple be:

(5)  QUIT    or     ENTER ANOTHER TRACKING #

To Quit, would I to "ThisForm.Release" under the click event of that command button?

and

Not quite sure how to clear the form to start over like they just entered it for the 1st time...

Thanks!

Charlie
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
You may change nothing, the form will work by entering another tracknumber over the previous one, the filter will change and apply the new tracking number. Why not try it? You may clear both the delivery number and the tracking number after the tracking number has been applied. I would perhaps neither empty the tracking number nor the delivery number, as users might process a series of numbers, both delivery and tracking numbers.

If you put an index on the tracking number field (INDEX ON TrackNo Tag Trackno, just needed once on the command window for example, not in the form) you could peek, if the tracking number entered is already bound to another delivery number by checking

If IndexSeek(ThisForm.txtMode_num.Value,.F.,"label","TrackNo")
   MessageBox("The Tracking number entered already is given to another delivery",0,"Application Name")
Else
   * Update-SQL.
Endif

And yes, Thisfrom.Release() will be sufficient for a close button of the form.

Bye, Olaf.
You are all extremely helpful and I am very grateful for the help. Thank you...