Scripting

From DXLog.net
Jump to navigation Jump to search

Introduction to C#

DXLog.net has been written in such a way that custom C# scripts can be compiled into DXLog.net at runtime.
With only basic knowledge of C# you can create powerful additions to the program.
This makes DXLog.net a very versatile and powerful contest logging tool.

To learn the basics about C# there are plenty of resources available on the internet.
Some examples are

http://www.homeandlearn.co.uk/csharp/csharp.html
https://youtu.be/gfkTfcpWqAY
https://youtu.be/UKaZ2S4AJAA


Microsoft have a free fully functional version of Visual Studio called Community, available from https://visualstudio.microsoft.com/vs/community

C# script structure

Scripts must start with references to the relevant objects:

  //INCLUDE_ASSEMBLY System.dll
  //INCLUDE_ASSEMBLY System.Windows.Forms.dll
  //INCLUDE_ASSEMBLY CWKeyer.dll

Each script needs to implement three methods, as shown in the examples below:

Initialize - is called when the script is compiled and the object created. This is used to hook on events or initialize additional properties etc.

Deinitialize - is called when the object is going to be closed, for example when we exit DXLog.net.

Main - is called each time the script is called with a defined shortcut or from a macro message.

To access DXLog.net internal objects and methods from the script, three main objects are available:

FrmMain - Refers to the main Window - from this you can access any other public object in main code and you can access public properties, methods, hook on events etc.
This object has access to everything within in the main DXLog.net code.

ContestData - Contains all the properties and methods related to the currently open log.

COMMain - The main communications object which is used to access everything that is configured in the "Configure interfaces" window.
Via this you can e.g. access the radio(s) or any other enabled serial or parallel port object.

Required references in Visual Studio's Solution Explorer

An extremely useful feature of Visual Studio is the "Intellisense" mechanic which allows
not only as-you-type syntax checking but also interactive searching for available objects and methods.
By adding references to the DXLog.net binaries in Visual Studio's Solution Explorer, Intellisense will be
able to automatically list available options for objects as you type, and you will also be able to search
for objects and methods by name using the top search bar in Solution Explorer or by using the Object Browser.

You add references to the binaries by right-clicking on "References", select "Add Reference" and in the
bottom of the Reference Manager panel use the "Browse" button. The DXLog.net binaries are typically located
in C:\Program Files (x86)\DXLog.net on 64-bit Windows or C:\Program Files\DXLog.net
on 32-bit Windows.
Important: the custom C# code script must be included as source code, DO NOT COMPILE.
Scripts can be located anywhere on your hard drive but must always be added using the scripts
manager (Tools|Scripts Manager) in DXLog.net.



Example scripts

Various example scripts are available for download from http://dxlog.net/sw/#files%2Fcustomization%2Fscripts

For more advanced use cases, a boolean FrmMain.ScriptContinue is available. If this is set true in
the script, executing the script from a keypress will also execute the normal function of the key
after having executed the script.

The following script is an example of a C# script which keeps the Elecraft K3
keyer speed in sync with the CW speed set in DXLog.net.


 //INCLUDE_ASSEMBLY System.dll
 //INCLUDE_ASSEMBLY System.Windows.Forms.dll
 //INCLUDE_ASSEMBLY CWKeyer.dll
 
 using System;
 using System.Windows.Forms;
 using CWKeyer;
 
 namespace DXLog.net
 {
      public class Script : ScriptClass
      {
          FrmMain mainForm;
 
          public void Initialize(FrmMain main)
          {
              mainForm = main;
              if (mainForm._cwKeyer != null)
                  mainForm._cwKeyer.CWSpeedChange += new CWKey.CWSpeedChangeDelegate(handleCWSpeedChange);
          }
  
          public void Deinitialize()
          {
              // Unclear if this is required 
              if (mainForm._cwKeyer != null)
                  mainForm._cwKeyer.CWSpeedChange -= handleCWSpeedChange;
          }
 
          public void Main(FrmMain main, ContestData cdata, COMMain comMain)
          {
          }
 
           private void handleCWSpeedChange(int radioNumber, int newSpeed)
           {
               CATCommon radioObject = mainForm.COMMainProvider.RadioObject(radioNumber);
               if (radioObject == null)
               {
                   mainForm.SetMainStatusText(String.Format("CAT object for radio {0} isn't available!", radioNumber));
                   return;
               }
               radioObject.SendCustomCommand(String.Format("ks0{0};", newSpeed));
               mainForm.SetMainStatusText(String.Format("Radio {0} CW speed changed to {1} wpm!", radioNumber, newSpeed));
           }
      }
 }


