MBS Plugin Advent calendar

Door 1 - Barcodes

Fact of the day
Did you know that you can generate over 80 different barcode types with MBS?

Welcome to the first door of our advent calendar. In this advent calendar I will introduce you to various components of the MBS FileMaker Plugin and show you how to use them. Today it's all about barcodes. Did you know that you can create over 80 different barcode types with the MBS FileMaker Plugin? How this works I will show you today in a project in which we create a WIFI QR code. If your guests get bored over the Christmas period, they can simply scan this QR code with their smartphone to gain access to your WiFi.

First of all, let's consider the question of how such a WiFi QR code is actually structured. The information you need for the QR code is the SSID, In other words the network name, the access password, the type of WiFi encryption and the visibility. For this test, let's imagine we have a WLAN with the following data:

Password: 123456789
Encryption WPA 2
Visibility: Yes

First of all, we have to specify in the QR code that it is a WIFI QR code, so we first enter the service tag WIFI. This way, many QR code scanners know what to do with the information. Now the information about our network follows. The individual information is provided with an associated key so that you know what information it is. The key and the corresponding value are separated from each other with a colon. The individual values are separated from each other with a semicolon. The QR code type is followed by the used encryption protocol. The key to the encryption protocol is T, followed by the information on the protocol. For the protocols, we have the choice between WPA, WEP and nopass. We use WPA in our example. This is followed by the key S for the SSID and the corresponding value. If the WLAN requires a password, this is specified with the P key. If there is no password, the value remains empty. Now we need to specify the visibility of the network. Here we can only choose between the values true and false. If it is hidden then our value is true, if it is visible then the value is false. The corresponding key is H. If the network is visible, we can also omit the value here.

For our example network, this would be the content of the QR code:

In our example, we have a field for each of the entries and a container in which the image of the barcode should be displayed.

Now we come to our script with which we create the Wifi code. We have two options for this. We can use the Barcode.Generate function and pass it the constructed string. We can also define further properties for the QR code in the other parameters, but more on that in a moment. Or we can use the Barcode.GenerateJSON function. We pass it a JSON with our settings. In this example, we choose the Barcode.Generate function.
First, we assemble the string.

Set Variable [ $Text ; Value: "WIFI:T:" & DoorOne::Encryption & "; S:" & DoorOne::SSID & ";P:" & DoorOne::Password & ";H: "& DoorOne::Hidden &";" ]

We want to create a QR Code with a high ECC level. We can select an ECC of 1-4. A high ECC level helps us to recognize a barcode better, even if parts of the code are missing, because the information in a QR code appears several times. The higher the ECC level, the more extensive the QR code is, but the higher the chances of it being recognized. For a QR code, we need to assign the value 4 to option1.

Set Variable [ $r ; Value: MBS( "Barcode.SetOption"; 1; 4) ]

Now we call the Barcode.Generate function. In the first parameter we specify what type of barcode it is, in our case a QR code. This is followed by the content of our QR code, in this case our string. Optionally, we can now specify the size of the QR code, if we do not want to define fixed values for height and width, we enter a 0 in each case. Now we can decide whether we want to rotate our QR code, which we do not want to do, so we also enter a 0 here. The next parameter is interesting for us again, because this is about the scaling. This should be at least 4 for a QR code that may later be printed. In our example, we are finished with the parameters, but you can also specify in your own projects whether, for example, the text under the barcode should be displayed for an EAN barcode and whether we want an encoding that differs from the standard UTF-8 encoding.

Set Variable [ $QR_Ref ; Value: MBS("Barcode.Generate"; "QRCode"; $Text; 0; 0; 0; 4) ]

However, this function does not yet return the image of our barcode, but a GMImage reference. We could then use GMImage references to edit the image further. However, we do not want to do this today and for this reason we now write the barcode to the container with GMImage.WriteToPNGContainer. We then have to release the resulting reference.

Set Field [ DoorOne::Barcode ; MBS("GMImage.WriteToPNGContainer"; $QR_Ref; "barcode.png") ] Set Variable [ $r ; Value: MBS("GMImage.FreeAll") ]

Now you can happily share your WIFI with your friends and family. I wish you lots of fun.


Door 2 - Vision

Fact of the day
The MBS FileMaker Plugin not only offers you the Apple Vision as a framework, using MapKit you can also integrate Apple maps into your application or take pictures with your iPhone via Continuty Camera and store them directly in FileMaker.

The second door provides something for Mac users. Apple provides a framework called Vision. With this framework you can recognize barcodes, recognize texts from images or classify images into categories with the help of machine learning, e.g. so that you can search for them in your image database. This framework makes MBS usable with the plugin for FileMaker.

Barcode recognition

Let's first look at the possibility of recognizing multiple barcodes on an image. You simply pass the image to the Vision.DetectBarcodefunction as a parameter and Vision then searches for the barcodes on the image and returns a JSON in which the barcodes are listed. If you only want to search for QR codes on the image, for example, you can restrict the barcode types in the search by specifying the desired types as parameters. You can find out which barcode types can be recognized with Vision on your computer using the Vision.SupportedSymbologies function.


Result from Vision.DetectBarcode

Text recognition

We now have two possible returns for text recognition. If we use the Vision.RecognizeText function, we get plain text as a return, which we can then output in a field, for example. The other return option is a JSON. This JSON not only contains the plain text, but also the position of the individual lines. This has the advantage that you can, for example, make a page out of the image with DynaPDF and place the text behind it so that the text can be marked.

In the example, we have created both options as buttons. Otherwise, the parameters that we have to pass to the two text recognition functions are identical. First we pass the image from which the text should be recognized. This is sufficient for now, the other parameters are optional. Here we can specify the type of recognition to be performed. We can choose between Fast and Accurate. Accurate uses neurological networks for word recognition. Fast recognizes the individual characters. Fast also works well if the desired language is not available. Next, we can specify the language of the text to be recognized. The Vision.SupportedRecognitionLanguages function tells you which languages can be recognized. Then you can specify a list of words that may occur in the text but are not in the dictionary so that they can be recognized more easily. The last parameter PageLimit is interesting for PDF documents, because with Vision you can use text recognition not only in images, but also in PDF documents. With the last parameter, we decide how many pages the text recognition should run over. If we do not specify anything, only the text recognition for the first page is performed automatically.

Here you can see the code from our example for the JSON function. We did not need the last two parameters in our example:

Set Variable [ $JSON ; Value: MBS("Vision.RecognizeTextJSON"; DoorTwo::Input_Image;"Accurate"; "en_US") ] Set Field [ DoorTwo::Output ; MBS("JSON.Colorize"; MBS("JSON.Format"; $JSON)) ]

Result from Vision.RecognizeText

Result from Vision.RecognizeTextJSON

Classification of images

Vision also offers the option of automatically classifying images, which can help you, for example, if you have a photo library and you want to search for images from a specific category. You can then enter and use this classification in a field. Vision.ClassifyImage provides us with a result for this.

Set Variable [ $Content ; Value: MBS( "Vision.ClassifyImage";DoorTwo::Input_Image; 0 ) ]

Here again, we first pass the image. As an optional parameter, we can now specify whether we only want to get back the most suitable category (the default) or whether we want to receive a JSON with all suitable categories. The JSON also contains the probability with which the image fits into this category.

Now you can get more out of your images. We hope you enjoy using these functions.


Door 3 - Binary File

Fact of the day
You may also be interested in the Text component. Because you can also create a text file with this component.

Behind door 3 is a component that is mostly unseen, but is used in many applications by customers and has become essential. It is the BinaryFile component. You can use it to read binary data from a file and also write it to a file.

But what is it actually good for? Although the texts are saved as binary data, you can also create normal text files with this component and read them out again later. For example, you can create a log file yourself in which you can write information. We would like to do this now by logging in the file when a certain script has been called and what result it has. The aim is to have as less code as possible in the script that we want to log so that the script doesn't become confusing. For this reason, we use the Perform Script script step and call a script that writes the desired text to the log file. We pass the text that we want to write to the log file as a parameter. In this way, for example, the results that were calculated in the script can also be written to the file.

In the script below, a line should be written to the file containing the timestamp, the script name of the calling script and the information that the script was started at this time. The script is then executed. In this script we stop for 2 seconds so that we can also see a change in the timestamp and set the result of the script in the variable $res. Let's call the same script as at the beginning again and pass the text in the parameter again. This again contains the timestamp and script name and this time still returns the result that we have in the calling script.

Perform Script [ Specified: From list ; "Write" ; Parameter: Get ( CurrentTimestamp ) & "-" & Get(ScriptName) & "-Start" ] Pause/Resume Script [ Duration (seconds): 2 ] Set Variable [ $res ; Value: 1 ] Perform Script [ Specified: From list ; "Write" ; Parameter: Get ( CurrentTimestamp ) & "-" & Get(ScriptName) & "-Finish-res:" & $res ]

The Write script which executes the log entries looks like this:

Set Variable [ $Param ; Value: Get(ScriptParameter) ] Set Variable [ $Path ; Value: MBS("Path.AddPathComponent"; MBS("Folders.UserDesktop"); "MBS_Binary_File.txt") ] If [ MBS("Files.FileExists"; $Path) ] # Add Information Set Variable [ $ref ; Value: MBS( "BinaryFile.Append"; $Path ) ] Set Variable [ $r ; Value: MBS( "BinaryFile.WriteText"; $ref; "¶" & $Param ; "UTF-8" ) ] Set Variable [ $r ; Value: MBS( "BinaryFile.Close"; $ref ) ] Else # Create one Set Variable [ $ref ; Value: MBS( "BinaryFile.Create"; $Path ) ] Set Variable [ $r ; Value: MBS("BinaryFile.WriteText"; $ref; $Param;"UTF-8") ] Set Variable [ $r ; Value: MBS( "BinaryFile.Close"; $ref ) ] End If

First we save the text from the passed parameter. Next, we assemble the path where the file is located. More about this in a later door ;-). The path now leads to a file with the name MBS_Binary_File.txt which is located on the desktop. Now we have to make our procedure dependent on whether the file already exists or still has to be created. We check this with the Files.FileExists function.

If the file already exists, we use the "BinaryFile.Append" function to open the file. It returns a reference to this file. The cursor is at the last position of the document. We now call BinaryFile.WriteText and first pass the document reference and the text to be written to the file. We place a line break in front of the text so that each new entry is on a new line. We can also specify the encoding in the optional parameter. By default this is UTF-8 but can be changed to ANSI, ISO-8859-1, Latin1, Mac, Native, DOS, Hex, Base64 or Windows. When we are finished with the entry, we call the function "BinaryFile.Close", which closes the file again.

It is a little different if we do not yet have a file. In this case, it is first created and opened with the "BinaryFile.Create" function. We receive the reference number again and can then enter the text in the file as usual, with the difference that we do not need the line break here, because we write the first line in the document.

For example, it is also possible for you to log errors. Here we have a script in which an MBS function is used incorrectly and thus throws an error, the reason being that the sybology ABC does not exist. This error is now entered in the text file.

... Set Variable [ $r ; Value: MBS("Barcode.Generate"; "ABC"; "xyz") ] If [ MBS("IsError") ] Perform Script [ Specified: From list ; "Write" ; Parameter: Get ( CurrentTimestamp ) & "-" & Get(ScriptName) & "Error: " & $r ] Exit Script [ Text Result:] End If ...

We can now not only write normal text to the document, but also a value in hexadecimal, for which we have the BinaryFile.WriteHex function. In the parameters, you can specify a character string coded in hexadecimal, which is then written to the document as text. For example, if we pass the character string 48656C6C6F20576F726C64 to this function, our output will look like this:

ke to add a line break before the hexadecimal text. We would therefore like to enter a Chr(13) in the document. This can be realized with the function BinaryFile.WriteByte. We write a single byte into the file with this function. We pass the desired character to be written to this function.

... Set Variable [ $r ; Value: MBS("BinaryFile.WriteByte"; $ref; 13) ] Set Variable [ $r ; Value: MBS("BinaryFile.WriteHex"; $ref; $Param) ] ...

In addition to writing text to a file, you can also write the content of a container to a file. To do this, use the BinaryFile.WriteContainer function. In the parameters, enter the reference to the file and the container of which the content is supposed to be written to the file.

Set Variable [ $Param ; Value: Get(ScriptParameter) ] Set Variable [ $Path ; Value: MBS("Path.AddPathComponent"; MBS("Folders.UserDesktop"); "MBS_Container.png") ] Set Variable [ $ref ; Value: MBS( "BinaryFile.Create"; $Path ) ] Set Variable [ $r ; Value: MBS("BinaryFile.WriteContainer"; $ref; DoorThree::Container) ] Set Variable [ $r ; Value: MBS( "BinaryFile.Close"; $ref ) ]

Now, of course, we don't just want to write data to a file, we also want to be able to read it. We have the "BinaryFile.ReadText" function for this purpose. This reads out as many letters as we specify in the parameters, starting from our current position. As we do not want to change the file in this case, we open it with the "BinaryFile.Open" function.

Set Variable [ $Path ; Value: MBS("Path.AddPathComponent"; MBS("Folders.UserDesktop"); "MBS_Binary_File.txt") ] Set Variable [ $ref ; Value: MBS("BinaryFile.Open"; $Path) ] Set Variable [ $r ; Value: MBS("BinaryFile.Seek"; $ref; DoorThree::Start) ] Set Field [ DoorThree::Text ; MBS( "BinaryFile.ReadText"; $ref; DoorThree::Length; "UTF-8" ) ] Set Variable [ $r ; Value: MBS("BinaryFile.Close"; $ref) ]

In our example, you have the option of defining the start position and the readout length in two fields. We can change the current position with the BinaryFile.Seek function. If you want to determine what the current position is, use the BinaryFile.Position function. If you want the length of the entire document, call BinaryFile.Length.

But here, too, we not only have the option of reading out normal text, but we can also read out hexadecimal values, for example. To do this, we use the BinaryFile.ReadHex function instead of the BinaryFile.ReadText function.

In the image you can see that the first two characters in this document are a 0 and a 3 which have been output in hexadecimal.

I hope you enjoyed this excursion into the world of binary files and I'll see you again tomorrow.

Door 4 - Speech

Fact of the day
A voice output can not only make reading easier, but also creates freedom for people with physical disabilities.

In this door, you have the opportunity to ensure that your solution never has to be at a loss for words again. Today we are teaching your application to speak, because MBS offers speech output for Windows, Mac and iOS with the Component Speech.

If we want to output a text, we can use the Speech.Speak function. We first pass the text to be read out in the parameters. That's enough for now. Optionally, we can also choose from various speakers. You can find out which speakers are available with the Speech.AvailableVoices function. This function provides you with a list of all speakers. You can specify in this function whether you want the speaker names or the ID for the speaker to be displayed in this list.

If you want to use a specific speaker in the Speech.Speak function, you need to enter the ID. In the Speech.Speak function, you can also set whether or not your script should be continued while the speech output is running. Stopping a script also makes sense if you call the Speech.Speak function in the script several times so that the text that follows is also read out and is not lost because another text is being read out. You can also set the volume and playback rate in the function. The volume is between 0.0 (silent) and 1.0 (full). The playback speed ranges between 0.0 (silent) and 2.0 (double speed). The default value for both settings is 1.0.

Set Variable [ $r ; Value: MBS( "Speech.Speak"; DoorFour::Text ; DoorFour::Speaker ; 1 ; 1; .7 ) ]

Of course, there are also functions with which you can stop the speech output. With Speech.Stop, you can stop the current output at the next possible time. The text is then completely interrupted and cannot be continued again. So if you only want to pause the output in order to continue it later, you need the Speech.Pause function. To resume the output, you need the Speech.Resume function. You should pay attention to the Stop and Pause functions: If a script has a Wait instruction from the Speech.Speak function, then of course we remain in the script and it cannot be stopped or the speech output terminated prematurely.

With just a few functions, you can integrate a very useful functionality into your solution. I wish you a lot of fun with it.

1 Like

Door 5 - WindowsOCR

Fact of the day
If you want to program a cross-platform solution or need text recognition for older Windows versions, you can use functions of the Tesseract component from the MBS FileMaker Plugin.

In Door 2 we already introduced you to a way of performing text recognition under Mac. But this possibility is not only available for Mac in the MBS FileMaker Plugin, but the MBS FileMaker Plugin also makes it possible to use Windows' own OCR functions. These WindowsOCR functions are available under Windows 10 and 11.

You can test whether you can use the functions under an operating system by running the WindowsOCR.Availablefunction. If you can use the functions, first create a new OCR engine with WindowsOCR.New. This function returns a reference number which you can use in other functions to address the OCR engine. In this function, you can also optionally specify which language is to be recognized by the engine. If you skip this parameter, the language, that you get back with the WindowsOCR.CurrentInputMethodLanguageTag function, is automatically used for language recognition. Which languages can be recognized depends on which languages are installed on your system. You can obtain a list of the languages that you can currently use in your system with WindowsOCR.AvailableRecognizerLanguages.

Show Custom Dialog [ "Supported languages" ; MBS( "WindowsOCR.AvailableRecognizerLanguages" ) ]

You can now select whether you want to recognize the text from an image from a container or an image file. If the text is on an image from a container, use the WindowsOCR.Recognize function and enter the reference number and the container in which the image is located, in the parameters.

If [ MBS( "WindowsOCR.Available" ) ] Set Variable [ $OCRen ; Value: MBS( "WindowsOCR.New" ; "en-US" ) ] ... Set Variable [ $r ; Value: MBS( "WindowsOCR.Recognize"; $OCRen; DoorFive::Container ) ] ...

Alternatively, you can recognize the text of an image in a file. To do this, use the WindowsOCR.RecognizeFile function and enter the file path to the file instead of the container.

... Set Variable [ $r ; Value: MBS( "WindowsOCR.RecognizeFile"; $OCRen; DoorFive::Path ) ] ...

You can now query the result. Again, you have two options as to how the result can be presented to you. If you only want the plain text, use WindowsOCR.Text and get the text back, which you can then store in a field, for example. If you want information beyond the plain text, you can use the WindowsOCR.Result function. Here you receive a detailed JSON in which the individual lines and the individual words are specified with their position and size.

... Set Variable [ $Text ; Value: MBS( "WindowsOCR.Text"; $OCRen ) ] ... Set Variable [ $JSON ; Value: MBS( "WindowsOCR.Result"; $OCRen ) ] ...

Here you can see such a generated JSON from an image:

{ "Text": "WindowsOCR: Functions for OCR in Windows 10 or 11.", "TextAngle": 0, "LineCount": 1, "Lines": [ { "Text": "WindowsOCR: Functions for OCR in Windows 10 or 11.", "WordCount": 9, "X": 19, "Y": 9, "Width": 2139, "Height": 61, "Words": [ { "Text": "WindowsOCR:", "X": 19, "Y": 9, "Width": 538, "Height": 61 }, { "Text": "Functions", "X": 604, "Y": 11, "Width": 364, "Height": 59 }, { "Text": "for", "X": 1000, "Y": 9, "Width": 107, "Height": 61 }, { "Text": "OCR", "X": 1137, "Y": 10, "Width": 167, "Height": 60 }, { "Text": "in", "X": 1337, "Y": 11, "Width": 58, "Height": 58 }, { "Text": "Windows", "X": 1432, "Y": 9, "Width": 343, "Height": 61 }, { "Text": "10", "X": 1815, "Y": 10, "Width": 84, "Height": 60 }, { "Text": "or", "X": 1935, "Y": 24, "Width": 78, "Height": 46 }, { "Text": "11.", "X": 2049, "Y": 11, "Width": 109, "Height": 58 } ] } ], "TextLines": "WindowsOCR: Functions for OCR in Windows 10 or 11.\r" }

