Normally, C/SIDE does a pretty good job at shielding you from the nitty gritty details of memory (de)allocation, calling conventions and pointer arithmetic. Reference parameters are one of the rare places where the curtain is lifted, allowing programmers a peek at the inner workings of the run-time.

Despite the fact that this is pretty standard behaviour in most programming languages, I remember being (temporarily) surprised when I first encountered a situation like the one below.

image

Since the Text parameter in function ByReference gets passed as a pointer, its formal definition (with a length of 30 characters) is basically irrelevant: the actual parameter LongText (with a value that is currently 40 characters long) defines how many characters it can hold: no overflow error there.

Note that this even applies if the ByReference function would assign a new value to the Text parameter: if LongText in OnRun is sufficiently large, Text can easily exceed the 30 character limitation of the function parameter.

This is the first entry in (what will hopefully become) a series of posts on common C/SIDE application patterns.

Intent
Differentiate between records created by the system and records created by users.

Motivation
In scenarios where the system allows users to add records to a (typically read-only) prepopulated record set, it might be important to differentiate between system-created and user-created records. Normally, users can delete user-created records, but not system-created ones. Also, in response to changes elsewhere in the system, the system may decide to recreate its system-defined records, without affecting their user-created counterparts.

Implementation
A non-editable option field called Source is added to the table. The first (and therefore default) option is User, thus marking any user-entered entries as such. Records created by the system (i.e. in C/AL code) are explicitly given the second option value - System. Form code may decide not to allow manual deletion of system-created records. Code that responds to other changes elsewhere in the system may delete and recreate system-created entries, while leaving user-created entries alone.

The other day, we received a call from a customer complaining that the navigation pane (intermittently, seemingly depending on the active form) did not respond to right mouse clicks (normally, right-clicking the navigation pane displays a context menu, allowing users to hide the selected menu option, create a shortcut to the selected menu option, etc.). Strange…

New Picture 

In hindsight, the NAV client’s behaviour was perfectly understandable: if a (child) window is opened modally, all input goes to that window until the user closes it – no other interface elements within the same application can normally receive mouse clicks or key strokes. If a form was started from another one, using  RUNMODAL, it would effectively block the rest of NAV’s UI, including the navigation pane. Forms that were started directly from the navigation pane, or from the application using something equivalent to FORM.RUN, did not prevent user interaction with the navigation pane. Problem solved! :-)

If your attempts to install custom controls in NAV2009SP1 on Windows 7 are met with an annoyingly persistent error message, you may want to try and run NAV as administrator.

Run as Admin

// VAR
//   MyCode : Code[6];
//   MyText : Text[6];

MyCode[3] := ‘A’;
MyText[3] := ‘A’;

MESSAGE(’MyCode=%1′, MyCode); // empty string 
MESSAGE(’MyText=%1′, MyText); // empty string

MyCode := PADSTR(”, MAXSTRLEN(MyCode));
MyText := PADSTR(”, MAXSTRLEN(MyText));

MyCode[3] := ‘A’;
MyText[3] := ‘A’;

MESSAGE(’MyCode=%1′, MyCode); // empty string
MESSAGE(’MyText=%1′, MyText); // ‘  A   ‘

MyCode := PADSTR(”, MAXSTRLEN(MyCode), ‘?’);
MyText := PADSTR(”, MAXSTRLEN(MyText), ‘?’);

MyCode[3] := ‘A’;
MyText[3] := ‘A’;

MESSAGE(’MyCode=%1′, MyCode); // ‘??A???’
MESSAGE(’MyText=%1′, MyText); // ‘??A???’

If your older NAV client crashes during startup after you’ve installed NAV6.00 or NAV6.00 SP1, try specifying a zup-file name on the older client’s command line, e.g. "fin.exe id=400sp3”.

Using the FileSystemObject from the Windows Script Host Object Model library, one can safely create file system folders, even if the containing folder does not yet exist.

CreateFolder(FolderName : Text[260])
// VAR
// FileSystemObject : Automation ‘Windows Script Host Object Model’.FileSystemObject

CREATE(FileSystemObject);

IF NOT FileSystemObject.FolderExists(FolderName) THEN BEGIN
  CreateFolder(FileSystemObject.GetParentFolderName(FolderName));
  FileSystemObject.CreateFolder(Foldername);
END;

Some reports can be printed from the Print Preview window, others cannot(you’ll get the error message “This report cannot be printed from Print Preview. Quit Print Preview and run the report.”).

The difference between these reports is the use of the CurrReport.PREVIEW function. If that function is called from your report code, it cannot be printed from the Print Preview window.

Quiz ;-)

The other day, instead of ANDOR-ing the results of HasCardForms and HasSubsidiaryTables, I wrote a function like this:

DataCaptionFieldsRequired(VersionNo : Integer;TableID : Integer) : Boolean
if HasCardForms then
  exit(true);
if HasSubsidiaryTables then
  exit(true);

Can you guess why?

The project I spoke about in the previous post also involved combining user-specified file paths (stored in a setup table) with hard-coded file names. Again, a seemingly straight-forward task, that, upon closer inspection, turns out to be little harder.The naive approach would be to simply join the different parts of the path, separated by a backslash.

NaiveApproach() : Text[260]
FilePathSetup.GET;
FilePathSetup.TESTFIELD(”File Path”);
EXIT(FilePathSetup.”File Path” + ‘\’ + ‘filename.ext’);

However, if the “File Path” field already contains a trailing backslash, the resulting path will contain two consecutive backslashes; we don’t want that. Maybe we should validate the user’s input, and remove any trailing backslashes.

File Path - OnValidate()
“File Path” := DELCHR(”File Path”, ‘>’, ‘\’);

When the user enters the root folder of a drive, the input is now truncated: “c:\” becomes “c:”. The former is a valid file path, the latter is just a drive letter. Again, not what we want.My suggestion for a best practice is to leave the user’s input as it is. Whenever we need to build a path, we remove the trailing backslash (if any), after which the path can be built like we did in the naive approach.

SuggestedApproach() : Text[260]
FilePathSetup.GET;
FilePathSetup.TESTFIELD(”File Path”);
EXIT(STRSUBSTNO(’%1\%2′, DELCHR(FilePathSetup.”File Path”, ‘>’, ‘\’), ‘filename.ext’));