The following script triggers the built-in digital voice keyer on ICOM 7000-series radios.
Important: Only serves as an example, this functionality is built into DXLog.


 //INCLUDE_ASSEMBLY System.dll
 //INCLUDE_ASSEMBLY System.Windows.Forms.dll
  
 using System;
 using IOComm;
  
 namespace DXLog.net
 {
     public class IcomRunDVK1 : ScriptClass
     {
         const byte DVK = 1;
       
         // Bare CI-V Command to trigger DVK. 
         // Method SendCustomCommand() adds preamble, address, etc.
         readonly byte[] IcomDVKCommand = new byte[] { 0x28, 0x00, DVK }; 
  
         public void Initialize(FrmMain main) { }
  
         public void Deinitialize() { }
  
         public void Main(FrmMain main, ContestData cdata, COMMain comMain)
         {
             CATCommon radioObject;
             bool modeIsSO2V = (cdata.OPTechnique == ContestData.Technique.SO2V);
  
             if (modeIsSO2V) // if SO2V send command to radio 1 regardless if VFO A or B are focused
                 radioObject = comMain.RadioObject(1);
             else
                 radioObject = comMain.RadioObject(cdata.FocusedRadio);
  
             if (radioObject == null)
             {
                 main.SetMainStatusText(String.Format("IcomRunDVK1: Radio {0} is not available.", 
                     cdata.FocusedRadio));
                 return;
             }
             radioObject.SendCustomCommand(IcomDVKCommand);
         }
     }
 }


The following script uses the FocusedRadioChange event to switch VFO knob focus
and Dual Watch in SO2V on ICOM radios with dual receivers.
Important: Only serves as an example, this functionality is built into DXLog.


 //INCLUDE_ASSEMBLY System.dll
 //INCLUDE_ASSEMBLY System.Windows.Forms.dll
  
 using System;
 using IOComm;
  
 namespace DXLog.net
 {
     public class IcomSO2V : ScriptClass
     {
         ContestData cdata;
         FrmMain mainForm;
  
         readonly byte[] IcomDualWatchOn = { 0x07, 0xC1 };
         readonly byte[] IcomDualWatchOff = { 0x07, 0xC0 };
         readonly byte[] IcomSelectMain = { 0x07, 0xD0 };
         readonly byte[] IcomSelectSub = { 0x07, 0xD1 };
         readonly byte[] IcomSplitOff = { 0x0F, 0x00 };
         static byte[] IcomSetSpeed = new byte[4] { 0x14, 0x0C, 0x00, 0x00 };
  
         bool tempStereoAudio;
 
         // Executes at DXLog.net start 
         public void Initialize(FrmMain main)
         {
             CATCommon radio1 = main.COMMainProvider.RadioObject(1);
             cdata = main.ContestDataProvider;
             mainForm = main;
                
             // Initialize temporary stereo mode to DXLog's stereo mode to support temporary toggle
             tempStereoAudio = (mainForm.ListenStatusMode != 0); 
  
             // Only subscribe to event and initialize if radio is ICOM
             if (radio1 != null)
                 if (radio1.IsICOM()) { // nesting if since method only valid if radio1 is != null
                     cdata.FocusedRadioChanged += new ContestData.FocusedRadioChange(HandleFocusChange);
  
                     // Initialize radio to DW off, Split off and Main VFO focused
                     radio1.SendCustomCommand(IcomDualWatchOff); 
                     radio1.SendCustomCommand(IcomSelectMain);
                     radio1.SendCustomCommand(IcomSplitOff);
                 }
         }
  
         public void Deinitialize() { } // Do nothing at DXLog.net close down
  
         // Toggle dual watch, execution of Main is mapped to a key, 
         // typically in upper left corner of keyboard
         public void Main(FrmMain main, ContestData cdata, COMMain comMain)
         {
             int focusedRadio = cdata.FocusedRadio;
             CATCommon radio1 = comMain.RadioObject(focusedRadio);
             bool radio1Present = (radio1 != null);
  
             if ((focusedRadio == 1) && cdata.OPTechnique == 4) // Only active in SO2V
                if (tempStereoAudio)
                 {
                     if (radio1Present)
                         radio1.SendCustomCommand(IcomDualWatchOff);
                 }
                 else
                 {
                     if (radio1Present)
                         radio1.SendCustomCommand(IcomDualWatchOn);
                 }
             tempStereoAudio = !tempStereoAudio;
         }
  
         // Event handler invoked when switching between radios (SO2R) or VFO (SO2V) in DXLog.net
         private void HandleFocusChange()
         {
             CATCommon radio1 = mainForm.COMMainProvider.RadioObject(1);
             int focusedRadio = mainForm.ContestDataProvider.FocusedRadio;
             // ListenStatusMode: 0=Radio 1, 1=Radio 2 toggle, 2=Radio 2, 3=Both
             int listenMode = mainForm.ListenStatusMode;
             bool stereoAudio = (listenMode != 0);
             bool modeIsSo2V = (mainForm.ContestDataProvider.OPTechnique == 4);
             int ICOMspeed;
  
             if (radio1 == null)
             {
                 mainForm.SetMainStatusText("IcomSO2V: Radio 1 is not available.");
                 return;
             }
  
             if (modeIsSo2V) // Only active in SO2V and with ICOM
             {
                 // Set temporary stereo mode to DXLog's stereo mode to support temporary toggle
                 tempStereoAudio = stereoAudio; 
  
                 if (focusedRadio == 1)
                     radio1.SendCustomCommand(IcomSelectMain);
                 else
                     radio1.SendCustomCommand(IcomSelectSub);
    
                 if (stereoAudio || (focusedRadio == 2))
                     radio1.SendCustomCommand(IcomDualWatchOn);
                 else
                     radio1.SendCustomCommand(IcomDualWatchOff);
             }
         }
     }
 }