The degree of text angle can also be found in the JSON. You can also determine this text angle separately with WindowsOCR.TextAngle. This allows you to straighten the text, for example.

... Set Variable [ $Angle ; Value: MBS( "WindowsOCR.TextAngle"; $OCRen ) ] ...

I hope you enjoyed this article. Have fun recognizing your texts.

Door 6 - MongoDB

Fact of the day
MongoDB is the backend of Claris Studio, unfortunately you cannot use the advantages of MongoDB directly with Claris Studio, but MBS makes it possible.

Welcome to December 6th. Today is St. Nicholas Day and we also want to take part in filling the Christmas boot with our calendar. Today it contains MongoDB. The special thing about MongoDB is that it is not a relational database based on tables and relationships, but its data has a JSON-like structure. This allows you to perform queries that were previously not possible due to the restriction of relationships or table limits.

In a MongoDB database we can have several collections. These collections can in turn contain several documents. The documents can be compared with data records, with the difference that different documents in a collection do not have to share the same structure. For example, you can have data in one and the same collection that describes the data for an employee and for a warehouse item. So you can easily find out who is in your company longer: the carpet in the warehouse or your trainee.

MongoDB will certainly be familiar to many of you, because Claris Studio is based on this backend. With the plugin, you can now use this flexibility directly. How to install a MongoDB server and how to use MongoDB with MBS is explained in this video.

MongoDB and MBS - A look under the hood

At the end of last year, the possibility to perform transactions in MongoDB databases was added with MBS. This means that you can make changes to the database, which can then all be committed at the same time. This has the advantage that if something goes wrong when changing the database or if the data change process is canceled, the changes can be discarded and we are back in the state in which the database was before the change and the data integrity remains guaranteed. If you are interested, simply take a look at our documentation on the topic.

See also

Door 7 - Paths

Fact of the day
Did you know that you can easily copy the path of a file under Mac if you press the option key in the menu of the right mouse button at the same time? The menu will then show the entry as Copy...as pathname

Today it's all about how you can build paths in FileMaker with the help of the MBS FileMaker Plugin. We already got a little insight into this topic in the third door, where we created a path to a file on the desktop. The MBS FileMaker Plugin offers you some of these paths to special folders in your system. For example, you can use the Folders.UserDesktop function to determine the path to a user's desktop. Or you can determine the path to the temporary folder with Folders.UserTemporary. In this picture you can see the functions that are available for special folders.

In most cases, this is not enough as a path specification and we want to address a very specific file in this folder. To do this, we can assemble the path with MBS. For example, we have the function Path.AddPathComponent, which appends a component to the path in accordance with the operating system. Let's assume we want to address a file on the desktop called xyz.pdf, then our script can look like this:

Set Variable [ $Dektop ; Value: MBS("Folders.UserDesktop") ] Set Variable [ $Path ; Value: MBS("Path.AddPathComponent"; $Dektop; "xyz.pdf") ]

First we get the path of the desktop with Folders.UserDesktop and append the file name as the last component.

If we already have a file path and want to know the last component of this file path, then we use the function Path.LastPathComponent.

Set Variable [ $Last ; Value: MBS("Path.LastPathComponent"; $Path) ]

It is also possible, for example, if you have a file path from a file and want to write another file in the same folder as this file, then you can also remove the last path component by using the function Path.RemoveLastPathComponent to remove the last component. The functions I have used so far do not always need to have a file name as the last component, but the last component can also be a folder if it is the path to a folder.

In the path component we have further functions that can help you when working with paths.

If you need the file URL of a file, then you get the file URL back by specifying the path with the function Path.FilePathToFileURL. For the reverse path from a file URL to a path, you can use the Path.FileURLToFilePath function.

However, we can not only determine the file URL for a file, but can also return the FileMaker path for a native path and the other way round. For each direction we use the function Path.NativePathToFileMakerPath and Path.FileMakerPathToNativePath.

We also have something special for Windows users, because in Windows you can have a long form for paths and a short one. So that you can convert from one type to the other within FileMaker as you wish, we have the two functions Path.LongPath and Path.ShortPath.

I hope you enjoyed this door and we'll see each other tomorrow.

1 Like

Door 8 - Determine your own position (for Mac users)

Fact of the day
A man from Munich parked his car in a parking lot in 2015 and was unable to find it again. The car was reported stolen in the meantime. After around 6 months, he was informed by the parking garage company that his car was still in the parking lot. This man should have remembered his location ;-)

Today I would like to show you how you can determine your own position as a Mac or iOS SDK user. The CoreLocation component is available for this purpose.

If you want to use this option in your apps, we need authorization. We can request various authorizations. The Always authorization requests permission to use location services whenever the app is running. The next weaker authorization is the when in use authorization. It Requests permission to use location services while the app is in the foreground. There is also the temporary full accuracy authorization. This access will expire automatically, but it won't expire while the user is still engaged with your app. In our example, we use the Always authorization. If the authorization fails, please check the settings in Security. You can check whether the authorization was successful using the CoreLocation.authorizationStatus function. It displays the current authorization status. The following responses are possible.

notDetermined The user has not yet made a choice regarding whether this app can use location services.
restricted This app is not authorized to use location services.
denied The user explicitly denied the use of location services for this app or location services are currently disabled in Settings.
authorizedAlways This app is authorized to start location services at any time.
authorizedWhenInUse This app is authorized to start most location services while running in the foreground.

In the example, I will show you how you can deal with a situation where the authorization does not correspond to the desired one.

Set Variable [ $r ; Value: MBS("CoreLocation.requestAlwaysAuthorization") ] Set Variable [ $status ; Value: MBS( "CoreLocation.authorizationStatus" ) ] If [ $status = "denied" ] Show Custom Dialog [ "No authorization" ; "We do not have an authorization. Take a look to security settings." ] Exit Script [ Text Result: ] End If

If the authorization was successful, we can start querying the current position. The CoreLocation.startUpdatingLocation function is available for this purpose. You can then use the CoreLocation.hasLocation function to test whether a location has been found. Now we can query several pieces of information about the location. First of all, the time at which this position was determined. To do this, we use the CoreLocation.timestampfunction, which, as the name suggests, returns a timestamp. We can also determine the longitude and latitude. We can find out exactly how these are specified using the CoreLocation.horizontalAccuracy function. We get back the radius in meters in which the location is located.

However, not only longitude and latitude can be determined, but also the height at which we are currently located. This information makes sense on a hike, for example. Use the CoreLocation.altitude function for this. Here, too, there is a function that returns the accuracy of this information (CoreLocation.verticalAccuracy).

... Set Variable [ $r ; Value: MBS( "CoreLocation.startUpdatingLocation" ) ] If [ MBS("CoreLocation.hasLocation") ] Set Field [ DoorEight::Time ; MBS("CoreLocation.timestamp") ] Set Variable [ $Lat ; Value: MBS("CoreLocation.latitude") ] Set Field [ DoorEight::Latitude ; $Lat ] Set Variable [ $lon ; Value: MBS("CoreLocation.longitude") ] Set Field [ DoorEight::Longitude ; $lon ] Set Field [ DoorEight::Horizontal accuracy ; MBS("CoreLocation.horizontalAccuracy") ] Set Field [ DoorEight::Altitude ; MBS("CoreLocation.altitude") ] Set Field [ DoorEight::Vertical accuracy ; MBS("CoreLocation.verticalAccuracy") ] ...

People who are gifted with miracles may be able to determine exactly where they are with the latitude and longitude information, but for most people it would be very useful if we additionally got an address. This is also possible with the CLGeocoder component from the plugin. The geocoder can provide the appropriate coordinates for an address, but also an address for coordinates. We use the CLGeocoder.ReverseGeocodeLocation for this. This function gives us a reference to the GeoCoder, from which we receive a JSON using the CLGeocoder.JSON function.

{ "location" : { "verticalAccuracy" : -1, "longitude" : 7.2572950031855843, "horizontalAccuracy" : 100, "latitude" : 50.545874889063896, "timestamp" : "2023-12-03 16:30:29 +0000" }, "region" : { "radius" : 70.756292662272614, "longitude" : 7.2574633999999998, "latitude" : 50.545909899999998, "identifier" : "<+50.54590990,+7.25746340> radius 70.76" }, "cancelled" : 0, "timeZone" : { "abbreviation" : "CET", "secondsFromGMT" : 3600, "name" : "Europe/Berlin" }, "address" : "Lindenstraße 4\n53489 Sinzig\nGermany", "placemarks" : [ { "ISOcountryCode" : "DE", "subThoroughfare" : "4", "areasOfInterest" : null, "subLocality" : "Sinzig", "administrativeArea" : "Rhineland-Palatinate", "country" : "Germany", "thoroughfare" : "Lindenstraße", "ocean" : null, "name" : "Lindenstraße 4", "postalCode" : "53489", "inlandWater" : null, "locality" : "Sinzig", "subAdministrativeArea" : "Ahrweiler" } ], "key" : "24002", "addressString" : null, "done" : 1, "error" : null, "started" : 1 }

We can then read out the address under the key "address". We then release the geocoder reference again by calling the CLGeocoder.Closefunction.

... If [ $Lat ≠ "" and $lon ≠ "" ] Set Variable [ $Geo ; Value: MBS("CLGeocoder.ReverseGeocodeLocation"; $Lat; $lon; 1) ] Set Variable [ $GeoJSON ; Value: MBS("CLGeocoder.JSON"; $Geo) ] Set Variable [ $address ; Value: JSONGetElement ( $GeoJSON ; "address") ] Set Variable [ $r ; Value: MBS("CLGeocoder.Close"; $Geo) ] Set Field [ DoorEight::address ; $address ] End If ...

