6

I find myself woefully lacking in understanding of when, why, and how (which type) to do screen draw/refresh/partial refresh. For instance, in a standalone Python script outside the application boundary, I have a function that is trying to draw graphic elements in the data view. Specifically, the elements will be a TextElement on top of a RectangleElement. The function loads the two elements into a GroupElement, which is in turn added to the ActiveView's GraphicsContainer for drawing on the screen. (As far as I can tell the graphic elements are valid.) I am really not understanding what Draw/Refresh/ParitalRefresh methods to put where.

Can you give me or point to a good explanation or tutorial about screen drawing/refreshing?

Below is a bit of selected code from my function. I have a PartialRefresh in the last line, but I don't know if that is correct or not.

pGroupElement = NewObj(esriCarto.GroupElement, esriCarto.IGroupElement3)
pTextElement = NewObj(esriCarto.TextElement, esriCarto.ITextElement)
pRectElement = NewObj(esriCarto.RectangleElement, esriCarto.IElement)
.
.
(configure TextElement)  [tedious code removed]
.
.
(configure RectElement)
.
.
ptxtElem = CType(pTextElement, esriCarto.IElement)

pGroupElement.AddElement(ptxtElem)
pGroupElement.AddElement(pRectElement)

pContainer = mxDoc.ActiveView.GraphicsContainer

pGrpElem = CType(pGroupElement, esriCarto.IElement)

pContainer.AddElement(pGrpElem, 0)

mxDoc.ActiveView.PartialRefresh(esriCarto.esriViewGraphics, None, None)

Added: So, is creating objects with the following code - as I do now - not creating them in process?

def NewObj(MyClass, MyInterface):
    """Creates a new comtypes POINTER object where\n\
    MyClass is the class to be instantiated,\n\
    MyInterface is the interface to be assigned"""
    from comtypes.client import CreateObject
    try:
        ptr = CreateObject(MyClass, interface=MyInterface)
        return ptr
    except:
        return None

If not, I gather I must use IObjectFactory. Assuming that my current objects are not in process, I must say I haven't seen any great performance hit, but I guess I'm just asking for trouble forcing lots of interprocess calls.

0

3 Answers 3

7

If you're "in a standalone Python script outside the application boundary" I think you'll need to use IObjectFactory. See page 13 of http://www.pierssen.com/arcgis/upload/misc/python_arcobjects.pdf

Also, if you're trying to refresh the display across a process boundary, you may need to send some extra windows messages to force a refresh. Here's a good explanation.

0
6

So, in the light of the tips in Kirk's link (ArcMap Automation: Man the Message Pumps!) and the knowledge that IObjectFactory must be used your scenario, here's my take on Python implementation of the discussed workaround.

To reiterate, the problem is that RPC calls, coming from a different process (python) might get stalled in ArcMap. The general idea described in the aforementioned article is that by pummeling ArcMap's message loop with WM_NULL messages, you force it to process your RPC calls as well.

I am not versed in Python, in fact this might only be my fifth piece of Python code or so, if memory serves. If anyone wants to improve/fix it in any way, go ahead. I ran it on Python 2.6.5.

import ctypes
import threading

class EsriAppMessageHelper(threading.Thread):
    def __init__(self, windowHandle):
        threading.Thread.__init__(self)
        self.running = True
        self.windowHandle = windowHandle

    def stop(self):
        self.running = False

    def run(self):
        while self.running:
            ctypes.windll.user32.SendMessageA(self.windowHandle, 0, 0, 0)
            #print 'WM_NULL to window %d sent' % self.windowHandle

    def __enter__(self):
        self.start()

    def __exit__(self, type, value, traceback):
        self.stop()

Basically, it's a thread which sends WM_NULL (0) messages to the given window. The __enter__ and __exit__ functions support Python's with statement. Here's how it's used (the ArcObjects related functions are taken from this post in a different question):

# Get the ArcMap's IApplication reference.
# We're in a different process than ArcMap's, so it gets resolved using IAppROT.
app = GetApp()

# WM_NULL messages will be sent from another thread while
# this block is executing
with EsriAppMessageHelper(app.hWnd):

    objectFactory = CType(app, esriFramework.IObjectFactory) # use IObjectFactory
    mxDoc = CType(app.Document, esriArcMapUI.IMxDocument)
    graphicsContainer = mxDoc.ActiveView.GraphicsContainer

    textElement = CType(objectFactory.Create("esriCarto.TextElement"), esriCarto.ITextElement)

    # initialize the symbols and elements, assign their geometries
    # etc...

    graphicsContainer.AddElement(element, 0)
    mxDoc.ActiveView.PartialRefresh(esriCarto.esriViewGraphics, None, None)

# At this point, we're out of the WITH block.
# The separate thread is stopped and no more WM_NULL messages are being sent.
print "DONE"

Try it out. I could not fully test it with my ArcMap 10 setup because I did not have any issues with the active view refresh before (it executed immediately and refresh was successful), but the WM_NULL messages do get sent.

1
  • Understood, I as well find this a rather nasty workaround..
    – Petr Krebs
    Commented Jan 18, 2011 at 13:48
3

It has been a while since I have done anything like this, but, if the elements are located on the page layout instead of within a data frame, they are handled differently. See Working with PageLayout elements and Working with map elements

Also see How to redraw the display

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.