The following script does a more sophisticated synchronization of radio keyer
speed for ICOM radios and also updates the speed when switching focus between VFO in SO2V.


   //INCLUDE_ASSEMBLY System.dll
   //INCLUDE_ASSEMBLY System.Windows.Forms.dll
   //INCLUDE_ASSEMBLY CWKeyer.dll
   
   // ICOM Synchronization of built-in keyer with DXLog.net.
   // Event driven, not mapped to any key.
   // Works for up to two radios in all operating scenarios including SO2V. 
  
   using System;
   using CWKeyer;
   using IOComm;
  
   namespace DXLog.net
   {
       public class IcomSpeedSync : ScriptClass
       {
           FrmMain mainForm;
           ContestData cdata;
           int lastFocus;
  
           static byte[] IcomSetSpeed = new byte[4] { 0x14, 0x0C, 0x00, 0x00 };
  
           public void Initialize(FrmMain main)
           {
               CATCommon radio1 = main.COMMainProvider.RadioObject(1);
               cdata = main.ContestDataProvider;
               mainForm = main;
               lastFocus = 1;
   
               if ((radio1 != null) && (mainForm._cwKeyer != null))
                   if (radio1.IsICOM())
                   {
                       // Subscribe to CW speed change event
                       mainForm._cwKeyer.CWSpeedChange += new CWKey.CWSpeedChangeDelegate(HandleCWSpeedChange);
                       // Subscribe to radio focus change event
                       cdata.FocusedRadioChanged += new ContestData.FocusedRadioChange(HandleFocusChange);
                   }
           }
  
           public void Deinitialize() { }
  
           public void Main(FrmMain main, ContestData cdata, COMMain comMain) { }
  
           // Executes every time DXLog.net keyer speed is changed 
           private void HandleCWSpeedChange(int radioNumber, int newSpeed)
           {
               CATCommon radioObject;
               bool modeIsSO2V = (mainForm.ContestDataProvider.OPTechnique == 4);
               int physicalRadio, ICOMspeed;
  
               // If SO2V, physical radio is always #1
               physicalRadio = modeIsSO2V ? 1 : radioNumber;
  
               radioObject = mainForm.COMMainProvider.RadioObject(physicalRadio);
  
               if (radioObject == null)
               {
                   mainForm.SetMainStatusText(String.Format("IcomSpeedSynch: Radio {0} is not available!", physicalRadio));
                   return;
               }
  
               // Update radio's keyer speed
               ICOMspeed = (255 * (mainForm._cwKeyer.CWSpeed(radioNumber) - 6)) / (48 - 6); // ICOM scales 6-48 WPM onto 0-255
               IcomSetSpeed[2] = (byte)((ICOMspeed / 100) % 10);
               IcomSetSpeed[3] = (byte)((((ICOMspeed / 10) % 10) << 4) + (ICOMspeed % 10));
               radioObject.SendCustomCommand(IcomSetSpeed);
           }
  
           // Event handler invoked when switching between radios (in SO2R) or VFO (in SO1R and SO2V) in DXLog.net
           private void HandleFocusChange()
           {
               CATCommon radio1 = mainForm.COMMainProvider.RadioObject(1);
               int focusedRadio = mainForm.ContestDataProvider.FocusedRadio;
               bool modeIsSo2V = (mainForm.ContestDataProvider.OPTechnique == 4);
  
               if (radio1 == null)
               {
                   mainForm.SetMainStatusText("IcomSpeedSynch: Radio 1 is not available.");
                   return;
               }
  
               if (modeIsSo2V && (focusedRadio != lastFocus)) // Only active in SO2V and with ICOM. Ignores redundant events.
               {
                   lastFocus = focusedRadio;
  
                   // Update radio's keyer speed
                   int ICOMspeed = (255 * (mainForm._cwKeyer.CWSpeed(focusedRadio) - 6)) / (48 - 6); // ICOM scales 6-48 WPM onto 0-255
                   IcomSetSpeed[2] = (byte)((ICOMspeed / 100) % 10);
                   IcomSetSpeed[3] = (byte)((((ICOMspeed / 10) % 10) << 4) + (ICOMspeed % 10));
                   radio1.SendCustomCommand(IcomSetSpeed);
               }
           }
       }
   }