A location like this can change from time to time if the device we are querying moves. We can use the functions CoreLocation.SetUpdateLocationEvaluate and CoreLocation.SetUpdateLocationHandler to specify an expression or a script that is executed when a new location has been detected. For a moving device, there is also other interesting information, such as the speed or the direction that you can query. If you no longer require new location updates, stop this process with CoreLocation.stopUpdatingLocation.

I hope this door helped you a little with your orientation. Maybe we'll read each other again tomorrow.

Door 9 - IBAN

Fact of the day
Saint Lucia has the longest IBAN with 32 characters. This country is not a member of the SEPA area. Within the SEPA area, Malta has the longest IBAN with 31 characters.

Today I would like to introduce you to a component that currently only contains 7 functions, but can be very useful to you if required. We are talking about the IBAN component. The IBAN number is an international number that can be clearly assigned to a specific account. It is intended to contribute to the standardization of global payment transactions. Mainly the EU countries have already implemented this standard, but countries outside of Europe are also adopting this standard.

The structure of the IBAN depends on the country from which the IBAN originates. All IBAN numbers start with the country code, which consists of two characters. With the MBS FileMaker Plugin, you can call up a list of these country codes using the IBAN.Countries function.

The country code is then followed by a check number, which is calculated from the remaining digits. This is to support the correctness of the IBAN. The MBS FileMaker Plugin can also calculate this check number separately. To do this, enter the IBAN in the parameters and enter 00 instead of the unknown check number, which consists of two digits

Set Variable [ $Sum ; Value: MBS( "IBAN.CalcCheckSum"; DoorNine::IBAN) ]

This is followed by a sequence of digits that can vary in length from country to country. With such a long number, it is easy to lose track. For this reason, it is practical that the plugin comes with a function that uses spaces to put the IBAN into a clearer form. Use the IBAN.Format function for this

Set Variable [ $IBAN ; Value: MBS( "IBAN.Format"; DoorNine::IBAN) ]

If we have the IBAN in a formatted form and want to remove the spaces or other characters that are not valid, the IBAN.Compact function is your tool of choice.

Set Variable [ $IBAN ; Value: MBS( "IBAN.Compact"; DoorNine::IBAN) ]

If you would like to find out more about the structure of an IBAN from a specific country, the IBAN.RegEx function provides you with a regular expression that describes the structure by specifying the appropriate country code.

Set Variable [ $reg ; Value: MBS("IBAN.RegEx"; DoorNine::Country) ]

You can check whether an IBAN has this structure and whether the check digit matches the IBAN using the really practical IBAN.IsValid function. Here too, invalid characters are ignored when entering the IBAN.

Set Variable [ $Valid ; Value: MBS( "IBAN.IsValid"; DoorNine::IBAN) ] If [ $Valid ] Show Custom Dialog [ "Is the IBAN valid?" ; DoorNine::IBAN & " is a valid IBAN" ] Else Show Custom Dialog [ "Is the IBAN valid?" ; DoorNine::IBAN & " is not a valid IBAN" ] End If

If you would like to save the IBAN in your database, it is recommended that you first use the IBAN.Compact function.

If you would like to experiment a little with the individual IBANs of the countries, you can use IBAN.Example to obtain an example IBAN for the appropriate country.

Set Variable [ $IBAN ; Value: MBS("IBAN.Example"; DoorNine::Country) ] If [ MBS("IBAN.IsValid"; $IBAN) ] Set Field [ DoorNine::IBAN ; $IBAN ] Else Show Custom Dialog [ "Error" ; $IBAN ] End If

I hope you enjoyed this excursion into the world of banking and that you can use some of it in your projects.

Door 10 - Phidgets

Fact of the day
Did you know that Phidgets are used in schools to get students interested in computer science?

Do you already know the small input/output devices from Phidgets Inc. Phidgets are small additional devices that you can connect to your computer and with which you can then input or output data. For example you can connect a small motor, a temperature sensor, a humidity sensor, a gyroscope or a small LED display and exchange data with these devices.

In this example, I would like to introduce you to three different Phidgets and how we can read data from these Phidgets in FileMaker. Our three phidgets are a light sensor, a temperature sensor and a slider. In our example, the phidgets are all connected together to a hub. This hub has various outputs to which the individual Phidgets can be connected. This hub is then connected to the computer with a USB cable. All Phidgets also draw their power via this USB cable. It is also possible to purchase a wireless HUB. In this case, a separate power supply is required.

I would now like to show you how to retrieve the Phidget data via FileMaker. First of all, we need to load the Phidget libary. To be able to load the libary, we first have to download it. You can find the appropriate download here.

If you do not save the installation in a separate location, the standard paths to the library look like this:

Windows: C:\Program Files\Phidgets\Phidget22\phidget22.dll
Mac: /Library/Frameworks/Phidget22.framework
Linux: libphidget22.so

We then pass this path to the Phidget.Load function, which loads the library for us. Now we can create an instance of the light sensor using the Phidget.Create function. Here we specify the appropriate type of phidget from the list of types. In our case LightSensor.

Set Variable [ $$phidget ; Value: MBS( "Phidget.Create"; "LightSensor" ) ]

Now we can set script triggers that refer to a script when a certain event comes from the Phidget. To do this, we call the function Phidget.SetScriptTrigger. First we specify the reference that we received from Phidget.Create. Then we specify the event for which the following script is to be called. In our case, we now want to handle the Attach and Detach events. There are events that exist for all Phidget like Attach and Detach, but the sensors can also have their own events, just as the light sensor has an IlluminanceChange event, but we will come to this in a moment. But first, let's define the scripts that should be called when a Phidget is connected. In this case, the Attach script should be called which is in the same file as the currently running script. If the Phidget is removed again, the Detached script is called. We will look at these scripts in a moment.

Set Variable [ $r ; Value: MBS( "Phidget.SetScriptTrigger"; $$phidget; "Attach"; Get(FileName); "Attached_Light" ) ] Set Variable [ $r ; Value: MBS( "Phidget.SetScriptTrigger"; $$phidget; "Detach_Light"; Get(FileName); "Detached" ) ]

After we have set the script triggers we can now open the Phidget channel to receive data. When it is ready it will send us an attach event.

Set Variable [ $r ; Value: MBS( "Phidget.Open"; $$phidget ) ]

Let's take a look at the attached script that is then called. When the Attach event fires, we get a JSON with information back. We first get this via Get(Scriptparameter) so that we can read it out. This is what such a JSON looks like:

{ "ID": "93005", "Tag": "", "ScriptName": "IlluminanceChange", "FileName": "Phidget Light Sensor", "Trigger": "IlluminanceChange", "illuminance": 595.57 }

We can now read the Phidget ID from this JSON. We need this to set and read the settings of the phidget.

Set Variable [ $parameter ; Value: Get(ScriptParameter) ] Set Variable [ $phidget ; Value: JSONGetElement ( $parameter ; "ID" ) ]

We first set the setting with which the speed of data transmission is set. We set the data interval to 2000. Then we actually come to the most important point of the solution, because we have to specify the script that is called when the value we get from the sensor has changed. To do this, we use the function "Phidget.SetScriptTrigger" again, specify the Phidget ID to identify the Phidget, the IlluminanceChange event and specify the script "IlluminanceChange", which is in the same file, as the script to be called.

Set Variable [ $r ; Value: MBS( "Phidget.SetProperty"; $phidget; "DataInterval"; 2000 ) ] Set Variable [ $r ; Value: MBS( "Phidget.SetScriptTrigger"; $phidget; "IlluminanceChange"; Get(FileName); "Illuminance_Change" ) ]

We'll see what the script does in a moment. The information we want now is the minimum and maximum value that our sensor can measure. To do this, we use the function "Phidget.GetProperty" to enter the ID in the parameters again and specify that we want the value MaxIlluminance to obtain the maximum value. For the minimum value for the light sensor, we specify MinIlluminance. To get the current value, we enter Illuminance here. Further values can also be determined using this function. The values to be determined depend on the sensor type. Please take a look at our documentation or the Phidgets documentation.

Set Field [ DoorTen::LightIntensityMax ; MBS("Phidget.GetProperty"; $phidget; "MaxIlluminance") ] Set Field [ DoorTen::LightIntensityMin ; MBS("Phidget.GetProperty"; $phidget; "MinIlluminance") ] Set Field [ DoorTen::Lightintensity ; MBS("Phidget.GetProperty"; $phidget; "Illuminance") ]

We have already talked about the most important script: IlluminanceChange, which we call when the incoming value changes. This script also receives a JSON with data from our event that we can read out. With the illuminance key, we get the new brightness value that we can write back into the field.

Set Variable [ $json ; Value: Get(ScriptParameter) ] Set Field [ DoorTen::Lightintensity ; JSONGetElement ( $json ; "illuminance" ) ]

Finally, if the Phidget is no longer to provide us with data, we must close the Phidget. You can see how this works in Script Close. The Phidget is closed with the Phidget.Close function and the memory space is released again with Phidget.Release.

This allows you to use your light sensor. It now works in a similar way with the other sensors, except that you have to specify different types, different events and different JSON keys.

The slider has another special characteristic. The slider is actually an adjustable resistor and we look at how much voltage passes through the resistor at a certain time interval. To do this, we need to know the channel on which the phidget is connected. We set this when we open the phidget in the open script.

Set Variable [ $$phidget ; Value: MBS( "Phidget.Create"; "VoltageInput" ) ] 
# set where to find the voltage input, in our case on first port of a hub
Set Variable [ $r ; Value: MBS( "Phidget.SetProperty"; $$phidget; "Channel"; 0 ) ] 
Set Variable [ $r ; Value: MBS( "Phidget.SetProperty"; $$phidget; "HubPort"; 0 ) ] 
Set Variable [ $r ; Value: MBS( "Phidget.SetProperty"; $$phidget; "IsHubPortDevice"; 1 ) ] 
Set Variable [ $r ; Value: MBS( "Phidget.SetScriptTrigger"; $$phidget; "Attach"; Get(FileName); "Attach_Slider" ) ] 
Set Variable [ $r ; Value: MBS( "Phidget.SetScriptTrigger"; $$phidget; "Detach"; Get(FileName); "Detached" ) ] 

