Professional UI Solutions
Site Map   /  Register
 
 
 

Scripting Support in Prof-UIS Applications

Download the project (with Prof-UIS&ProfAuto MBCS Debug libs&dlls), 3003KB

Contents

Introduction

Quite often nowadays, applications feature support for scripting. In other words such applications can be programmatically customized. This makes the application more valuable and appealing to the customers since they can add new functionality or customize existing features with script code.

Prof-UIS significantly eases implementing scripting facilities by providing the developer with a set of OLE Automation objects. This makes possible to easily customize Prof-UIS applications programmatically using one of the scripting languages available within the Active Scripting framework like VBScript or JScript. For example, you can use a script that adds some toolbars, named commands, buttons, and message handlers at runtime. Such a script may be loaded from a file, database or from whatever you want.

ProfAuto

Scripting facilities in Prof-UIS are provided by ProfAuto, a standalone COM library which should be registered with the RegSvr32 utility before it is used. ProfAuto implements a set of Automation classes for customizing graphical user interface elements at runtime programmatically:

Automation class COM interface Description
CExtAutoWindow IExtAutoWindow Allows you to manage:
  • command collection
  • menu bar (a set of menu lines in case of MDI applications)
  • toolbar collection
  • command categories
  • styles and settings
CExtAutoCommandsCollection IExtAutoCommandsCollection Allows you to add a new command or remove an existing one, to check whether the command exists, and to get the command object by its name or numeric identifier.
CExtAutoCommand IExtAutoCommand Enables you to manage a particular command object, which may be assigned to one or more toolbar buttons and/or menu items.
CExtAutoToolbarsCollection IExtAutoToolbarsCollection Allows you to access the menu bar and toolbars in the frame window as well as to add and remove user-defined toolbars.
CExtAutoToolbar IExtAutoToolbar Gives access to the toolbar's/menu bar's properties. This interface also allows you to get the collection of original buttons and collection of active buttons associated with the toolbar/menu.
CExtAutoToolButtonsCollection IExtAutoToolButtonsCollection Enables you to manage a button collection, which is associated with a toolbar, menu bar, command category, or popup menu.
CExtAutoToolButton IExtAutoToolButton Allows you to get and set properties of a particular button object. The button itself may contain a child button collection.
CExtAutoCategories IExtAutoCategories Enables you to manage command categories displayed on the Customize form.
CExtAutoStatusBar IExtAutoStatusBar Provides you with access to the status bar properties and collection of its status pane objects.
CExtAutoStatusBarItemsCollection IExtAutoStatusBarItemsCollection Allows you to add/remove panes to/from the status bar.
CExtAutoStatusBarItem IExtAutoStatusBarItem Enables you to manage a particular status pane.

The instance of CExtAutoWindow is a top-level object, which gives you access to all other objects in ProfAuto. In your MFC application, you can get a pointer to the IExtAutoWindow interface by calling the AutomateFrame global function, which takes a pointer to the CFrameWnd-based window as an input parameter.

Commands

The key object in ProfAuto is the CExtAutoCommand command wrapper. Each item in the menu or button in the toolbar is associated with a command. Besides commands are assigned to push buttons and buttons with submenus in toolbars, popup menu items, text/combo fields, color pickers, and scroll bar/slider items. Some particular command may be assigned to different objects, so, for example, by clicking a button in a toolbar or selecting a menu item in a menu, the very same command is executed. The command object has a set of properties and, changing these properties affects all toolbar and menu bar items corresponding to this command.

Before adding any custom button to a toolbar or popup menu, you need to assign an existing command to it. If you remove a command object, the corresponding toolbar/menu items are also removed automatically. Each command object is uniquely identified by the basic command identifier with a numeric value in the range of 1 to 65535. You may also identify the command by name. Such property is treated as an internal command name and may not be equal to the command text in the menu or toolbar.

Toolbars

You can manage toolbars with the CExtAutoToolbarsCollection object and a set of CExtAutoToolbars toolbars. There is one and only one menu bar, which is a special case of the toolbar. Each toolbar, except user-defined toolbars, has two sets of buttons. The first collection contains original or initial buttons, which used for restoring customized or active buttons to their initial state. Active buttons present the second collection. Please note that if a toolbar is user-defined (i.e., created in the Customize dialog), the Reset operation is not available and the ActiveButtons property should not be used because the user-defined toolbar does not keep the collection of initial buttons.

Scripting Events

At the moment ProfAuto supports handling of the following events:

Event Description
OnCommand Fired when a toolbar button or menu item is clicked/selected.
OnColor Fired when the current color the in a color picker is changed.
OnScroll Fired when the thumb position in a scroll bar/slider is changed.

To make life easier for those who want implement scripting events support in their applications, ProfAuto also allows the developer to handle these events within the CExtAutoWindow object. This requires much less code in C++ implementation of the active script host. In this case, your script handlers should look like:

'VBScript 
Sub AutoWindow_OnAnyCommand( objCommand )
...
End Sub
Sub AutoWindow_OnAnyCommand( objCommand )
...
End Sub
Sub AutoWindow_OnAnyCommand( objCommand )
...
End Sub

