MapInfo Pro Developers User Group

 View Only
Expand all | Collapse all

Using Python Script to Customize MapInfo Pro UI and Create Dialogs

  • 1.  Using Python Script to Customize MapInfo Pro UI and Create Dialogs

    Employee
    Posted 05-20-2020 05:32
    Edited by Anshul Goel 05-20-2020 05:41
    Based on the Continued content about using Python script to create addins for MapInfo Pro, This post will help you in creating a Python Addin which will allow to customize the MapInfo Pro Ribbon, add some button and Create Dialogs.

    Before we start, Please have a look at the post which will give you more information regarding the extent of support for Python in MapInfo Pro.

    Now let get started:

    Setup

    1. To Create an Addin in Python, we will first start by taking the Simple template provided in the MapBasic installation or attached. This template already has the startup code in main() class for MapInfo Pro Python Addin. 
      • SAMPLES\RIBBONINTERFACE\Python\py_addin_templates\Simple
    2. Once the template copied, we can start modifying it.
      • Start with renaming the files → change <renameHere> to your addin name.
      • Rename module reference as per your new file names.
      • Check for all TODO in python code and modify the code as per your need.

    Modifying the Ribbon

    In your new Python module created above, lets look at the main() class, which is responsible to initialize and load a Python AddIn. main class should always be present if writing an AddIn or the Python module will be treated as standalone script.
    # this class is needed with same name in order to load the python addin and can be copied 
    # as it is when creating another addin.
    class main():
        def __init__(self, imapinfopro):
            # imapinfopro is the object of type MapInfo.Type.IMapInfoPro 
            # giving access to the current running instance of MapInfo Pro.
            self._imapinfopro = imapinfopro
    
        def load(self):
            try:
                # uncomment these lines to debug the python script using VSCODE
                # Install ptvsd package into your environment using "pip install ptvsd"
                # Debug in VSCODE with Python: Attach configuration
    
                # ptvsd.enable_attach()
                # ptvsd.wait_for_attach()
                # ptvsd.break_into_debugger()
    
                # here initialize the addin class
                if self._imapinfopro:
                    # obtain the handle to current mbx application
                    thisApplication = self._imapinfopro.GetMapBasicApplication(os.path.splitext(__file__)[0] + ".mbx")
                    self._addin = MyAddin(self._imapinfopro, thisApplication)
            except Exception as e:
                print("Failed to load: {}".format(e))
        
        def unload(self):
            try:
                if self._addin:
                    self._addin.unload()
                    del self._addin
                self._addin = None
            except Exception as e:
                print("Failed to unload: {}".format(e))
        
        def __del__(self):
            self._imapinfopro = None
            pass
    
        # optional -- tool name that shows in tool manager
        def addin_name(self) -> str:
            # TODO: change here
            return "Python Add-in"
    
        # optional -- description that shows in tool manager
        def addin_description(self) -> str:
            # TODO: change here
            return "Python Add-in Description"
        
        # optional -- default command text in  tool manager
        def addin_defaultcommandtext(self) -> str:
            # TODO: change here
            return "Python Add-in Default Command"
    
        # optional -- default command when run or double-clicked in tool manager
        def addin_defaultcommand(self):
            # TODO: change here
            self.on_default_button_clicked(self)
    
        # optional -- image that  shows in tool manager
        def addin_imageuri(self) -> str:
            # TODO: change here
            return "pack://application:,,,/MapInfo.StyleResources;component/Images/Application/about_32x32.png"
    
        def on_default_button_clicked(self, sender):
            # TODO: change here
            try:
                print('default command executed')
            except Exception as e:
                print("Failed to execute: {}".format(e))

    In the above main class, we are creating MyAddin class which is where we will start adding our code.
    # TODO: change your addin class name here.
    class MyAddin():
        def __init__(self, imapinfopro, thisApplication):
            try:
                self._pro = imapinfopro
                self._thisApplication = thisApplication
    
                # TODO: Add your code here for addin initialization.
            except Exception as e:
                print("Failed to load: {}".format(e))
    
        def unload(self):
            # TODO: Add your code here for unloading addin.
            self._thisApplication = None
            self._pro = None
            pass

    Lets first start with modifying the MapInfo Pro Ribbon. We will create following things in MapInfo Pro Ribbon.
    • New Ribbon Tab
      • New Ribbon Group inside the Tab
        • A Standard Tool Button
        • A Custom Tool Button invoking a Python method.
        • Button Creating Windows Forms Dialog.
        • Button Creating A WPF Dialog.

    All the above modification will be done to the UI when MyAddin class object is created, therefore we to have modified the __init__ and unload method of MyAddin class as below.

    def __init__(self, imapinfopro, thisApplication):
            try:
                self._pro = imapinfopro
                self._newTab = None
                self._thisApplication = thisApplication
                # Create A new Ribbon Tab and Add controls.
                self.AddNewTabToRibbon()
            except Exception as e:
                print("Failed to load: {}".format(e))
    
    def AddNewTabToRibbon(self):
            pass
    
    def unload(self):
            if self._newTab:
                self._pro.Ribbon.Tabs.Remove(self._newTab)
            self._newTab = None
            self._thisApplication = None
            self._pro = None

    Add a new Ribbon Tab and Ribbon Group
    def AddNewTabToRibbon(self):
            # Create a New Ribbon Tab
            self._newTab = self._pro.Ribbon.Tabs.Add("RibbonCustomization","Ribbon Customization")
            if self._newTab:
                # Create a New Ribbon Group
                tabGroup = self._newTab.Groups.Add("DemoControls", "Demo Controls")
    
                # Now we can start adding controls in the Ribbon Group
                if tabGroup:
                    tabGroup.IsLauncherVisible = False

    Once the Ribbon Group is added, let us add Button, ToolButtons to Ribbon and attach custom Python methods on button click.
        def AddNewTabToRibbon(self):
            # Create a New Ribbon Tab
            self._newTab = self._pro.Ribbon.Tabs.Add("RibbonCustomization","Ribbon Customization")
            if self._newTab:
                # Create a New Ribbon Group
                tabGroup = self._newTab.Groups.Add("DemoControls", "Demo Controls")
    
                # Now we can start adding controls in the Ribbon Group
                if tabGroup:
                    tabGroup.IsLauncherVisible = False
    
                    # Add a standard tool button
                    buttonZoomIn = tabGroup.Controls.Add("ZoomInToolButton", "Zoom-In Tool", ControlType.ToolButton)
                    if buttonZoomIn:
                        buttonZoomIn.IsLarge = True
                        buttonZoomIn.KeyTip = "ZI"
                        buttonZoomIn.ToolTip = AddinUtil.create_tooltip("Zoom In", "Zoom In", "Open Mapper to use")
                        buttonZoomIn.LargeIcon = CommonUtil.path_to_uri("pack://application:,,,/MapInfo.StyleResources;component/Images/Mapping/zoomIn_32x32.png")
                        buttonZoomIn.SmallIcon = CommonUtil.path_to_uri("pack://application:,,,/MapInfo.StyleResources;component/Images/Mapping/zoomIn_16x16.png")
                        buttonZoomIn.CommandId = 1705
                        buttonZoomIn.Cursor = "129"
                        buttonZoomIn.DrawMode = 30
                        buttonZoomIn.BModifier = True
                
                    # Add a custom tool button
                    buttonCustomInfoTool = tabGroup.Controls.Add("InfoToolButton", "Info", ControlType.ToolButton)
                    if buttonCustomInfoTool:
                        buttonCustomInfoTool.IsLarge = True
                        buttonCustomInfoTool.KeyTip = "TI"
                        buttonCustomInfoTool.ToolTip = AddinUtil.create_tooltip("Info Tool", "Info Tool", "Open Map window to enable the tool.")
                        buttonCustomInfoTool.LargeIcon = CommonUtil.path_to_uri("pack://application:,,,/MapInfo.StyleResources;component/Images/Mapping/infoTool_32x32.png")
                        buttonCustomInfoTool.SmallIcon = CommonUtil.path_to_uri("pack://application:,,,/MapInfo.StyleResources;component/Images/Mapping/infoTool_16x16.png")
                        buttonCustomInfoTool.Cursor = "164"
                        buttonCustomInfoTool.DrawMode = 34
                        buttonCustomInfoTool.BModifierKeys = False
                        # attach a command invoking a python function
                        buttonCustomInfoTool.Command = AddinUtil.create_command(self.on_custom_info_tool_clicked)
    
                    tabGroup.Controls.Add("demoSeparator","Demo Seperator",ControlType.RibbonSeparator)
    
                    buttonOpenDialogWinForms = tabGroup.Controls.Add("OpenCustomDialogWinForms", "Open Dialog WinForms", ControlType.Button)
                    if buttonOpenDialogWinForms:
                        buttonOpenDialogWinForms.ToolTip = AddinUtil.create_tooltip("Open Dialog","Open Dialog", "Open Dialog")
                        # attach a command invoking a python function
                        buttonOpenDialogWinForms.Command = AddinUtil.create_command(self.on_open_dialog_winforms_click)
                        buttonOpenDialogWinForms.LargeIcon = CommonUtil.path_to_uri("pack://application:,,,/MapInfo.StyleResources;component/Images/Application/exportAsImage_32x32.png")
                        buttonOpenDialogWinForms.SmallIcon = CommonUtil.path_to_uri("pack://application:,,,/MapInfo.StyleResources;component/Images/Application/exportAsImage_16x16.png")
    
                    buttonOpenDialogWpf = tabGroup.Controls.Add("OpenCustomDialogWPF", "Open Dialog WPF", ControlType.Button)
                    if buttonOpenDialogWpf:
                        buttonOpenDialogWpf.ToolTip = AddinUtil.create_tooltip("Open Dialog","Open Dialog", "Open Dialog")
                        # attach a command invoking a python function
                        buttonOpenDialogWpf.Command = AddinUtil.create_command(self.on_open_dialog_wpf_click)
                        buttonOpenDialogWpf.LargeIcon = CommonUtil.path_to_uri("pack://application:,,,/MapInfo.StyleResources;component/Images/Application/exportAsImage_32x32.png")
                        buttonOpenDialogWpf.SmallIcon = CommonUtil.path_to_uri("pack://application:,,,/MapInfo.StyleResources;component/Images/Application/exportAsImage_16x16.png")
    
                    endProgram = tabGroup.Controls.Add("EndProgram", "End Program", ControlType.Button)
                    if endProgram:
                        endProgram.KeyTip = "LE"
                        endProgram.ToolTip = AddinUtil.create_tooltip("End Program Tooltip Description","End Program ToolTip Text", "End Program Disabled Text")
                        # attach a command invoking a python function
                        endProgram.Command = AddinUtil.create_command(self.on_end_application)
                        endProgram.LargeIcon = CommonUtil.path_to_uri("pack://application:,,,/MapInfo.StyleResources;component/Images/Table/closeTable_32x32.png")
                        endProgram.SmallIcon = CommonUtil.path_to_uri("pack://application:,,,/MapInfo.StyleResources;component/Images/Table/closeTable_16x16.png")
    
        def on_open_dialog_winforms_click(self, sender):
            try:
                pass
            except Exception as e:
                print("Failed to execute: {}".format(e))
    
        def on_open_dialog_wpf_click(self, sender):
            try:
                pass
            except Exception as e:
                print("Error: {}".format(e))
    
        def on_custom_info_tool_clicked(self, sender):
            try:
                x1 = CommonUtil.eval("commandinfo(1)")
                y1 = CommonUtil.eval("commandinfo(2)")
                # show a notification bubble on status bar.
                notification = NotificationObject()
                if notification:
                    formatted_msg = "X: {}\nY: {}".format(x1, y1)
                    CommonUtil.do("print \"{}\"".format(formatted_msg))
                    notification.Message = formatted_msg
                    notification.Title = "Map Coordinates"
                    notification.Type = NotificationType.Info
                    notification.TimeToShow = 2000
                    # MapInfo.Types.FrameworkElementExtension.GetScreenCoordinate method can also be used
                    # to get the screen coordinates of any FrameworkElement.
                    notification.NotificationLocation = Point(-1,-1)
    
                    self._pro.ShowNotification(notification)
            except Exception as e:
                print("Failed to execute: {}".format(e))
    
        def on_end_application(self, sender):
            try:
                if self._thisApplication:
                    self._thisApplication.EndApplication()
            except Exception as e:
                CommonUtil.sprint("Failed to load: {}".format(e))
    


    Creating a WinForms Dialog

    To create a WinForms dialog we will add a new class HelloForm in a separate Python module custom_winforms.py.

    • Class HelloForm extends WinForm.Form C# class
    • Adds Button to the Form. 
    • Attach a python method to click event of Button and execute Note mapbasic statement inside the event handler.
    import clr
    
    clr.AddReference("System.Windows.Forms")
    import System.Windows.Forms as WinForms
    
    from System.Drawing import Size, Point
    from mi_common_util import *
    
    class HelloForm(WinForms.Form):
        def __init__(self, parent):
            self.AutoScaleBaseSize = Size(8, 16)
            self.AutoScaleMode = WinForms.AutoScaleMode.Font
            self.ClientSize = Size(352, 212)
            self.Name = "Form1"
            self.Text = "Custom Dialog"
            self.StartPosition = WinForms.FormStartPosition.CenterParent
            self.Parent = WinForms.Control.FromHandle(parent)
    
            # Create the button
            self.button = WinForms.Button()
            self.button.Location = Point(13, 85)
            self.button.Size = Size(327, 23)
            self.button.Text = "Click Me!"
    
            # Register the event handler
            self.button.Click += self.button_Click
    
            # Add the controls to the form
            self.Controls.Add(self.button)
    
        def button_Click(self, sender, args):
            """Button click event handler"""
            CommonUtil.do("note \"Button Click\"")


    To Show the WinForms Dialog on click of Ribbon Button, let us modify the  on_open_dialog_winforms_click on MyAddin class.

    def on_open_dialog_winforms_click(self, sender):
            try:
                form = HelloForm(self._pro.MainHwnd)
                if form:
                    form.ShowDialog()
            except Exception as e:
                print("Failed to execute: {}".format(e))



    Creating a WPF Dialog

    First create a XAML for the dialog UI. UI that we have created has ContentControl to show Map and as three button at bottom of the dialog.

    Note : When created a XAML UI, we need to make sure to give x:Name to all the controls in the XAML so that Python can easily find the controls.

    <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    				Title="Custom Dialog"
    				Height="400" Width="400"
    				ShowInTaskbar="False"
    				WindowStartupLocation="CenterOwner">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="30"/>
            </Grid.RowDefinitions>
            <ContentControl x:Name="Mapper" Grid.Row="0"/>
            <StackPanel Grid.Row="1" Orientation="Horizontal">
                <Button x:Name="AddMapButton"  Margin="5,5,5,5"  VerticalAlignment="Bottom" IsDefault="False">Add Map</Button>
                <Button x:Name="ShowTableInfo" Margin="5,5,5,5"   VerticalAlignment="Bottom">Table Info</Button>
                <Button x:Name="CloseButton" Margin="5,5,5,5"  VerticalAlignment="Bottom" IsDefault="True" IsCancel="True">Close</Button>
            </StackPanel>
        </Grid>
    </Window>


    In Creating a WPF Dialog we will take MVVM approach, which will have a view, presenter and model.

    We will create a Python module (custom_view_view.py) which will parse this XAML and convert it into a WPF Dialog. This module will be out view.

    from mi_addin_util import AddinUtil
    from os.path import join, dirname
    
    class View:
        XAML_FILE = "custom_view.xaml"
        def __init__(self):
            self._window = None
    
        @property
        def window(self):
            if not self._window:
                self._window = AddinUtil.create_user_control(join(dirname(__file__), self.XAML_FILE))
            return self._window
        
        def _on_close(self, new_value):
            if new_value:
                self.window.Closing += new_value
                self.CloseButton.Click += new_value
        on_close = property(None, _on_close)
        
        def showdialog(self, parent):
            if self._window is not None:
                AddinUtil.set_dialog_parent(self._window, parent)
                return self._window.ShowDialog()
            return None
                
        def _add_map_click(self, new_value):
            if new_value:
                self.AddMapButton.Click += new_value
        add_map_click = property(None, _add_map_click)
    
        def _info_click(self, new_value):
            if new_value:
                self.ShowTableInfo.Click += new_value
        info_click = property(None, _info_click)
    
        def __getattr__(self, name):
            return AddinUtil.find_logical_control(self.window, name)


    The above custom_view_view.py module needs a presenter or controller to handle user interaction/clicks and allow interaction between MapInfo Pro and Python. To all this stuff we will create another Python module custom_view_presenter.py.

    import sys
    from custom_view_model import Model
    from custom_view_view import View
    from MapInfo.Types import MessageOutput
    from mi_addin_util import AddinUtil
    from mi_common_util import CommonUtil
    from System import Int32
    
    # redirect python stdio to Pro
    sys.stdout = sys.stderr = sys.stdin = MessageOutput()
    
    class Presenter:
        def __init__(self, view, imapinfopro):
            self._view = view
            self._pro = imapinfopro
            self._winId = -1
            self._table_name = None
            #self.model = model
    
        def add_map(self, event, sender):
            try:
                if self._winId is not -1:
                    self.close_map()
    
                table_name = input("Table Name")
                if table_name:
                    el, self._winId = self._pro.CreateUnattachedWindow("map from {}".format(table_name), Int32(0))
                    if el:
                        self._view.Mapper.Content = AddinUtil.create_user_control_from_handle(el)
                        self._table_name = table_name
    
            except Exception as e:
                print("Failed to execute: {}".format(e))
    
        def close_map(self):
            try:
                self._view.Mapper.Content = None
                if self._winId is not -1:
                    win = self._pro.Windows.FromId(self._winId)
                    if win:
                        win.Close()
                        self._winId = -1
                        self._table_name = None
            except Exception as e:
                print("Failed to execute: {}".format(e))
        
        def on_close(self, event, sender):
            self.close_map()
    
        def info_click(self, event, sender):
            try:
                if self._table_name:
                    CommonUtil.do("open window message")
                    print(CommonUtil.eval("TableInfo({}, TAB_INFO_NAME)".format(self._table_name)))
                    print(CommonUtil.eval("TableInfo({}, TAB_INFO_NCOLS)".format(self._table_name)))
                    print(CommonUtil.eval("TableInfo({}, TAB_INFO_NROWS)".format(self._table_name)))
                    print(CommonUtil.eval("TableInfo({}, TAB_INFO_MAPPABLE)".format(self._table_name)))
                    print(CommonUtil.eval("TableInfo({}, TAB_INFO_COORDSYS_CLAUSE)".format(self._table_name)))
                    print(CommonUtil.eval("TableInfo({}, TAB_INFO_CHARSET)".format(self._table_name)))
            except Exception as e:
                print("Failed to execute: {}".format(e))
    
        def showdialog(self):
            try:
                self._view.on_close = self.on_close
                self._view.add_map_click = self.add_map
                self._view.info_click = self.info_click
                self._view.showdialog(self._pro.MainHwnd)
            except Exception as e:
                print("Failed to execute: {}".format(e))
    
        @staticmethod
        def create(imapinfopro):
            try:
                return Presenter(View(), imapinfopro)
            except Exception as e:
                print("Error: {}".format(e))
    


    Final step in creating a WPF dialog is to invoke it when user click button on MapInfo Pro Ribbon for which we will again look into MyAddin class and modify  on_open_dialog_wpf_click method.

        def on_open_dialog_wpf_click(self, sender):
            try:
                presenter = Presenter.create(self._pro)
                if presenter:
                    presenter.showdialog()
                presenter = None
            except Exception as e:
                print("Error: {}".format(e))


    We can run this Addin in MapInfo Pro by selecting the module with main() class (py_mapinfo_customization.py) from Run Program dialog.

    This completes our Python Addin development. Final developed addin mentioned in this post is attached.

    Thanks
    Anshul



    ------------------------------
    Anshul Goel
    Knowledge Community Shared Account
    Shelton CT
    ------------------------------

    Attachment(s)

    zip
    Simple.zip   1 KB 1 version


  • 2.  RE: Using Python Script to Customize MapInfo Pro UI and Create Dialogs

    Employee
    Posted 05-20-2020 08:53
      |   view attached
    I have updated the attached final addin with latest one.
    Stay Tuned for the next post in thread regarding debugging of Python Addin in Visual Studio Code.

    ------------------------------
    Anshul Goel
    Knowledge Community Shared Account
    Shelton CT
    ------------------------------

    Attachment(s)



  • 3.  RE: Using Python Script to Customize MapInfo Pro UI and Create Dialogs

    Posted 05-22-2020 08:38
    Thanks Anshul!
    Your post was very helpful. I have another question.

    Can we use Python PYQT5 module created dialogs as well within MapInfo instead of Winform? It is very easy to create dialog in PYQT5designer but I don't know how to make those interacting with MapInfo.
    If this is possible, can you please give some example on how to interact those with MapInfo.

    Thanks,
    Navjot


    ------------------------------
    Navjot Kaur
    Knowledge Community Shared Account
    ------------------------------



  • 4.  RE: Using Python Script to Customize MapInfo Pro UI and Create Dialogs

    Employee
    Posted 05-29-2020 07:13
    Hello Navjot,

    Here is a basic example of reparenting a dialog created in PyQT5 into MapInfo Pro and than interact with Pro using .Net API or MapBasic form Dialog button handlers. You can create python addin with PyQT5 dialogs using the sample approach.

    For this example to run you have to install pyqt5 and pywin32 module in MapInfo Pro python installation.

    After installing pywin32, you would need some aditional steps to get pywin32 module running in our environment, mentioned below.
    • move the pywintype37.dll and pythoncom37.dll from <Pro Install Dir>\Python37\Lib\site-packages\pywin32_system32 folder to <Pro Install Dir>\Python37\Lib\site-packages\win32

    import os
    os.environ['QT_USE_NATIVE_WINDOWS'] = "1"
    import sys
    from PyQt5.QtWidgets import QApplication, QPushButton, QWidget, QDialog, QVBoxLayout
    from PyQt5 import QtGui
    from PyQt5 import QtCore
    import win32.win32gui
    import win32.lib.win32con as win32con
    
    def application():
       app = QApplication(sys.argv)
       try:
          showdialog()
       finally:
          app.exit()
    
    def showdialog():
       # Get MapInfoPro Main Window handle.
       proHwnd = pro.MainHwnd.ToInt32() 
       d = QDialog()
       d.resize(500,500)
       
       if d:
          
          b = QPushButton()
          b.setText("Say Hello!")
          b.clicked.connect(button_action)
          
          layout = QVBoxLayout()
          layout.addWidget(b)
          d.setLayout(layout)
          
          d.setWindowTitle("Dialog")
          d.show()
    
          # Get QT Dialog handle
          dhwnd = int(d.winId())
          
          # reparent the qt dialog into MapInfo Pro.
          win32.win32gui.SetWindowLong(dhwnd, win32con.GWL_HWNDPARENT, proHwnd)
          win32.win32gui.EnableWindow(proHwnd, 0)
          d.exec_()
          win32.win32gui.EnableWindow(proHwnd, 1)
          win32.win32gui.SetForegroundWindow(proHwnd)
    
    
    def button_action():
       try:
          print("Hello from QT!")
          table = pro.Catalog.OpenTable("D:\\maps\\asia\\asia.tab")
          if table:
             print(table.Alias)
             do("map from {}".format(table.Alias))
       except Exception as e:
          print("failed {}".format(e))
    
    application()


    Thanks
    Anshul

    ------------------------------
    Anshul Goel
    Knowledge Community Shared Account
    Shelton CT
    ------------------------------



  • 5.  RE: Using Python Script to Customize MapInfo Pro UI and Create Dialogs

    Posted 05-29-2020 07:46
    It worked for me.

    Thank you very much Anshul!

    ------------------------------
    Navjot Kaur
    Knowledge Community Shared Account
    ------------------------------



  • 6.  RE: Using Python Script to Customize MapInfo Pro UI and Create Dialogs

    Posted 06-01-2020 19:30
    Hi Anshul,

    Can you please help to create simple sample for following:
    1. A customized button in MapInfo
    2. When user clicks on that button then a PYQT5 dialog box opens up which is parent to MapInfo.

    I am looking for something similar type of dialog box example that you have posted for WINFORM dialog but built with PYQT5.

    Thanks,
    Navjot

    ------------------------------
    Navjot Kaur
    Knowledge Community Shared Account
    ------------------------------



  • 7.  RE: Using Python Script to Customize MapInfo Pro UI and Create Dialogs

    Employee
    Posted 06-02-2020 03:12
      |   view attached
    Hi Navjot,

    I have updated the above sample to open up a PyQT5 dialog on button click of ribbon.

    Thanks
    Anshul

    ------------------------------
    Anshul Goel
    Knowledge Community Shared Account
    Shelton CT
    ------------------------------

    Attachment(s)



  • 8.  RE: Using Python Script to Customize MapInfo Pro UI and Create Dialogs

    Posted 06-02-2020 09:00
    Can you please create class in PyQT5 dialog code and then import that in py_mapinfo_customization. I want to see how sample code looks for similar PyQT5 dialog when we create class in custom_pyqt.py

    Thanks,
    Navjot

    ------------------------------
    Navjot Kaur
    Knowledge Community Shared Account
    ------------------------------



  • 9.  RE: Using Python Script to Customize MapInfo Pro UI and Create Dialogs

    Employee
    Posted 06-03-2020 03:47
    Hi Navjot,
    You can convert the PyQT5 dialog in custom_pyqt.py into a dialog like below:
    import os
    os.environ['QT_USE_NATIVE_WINDOWS'] = "1"
    from PyQt5.QtWidgets import QPushButton, QWidget, QDialog, QVBoxLayout
    import win32.win32gui
    import win32.lib.win32con as win32con
    from mi_common_util import CommonUtil
    
    class CustomQTDialog(QDialog):
       def __init__(self, pro, *args, **kwargs):
    
          super(CustomQTDialog, self).__init__(*args, **kwargs)
          self._pro = pro
          self.setWindowTitle("Dialog")
          self.resize(500, 500)
          b = QPushButton()
          b.setText("Say Hello!")
          b.clicked.connect(self.button_action)
          layout = QVBoxLayout()
          layout.addWidget(b)
          self.setLayout(layout)
       
       def button_action(self):
          try:
             print("Hello from QT!")
             table = self._pro.Catalog.OpenTable("D:\\maps\\asia\\asia.tab")
             if table:
                   print(table.Alias)
                   CommonUtil.do("map from {}".format(table.Alias))
          except Exception as e:
             print("failed {}".format(e))
          
       def showdialog(self, proHwnd):
          self.show()
    
          # Get QT Dialog handle
          dhwnd = int(self.winId())
          
          # reparent the qt dialog into MapInfo Pro.
          win32.win32gui.SetWindowLong(dhwnd, win32con.GWL_HWNDPARENT, proHwnd)
          win32.win32gui.EnableWindow(proHwnd, 0)
          self.exec_()
          win32.win32gui.EnableWindow(proHwnd, 1)
          win32.win32gui.SetForegroundWindow(proHwnd)


    Than this dialog can be invoked:
        def on_open_dialog_qt_click(self, sender):
            try:
                # Get MapInfoPro Main Window handle.
                proHwnd = self._pro.MainHwnd.ToInt32()
                qtdlg = CustomQTDialog(pro=self._pro)
                qtdlg.showdialog(proHwnd)
            except Exception as e:
                print("Error: {}".format(e))


    ------------------------------
    Anshul Goel
    Knowledge Community Shared Account
    Shelton CT
    ------------------------------



  • 10.  RE: Using Python Script to Customize MapInfo Pro UI and Create Dialogs

    Posted 06-10-2020 13:46
    Thanks it worked for me.

    I have one another question. Once I create customized buttons add-ins and  dialog boxes etc. in PYQT5, what's the  procedure to create installation file to install MapInfo along with all the customized dialogs and add-ins on others computers.

    Thanks,
    Navjot

    ------------------------------
    Navjot Kaur
    Knowledge Community Shared Account
    ------------------------------



  • 11.  RE: Using Python Script to Customize MapInfo Pro UI and Create Dialogs

    Employee
    Posted 06-11-2020 05:06
    Hi,

    You can install MapInfo Pro silently using for example a bat file.

    Our Install Guide gives you an overview over the parameters you can pass to the installer.  Look for the chapter "Silent Installation Procedures for System Administrators".

    Your bat file can then copy your Python files to a location of your choice.

    ------------------------------
    Peter Horsbøll Møller
    Distinguished Engineer
    Pitney Bowes Software & Data
    ------------------------------



  • 12.  RE: Using Python Script to Customize MapInfo Pro UI and Create Dialogs

    Posted 06-23-2020 21:13
    Hi,

    What is the correct way to Create Thematic map based on individual values in Python. In my data, I don't want to specifically mention values. Is there any way it can automatically take all values from a column and generate thematic map.
    For example, in this case, I don't want to mention values because I am using Oracle table whose values keeps on changing.
    do("shade Window FrontWindow() tablename With columnname Values 1, 2, 3"

    Thanks,
    Navjot

    ------------------------------
    Navjot Kaur
    Knowledge Community Shared Account
    ------------------------------



  • 13.  RE: Using Python Script to Customize MapInfo Pro UI and Create Dialogs

    Employee
    Posted 06-24-2020 06:29
    Edited by Anshul Goel 06-24-2020 06:38
    Hi Navjot,

    You can do this by query out the values using a Select statement: Select SOMECOLUMN from SOMETABLE Group By SOMECOLUMN and then build shade statement.

    Below is the small python code which you can use as it is.

    ### Description: Shows how to automate the creation on dynamic individual value theme
    ### Category: Mapping
    
    import os
    from os.path import join, dirname, abspath
    import numpy as np
    
    def get_random_rgb():
        rgb_tuple = tuple(np.random.randint(256, size=3))
        return rgb_tuple[0] << 16 | rgb_tuple[1] << 8 | rgb_tuple[2]
    
    
    def shade_ind(objectType:int, tableAlias:str, col:str):
        try:
            do("map from {}".format(tableAlias))
            mapId = int(eval("frontwindow()"))
            layer = 1
    
            qry = None
            
            if objectType is 7:
                #Define OBJ_TYPE_REGION                          7
                qry = "Select {0} \"VALUE\" \
                            From {1} \
                                Where OBJ \
                                    Group By {2} \
                                        Order By {3} \
                                            Into __THEME__STYLES NoSelect Hide"
            elif objectType is 3:
                #Define OBJ_TYPE_LINE                            3
                qry = "Select {0} \"VALUE\" \
                            From {1} \
                                Where OBJ \
                                    Group By {2} \
                                        Order By {3} \
                                            Into __THEME__STYLES NoSelect Hide"
            elif objectType is 5:
                #Define OBJ_TYPE_POINT                           5
                qry = "Select {0} \"VALUE\", \
                        Str$(ObjectInfo(OBJ, OBJ_INFO_SYMBOL)) \"SYMBOL\" \
                            From {1} \
                                Where OBJ \
                                    Group By {2} \
                                        Order By {3} \
                                            Into __THEME__STYLES NoSelect Hide"
            
            if qry:
                do(qry.format(col, tableAlias, col, col))                         
                numRows = int(eval("TableInfo(__THEME__STYLES, TAB_INFO_NROWS)"))
                print(numRows)
                if numRows > 0:
                    themeCommand = None
                    symbol = None
                    value = None
                    # Building the Shade statement by looping the unique values
                    do("Fetch First From __THEME__STYLES")
                    for i in range(numRows):
                        value = str(eval("__THEME__STYLES.VALUE"))
                        if objectType is 7:
                            rgb = get_random_rgb()
                            pen = "Pen (1,2, {})".format(rgb)
                            brush = "Brush (2, {}, 16777215)".format(rgb)
                            if not themeCommand:
                                themeCommand = " Values \"{0}\" {1} {2}".format(value, pen, brush)
                            else:
                                themeCommand += ", \"{0}\" {1} {2}".format(value, pen, brush)
                        elif objectType is 3:
                            #Define OBJ_TYPE_LINE                            3
                            pen = str(eval("__THEME__STYLES.PEN")).replace("Pen", "Line")
                            if not themeCommand:
                                themeCommand = " Values \"{0}\" {1}".format(value, pen)
                            else:
                                themeCommand += ", \"{0}\" {1}".format(value, pen)
                        elif objectType is 5:
                            #Define OBJ_TYPE_POINT                           5
                            symbol = str(eval("__THEME__STYLES.SYMBOL"))
                            if not themeCommand:
                                themeCommand = " Values \"{0}\" {1}".format(value, symbol)
                            else:
                                themeCommand += ", \"{0}\" {1}".format(value, symbol)
                        
                        do("Fetch Next From __THEME__STYLES")
    
                    do("Close Table __THEME__STYLES")
                    do("Shade Window {0} {1} With {2} {3}".format(mapId, layer, col, themeCommand))
                    do("Create Designer Legend From Window {0} Behind Default Frame Style \"#\" Font (\"Arial\",0,8,0) Frame From Layer {1}".format(mapId, layer))
        except Exception as e:
            print("Error: {}".format(e))
    
    try:
       table = pro.Catalog.OpenTable(abspath(join(dirname(__file__), r"..\Data\World\WORLD.TAB")))
       if table:
           shade_ind(7, table.Alias, "Country")
    except Exception as e:
            print("Error: {}".format(e))


    Thanks
    Anshul

    ------------------------------
    Anshul Goel
    Knowledge Community Shared Account
    Shelton CT
    ------------------------------



  • 14.  RE: Using Python Script to Customize MapInfo Pro UI and Create Dialogs

    Posted 06-24-2020 09:11
    Thanks it works.

    I have another question. Suppose there is a customized button in Mapinfo that open a map for example "worldcap" when user clicks on it. When user clicks on the button for the second time, how to make sure it closes the previous opened map/or any action done in the first click.

    Can you please suggest best way for that in Python

    Thanks

    ------------------------------
    Navjot Kaur
    Knowledge Community Shared Account
    ------------------------------



  • 15.  RE: Using Python Script to Customize MapInfo Pro UI and Create Dialogs

    Employee
    Posted 06-25-2020 06:02
    Hi Navjot,
    I think you should keep the state after the button click and clear it before the button click. Same logic could apply in any coding language.
    Thanks
    Anshul

    ------------------------------
    Anshul Goel
    Knowledge Community Shared Account
    Shelton CT
    ------------------------------



  • 16.  RE: Using Python Script to Customize MapInfo Pro UI and Create Dialogs

    Posted 07-06-2020 18:26
    Hi, 

    How can we create a button on Ribbon with Python that is disabled initially and gets enabled only when some map is open. For example, in above Py customization file, The "Open Dialog Winform Button"

    Thanks

    ------------------------------
    Navjot Kaur
    Knowledge Community Shared Account
    ------------------------------



  • 17.  RE: Using Python Script to Customize MapInfo Pro UI and Create Dialogs

    Employee
    Posted 07-07-2020 12:00
    Hi Navjot,

    To enable/disable button you can assign a canexecute handler while creating commands and attach to buttons. In the canexecute handler you can check for condition on which button needs to be enabled.
    See example below:
    from MapInfo.Types import WindowType
    
                    buttonOpenDialogWinForms = tabGroup.Controls.Add("OpenCustomDialogWinForms", "Open Dialog WinForms", ControlType.Button)
                    if buttonOpenDialogWinForms:
                        buttonOpenDialogWinForms.ToolTip = AddinUtil.create_tooltip("Open Dialog","Open Dialog", "Open Dialog")
                        # attach a command invoking a python function and also attach can execute handler
                        buttonOpenDialogWinForms.Command = AddinUtil.create_command(self.on_open_dialog_winforms_click, self.on_open_dialog_winforms_click_can_execute)
                        buttonOpenDialogWinForms.LargeIcon = CommonUtil.path_to_uri("pack://application:,,,/MapInfo.StyleResources;component/Images/Application/exportAsImage_32x32.png")
                        buttonOpenDialogWinForms.SmallIcon = CommonUtil.path_to_uri("pack://application:,,,/MapInfo.StyleResources;component/Images/Application/exportAsImage_16x16.png")
    
    
    
        def on_open_dialog_winforms_click_can_execute(self, param):
            try:           
                for win in self._pro.Windows:
                    if win.WindowType == WindowType.ViewWindow:
                        # as soon as we get any map window
                        # enable this control
                        return True
                return False
            except:
                # disable button in case of exception.
                return False


    Thanks
    Anshul

    ------------------------------
    Anshul Goel
    Knowledge Community Shared Account
    Shelton CT
    ------------------------------