In addition, we define a property in the attach script that specifies how often we fetch the value

Set Variable [ $r ; Value: MBS( "Phidget.SetProperty"; $phidget; "VoltageChangeTrigger"; ,1 ) ]

Now you can realize great projects with your Phidgets. If you have realized a project with the Phidgets, please let us know what you have created. We are always happy to get feedback. Who knows, maybe Santa Claus will be driving his sleigh with Phidgets next year.

Door 11 - WindowsLocation

Fact of the day
Did you know that one of the reasons why the Greenwich meridian became established was because the area on the other side, i.e. the International Date Line, was sparsely settled? This had the advantage that you rarely had to add a whole day when you were traveling. This avoided confusion.

In Door 8 we have already seen how we can determine a location from a Mac or an iOS device. With the WindowsLocation component, we also have a way to determine the location for Windows.

First we have to initialize the location functions for Windows with the function WindowsLocation.Initialize. Once this has worked, we ask Windows for permission to query the location. To do this, we use the WindowsLocation.RequestPermissions function. Now we can get the following statuses with WindowsLocation.Status.

  • Not supported
  • Error
  • Access Denied
  • Initializing
  • Running

If we have Running, we can query the location with WindowsLocation.Location. We then receive a JSON with our data that can look like this, for example:

{ "Latitude": 50.4833, "Longitude": 7.4655, "Altitude": 0, "ErrorRadius": 6713, "AltitudeError": 0, "SensorID": "{00000000-0000-0000-0000-000000000000}", "Timestamp": "06.12.2023 12:38:40,532" }

We can then read this information from the JSON using JSON functions. For example, here we see the script step with which we read the latitude:

... Set Variable [ $JSON ; Value: MBS( "WindowsLocation.Location" ) ] # Set Variable [ $lat ; Value: JSONGetElement ( $JSON ; "Latitude") ] Set Field [ DoorEleven::Latitude ; $lat ] ...

I hope you enjoy identifying your location

Door 12 - Files

Fact of the day
Did you know that the first hard disk in 1956 only had a storage capacity of 3.75 megabytes? Today, many of the pictures we take with a high-resolution digital camera are already larger than that.

Today we come to a topic that I already mentioned in a previous door: Today we come to the Files section. This is all about your files. In Door 3, we already got to know the Files.FileExists function, which can tell us whether a file is stored behind a certain path. However, this function is not only available for files but also for folders. With Files.DirectoryExists you can check whether a path specified in the parameters leads you to a folder. If a file is hidden behind the path, the result is 0 and if it is a folder, the result is 1. If we want to know whether a folder or a file is hidden behind this path, we use the Files.ItemExists function, which returns a 1 for a file and a folder.

If you have a file, you can use the component to query a lot of information about this file. Firstly, we have the Files.FileInfo function, which provides us with various pieces of information. This is intended for bundled app/plugin/framework files on Mac and on Windows for EXE/DLL files. In addition to the path, we also specify the desired selector in the function. On Mac, this can be Version, ShortVersion, MinimumSystemVersion, InfoString, ExecutableName and Identifier for the bundle identifier.

Under Windows, we have the selectors Description, Version, InternalName, CompanyName, LegalCopyright, OriginalFilename, ProductName, ProductVersion, and Copyright

We also have functions that query individual file information. For example: Files.FileName, Files.FileNameWithoutExtension, Files.AccessDate, Files.CreationDate, Files.ModificationDate, Files.FileKind, Files.FileSize, Files.IsHidden, Files.IsReadOnly, Files.IsExcludedFromBackup, Files.IsPackage, Files.GetFinderLabel or Icon.GetIcon. You can query all of this information using functions. For most functions, there is also a function with which you can change these properties.

You can also display a list of the files that are stored in a specific folder. This makes sense, for example, if you want to import all the files in a folder into the database. You can use Files.List for the list. If you would prefer to receive this list as JSON and get additional information about the files, you can use the Files.ListAsJSON function. If you use Files.List and Files.ListAsJSON, we always keep the list at the top level, which means we don't know what happens in the subfolders. For this we have the Files.ListRecursive function, which also returns the paths of the subfolders.

But you can not only query but also work with the files. You can create a new folder with Files.CreateDirectory. You can also copy one or more files from one folder to another using the Files.CopyFile and Files.CopyFiles functions. Creating an alias is also no problem with Files.CreateAlias.

Another cool thing that many of our customers use is that you can open a file from which you have the path, using a script. To do this, use the Files.LaunchFile function for normal files. This opens a file or folder. Use Files.Lauch e.g. to open a database file with FileMaker or your runtime solution. In addition, you can not only copy a file, but also move the file. This is where Files.MoveFile comes into play. A special form of moving is offered by the Files.MoveToTrash function, which places the file in the trash without a dialog. If you want to delete the file directly without going through the trash, you can use the Files.Delete function. In this case, use Files.DeleteFolder for a folder. There is another function for deleting that is still relatively new: Files.DeleteLater. This function writes a file to a list that is to be deleted afterwards. The files on this list are deleted when File Maker is closed. This function makes sense because there are some functions that can only be used with a path, such as the Files.LaunchFile function. So if we want to open a file from a container, we write this file to the temporary folder and enter the file in the deletion list.

Set Variable [ $Temp ; Value: MBS("Folders.UserTemporary") ] Set Variable [ $Name ; Value: MBS( "Container.GetName"; DoorTwelve::Container ) ] Set Variable [ $Path ; Value: MBS("Path.AddPathComponent"; $Temp; $Name) ] Set Variable [ $r ; Value: MBS( "Container.WriteFile"; DoorTwelve::Container; $Path ) ] Set Variable [ $r ; Value: MBS("Files.DeleteLater"; $Path) ] Set Variable [ $r ; Value: MBS("Files.LaunchFile"; $Path) ]

If we want to read a file from the disk into a container, this also works with this component. We have various functions for the different data formats that enable the file to be read: Files.ReadFile, Files.ReadJPEG, Files.ReadPNG and Files.ReadPDF.

Here you can see how a PNG is loaded into the container.

Set Variable [ $File ; Value: MBS( "Files.ReadPNG"; DoorTwelve::Path ) ] Set Field [ DoorTwelve::Container ; $File ]

I hope you enjoy using these functions, which I hope will also be helpful for you.

Door 13 - LibXL

Fact of the day
XL Did you know that there is even an Excel World Championship in which 8 Excel experts compete against each other to win the prize money of $10,000?

Welcome to door 13 of our advent calendar. Today it's all about the LibXL component. Did you know that you can use the MBS FileMaker Plugin to read, create and modify Excel files without having Excel installed?

This is made possible by the LibXL component. LibXL is an independent product that can be used in FileMaker with the plugin. This means that you need an additional LibXL license in addition to your plugin license to be able to use the functions properly. Today I will show you how you can read and modify data with LibXL and even create your own files.


But before we start our work, we first need to make the LibXL library available so that we can use the functions in the plugin. The library file you need can be found in the examples supplied with the plugin download. Exactly which file you need depends on your operating system. If you want to initialize the library on a Mac, then you need the library file with the extension dylib. If you use Windows, we have a file for the old Windows computers that work with 32 bit and a file with the extension dll for the 64 bit systems. If you want to use LibXL on a Linux server, we have included libxl.so with the Linux plugins.

For initialization we have the function XL.Initialize. Here we enter the path to the library. If you already have a LibXL license, enter the name and license key here. If you have customers who use different operating systems, you can also copy the InitXL script from the examples, the libraries can then simply be placed in the same folder as the database file. But for actual deployment, it may be easier to put the libXL file into the same folder as the plugin. Then you can pass "" as path and the plugin automatically checks the plugin folder. At the end of the script, the Initialize function is called with the appropriate parameters. You can adjust the values of the parameters in the script previously.

# If you like to get a LibXL license, please follow links on our pricing page:
# https://www.monkeybreadsoftware.de/filemaker/pricing.shtml

Set Variable [ $LicenseeName ; Value: "your name" ] 
Set Variable [ $LicenseKey ; Value: "your LibXL license key" ] 
Set Variable [ $r ; Value: MBS( "XL.Initialize"; $path; $LicenseeName; $LicenseKey) ] 
If [ $r  ≠ "OK" ] 
	Show Custom Dialog [ "Error" ; $r ] 
	Halt Script
End If

When we work in a script, it makes sense to check at the beginning of each script whether the LibXL is initialized. To do this, we use the XL.IsInitialized function which returns a 0 if the initialization has not yet been performed. The beginning can look like this:

If [ MBS("XL.IsInitialized") ≠ 1 ] 
	Perform Script [ Specified: From list ; "InitXL" ; Parameter:    ]
End If

Create file

Let's start our work. First we want to create a file in which we want to enter data. An Excel file is called a book in LibXL. The individual tables within such a file are on sheets. So we want to create a new workbook. To do this, we have the XL.NewBook function. It is used to create a working environment in the memory for which we receive a reference number back with the function, which we can then continue to work with. In an optional parameter we can also decide whether an Excel file should be created in the old xls format (parameter = 0) or the newer xlsx format (parameter = 1). Of course, our sheet must now be added to this workbook using XL.Book.AddSheet. In the parameters we specify the reference to which the book belongs and the name that the sheet should have. If you want to use an already created sheet as a template for the new one, you can optionally specify the sheet index of the template here. We then receive the sheet index of the sheet just created as a return.

Set Variable [ $Book ; Value: MBS("XL.NewBook"; 1) ] 
Set Variable [ $Sheet ; Value: MBS("XL.Book.AddSheet"; $Book; "Sheet 1") ]

We can now fill this with life.