Typical scripting scenario

How to implement the active scripting support in your C++ application is described in the next sections. Let us take a look at what you should pay attention to when you write a ProfAuto script:

  1. To access properties and methods of the CExtAutoWindow object, use the name you have selected when implementing the active script host, e.g. AutoWindow.
  2. AutoWindow gives you access to the collections implemented in ProfAuto:
    'VBScript 
    Set commands   = AutoWindow.Commands
    Set toolbars   = AutoWindow.Commands
    Set categories = AutoWindow.Categories
    
  3. When a script is executed for the first time (or when you deleted the corresponding registry key), new commands are added. So, every time the application starts, you need to check whether a particular command exists:
    'VBScript 
    'Suppose you want to have a command with the "Extra Command" name 
    Dim command
    If commands.IsCommandExist("Extra Command") Then
    Set command = AutoWindow.Commands.Item("Extra Command")
    Else
    Set command = AutoWindow.Commands.Add (0)
       With command
          Name      = "Extra Command"
          TextInMenu   = strCommandName & " in Menu"
          TextInToolbar    = strCommandName & " in Toolbar"
          StatusTipText    = "Some text"
          TooltipText    = "Some text"
          LoadIconFromModule "%SystemRoot%\system32\SHELL32.dll", 3
       End With
    End With
    
  4. The same logic described in paragraph 3 is applicable to toolbars.
  5. To check whether a toolbar is the menu bar, use the method IsMenuBar:
    'VBScript 
    Set toolbars = AutoWindow.Commands
    For i = 0 To toolbars.Count - 1 
       If toolbars.Item(i).IsMenuBar Then 
          Set menubar = toolbars.Item(i)
          Exit For
       End If
    Next
    
  6. To add buttons to a toolbar, get its CExtAutoToolButtonsCollection first and then use the method Add of the latter:
  7. 'VBScript
    'Do not forget to pass the command object as the second parameter
    Set button = toolbar.ActiveButtons.Insert ( _
       toolbar.ActiveButtons.Count, _
       command
    ) 
    
  8. To build hierarchical command trees, use the Children property of the button object.
  9. Use AutoWindow_OnAnyCommand, AutoWindow_OnAnyColor, and AutoWindow_OnAnyScroll handlers for handling events. The objCommand command object passed as a parameter should be analyzed and appropriate actions for a particular command made.

Please note that to have access to the objects that are specific for your application (not dealing with Prof-UIS), you additionally need to implement scripting support for such objects. You may take a look at how it is done for ProfAuto and follow a similar approach.

Active Script Host

This section very briefly outlines the steps on how to use the Windows Active Scripting technology. For detailed information on this topic, please refer to MSDN (e.g., Microsoft Windows Script Interfaces-Introduction or Scripting Events) and other sources (e.g., The article by George Shepherd in MSJ).

Windows Active Scripting implies two sides:

  1. Active Scripting Engine is a COM component that implements some standard interfaces. Its main function is to play scripts written in a certain language. There are script engines for VBScript, JScript, and other languages. You need to decide which script engine you will instantiate in your application.
  2. Active Script Host is a COM object that at least implements IActiveScriptSite interface, instantiates the scripting engine, provides the scripting engine with the script text, and performs some other tasks. It is a kind of proxy to the scripting engine.

Let's take see how you may implement the Active Scripting support in your application:

  1. Create a script host class and implement the IActiveScriptSite interface in it.
  2. Implement the IActiveScriptSiteWindow interface in it if you want support for popup windows in the scripting code (i.e., to make MsgBox() work in VBScript).
  3. Add a method to your script host class in which:
    • Create an instance of the scripting engine
    • Pass the address of your script host to the engine via the SetScriptSite method of the IActiveScript interface
    • Get a pointer to the IActiveScriptParse interface and parse the script text
    • Add required named items to the engine via the AddNamedItem method of the IActiveScript interface. In case of ProfAuto, select a name for the CExtAutoWindow object (e.g., AutoWindow) and pass it as a first parameter. Use the very same name when you implement the GetItemInfo method of the IActiveScriptSite interface. Do not forget to specify the SCRIPTITEM_ISSOURCE flag if you want to have scripting support.
    • Call the IActiveScript::SetScriptState(SCRIPTSTATE_CONNECTED) method to run the script.

Adding Scripting: Step-by-Step Tutorial

Here are suggested steps on making your application scriptable. We will take an SDI application generated by the Visual C++ 6 and turn it into the Prof-UIS application that will allow you to run scripts written in VBScript.

  1. Run Visual C++ 6, select File->New, select MFC AppWizard (exe), type ProfUISTestScripting and click OK.
  2. Select Single document and unselect Document/View architecture support in Step 1.
  3. Leave all other settings intact and click Finish.

Adding Prof-UIS