Custom Forms

IMPORTANT: DXLOG SOURCE CODE ONLY WORKS WITH VISUAL STUDIO 2019

DXLog.net offers a unique customization ability in that you can design your own "subwindows" also referred to as Forms.
A Form can contain information, data or metrics that you do not get from one of the standard DXLog.net windows.

A sample in the shape of a zip file can be found at http://dxlog.net/sw/#files%2Fcustomization%2Fforms

More samples may be found here:

https://github.com/bjornekelund/DXLogbuttonbar
https://github.com/bjornekelund/DXLogCustomForms
https://github.com/bjornekelund/ScenarioPanel. (the corresponding DLL is here http://dxlog.net/sw/#files%2Futilities)

A custom form is installed as a DLL file in a subfolder to DXLog.net's installation folder.
Contrary to DXLog.net script files, this means you have to compile your code into a executable library, a DLL.

Some advice for designing custom Forms in DXLog.net:

  • To write, modify and compile custom Forms you need to install Visual Studio.
The "Community" version is free for personal use.
  • The example code should be opened through its solutions file (.sln)
  • If you can not see the code, right click on the Form and select "view code"
  • Just as with the scripts, you need to add proper references to relevant parts
of the DXLog.net binaries such as DXLog.net.exe, DXLogDAL.dll etc.
Without this, Visual Studio Intellisense will complain about e.g. variable names
not being available and you will not be able to build.
  • Make sure Visual Studio is set up to build for x86.
(Click on the selection arrow where it says "any CPU" and change to "x86")
  • The shortcut key to building the binary is Shift-Ctrl-B.
  • If it does not already exist, create a folder called "CustomForms" in the DXLog.net
data folder, typically %appdata%\DXLog.net\CustomForms.
This folder is easily accessible from the File->Open configuration directory drop down menu option.
  • Copy the created DLL file (located in ...DXLCusFrm1\bin\x86\Debug or
similar, depending on your settings.) to the CustomForms folder.
  • Start DXLog.net and find a new drop-down menu called "Custom" between "Windows" and "Help".
Use it to activate your new custom Form.

You can contain as many custom Forms as you like in a single DLL but they must

  • Have different names (of the KForm)
  • Have different FormsID (change both in the CusFormID method for the class and in the Form's properties)