If you do not yet have a license, there is always a warning text in the first line of the document and you cannot write to this line. If you try to do this, an error will occur, which is why we want to start filling the second row in our example. We have many different functions for writing in a cell, which we use depending on what we want to write in the cell. If we want to write a simple text, for example, we use the XL.Sheet.CellWriteText function. Here we first enter the book reference and then the sheet index in the parameters, followed by the cell that is to be written to in the table. In our case the second row and the first column. Since we start counting at 0, we have a 1 as the value for the row and a 0 for the column. Now the text follows and we can optionally specify a format, but more on that later.

Set Variable [ $r ; Value: MBS("XL.Sheet.CellWriteText"; $Book; $Sheet; 1; 0; "Hello") ]

Of course, there is not only a function for text, but also for numbers, dates, a Boolean value or formulas. For formulas, we enter the formula we want to calculate as the value. We can even use cells with their coordinates as values or, for example, calculate totals in formulas over entire ranges. In this example, cells B2 and C2 are added together. We have also previously set the values 49 and 2.

Set Variable [ $r ; Value: MBS("XL.Sheet.CellWriteNumber"; $Book; $Sheet; 1; 1; 49) ] 
Set Variable [ $r ; Value: MBS("XL.Sheet.CellWriteNumber"; $Book; $Sheet; 1; 2; 2) ] 
Set Variable [ $r ; Value: MBS("XL.Sheet.CellWriteFormula"; $Book; $Sheet; 1; 3; "B2+C2") ] 

If we use the XL.Sheet.CellWriteDate function, we get a number in a cell that is formatted as text, for example. If we want this date to be displayed as a date, the cell must be of type Date

Set Variable [ $r ; Value: MBS( "XL.Sheet.CellWriteDate"; $Book; $Sheet;1; 4; Get(CurrentTimestamp)  ) ] 

For a Boolean value, we can use 1 for TRUE and 0 for FALSE.

Set Variable [ $r ; Value: MBS( "XL.Sheet.CellWriteBoolean"; $Book; $Sheet;1; 5; 1) ]

We have already talked about formats and that we can also pass a format in each of these functions. This formats the text of a document and thus defines its appearance.

Here, for example, we first create a font, which gets the font via the XL.Font.SetName function. In addition, the font should be italic and have a size of 20pt. Now we create a format ( XL.Book.AddFormat ) and transfer the created font to this format ( XL.Format.SetFont ). In a format, you could now enter additional information about the border, for example. You can then specify the format as an additional parameter in the XL.Sheet.CellWriteText function, for example.

Set Variable [ $Font ; Value: MBS("XL.Book.AddFont"; $Book) ] 
Set Variable [ $r ; Value: MBS("XL.Font.SetName"; $Book; $Font; "Comic Sans MS") ] 
Set Variable [ $r ; Value: MBS("XL.Font.SetItalic"; $Book; $Font; 1) ] 
Set Variable [ $r ; Value: MBS("XL.Font.SetSize"; $Book; $Font; 20) ] 
Set Variable [ $CellFormat ; Value: MBS("XL.Book.AddFormat"; $Book) ] 
Set Variable [ $r ; Value: MBS( "XL.Format.SetFont"; $Book; $CellFormat; $Font ) ] 
Set Variable [ $r ; Value: MBS("XL.Sheet.CellWriteText"; $Book; $Sheet; 1; 0; "Hello"; $CellFormat) ] 

Another possibility to transfer formatting from the FileMaker database to the fields is the XL.Sheet.CellWriteStyledText function. This takes the formatting from your field in FileMaker

Set Variable [ $r ; Value: MBS( "XL.Sheet.CellWriteStyledText"; $Book; $Sheet; 1; 6; DoorThirteen::Text ) ] 

If you now want to write this Excel document to a file, you can use the XL.Book.SaveToFile function to write the document from the working memory to a file. If you want to place the file in a container in your database, please use XL.Book.Save.

Set Variable [ $r ; Value: MBS( "XL.Book.SaveToFile";$Book; "/Users/sj/Desktop/AdventXl.xlsx" ) ] 

So that the memory is again free and can continue to be used, call XL.Book.ReleaseAll to remove all Excel working environments. If you only want to remove a specific one, use the XL.Book.Release function and specify the reference in the parameters.

But we can not only create files, we can also load existing files. To do this, we use the XL.LoadBook function, which gives us an Excel file that is located in a file at a specific path or in a container. As a return we then get the reference number with which we can continue working.

Set Variable [ $Book ; Value: MBS( "XL.LoadBook"; "/Users/sj/Desktop/AdventXl.xlsx") ]

Just as we have different functions for writing values, we also have different functions for reading cells. With the XL.Sheet.CellReadValue we have a function that reads the value from the cell and returns a value of the corresponding type. We also have the appropriate function for each individual type.

Set Variable [ $A2 ; Value: MBS("XL.Sheet.CellReadText"; $Book; 0; 1; 0) ] 
Set Variable [ $B2 ; Value: MBS("XL.Sheet.CellReadNumber"; $Book; 0; 1; 1) ] 
Set Variable [ $C2 ; Value: MBS("XL.Sheet.CellReadNumber"; $Book; 0; 1; 2) ] 
Set Variable [ $D2 ; Value: MBS("XL.Sheet.CellReadFormula"; $Book; 0; 1; 3) ] 

The Componte XL has many more cool functions available, just take a look at the documentation or try out our examples. I wish you lots of fun with your tables.

1 Like

Door 14 - Progress Dialog

Fact of the day
And speaking of waiting, did you know that in Gary, Indiana, the law says you can't go to the movies or theater until four hours after eating garlic?

Do you know this? You have to go through many records and it takes and takes and takes time? Some users then become frustrated and start hammering on the keyboard or pressing all kinds of buttons because they don't know that there are already processes running in the background that simply need its time.

With the MBS Progress dialog, you can display a dialog to your users so that there is no confusion or panic. To do this, you have the functions from the ProgressDialog component.

Before we can display the progress dialog, we first have to make a few settings for the dialog. The special thing about the dialog is that you can set the content in a script and then call it up at a later time in the different layouts in our solution. For this reason, we must first reset the dialog to its initial state to remove legacy content before we make new settings. To do this, we use the ProgressDialog.Reset function. Now we can give the dialog box a title. We use ProgressDialog.SetTitle for this. In the dialog you can also enter a text above the progress bar and one below. We use the ProgressDialog.SetTopText and ProgressDialog.SetBottomText functions for this. We can also select a font and size for these two texts. We set these values in the ProgressDialog.SetFont function. To make the dialog look nicer, we can also display an icon in the dialog to make it easier to understand. In our case, this is the monkey with the Christmas hat. We set this image, which is in a container, with the ProgressDialog.SetImage. If you use a PNG with transparent background, you don't get the white border around.

Set Variable [ $r ; Value: MBS("ProgressDialog.SetTitle"; DoorFourTeen::Title) ]
Set Variable [ $r ; Value: MBS("ProgressDialog.SetFont"; "Comic Sans MS"  ; 14  ) ]
Set Variable [ $r ; Value: MBS("ProgressDialog.SetTopText"; DoorFourTeen::Text) ]
Set Variable [ $r ; Value: MBS("ProgressDialog.SetBottomText"; "Wait...") ]
Set Variable [ $r ; Value: MBS("ProgressDialog.SetImage"; DoorFourTeen::Container) ]

If we want to display the dialog, we use the ProgressDialog.Show function. This shows the dialog. The dialog is displayed as long as FileMaker is not closed or the ProgressDialog.Hide function is not called.

We see that the dialog still has a button, which is usually used as a cancel button. If we do not want this button to be displayed, we use the ProgressDialog.SetShowButton function and pass a 0 in the parameters. If we want to change the text of the button, we use the ProgressDialog.SetButtonCaption function to which we pass the text to be displayed on the button. If we leave the script as it is and display the dialog, nothing happens when we click on the button. Because we first have to define a script that is called when the button is pressed or specify an expression. If we want to specify a script, we use the Progressdialog.SetScript function. In our example, we use an expression that we specify in the Progressdialog.SetEvaluate function. We want to set the cancel flag in this function to 1. I'll explain what this does for us in more detail later. The flag is set with the ProgressDialog.SetCancel function *.

Set Variable [ $r ; Value: MBS("Progressdialog.SetEvaluate"; "MBS(\"ProgressDialog.SetCancel\"; 1)") ] 

Now our dialog should not only show the standard animation of the bar, but should also show the progress. In our case, we would like to display a bar that runs for approximately a number of seconds, but it could also be the passes with which data records are written to your database, or whatever else you need the bar for. To do this, we first set the progress bar to the value 0, i.e. at the very beginning of the bar. Then we pause for a second because the animation in the progress dialog should run to the end before it starts at 0. The value that we set in ProgressDialog.SetProgress again and again, in the following loop, can have values between 0 and 100. For this reason, we first want to calculate how much the value of the progress increases with each loop run. To do this, we calculate 100/number of loop passes. We then always add this calculated value to the current value in the loop and enter this as the new value to be set. The value for the loop passes originally comes from a field. Here we have to make sure that the field is of type number, otherwise the value is recognized as text and the number of loop passes is usually incorrect.

Within the loop, we also change the text below the progress bar. To do this, we again use the ProgressDialog.SetBottomTextfunction. To ensure that this change, as well as all other changes in the dialog, are displayed reliably, we call the ProgressDialog.Update function, which sets a flag so that the dialog is redrawn when the memory usage allows it.

We exit the loop when we are either done with the runs or when the cancel flag is set. This means when someone has pressed the cancel button in the dialog. We query this flag with ProgressDialog.GetCancel.

After the loop has been exited, we then hide the dialog again with ProgressDialog.Hide