Paths

  1. Add paths to Prof-UIS files. Select Tools->Options..., open the Options dialog and select the Directories tab. Add
    • ...\FOSS Software\Prof-UIS\Include and ...\FOSS Software\Prof-UIS\PROFAUTO paths for include files
    • ...\FOSS Software\Prof-UIS\Bin_600 for library files
  2. Open the Settings dialog (Project->Settings) and select Debug. Type the path to the Prof-UIS dll file in the Working directory: field.
  3. Include the Prof-UIS-AutomationPack.h in StdAfx.h (the headers for Prof-UIS and ProfAuto will be included automatically):
    #include <Prof-UIS-AutomationPack.h>
    
  4. Basic functionality

  5. In the MainFrame.h file replace CStatusBar with CExtStatusControlBar and CToolBar with CExtToolControlBar. Add the definition for the menu bar:
    protected:
       CExtMenuControlBar m_wndMenuBar;
    
  6. In the CMainFrame::OnCreate method replace:
    if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD 
          | WS_VISIBLE | CBRS_TOP   | CBRS_GRIPPER | CBRS_TOOLTIPS 
          | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) |
          |!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
    {
       TRACE0("Failed to create toolbar\n");
       return -1;      // fail to create
    }
    
    with
    if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
          | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) 
          ||   !m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
    {
       TRACE0("Failed to create toolbar\n");
       return -1;      // fail to create
    }
    
  7. Open String Table in the resources and add a string with ID equal to ID_VIEW_MENUBAR. Set the following caption for it: Show or hide the menu bar\nToggle Menu Bar.
  8. Just before the code for creating and loading the toolbar, add the code for creating the menu bar:
    if (!m_wndMenuBar.Create( NULL, this, ID_VIEW_MENUBAR  ))
    {
       TRACE0("Failed to create menubar\n");
       return -1;      // fail to create
    } 
    
  9. Replace the MFC's function EnableDocking:
    if(   ! CExtControlBar::FrameEnableDocking( this ) )
       {
          ASSERT( FALSE );
          return -1;
       } 
    
    with that of Prof-UIS
    m_wndMenuBar.EnableDocking(CBRS_ALIGN_ANY);
    DockControlBar(&m_wndMenuBar);
    
  10. Insert
    m_wndMenuBar.EnableDocking(CBRS_ALIGN_ANY);
    
    before
    m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
    
  11. Insert
    DockControlBar(&m_wndMenuBar);
    
    before
    DockControlBar(&m_wndToolBar);
    
  12. Add this line before the method before return 0:
    RecalcLayout();
    
  13. Override the CMainFrame::PreTranslateMessage virtual function so that it will look like :
    BOOL CMainFrame::PreTranslateMessage(MSG* pMsg) 
    {
       if( m_wndMenuBar.TranslateMainFrameMessage(pMsg) )   return TRUE;
       return CFrameWnd::PreTranslateMessage(pMsg);
    }
    
  14. To make the menus open immediately from the menu bar and disable the "rarely-used items" feature, add these two lines to the constructor of CMainFrame:
    CExtPopupMenuWnd::g_bMenuExpanding = false;
    CExtPopupMenuWnd::g_bMenuHighlightRarely = false;
    
  15. To show/hide the menu bar with a command, open the IDR_MAINFRAME menu resource and add the ID_VIEW_MENUBAR item to the View menu (type &Menu Bar for the caption and Show or hide the menu bar\nToggle Menu Bar for the prompt). To enable this command, add the corresponding handlers to the CMainFrame message map (just before END_MESSAGE_MAP()):
    ON_COMMAND_EX( ID_VIEW_MENUBAR, OnBarCheck )
    ON_UPDATE_COMMAND_UI( ID_VIEW_MENUBAR, OnUpdateControlBarMenu )
    
  16. The command manager is a key global object in Prof-UIS. You need to register the commands in it. Insert this code snippet at the beginning of the CMainFrame::OnCreate method:
    CWinApp * pApp = ::AfxGetApp();
    ASSERT( pApp != NULL );
    ASSERT( pApp->m_pszRegistryKey      != NULL );
    ASSERT( pApp->m_pszRegistryKey[0]   != _T('\0') );
    ASSERT( pApp->m_pszProfileName      != NULL );
    ASSERT( pApp->m_pszProfileName[0]   != _T('\0') );
    ASSERT( pApp->m_pszProfileName      != NULL );
    HICON hIcon = pApp->LoadIcon( IDR_MAINFRAME );
    ASSERT( hIcon != NULL );
    SetIcon( hIcon, TRUE );
    SetIcon( hIcon, FALSE );
    
    CAutoCmdProfile *pProfile = new CAutoCmdProfile;
    pProfile->m_sName = pApp->m_pszProfileName;
       
    VERIFY(g_CmdManager->ProfileSetup(pApp->m_pszProfileName,   
       GetSafeHwnd(),  pProfile));
    VERIFY(g_CmdManager->UpdateFromMenu(pApp->m_pszProfileName,   
       IDR_MAINFRAME));
    VERIFY(g_CmdManager->UpdateFromToolBar(pApp->m_pszProfileName,   
       IDR_MAINFRAME));
    
  17. Run the application. Now we will add support for GUI persistence and replace the base class of CChildView so that we can edit scripts. We will also make the ProfUISTestScripting application customizable since otherwise ProfAuto cannot be used.
  18. Serialization

  19. Pass Foss instead of Local AppWizard-Generated Applications in the SetRegistryKey function called in the InitInstance method of the CProfUISTestScriptingApp class and call the function LoadStdProfileSettings then:
    SetRegistryKey(_T("Foss"));
    LoadStdProfileSettings();
    
  20. Declare a variable of the WINDOWPLACEMENT type in the public section of CMainFrame:
    WINDOWPLACEMENT m_dataFrameWP;
    
  21. Add these lines to the constructor of CMainFrame:
    ::memset( &m_dataFrameWP, 0, sizeof(WINDOWPLACEMENT) );
    m_dataFrameWP.showCmd = SW_HIDE;
    
  22. Load the GUI state in CMainFrame::OnCreate just before you call the function DockControlBar:
    g_CmdManager->SerializeState(
       pApp->m_pszProfileName,
       pApp->m_pszRegistryKey,
       pApp->m_pszProfileName,
       false
    );
    CExtControlBar::ProfileBarStateLoad(
       this,
       pApp->m_pszRegistryKey,
       pApp->m_pszProfileName,
       pApp->m_pszProfileName,
       &m_dataFrameWP
    );
    
  23. Override the DestroyWindow method in CMainFrame:
    BOOL CMainFrame::DestroyWindow() 
    {
    CWinApp * pApp = ::AfxGetApp();
    ASSERT( pApp != NULL );
    ASSERT( pApp->m_pszRegistryKey != NULL );
    ASSERT( pApp->m_pszRegistryKey[0] != _T('\0') );
    ASSERT( pApp->m_pszProfileName != NULL );
    ASSERT( pApp->m_pszProfileName[0] != _T('\0') );
    VERIFY(
    CExtControlBar::ProfileBarStateSave(
          this,
          pApp->m_pszRegistryKey,
          pApp->m_pszProfileName,
          pApp->m_pszProfileName,
          &m_dataFrameWP
       )
    );
    VERIFY(
       g_CmdManager->SerializeState(
          pApp->m_pszProfileName,
          pApp->m_pszRegistryKey,
          pApp->m_pszProfileName,
          true)
     );
    return CFrameWnd::DestroyWindow();
    }
    
    Run the application.
  24. Customization

  25. Inherit CMainFrame from CAutoCustomizeSite, an extended version if CExtCustomizeSite:
    class CMainFrame : public CFrameWnd, 
          public CAutoCustomizeSite
    
  26. Prof-UIS implementation of the customize features requires OLE to be initialize. Because of this, call the AfxOleInit in the CProfUISTestScriptingApp::InitInstance method just after AfxEnableControlContainer():
    if( ! AfxOleInit() )
    {
       AfxMessageBox("OLE initialization failed.");
       ASSERT( FALSE );
       return FALSE;
    }
    
  27. Before the call of g_CmdManager->SerializeState() in CMainFrame::OnCreate(), add the following code:
    VERIFY(MenuInfoAdd(
          this,
          _T("Default"),
          IDR_MAINFRAME,
          true,
          false)
    );
    VERIFY(MenuInfoLoadAccelTable(_T("Default"),IDR_MAINFRAME));
    
    if(!EnableCustomization(this, __ECSF_DEFAULT))
    {
       ASSERT( FALSE );
       return -1;
    }
    
  28. After the call of g_CmdManager->SerializeState() in CMainFrame::OnCreate(), add:
    CategoryUpdate( IDR_MAINFRAME );
    CategoryMakeAllCmdsUnique();
    CategoryAppendAllCommands();
    CategoryAppendNewMenu();
    CustomizeStateLoad(
       pApp->m_pszRegistryKey,
       pApp->m_pszProfileName,
       pApp->m_pszProfileName
    );
    
  29. Add the call of CustomizeStateSave in CMainFrame::DestroyWindow() (after you got a pointer to CWinApp):
    CustomizeStateSave(
    pApp->m_pszRegistryKey,
    pApp->m_pszProfileName,
    pApp->m_pszProfileName
    );
    
    Run the application.
  30. Making CChildView CEdit-based

  31. In ChildView.h declare CChildWnd as follows:
    class CChildView : public CEdit
    
  32. Remove the declaration of OnPaint() from ChildView.h and its implementation from ChildView.cpp.
  33. Modify creation of the CChildView object in CMainFrame::OnCreate() as follows:
    if (!m_wndView.Create(
       AFX_WS_DEFAULT_VIEW
       |WS_HSCROLL|WS_VSCROLL
       |ES_LEFT|ES_MULTILINE
       |ES_AUTOHSCROLL|ES_AUTOVSCROLL,
       CRect(0, 0, 0, 0),
       this,
       AFX_IDW_PANE_FIRST
       )
    )
    {
       TRACE0("Failed to create view window\n");
       return -1;
    }
    
    Run the application and try the CEdit-based window.

