Monday, March 30, 2015

Dexterity Template - record Exists

Exists
function returns boolean Exists;
in RecordID RecordID;

call Get of form RecordForm, RecordID, table RecordTable, GET;

if err(table RecordTable) = OKAY then
Exists = true;
else
Exists = false;
end if;

Get
http://makguidetosoft.blogspot.com/2015/03/dexterity-template-for-record-get.html

Dexterity Template - Clear and Delete button

Clear
release table RecordTable;

restart form;

Delete
if empty(RecordID) then
abort script;
end if;

if ask(getmsg(1300),getmsg(3),getmsg(1),"") = ASKBUTTON1 then
release table RecordTable;
RecordID of table RecordTable = RecordID;
change table RecordTable;
if err() = OKAY then
remove table RecordTable;
end if;
release table RecordTable;
restart form;
end if;

Dexterity Template - Entry window Open()


Open()
{
Called by RecordID Zoom and Record Lookup Open button
}
in RecordID RecordID;

if isopen(window RecordWindow of form RecordWindow) then
open window RecordWindow of form RecordWindow;
else
open form RecordWindow;
end if;

if not empty(RecordID) then
if changed(window RecordWindow of form RecordWindow) then
run script 'Handle Changes'of window RecordWindow of form RecordWindow;
if 'Handle Changes' of window RecordWindow of form RecordWindow then
run script 'Clear Button' of window RecordWindow of form RecordWindow;
'Temp Record ID' of window RecordWindow of form RecordWindow = RecordID;
run script 'Temp Record ID' of window RecordWindow of form RecordWindow;
focus RecordID of window RecordWindow of form RecordWindow;
end if;
else
run script 'Clear Button' of window RecordWindow of form RecordWindow;
'Temp Record ID' of window RecordWindow of form RecordWindow = RecordID;
run script 'Temp Record ID' of window RecordWindow of form RecordWindow;
focus RecordID of window RecordWindow of form RecordWindow;
end if;
end if;

Open Button on Lookup
if empty(RecordID of window RecordLookupWindow) then
warning getmsg(2154); {Please select a record first;}
    focus 'Open Button';
abort script;
end if;

call Open of form RecordForm, RecordID of window RecordLookupWindow;

Dexterity Template - Temp record ID change (what populates when a lookup returns)

{--------------------------------------------------------------------------------
Re-open the window in case it is behind a lookup;
--------------------------------------------------------------------------------}
open window RecordWindow;

{--------------------------------------------------------------------------------
Abort if the selected record is the record currently displayed;
--------------------------------------------------------------------------------}
if '(L) Temp Record ID' = RecordID then
abort script;
end if;
{--------------------------------------------------------------------------------
Handle changes, if any, to the present record;
--------------------------------------------------------------------------------}
if changed(window RecordWindow) then
run script 'Handle Changes';
if not 'Handle Changes' then
RecordID of table RecordTable = RecordID;
abort script;
end if;
else
release table RecordTable;
end if;

{--------------------------------------------------------------------------------
Display the selected record;
--------------------------------------------------------------------------------}
RecordID = '(L) Temp Record ID';
run script 'Display Existing Record';

Dexterity template - Handle Changes

local integer ask;

{1000 "Do you want to save changes?"  2  Save  4 Discard  1 Cancel}
case ask(getmsg(1000),getmsg(2),getmsg(4),getmsg(1))
in [ASKBUTTON1]
'Handle Changes' = Save(table RecordTable) of form RecordForm;
in [ASKBUTTON2]
release table RecordTable;
'Handle Changes' = true;
else
'Handle Changes' = false;
end case;

Dexterity template - Save() record

function returns boolean Saved;
inout table RecordTable;

local integer Status;

default window to RecordWindow;

if not required(window RecordWindow) then
call Warning_Dialog,2351,"","","";
Saved = false;
abort script;
end if;

release table RecordTable;
RecordID of table RecordTable = RecordID;
change table RecordTable by number 1;

copy from window RecordWindow to table RecordTable;

save table RecordTable;

Status = err();

if Status = OKAY then
Saved = true;
abort script;
end if;

Saved = false;

case Status
 in [RECORDCHANGED]
  call Warning_Dialog,5202,"","","";
clear changes window RecordWindow;
run script RecordID;
focus 'Save Button';
 in [DUPLICATE]
  call Warning_Dialog,5283,"","","";
unlock RecordID;
focus RecordID;
end case;

Dexterity template for Display Existing Record

if empty(RecordID) then
abort script;
end if;

call Get of form RecordForm, RecordID, table RecordTable, CHG;

if err(table RecordTable) <> MISSING then
'Display Existing Record' = true;
copy from table RecordTable to window RecordWindow;
clear changes form RecordForm;
else
'Display Existing Record' = false;
clear table RecordTable;
end if;

lock RecordID;
focus RecordDescription;

Dexterity template for record Get

<RecordForm>.Get procedure

in 'Record ID' 'Record ID';
inout table RecordTable;
optional in integer Op = GET;

release table RecordTable;
clear table RecordTable;
'Record ID'of table RecordTable= 'Record ID';

case Op
in [GET]
get table RecordTableby number 1;
in [CHG]
change table RecordTable by number 1;
end case;

Friday, March 27, 2015

Dexterity template for filling a user-defined drop down list

{
Assumptions
Window = Maintenance
Type = the drop down list
TypeName = a string
Type_MSTR = table with the user-defined type strings

}