Set Variable [ $r ; Value: MBS("ProgressDialog.SetProgress"; 0) ] 
Pause/Resume Script [ Duration (seconds): 1 ] 
Set Variable [ $TotalLoops ; Value: DoorFourTeen::Time ] 
Set Variable [ $CurrentProgress ; Value: 0 ] 
Set Variable [ $ProgressValue ; Value: 100/$TotalLoops ] 
Set Variable [ $i ; Value: 0 ] 
	Set Variable [ $CurrentProgress ; Value: $CurrentProgress + $ProgressValue ] 
	Set Variable [ $r ; Value: MBS("ProgressDialog.SetProgress"; $CurrentProgress) ] 
	Set Variable [ $r ; Value: MBS("ProgressDialog.SetBottomText"; $i+1 & " of " & $TotalLoops) ] 
	Exit Loop If [ $i  ≥ $TotalLoops or MBS("ProgressDialog.GetCancel") ] 
	Pause/Resume Script [ Duration (seconds): 1 ] 
	Set Variable [ $r ; Value: MBS("ProgressDialog.Update") ] 
	Set Variable [ $i ; Value: $i+1 ] 
End Loop
Set Variable [ $r ; Value: MBS("ProgressDialog.Hide") ] 

I hope you enjoyed this door and can't wait to see your progress bars in your solutions.

  • The cancel flag is set automatically if you don't add a custom action to the button, but we still like to show you how to do the evaluate.

Door 15 - DynaPDF

Fact of the day
The groundwork for today's PDF documents was laid back in 1991 with the Camelot project. The aim was to develop a file format that can capture data from all programs, this document can be shared and displayed in the same way on any end device so that it can also be printed

Today I would like to introduce you to an area that is very appreciated by our customers. We are talking about DynaPDF. With our DynaPDFfunctions you can work with your PDF documents. You can create PDF documents according to your wishes, write in these documents, sign them, create forms and much much more. In order to be able to work with DynaPDF without a watermark being permanently superimposed on your PDF pages, you need the correct additional DynaPDF license. Which license you need depends on what you want to do with DynaPDF. If you are only working create a PDF from scratch, for example, then all you need is a Starter license. If you want to load an existing PDF file, you need a Lite license. If you also want to optimize or render your PDF file, you need a Professional license. This table can help you decide which licenses you need.

Today in the example I would like to show you how to merge two PDF files and then add page numbers to them. As with LibXL, we first have to initialize DynaPDF and specify a library to work with. From the examples, you can use the InitDynaPDF script in your database and place the appropriate library file in the same folder as your database, or you can use the DynaPDF.Initialize function and enter the path to the appropriate library and your license key in the parameters. If you do not have a license key yet, because you want to test DynaPDF, then leave the license key blank. If you want to test whether your solution works with a specific license, then write the license name in the place of the license key. If you are using Windows, you must use the dll files. There are two dll files, one is for use on 32 bit systems and the other on 64 bit systems. On Mac you need the file with the extension .dylib. For use on a Linux server you need the dynapdf.linux.so file.

You need to perform the initialization before you use the first DynaPDF function. If you are working with the initialization script from the examples, it is advisable to place the following lines before each script in which DynaPDF functions are used.

If [ MBS("DynaPDF.IsInitialized")  ≠  1 ] 
	Perform Script [ Specified: From list ; "InitDynaPDF" ; Parameter:    ]
End If
If [ MBS("DynaPDF.IsInitialized")  ≠  1 ] 
	Exit Script [ Text Result:    ] 
End If

First we have to create a fresh working environment into which we can import our two files one after the other. To do this, we use the DynaPDF.New function. This creates the working environment and returns a reference number with which we can continue working in the other functions.

Set Variable [ $pdf ; Value: MBS("DynaPDF.New") ]

Before we can import a file, we have to open this file. We can either import files from a PDF file on disk, in which case we use the DynaPDF.OpenPDFFromFile function, or we can import the file from a container, in that case we use the DynaPDF.OpenPDFFromContainer function to open the file. In the parameters, we first specify our working environment, followed by the file path or container. If your document to be opened here has a password, we can first enter the password type and then the password itself. Now we come to the import. If you only want to import one page, it is best to use the DynaPDF.ImportPDFPage function. If you want to import several pages, as in this example, use the DynaPDF.ImportPDFFile function. Here we specify the working environment in which we want to write our import and specify the page on which the first page of the imported document is to be placed. In Document 1, this is the first page. The function returns how many pages it has imported. We need this information for the start page of document two. We repeat the process with the second document.

# Combine 
Set Variable [ $pdf1 ; Value: MBS("DynaPDF.OpenPDFFromContainer"; $pdf; DoorFifteen::FirstDocument) ] 
Set Variable [ $pages ; Value: MBS("DynaPDF.ImportPDFFile"; $pdf; 1) ] 
Set Variable [ $pages ; Value: $pages + 1 ] 
Set Variable [ $pdf2 ; Value: MBS("DynaPDF.OpenPDFFromContainer"; $pdf; DoorFifteen::SecondDocument) ] 
Set Variable [ $pages ; Value: MBS("DynaPDF.ImportPDFFile"; $pdf; $pages) ] 

Now we have a document in the working environment that contains both PDF files and it would be nice if we could number these pages. To do this, we write a text with the page number in a specific place on each individual page. First we set the coordinate alignment so that we can orient ourselves better. Normally the coordinates run from bottom to top and from left to right. We now want to change this and set the coordinates to take on larger values from top to bottom. This orientation is more intuitive for most people.

Then we determine the number of pages that are in the working environment. To do this we use the DynaPDF.GetPageCount function. We also specify the page number we want to start with. Before we can write the text on the page, we want to determine the dimensions of the page with DynaPDF.GetPageWidth and DynaPDF.GetPageHeight. With DynaPDF.EditPage we make a page whose index we specify in the parameters editable. We then use the DynaPDF.SetFont function to set the font and font size of the text that we want to write with the DynaPDF.WriteFTextEx function. This function writes the text with formatting commands to the current open page. In the parameters we specify our reference again, followed by the position of the text. We determine the position of the top left corner of the text field in our case 50 pixels away from the left and bottom edge. Then comes the size of the text field. In our case, we take the page width minus the margins to the right and left of 50 pixels. The height is 30. Now we can use the text alignment to decide exactly where our page number should be. We want it to be on the right-hand side, so we choose right-aligned text. Last but not least follows our text. This is made up as follows: Page number of total number of pages.

After we write our text on the page, we can stop editing this page and run through the loop again until we have reached the last page.

Set Variable [ $r ; Value: MBS( "DynaPDF.SetPageCoords"; $PDF; "TopDown" ) ] 
Set Variable [ $PageCount ; Value: MBS( "DynaPDF.GetPageCount"; $pdf ) ] 
Set Variable [ $PageNumber ; Value: 1 ] 
Set Variable [ $pageWidth ; Value: MBS("DynaPDF.GetPageWidth"; $pdf) ] 
Set Variable [ $pageHeight ; Value: MBS("DynaPDF.GetPageHeight"; $pdf) ] 
	Set Variable [ $r ; Value: MBS("DynaPDF.EditPage"; $pdf; $PageNumber) ] 
	Set Variable [ $r ; Value: MBS( "DynaPDF.SetFont"; $pdf; "Helvetica"; 0; 20) ] 
	Set Variable [ $r ; Value: MBS( "DynaPDF.WriteFTextEx"; $pdf; 50; $pageHeight - 50; 
	 	$pageWidth-100; 30; "right"; GetAsText($PageNumber) & " of " & $PageCount) ] 
	Set Variable [ $r ; Value: MBS("DynaPDF.EndPage"; $pdf) ] 
	Set Variable [ $PageNumber ; Value: $PageNumber +1 ] 
	Exit Loop If [ $PageNumber > $PageCount ] 
End Loop

Finally, we save the PDF document in a separate container. To do this, we use the DynaPDF.Save function.

Set Field [ DoorFifteen::Combine ; MBS( "DynaPDF.Save"; $PDF ; "Combine.pdf") ] 

After we have finished our work we have to release the working memory. If we want to release all references at the same time we use the function "DynaPDF.ReleaseAll. If we only want to release a single reference, we can use the DynaPDF.Release function by specifying it in the parameters.

I hope you liked the door this time as well and we'll see us again tomorrow for the next door.

Door 16 - GraphicsMagick

Fact of the day
MBS already had an advent calendar for last year. So take a trip back in time to last year and enjoy the articles

Did you know that with FileMaker and the component GraphicsMagick you can change a lot in your images?

Last year we had an advent calendar that only dealt with GraphicsMagick. You are welcome to have a look here as well. To be able to work with an image, the image must be loaded from a file or a container. The appropriate function then gives us a reference number with which we can continue working.

Set Variable [ $Image ; Value: MBS("GMImage.NewFromContainer"; DoorSixTeen::Container) ]

First of all, you can easily query the height and width of an image in a container using the functions GMImage.GetWidth and GMImage.GetHeight.

If we want, we can rotate the image. To do this, we use the GMImage.Rotate function. If we enter a positive angle in the parameters, we rotate to the right. If we enter a negative angle, we rotate to the left.

We can also mirror such an image vertically or horizontally. To do this, we use the functions GMImage.Flip for vertical flipping and GMImage.Flopfor horizontal mirroring.

There are also several effects available for editing the image. Let's start with the blur effect. We can create this with GMImage.Blur. We can then set the intensity in the parameters.

We can also use the Sharpen effect with the GMImage.Sharpen function when editing images. However, this effect is not the opposite function to Blur!

If we want to use the effect of a charcoal drawing, GMImage.Charcoal is there to help us.

If you want to try your hand at being a real Leonardo da Vinci, you can use the GMImage.OilPaint function to apply an oil painting effect to your photos.

If you want everything to be really swirly on your images, use the GMImage.Swirl function. This creates a swirl in your image.

A little depth in the images can often do no harm either. You can add an embossing effect with the GMImage.Emboss function.

Grayscale images in photography can be very aesthetic and give the image a completely different mood. Use the GMImage.SetType function and enter a 2 in the parameters to obtain a grayscale image.