Adding scripting support

ProfAuto

As it was mentioned above, first of all, in addition to Prof-UIS, you should use the ProfAuto library. Since it is a COM library, you need to register it with the RegSvr32 utility. You should also use the CAutoCustomizeSite class instead of CExtCustomizeSite (see Customization).
  1. The CComPtr wrapper is handy for managing interface pointers, so include the corresponding header file to StdAfx.h:
    #include <atlbase.h>
    
  2. Declare a pointer to the IExtAutoWindow interface in the protected section of the CMainFrame class:
    CComPtr < IExtAutoWindow >   m_pAutoWindow;
    
  3. To initialize the pointer to IExtAutoWindow, call the AutomateFrame global function of ProfAuto in CMainFrame:OnCreate() (just after the call of RecalculateLayout()):
    if ( FAILED ( ::AutomateFrame( 
    STATIC_DOWNCAST( CFrameWnd, this), 
    &m_pAutoWindow )))
    {
       TRACE0("Failed to create AutoWindow\n");
       return -1;      // fail to create
    }
    
  4. Add a WM_CLOSE message handler and release a pointer to IExtAutoWindow in it (Leave CFrameWnd::OnClose() intact):
    m_pAutoWindow.Release();
    CFrameWnd::OnClose();
    
  5. Script Host

  6. Add a new class derived from the IActiveScriptSite and IActiveScriptSiteWindow public interfaces. Call it CActiveScriptHost.
  7. Include the header file for Active Scripting interfaces in StdAfx.h:
    #include <activscp.h> // Active Scripting headers
    
  8. Include the header file for interface constants (like CLSID_ExtAutoWindow) to StdAfx.cpp:
    #include <../ProfAuto/ProfAuto_i.c> //to StdAfx.cpp
    
  9. Somewhere at the beginning of ActiveScriptHost.cpp, add a macro definition to assert code conditions:
    #define ASRT(X); if (FAILED(X)) { ASSERT ( FALSE ); return FALSE; }
    
  10. Add a public method for initializing the script host. The method may take in a script text and return a value that specifies whether initialization is successful:
    bool Initialize(IExtAutoWindow* pAutoWindow, 
          CString& strScriptBody );
    
  11. Declare CLSID and HRESULT variables and get the class id for VBScript in CActiveScriptHost::Initialize():
    CLSID clsid;
    HRESULT hr;
    ASRT( CLSIDFromProgID(OLESTR("VBScript"), &clsid) );
    
  12. Declare a pointer to IExtAutoWindow in the private section of the script host class:
    CComPtr < IExtAutoWindow > m_pExtAutoWindow;
    
  13. In CActiveScriptHost::Initialize(), initialize m_pExtAutoWindow, which will be used for querying interfaces of CExtAutoWindow:
    m_pExtAutoWindow = pAutoWindow;
    
  14. Declare pointers to the IActiveScriptParse and IActiveScript interfaces in CActiveScriptHost:
    protected:
    CComPtr  m_pParser; 
    public:
       CComPtr < IActiveScript >  m_pEngine;
    
  15. Create the active script engine -- you get a pointer to IActiveScript -- and, with SetScriptSite(), tell the scripting engine that this CActiveScriptHost object is a scripting host that it will interact with:
    ASRT ( m_pEngine.CoCreateInstance (clsid, NULL, CLSCTX_INPROC_SERVER ) );
    ASRT( m_pEngine->SetScriptSite(this) );
    
  16. Query a pointer to IActiveScriptParse, initialize the engine's parser, parse the script, and release the pointer to IActiveScriptParse:
    ASRT( m_pEngine->QueryInterface(IID_IActiveScriptParse, 
       (void**)&m_pParser) );
    ASRT( m_pParser->InitNew() ); 
    CComBSTR bstrScript ( strScriptBody );
    hr = m_pParser->ParseScriptText( bstrScript, NULL, 
       NULL, NULL, 0, 0, 0L, NULL, NULL);
    m_pParser.Release();
    if ( FAILED (hr) ) return false;
    
  17. Declare a member variable of the CString type to the script host class. It will specify the name of the CExtAutoWindow object. Initialize it in the constructor:
    CString m_strAutoWindow;
    ...
    //In constructor CActiveScriptHost::CActiveScriptHost:
    m_strAutoWindow = "AutoWindow";
    
  18. Let the scripting engine know, that the name AutoWindow is associated with an object. The information about this object can be requested via IActiveScriptSite interface. The SCRIPTITEM_ISSOURCE flag tells the engine that AutoWindow sources events that the script can sink. The SCRIPTITEM_ISVISIBLE flag indicates that the name AutoWindow is available in the name space of the script:
    USES_CONVERSION;
    ASRT( m_pEngine->AddNamedItem(
       T2COLE(LPCTSTR(m_strAutoWindow)),
       SCRIPTITEM_ISSOURCE | SCRIPTITEM_ISVISIBLE)
       );
    
  19. The initialization of the script host is over, so add return true; at the end of CActiveScriptHost::Initialize().
  20. Add a public method named RunScript, which returns a boolean value, to the script host class. Add the following code to the method:
    HRESULT hr;
    m_pEngine->SetScriptState(SCRIPTSTATE_CONNECTED);
    if ( FAILED(hr) ) 
    {
       AfxMessageBox("Error when invoking script");
       return false;
    }
    return true;
    
  21. Since CActiveScriptHost is inherited from IActiveScriptSite and IActiveScriptSiteWindow, then we need to declare and implement their virtual methods. First declare them in the public section of the script host class:
    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void** ppvObj);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();
    
    // IActiveScriptSite 
    STDMETHODIMP GetLCID( LCID *plcid );
    STDMETHODIMP GetItemInfo(
       LPCOLESTR pstrName,
       DWORD dwReturnMask,
       IUnknown **ppunkItem,
       ITypeInfo **ppTypeInfo
    );
    STDMETHODIMP GetDocVersionString( BSTR *pbstrVersionString );
    STDMETHODIMP OnScriptTerminate(   
       const VARIANT *pvarResult,   
       const EXCEPINFO *pexcepinfo
    );
    STDMETHODIMP OnStateChange(   SCRIPTSTATE ssScriptState );
    STDMETHODIMP OnScriptError( IActiveScriptError *pase ); 
    STDMETHODIMP OnEnterScript( void );
    STDMETHODIMP OnLeaveScript( void );
    
    // IActiveScriptSiteWindow 
    STDMETHODIMP GetWindow( HWND *phwnd );
    STDMETHODIMP EnableModeless( BOOL fEnable );
    
  22. Add a reference counter to the private section of the class and intitilize it in the constructor:
    ULONG m_ulRefs;
    
    //In constructor CActiveScriptHost::CActiveScriptHost:
    m_ulRefs = 0;
    
  23. The methods of IUnknown may be implemented like this:
    STDMETHODIMP CActiveScriptHost::QueryInterface(REFIID riid, void ** ppvObj)
    if (riid == IID_IUnknown)
       *ppvObj = static_cast(this);
    else if (riid == IID_IActiveScriptSite)
       *ppvObj = static_cast(this);
    else if (riid == IID_IActiveScriptSiteWindow)   
       *ppvObj = static_cast(this);
    else {
       *ppvObj = NULL;
       return E_NOINTERFACE;
    }
    static_cast(*ppvObj)->AddRef();
    return S_OK;
    }
    
    STDMETHODIMP_(ULONG) CActiveScriptHost::AddRef()
    {
       return ++m_ulRefs;
    }
    
    STDMETHODIMP_(ULONG) CActiveScriptHost::Release()
    {
       ULONG ulRefs = --m_ulRefs;
       if (ulRefs == 0)
          delete this;
    
       return ulRefs;
    }
    
  24. Now let's get to IActiveScriptSite. In the GetLCID method you may specify the language used in the popup messages generated from the scripting engine:
    STDMETHODIMP CActiveScriptHost::GetLCID( LCID *plcid )
    {
       *plcid = MAKELCID(MAKELANGID(LANG_ENGLISH,
              SUBLANG_ENGLISH_US),SORT_DEFAULT);
    //*plcid = GetSystemDefaultLCID(); //Or use this
       return S_OK;
    }
    
  25. The GetItemInfo method may be implemented like this:
    STDMETHODIMP CActiveScriptHost::GetItemInfo(
       LPCOLESTR pstrName, DWORD dwReturnMask,
       IUnknown **ppunkItem,
       ITypeInfo **ppTypeInfo
    )
    {
    if (dwReturnMask & SCRIPTINFO_IUNKNOWN){
          if (!ppunkItem) return E_INVALIDARG;
          *ppunkItem = NULL;
       }
       if (dwReturnMask & SCRIPTINFO_ITYPEINFO){
          if (!ppTypeInfo) return E_INVALIDARG;
          *ppTypeInfo = NULL;
       }
    
       USES_CONVERSION;
    if (!_wcsicmp(T2COLE(LPCTSTR(m_strAutoWindow)), pstrName)){
          if (dwReturnMask & SCRIPTINFO_IUNKNOWN)
             {
                m_pExtAutoWindow->QueryInterface(
                   IID_IUnknown,
                   (void**)ppunkItem
                   );
                return S_OK;
             }
          else if (dwReturnMask & SCRIPTINFO_ITYPEINFO)
          {
             LPTYPELIB ptlib = NULL;
             LPTYPEINFO ptinfo = NULL; 
             HRESULT hr = LoadRegTypeLib(
                LIBID_PROFAUTOLib,
                1,
                0,
                0,
                &ptlib
                );
    
             if (FAILED(hr)) return E_NOTIMPL;
    
             hr = ptlib->GetTypeInfoOfGuid(CLSID_ExtAutoWindow, &ptinfo);
             if (FAILED(hr))
             {
                ptlib->Release();
                return E_NOTIMPL;
             }
             ptlib->Release();
             *ppTypeInfo = ptinfo;
             return S_OK;
          }
       }
       return TYPE_E_ELEMENTNOTFOUND;
    }
    
  26. If you want to catch and display script errors, implement OnScriptError() like this:
    STDMETHODIMP CActiveScriptHost::OnScriptError(IActiveScriptError *pError)
    {
    //Display a message box with information about the error.
    EXCEPINFO einfo;
    HRESULT hr = pError->GetExceptionInfo(&einfo);
    
    if (SUCCEEDED(hr))
    {
       CString str(einfo.bstrDescription);
       CString strError("Script Error");
       strError += str;
    
       DWORD dwSourceContext;
       ULONG ulLineNumber;
       LONG uCharPosition;    
       hr = pError->GetSourcePosition( &dwSourceContext,
                      &ulLineNumber,
                      &uCharPosition
                      );
       CString strPos;
       if (SUCCEEDED(hr))
       {
          
          strPos.Format("\nLine: %d\nSymbol: %d", 
             ulLineNumber + 1, uCharPosition);
          strError += strPos;
       }
          ::MessageBox(
             AfxGetMainWnd()->GetSafeHwnd(),
             strError,
             _T("ActiveScripts"),
             MB_ICONEXCLAMATION
          );
       }   
       return S_OK;
    }
    
    
  27. To simplify the tutorial, we will leave other methods of IActiveScriptSite unimplemented:
    STDMETHODIMP CActiveScriptHost::GetDocVersionString(
       BSTR *pbstrVersionString)
    {
       return E_NOTIMPL;
    }
    
    
    STDMETHODIMP CActiveScriptHost::OnScriptTerminate(
       const VARIANT *pvarResult, 
       const EXCEPINFO *pexcepinfo
    )
    {
    return S_OK;
    }
    
    
    STDMETHODIMP CActiveScriptHost::OnStateChange( SCRIPTSTATE ssScriptState)
    {
       return S_OK;
    }
    STDMETHODIMP CActiveScriptHost::OnEnterScript(void)
    {
       return S_OK;
    }
    
    
    STDMETHODIMP CActiveScriptHost::OnLeaveScript(void)
    {
    return S_OK;
    }
    
  28. To tell the scripting engine what window will be used for outputting popup messages, implement the IActiveScriptSiteWindow interface:
    STDMETHODIMP CActiveScriptHost::GetWindow(HWND *phwnd)
    {
       if(phwnd==NULL) return E_POINTER;
    
       *phwnd = AfxGetMainWnd()->GetSafeHwnd();
    
       return S_OK;
    }
    STDMETHODIMP CActiveScriptHost::EnableModeless(BOOL fEnable)
    {
       return S_OK;
    }
    
  29. Putting all together

  30. Add this line to MainFrame.cpp:
    #include "ActiveScriptHost.h"
    
  31. In CMainFrame, declare a pointer to IActiveScript and an inline function that will release this pointer:
    CComPtr < IActiveScript > m_pActiveScript;
    void _CloseScript()
    {
    if (m_pActiveScript)
       {
          m_pActiveScript->Close();
          m_pActiveScript.Release();
       }
    }   
    
  32. Call the _CloseScript function at the beginning of CMainFrame::OnClose() to release the scripting engine and delete the script host object:
    _CloseScript();
    
  33. Open the resource editor and create a menu item like on the figure below:
  34. In the resource editor, add a button to the IDR_MAINFRAME toolbar and associate it with command ID_SCRIPT_RUN:
  35. Add a message handler for the command ID_SCRIPT_RUN:
    void CMainFrame::OnScriptRun() 
    {
       CString strScriptBody;
       m_wndView.GetWindowText( strScriptBody );
       if ( strScriptBody != "" ){
          _CloseScript();
          CActiveScriptHost* pHost = new CActiveScriptHost;
          if ( pHost->Initialize( m_pAutoWindow, strScriptBody ) ) {
             pHost->RunScript();
          }
          m_pActiveScript = pHost->m_pEngine;
       };
    }
    
  36. Allow ProfAuto to handle commands first at the beginning of CMainFrame::OnCmdMsg():
    if (CAutoCustomizeSite::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
       return TRUE;
    
  37. This step, which is optional, should allow you to use the "copy/paste" feature when you try the application. Add COMMAND and UPDATE_COMMAND_UI handlers for ID_EDIT_COPY, ID_EDIT_CUT, and ID_EDIT_PASTE in the CChildView class:
    void CChildView::OnEditCopy() 
    {
       Copy();
    }
    void CChildView::OnUpdateEditCopy(CCmdUI* pCmdUI) 
    {   
       int nStart, nEnd;
       GetSel( nStart, nEnd );
       BOOL bEnable = nStart != nEnd;
       pCmdUI->Enable( bEnable );
    }
    void CChildView::OnEditCut() 
    {
       Cut();
    }
    void CChildView::OnUpdateEditCut(CCmdUI* pCmdUI) 
    {
       OnUpdateEditCopy( pCmdUI );
    }
    void CChildView::OnEditPaste() 
    {
       Paste();
    }
    void CChildView::OnUpdateEditPaste(CCmdUI* pCmdUI) 
    {
       pCmdUI->Enable( IsClipboardFormatAvailable( CF_TEXT ) );
    }
    
  38. Testing the application

  39. Make sure that
    MsgBox "Some text" 
    
    works and some wrong script causes a message box with the error description.
  40. Copy the following script and paste it in the edit window:
    '[VBScript]
    '***************************************************************************
    ' This script adds a custom toolbar with buttons and a top levels menu with
    ' menu items. The toolbar buttons and menu items are associated with 
    ' commands which are also added. The commands are assigned click handlers 
    ' in the AutoWindow_OnAnyClick() subroutine.
    '***************************************************************************
    Option Explicit
    
    Dim i, j
    Dim toolbars, toolbar
    Dim strCustomToolbar
    strCustomToolbar    = "Custom toolbar"
    
    
    Dim commands 'the single collection of commands 
    Dim command(20), strCommand, strCommandName
    strCommand = "Cmd"
    
    
    Set commands = AutoWindow.Commands
    '***************************************************************************
    ' IF CUSTOM COMMANDS DO NOT EXIST THEY SHOULD BE ADDED
    '***************************************************************************
    For i = 0 To UBound(command) - 1
       strCommandName = strCommand & CStr(i+1)
       If commands.IsCommandExist(strCommandName) Then
          Set command(i) = AutoWindow.Commands.Item(strCommandName)
       Else
          Set command(i) = AutoWindow.Commands.Add (0)
          With command(i)
             .Name          = strCommandName
             .TextInMenu    = strCommandName & " in Menu"
             .TextInToolbar = strCommandName & " in Toolbar"
             .StatusTipText = "Status tip for " & strCommandName
             .TooltipText    = "Tooltip for " & strCommandName
             .Enabled = True
             .LoadIconFromModule "%SystemRoot%\system32\SHELL32.dll", 1 + i
          End With
       End If
    Next
    '***************************************************************************
    ' CHECK WHETHER THE TOOLBAR FOR CUSTOM COMMANDS EXISTS
    '***************************************************************************
    toolbar = Null
    Set toolbars = AutoWindow.Toolbars
    
    For i = 0 To toolbars.Count - 1 
       If toolbars.Item(i).Name = strCustomToolbar Then 
          Set toolbar = toolbars.Item(i)
          Exit For
       End If
    Next
    '***************************************************************************
    ' IF THE TOOLBAR DOES NOT EXIST, IT IS ADDED. 
    ' THE BUTTONS ASSOCIATED WITH CUSTOM COMMANDS SHOULD BE INSERTED TOO
    '***************************************************************************
    Dim button
    If IsNull(toolbar) Then
       Set toolbar = toolbars.Add (strCustomToolbar)
       For i = 0 To UBound(command) - 1
          Set button = toolbar.ActiveButtons.Insert (i, command(i))
          If Round(i/4)*4 = i or i = 2 Then button.GroupStart = True
       Next
       toolbar.Reset
    End If
    
    '***************************************************************************
    ' MENU BAR
    '***************************************************************************
    Dim menubar
    Dim topmenucmd, strCustomTopMenuCmd
    Dim topmenubtn,  strTopMenuBtn
    strCustomTopMenuCmd = "CustomTopMenuButton"
    
    '***************************************************************************
    ' THE ITEM IN THE TOP MOST MENU (AND ITS COMMAND) SHOULD BE ADDED IF MISSED
    '***************************************************************************
    If Not commands.IsCommandExist(strCustomTopMenuCmd) Then
       Set topmenucmd = commands.Add(0)
       With topmenucmd
          .Name   = strCustomTopMenuCmd
          .TextInMenu = Chr(38) & "Custom"
          .CommandType = 1
          
       End With
    Else
       Set topmenucmd = commands.Item(strCustomTopMenuCmd)
    End If
    
    For i = 0 To toolbars.Count - 1 
       If toolbars.Item(i).IsMenuBar Then 
          Set menubar = toolbars.Item(i)
          Exit For
       End If
    Next
    
    topmenubtn = Null
    
    For i = 0 To menubar.ActiveButtons.Count - 1
       If (menubar.ActiveButtons.Item(i).ActiveCommand.Name _
          = strCustomTopMenuCmd) Then
          Set topmenubtn = menubar.ActiveButtons.Item(i)
          Exit For
       End If
    Next
    
    Dim child
    Dim buttons
    If IsNull(topmenubtn) Then
       Set topmenubtn = menubar.ActiveButtons.Insert (4, topmenucmd)
       Set buttons = topmenubtn.Children
       For i = 0 To UBound(command) - 1
          Set child = buttons.Insert (i, command(i))
          If Round(i/4)*4 = i Then child.GroupStart = True
       Next
    End If
    '***************************************************************************
    ' EVENT HANDLERS SECTION
    '***************************************************************************
    Sub AutoWindow_OnAnyCommand( objCommand )
       
       MsgBox "The command " & objCommand.Name & _
             " executed"
    End Sub
    
  41. Run the script. Make sure that the script handles the click event.
  42. NOTE: The application's GUI state is stored in the registry under the HKEY_CURRENT_USER/Software/Foss/ProfUISTestScripting. So, if you want to reset the application to its initial state, delete this key.
Back To Top Other Articles...