local TypeName curChoice; {Save the currently selected value in case the user added more values to the table after choosing an option in the dropdown}

default window to Maintenance;

if not empty(Type) then
curChoice = itemname(Type, Type);
end if;

clear Type;

call SetRangeTypeMSTR of form TypeAPI, table Type_MSTR;
get first table Type_MSTR by number 1;
while err() = OKAY do

add item TypeName of table Type_MSTR to field Type;

get next table Type_MSTR by number 1;
end while;

Type = finddata(Type, curChoice);
if empty(Type) and countitems(Type) > 0 then
Type = 1;
end if;

Thursday, March 26, 2015

Duolingo

The latest update removed the heart system, which is awesome. That should save me some time when I do lessons

Strange behavior with locked/disabled fields when using restart window

I have a button that I click that disables other fields, processes something, and then enables the other fields again. This is legacy code, and for some reason it's restarting the window in the middle of processing. The strange behavior is that if i click on a field when it appears to be disabled, the field change script STILL RUNS!

From this, I can infer how GP processes commands. It must use a stack, and pushes down commands on the stack, and then processes them in order that they are popped. Restarting a window must be re-enabling fields without appearing like it's doing that, which is why it takes the button click event and pushes it on the stack.

For example:
1. Click processing button - disable back button and click it.
2. In the middle of processing restart the window
3. Renable the back button

The stack looks like this:
win pre script
back button click

because the back button click happened first, it gets pushed on the stack.

Wednesday, March 25, 2015

Cross dictionary form "return to" doesn't work - here's a workaround

Problem
I have a lookup in dictionary 1, and want to use that lookup in dictionary 2. Unfortunately when i use the standard "return" it's returning nothing to dic 2's window. Apparently cross dic "return to" doesn't work. I could duplicate the lookup and transfer it to dic 2, but that violates the DRY principle (do not repeat yourself).

Solution
1. Add a procedure in the lookup called CrossDicOpen
    Pass in the fully qualified field to return to, and the dictionary ID.
    For example:
    Fully Qualified Return To Field = 'Schedule ID' of window Schedules of form Schedules;
    Dictionary ID = 2

2. Add fully qualified field and dictionary ID fields to the lookup window as hidden fields

3. When select is chosen check if it was opened by another dictionary. If it is then do a cross dic call to that dictionary, setting Fully Qualified Return To Field = the selected record, and running that field's change script.

Note: Make sure to escape any quote marks.

Tuesday, March 24, 2015

SqlBulkCopy inserting dates wrong

Problem
I'm adding stuff to a DataTable and then using SqlBulkCopy to bulk insert it. One of the things is a DateTime field. Specifically, I am putting in new DateTime(1900, 1, 1, date.Hour, date.Minute, 0); The problem is when I bulk copy this it ends up in the database as 1/1/2000 instead of 1/1/1900.

Solution
When initializing the DataTable i simply had to specify the type of the datetime.
dataTable.Columns.Add("JustTheTime", typeof(DateTime));

After explicitly specifying the type it's now inserting 1/1/1900 as expected.

Date and time check constraints in Dexterity

Problem
I'm using C# to insert date/time fields into a Dexterity-generated table, and i'm getting the following error:

The INSERT statement conflicted with the CHECK constraint "CK__<tablename>__Starti__027755D3". The conflict occurred in database "GPDAT", table "dbo.<tablename>", column 'StartingDate'.

Solution
Date constraint = time must be 00:00:00
Time constraint = date must be 1/1/1900

DateTime is immutable, so the way to zero out these values is to make a new DateTime object using the constructor DateTime(year, month, day, hour, minute, second), passing in the year/month/date from your DateTime object, and then 0's for hour/minute/second, or the opposite of that if it's going into a time field.

Dexterity - ExecuteNonQuery passthrough SQL template

{
Clean Code approach to passthrough SQL
}
inout text SQL;

pragma(disable warning LiteralStringUsed);
local long SQLConnection;

debug SQL;

if SQL_Connect(SQLConnection) <> OKAY  then
throw SQL_EXCEPTION, 0, "Connection to SQL has not been established.";
end if;

if SQL_Execute(SQLConnection, SQL) <> OKAY then
call SQLErrorHandler, SQLConnection;
end if;

SQL_Terminate(SQLConnection);

pragma(enable warning LiteralStringUsed);

Saturday, March 21, 2015

Learning strategy

I just finished a book on algorithms and am now looking for my next read. Before reading the Pragmatic Programmer book I didn't really have any plans for improving as a programmer, and just did projects and learned specific things when I needed them for a project. Since reading that, a little over a year ago now, I've read quite a few software development books. My strategy is to read one books at a time at different levels:
1. High level - for example, project management, Joel on Software 
2. Design level - for example, refactoring, design patterns
3. Code level - for example, algorithms

In college I was mainly taught at the coding level. That's great, because it gave me a strong foundation. The other two levels are what I was lacking. Since reading books in those levels I have found myself improving as a programmer. I believe a strong coding foundation is vital, and the other two levels are what key to becoming a great programmer.

So now that I've finished a book in the coding level I'm going to head back up to the high level, perhaps something on agile methodology.


Tuesday, March 17, 2015

Intellisense stops working in VS2013

Solution
1. Right-click the solution and choose Open Folder Location (or whatever)
2. Close VS
3. In the solution folder delete the .suo file. (Note: I had two different ones)
4. Reopen VS

Reference
http://stackoverflow.com/a/23983883/1538717 
There was an error in this gadget