# Load from container 
Set Variable [ $Image ; Value: MBS("GMImage.NewFromContainer"; DoorSixTeen::Original) ] 
# Grayscale
Set Variable [ $result ; Value: MBS("GMImage.SetType";$Image; 2) ] 
If [ MBS("IsError") = 0 ] 
	# Write to container
	Set Variable [ $result ; Value: MBS("GMImage.WriteToPNGContainer";$Image) ] 
	Set Field [ DoorSixTeen::Output ; $result ] 
End If
# Release image
Set Variable [ $r ; Value: MBS("GMImage.Free"; $Image) ] 

Many people only see the negative in everyday life. For you, this should only be the case with images. You can use the GMImage.Negatefunction to view the negative of an image. If you use the function twice in a row, you will get back the original image.

The GMImage.Solarize function offers us a similar effect. However, if we use the function twice in a row, the pure white areas remain black. If we combine the function with GMImage.Negate, our monkey gets a pink cap. The functions are therefore similar but not identical.

This and much more awaits you in the GraphicsMagick component. I hope you enjoy using it!

1 Like

Door 17 - Dialogs

Fact of the day
Did you know that you can also influence FileMaker's own dialogs a little?
Take a look at the DialogModifications component in the plugin.

The dialog box for displaying information to the user is essential and can be found in almost every application. But sometimes you want more design options for the dialog box and this is where the plugin comes into play, because with this you can build your dialog box according to your wishes. I will show you how this works in this door.

Such a dialog is valid for the entire application, which means that you can make the settings for a dialog in one script and call the dialog in another script. However, this also means that we have to get rid of old settings at the beginning. To do this, we use the Dialog.Reset function, which resets all dialog settings to their original state.

Set Variable [ $r ; Value: MBS( "Dialog.Reset" ) ]

Now we can start making our settings. We can set the text to be displayed in the dialog. We use the function Dialog.SetMessage for this. If we also want to display an additional information text (a text in a smaller font), this can be specified with Dialog.SetInformativeText. Icons can be very practical so that you can see at first glance what the dialog might be about. You can use the Dialog.SetIcon function to bring this icon into your dialog from a container, for example. Would you like to display your text in the dialog right-aligned? No problem Dialog.SetTextAlignment makes it possible. All you have to do is select the appropriate alignment in the parameters.

Set Variable [ $r ; Value: MBS( "Dialog.SetMessage"; DoorSevenTeen::Text ) ] 
Set Variable [ $r ; Value: MBS( "Dialog.SetInformativeText"; DoorSevenTeen::Infotext ) ] 
Set Variable [ $r ; Value: MBS( "Dialog.SetIcon"; DoorSevenTeen::Icon ) ] 
Set Variable [ $r ; Value: MBS( "Dialog.SetTextAlignment"; "right") ] 

Would you like to have several options for the buttons? That's no problem at all to have up to 10 more buttons in addition to the default button. First of all, we can change the title of the default button if we don't like the default OK. To do this, we use the function Dialog.SetDefaultButton and enter the new name in the parameters. If we now want more buttons, we can use the function Dialog.SetButton to set the titles of the buttons that then appear. In the parameters, we specify the index to say which button we want to add now, followed by the title.

Set Variable [ $r ; Value: MBS( "Dialog.SetDefaultButton"; "DefaultButton" ) ] 
Set Variable [ $r ; Value: MBS( "Dialog.SetButton"; 1; "Santa Clause" ) ] 

But we can not only add buttons, for example, if you need information from your user, you can also use fields under Mac in the dialog. The Dialog.AddField function is available for this purpose. Here we have many optional options in addition to the label title of the field.

MBS( "Dialog.AddField"; Label { ; Text; Placeholder; Password; TextViewHeight } )  

After the title, we can place a text that has already been entered in the field. This is useful, for example, if the data has already been entered in the database and you want to check it again so that the user can make changes if necessary.
With the Placeholder parameter, you can display a text in the field that tells you what should be entered in the field. The Password parameter expects a 0 or 1 as a value and determines whether only dots are displayed in the field instead of the plain text, as we know it from password entries. Last but not least, we can also influence the height of the text field. With a higher text field, we can specify a list, for example. If the value we specify in this parameter, is higher than 20, the field changes from a standard field to a text view. This text view behaves a little differently to a normal field, as we cannot create a placeholder text or use a password option. The values that are set in this way are simply ignored, but we can already enter text in this field. The following configuration creates this dialog

# Fields
Set Variable [ $r ; Value: MBS( "Dialog.AddField"; "Name" ) ] 
Set Variable [ $r ; Value: MBS( "Dialog.AddField"; "FirstName" ; ""; " Write your name" ) ] 
Set Variable [ $r ; Value: MBS( "Dialog.AddField"; "Do you like gingerbread" ; "Yes"; "Type 'Yes' or 'No'" ) ] 
Set Variable [ $r ; Value: MBS( "Dialog.AddField"; "Your Secret Santa" ; ""; ""; 1 ) ] 
Set Variable [ $r ; Value: MBS( "Dialog.AddField"; "Your special wish" ; ""; "What do you want for Christmas"; 0; 50 ) ] 

We can also determine the position of the dialog on the screen. To do this, we can specify the coordinates of the upper left corner of the dialog.

# Position 
Set Variable [ $r ; Value: MBS( "Dialog.SetTop"; 100 ) ] 
Set Variable [ $r ; Value: MBS( "Dialog.SetLeft"; 100 ) ] 

Under windows the dialog looks a little different. Here the dialog also has a title bar. We can set this title with Dialog.SetWindowTitle.

If we now run the script, we don't see anything yet, because we first have to give the program the command that the dialog should be displayed. We do this with Dialog.Run.

# Show Dialog
Set Variable [ $r ; Value: MBS( "Dialog.Run" ) ] 

At this point, our script is stopped and we are shown the dialog. We can now enter our values. By clicking on one of the buttons, the dialog disappears. Now, of course, we want to know what the user has clicked or what is in the fields. We can find out which button was clicked with the Dialog.GetButtonPressed function. This provides us with the index of the button that was pressed. We can then use the Dialog.GetButton function to find out the title of the button. We have to be careful here. If we have previously omitted an index when creating the button, a mistake will occur here and we will get back the wrong title of the button. It is therefore important to ensure that sequential indexes are used when creating the button.

# Results
# Button
Set Variable [ $Button ; Value: MBS( "Dialog.GetButtonPressed" ) ] 
Set Variable [ $TitelOfButton ; Value: MBS("Dialog.GetButton"; $Button) ] 
Set Field [ DoorSevenTeen::Button Result ; $TitelOfButton ] 

For each field that we have in our dialog, we query the value individually. This is where the Dialog.GetFieldText function comes into play. We enter the index of the field we want to query and get the content back.

Set Variable [ $F1 ; Value: MBS("Dialog.GetFieldText"; 0) ] 
Set Variable [ $F2 ; Value: MBS("Dialog.GetFieldText"; 1) ] 
Set Variable [ $F3 ; Value: MBS("Dialog.GetFieldText"; 2) ] 
Set Variable [ $F4 ; Value: MBS("Dialog.GetFieldText"; 3) ] 
Set Variable [ $F5 ; Value: MBS("Dialog.GetFieldText"; 4) ] 
Set Field [ DoorSevenTeen::Fields Result ; "Name:" & $F1 & "¶¶First name:" & $F2 & "¶¶Gingerbread:" & $F3 & "¶¶Your Secret Santa:" & $F4 & "¶¶Wish:" & $F5 ] 

If we have now entered the information in our dialog, a return can look like this, for example:

I hope you enjoyed this insight into the dialogs.


Door 18 - Preview

Fact of the day
Did you know that you can also embed an XML file in PDF documents? This is also done with the ZUGFeRD format, for example. With the help of DynaPDF and the plugin, you can create and read such files yourself

Did you know that MBS offers a PDF preview for Windows and Mac, with which you can view the document and even copy content with the mouse?

We have included this control in our plugins since version 13.3 of this year. You can use this function on Windows 10 and MacOS. You can easily test whether your system fulfills the requirements with the Preview.Available function.

If [ MBS( "Preview.Available" ) ] 

If this fits, you can create the preview control. We have two options for this. First, we can create a PDF control with a fixed size and a specific position. To do this, we use the Preview.Create function. We first enter the window reference in the parameters. If it is the window that is furthest forward, enter a 0 or find out the reference with Window.FindByTitle or Window.FindByIndex. Then comes the position, which we specify with x and y for the top left corner. Finally, enter the width and height of the control.

Set Variable [ $$Preview ; Value: MBS( "Preview.Create"; 0; 177; 64 ; 320; 420 ) ] 

The other option is to position the preview with a control. To do this, we use the Preview.CreateWithControl function. For example, a rectangle that has been positioned on the layout can be used as such a control. In the parameters, we again specify the window reference and also the name of the placeholder. In this case it is now Placeholder. If required, we can now specify an offset. In other words, values that move the preview from the placeholder object by a certain value, but we are don't use this here.

Set Variable [ $$Preview ; Value: MBS( "Preview.CreateWithControl"; 0; "Placeholder"  ) ] 

Before we can see the PDF, we must first load it. Again, we have two options. It can be from a container, in which case we use the "Preview.LoadContainer" function.

Set Variable [ $r ; Value: MBS( "Preview.LoadContainer"; $$Preview; DoorEightTeen::Container ) ]

The other option is to load the PDF file from a file that is stored in a path. Then we use the "Preview.LoadFile" function.

Set Variable [ $r ; Value: MBS( "Preview.LoadFile"; $$Preview; DoorEightTeen::File ) ]

The PDF file should now be displayed and we can view the pages and select and copy content using the mouse menu.

If you want to remove the file from the PDF view again, but the preview may be needed again. The function Preview.Unload unloads the current PDF document.

If you no longer need the preview, release the preview again. You can use Preview.Release to release a single preview by specifying the preview reference, or you can use Preview.ReleaseAll to release all currently existing previews at the same time.

I hope the new preview can help you in your work with PDF documents.