Print M3 programs locally

Did you ever want to quickly print a screen capture of the current M3 program using any of your local Windows printers and printer preferences?

Print Screen

One known technique is to press the Print Screen button on the keyboard, and to paste the resulting bitmap image into Microsoft Paint or Microsoft Word, and to print from there. The result is a bitmap print. The advantages are: the truthfulness of the result which is a replica of what the user sees on the screen (WYSIWYG), the ability to print locally using the locally configured Windows printers, as well as the last minute control of the printer preferences. The disadvantage is the poor non vectorial quality, and the waste of printer ink used in printing background colors.

MOM, StreamServe

The other known technique is to print using the standard M3 functionality via MOM and streamfiles to StreamServe. The result is a vectorial print. The advantages are: the high quality of the vectorial print, the ability to customize the resulting documents via StreamServe Design Center, and the pre-configuration of the printer preferences in MOM. Also, StreamServe can print more complex content like barcodes. The disadvantage is that pretty much each M3 program needs to be configured via MOM and StreamServe, which requires custom implementation, as well as the inability to print locally using the locally configured Windows printers, and the inability to have last minute control of the printer preferences.

Script

It’s actually possible to programmatically print a screen capture of the current M3 program with a simple Personalized Script for Lawson Smart Office in JScript.NET. The result is similar to using the Print Screen button, while avoiding the extra steps of pasting the bitmap image into Microsoft Paint or Microsoft Word. One click print, locally.

For that, the script must get a reference to the current M3 window, convert the content from vectorial to bitmap using RenderTargetBitmap and BmpBitmapEncoder, and to print it using PrintDocument.

import System;
import System.IO;
import System.Drawing;
import System.Drawing.Printing;
import System.Windows.Media;
import System.Windows.Media.Imaging;

package MForms.JScript {
	class PrintMe {
		var img;
		public function Init(element: Object, args: Object, controller : Object, debug : Object) {
			try {

				// get the current window
				var h = controller.RenderEngine.Host;
				var e = h.VisualElement;

				// convert vectorial to bitmap
				var RTbmap: RenderTargetBitmap = new RenderTargetBitmap(h.Width, h.Height, 96, 96, PixelFormats.Default);
				RTbmap.Render(e);
				var encoder = new BmpBitmapEncoder();
				encoder.Frames.Add(BitmapFrame.Create(RTbmap));
				var stream = new MemoryStream();
				encoder.Save(stream);
				var gdiBitmap = new Bitmap(stream);
				stream.Close();
				stream.Dispose();
				this.img = gdiBitmap;

				// print
				var pd: PrintDocument = new PrintDocument();
				pd.add_PrintPage(OnPrintPage);
				pd.Print();

			} catch (ex: Exception) {
				debug.WriteLine(ex);
			}
		}
		function OnPrintPage(sender: Object, e: PrintPageEventArgs) {
			e.Graphics.DrawImage(this.img, 0, 0);
		}
	}
}

There are also options to preview the document and to open the printer preferences but I haven’t yet succeeded in using them correctly:

(new PrintPreviewDialog()).ShowDialog();
(new PrintDialog()).ShowDialog();

Suppose you have an M3 program like this:

The result of the print would look like this (using printer PDF995):

The next step is to find a solution to add a margin, polish the print, and show the print preview and printer preferences.

The last step would be to place the script in a new Print button in the M3 panel, or to inject a new Print option in the File menu, to deploy the script on the server, and to attach it to the desired M3 programs with the XML Customization files. With that, the user would be able to quickly and locally print the M3 programs.

SQL to XML in a Script

Here’s an example of how to read data from M3’s database using SQL, and how to convert the result into XML, in a Personalized Script for Lawson Smart Office. This example is for Microsoft SQL Server.

import System.Data;
import System.Data.SqlClient;

package MForms.JScript {
     class Test {
         public function Init(element: Object, args: Object, controller : Object, debug : Object) {
             var connection = new SqlConnection('server=sqlserver;database=M3EDBTST;uid=userid;pwd=password');
             connection.Open();
             var cmd: SqlCommand = new SqlCommand('SELECT DISTINCT OKCONO, OKCUNO FROM MVXJDTA.OCUSMA', connection);
             var da: SqlDataAdapter = new SqlDataAdapter(cmd);
             var ds: DataSet = new DataSet('result');
             da.Fill(ds);
             debug.WriteLine(ds.GetXml());
         }
     }
}

It produces the following XML:

<result>
  <Table>
    <OKCONO>1</OKCONO>
    <OKCUNO>Y60000    </OKCUNO>
  </Table>
  <Table>
    <OKCONO>1</OKCONO>
    <OKCUNO>Y60001    </OKCUNO>
  </Table>
  ...
</result>

The result looks like:

Create XML in a Script

There are several techniques to create an XML document in a Personalized Script for Lawson Smart Office. The programming language is JScript.NET.

1) XElement

Here’s an example with LINQ’s XElement :

import System;
import System.Xml.Linq;

package MForms.JScript {
     class Test {
         public function Init(element: Object, args: Object, controller : Object, debug : Object) {
             var x: XElement = new XElement('hello',
                 new XAttribute('id', 'message'),
                 new XElement('world', 'Hello World!'));
             debug.WriteLine(x.ToString());
         }
     }
 }

It produces the following XML:

<hello id="message">
  <world>Hello World!</world>
</hello>

Here’s a screenshot of the result:

2) XmlDocument

Here’s an example with XmlDocument:

import System;
import System.Xml;

package MForms.JScript {
     class Test {
         public function Init(element: Object, args: Object, controller : Object, debug : Object) {
             var doc: XmlDocument = new XmlDocument();
             var dec: XmlDeclaration = doc.CreateXmlDeclaration('1.0', null, null);
             doc.AppendChild(dec);
             var root: XmlElement = doc.CreateElement('hello');
             doc.AppendChild(root);
             var child: XmlElement = doc.CreateElement('world');
             child.SetAttribute('id', 'message');
             child.InnerText = 'Hello World!';
             root.AppendChild(child);
             debug.WriteLine(doc.OuterXml);
         }
     }
}

It produces the following XML:

<?xml version="1.0"?><hello><world id="message">Hello World!</world></hello>

Here’s a screenshot of the result:

3) XmlWriter

And here’s an example with XmlWriter:

import System;
import System.Xml;
import System.Text;

package MForms.JScript {
     class Test {
         public function Init(element: Object, args: Object, controller : Object, debug : Object) {
             var s: StringBuilder = new StringBuilder('');
             var writer: XmlWriter = XmlWriter.Create(s);
             writer.WriteStartDocument();
             writer.WriteStartElement('hello');
             writer.WriteAttributeString('id', 'message');
             writer.WriteElementString('world', 'Hello World');
             writer.WriteEndElement();
             writer.WriteEndDocument();
             writer.Flush();
             debug.WriteLine(s);
         }
     }
}

It produces the following XML:

<?xml version="1.0" encoding="utf-16"?><hello id="message"><world>Hello World</world></hello>

Here’s a screenshot of the result:

4) XmlSerializer

Here’s an example with XmlSerializer:

import System.Text;
import System.Xml;
import System.Xml.Serialization;

package MForms.JScript {
     class Test {
         public function Init(element: Object, args: Object, controller : Object, debug : Object) {
             var o = new Hello();
             var s = new XmlSerializer(o.GetType(), 'thibaudns');
             var b: StringBuilder = new StringBuilder('');
              var writer: XmlWriter = XmlWriter.Create(b);
             s.Serialize(writer, o);
             debug.WriteLine(b);
         }
     }
     class Hello {
         var World: String = 'Hello World!';
     }
}

It produces the following XML:

<?xml version="1.0" encoding="utf-16"?>
<Hello xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="thibaudns">
<World>Hello World!</World>
</Hello>

Here’s a screenshot of the result:

Discussion

Here is a discussion on when to use which solution.

Note: I tested these examples with Lawson Smart Office 9.1.3.1.7.

Send SMS from Smart Office with Skype

To send an SMS text message to a mobile phone from a Personalized Script in Lawson Smart Office using Skype do:

skype.SendSms("+18472874945", "Hello World", null)

Script

A simple script that sends an SMS would be:

import System;
import System.Reflection;

package MForms.JScript {
    class SendSms {
         public function Init(element: Object, args: Object, controller : Object, debug : Object) {
             try {
                 var assembly: Object = Assembly.LoadFrom('C:\\Program Files\\Skype\\SEHE\\Interop.SKYPE4COMLib.dll');
                 var skype = assembly.CreateInstance('SKYPE4COMLib.SkypeClass');
                 skype.Attach(8, false);
                 skype.SendSms('+18472874945', 'Hello World!', null);
             } catch (ex: Exception) {
                 debug.WriteLine(ex);
             }
         }
    }
}

Note: The programming language for scripts in Smart Office is JScript.NET.

Installation

Follow these steps to run the script above:

  1. Download and install Skype on the computer that is running Smart Office (the script must communicate with Skype locally). Then sign in to Skype (the script will not work if you are not signed in). Also, your Skype account must have credit (USD, EUR, etc.) to be able to send SMS.
  2. Download and unzip Skype4COMsomewhere in your computer, for example C:\Program Files\Skype\skype4com-1.0.36\ . Skype4COM is the API used to send/receive Skype commands. Then register the DLL Skype4COM.dll with the following command:
    regsvr32 Skype4COM.dll

  3. Download and install SEHE and place the file Interop.SKYPE4COMLib.dll somewhere in your computer or somewhere on the network so that it is accessible by the Smart Office computer, for example C:\Program Files\Skype\SEHE\Interop.SKYPE4COMLib.dll or http://host/path/Interop.SKYPE4COMLib.dll . That DLL contains the Interop code to be able to call Skype4COM from the .NET framework.
  4. Launch Smart Office, and log in.
  5. For the Script Tool it is necessary to have an M3 program open, so open for example Customer. Open – CRS610.
  6. Open the Script Tool with the following command:
    mforms://jscript
  7. Copy/paste the sample script above into the Script Tool
  8. Change the path to the DLL. In my example I used C:\\Program Files\\Skype\\SEHE\\Interop.SKYPE4COMLib.dll . Make sure to escape the backslashes in the String with double backslashes.
  9. Change the phone number. It must be in international notation. In my example I used +18472874945.
  10. Click Compile
  11. Click Run
  12. Skype will show the message “LawsonClient.exe wants to use Skype”. Click Allow access; Skype will only ask once.
  13. Skype will now send the SMS. Check in your mobile phone that you received it. That’s it!

Note: I tested this on Windows XP and on Windows 7 32-bit with success. It doesn’t seem to work on Windows 64-bit. Also, I tested this in Smart Office 9.x.

More advanced script

A more elaborated script with an editable SMS text message in a pop-up looks like: