Auto Refresh in ArcGIS using Python - python

I am trying to create an "auto refresh" tool for ArcMap, to refresh the DataFrame. I believe version 10 had an add-on you could download for this purpose.. however we are running 10.1 at work and there is no such tool.
EDIT wxPython's timer should work, however using wx in arc is tricky. Here's what the code looks like currently:
import arcpy
import pythonaddins
import os
import sys
sMyPath = os.path.dirname(__file__)
sys.path.insert(0, sMyPath)
WATCHER = None
class WxExtensionClass(object):
"""Implementation for Refresher_addin.extension (Extension)"""
_wxApp = None
def __init__(self):
# For performance considerations, please remove all unused methods in this class.
self.enabled = True
def startup(self):
from wx import PySimpleApp
self._wxApp = PySimpleApp()
self._wxApp.MainLoop()
global WATCHER
WATCHER = watcherDialog()
class RefreshButton(object):
"""Implementation for Refresher_addin.button (Button)"""
def __init__(self):
self.enabled = True
self.checked = False
def onClick(self):
if not WATCHER.timer.IsRunning():
WATCHER.timer.Start(5000)
else:
WATCHER.timer.Stop()
class watcherDialog(wx.Frame):
'''Frame subclass, just used as a timer event.'''
def __init__(self):
wx.Frame.__init__(self, None, -1, "timer_event")
#set up timer
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.onTimer, self.timer)
def onTimer(self, event):
localtime = time.asctime( time.localtime(time.time()) )
print "Refresh at :", localtime
arcpy.RefreshActiveView()
app = wx.App(False)
You will notice the PySimpleApp stuff in there. I got that from the Cederholm's presentation. I am wondering if I am misunderstanding something though. Should I create an entirely separate addin for the extension? THEN, create my toolbar/bar addin with the code I need? I ask this because I don't see the PySimpleApp referenced in your code below, or any importing from wx in the startup override method either... which I thought was required/the point of all this. I do appreciate your help. Please let me know what you see in my code.

You can't do this the way you are trying, because time.sleep will block and lock up the entire application. Python addins in ArcGIS is pretty new stuff, and there's a lot of functionality that hasn't been implemented yet. One of these is some kind of update or timer event like you get in .NET and ArcObjects. You might think of using threading.Thread and threading.Event in a case like this, but nothing to do with threads will work in the Python addin environment. At least I can't get it to work. So what I've done in situations like this is use wxPython and the Timer class. The code below will work if the addin is set up correctly.
import time
import os, sys
import wx
import arcpy
mp = os.path.dirname(__file__)
sys.path.append(mp)
WATCHER = None
class LibLoader1(object):
"""Extension Implementation"""
def __init__(self):
self.enabled = True
def startup(self):
global WATCHER
WATCHER = watcherDialog()
class ButtonClass5(object):
"""Button Implementation"""
def __init__(self):
self.enabled = True
self.checked = False
def onClick(self):
if not WATCHER.timer.IsRunning():
WATCHER.timer.Start(5000)
else:
WATCHER.timer.Stop()
class watcherDialog(wx.Frame):
'''Frame subclass, just used as a timer event.'''
def __init__(self):
wx.Frame.__init__(self, None, -1, "timer_event")
#set up timer
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.onTimer, self.timer)
def onTimer(self, event):
localtime = time.asctime( time.localtime(time.time()) )
print "Refresh at :", localtime
arcpy.RefreshActiveView()
app = wx.App(False)
Make an extension addin, with a toolbar and a button class. Override the startup method of the extension as shown above. That will create an instance of a Frame subclass with a timer. Then, whenever you click the button on the toolbar, the timer will toggle on or off. The Timer argument is in milliseconds, so the code as shown will refresh every 5 seconds.
You can read more about using wxPython in addins here. Pay particular attention to MCederholm's posts, like about the print statement not working.
EDIT
The code uses a startup method override of the addin extension class. This method is supposed to run when Arcmap starts, but it seems from your comments that this startup method is failing to run on startup. That's possible if you don't create your addin just right, but it works fine for me in my tests. If you continue to get "AttributeError: 'NoneType' object has no attribute 'timer'", then change the onClick method of your button class like so:
def onClick(self):
if WATCHER is None:
global WATCHER
WATCHER = watcherDialog()
if not WATCHER.timer.IsRunning():
WATCHER.timer.Start(5000)
else:
WATCHER.timer.Stop()
The first 3 lines check to make sure that the WATCHER variable has been set to an instance of watcherDialog and is not still set to None. Don't know why your startup method is not running, but hopefully this will fix things for you.

You can use either the RefreshTOC or RefreshActiveView Method. Just add a timer

Related

How to shared data between instances classes (backend) and Kivy graphical elements (frontend)?

I'm a beginner with Kivy and I'm trying to create gui for a python app.
I have a backend programmed in python, working alone (keyboard as input) and now I want frontend with kivy. I have two questions:
- How can I run both components (back and front end) at once?
- How can I share objects of existing classes in my backend to display information in the frontend (kivy)?
classtest.py
class Test(object):
def __init__(self, attr):
self.attr = attr
gui.py
class LoginScreen(GridLayout):
def __init__(self, **kwargs):
super(LoginScreen, self).__init__(**kwargs)
self.cols = 2
self.add_widget(Label(text='User Name'))
self.username = TextInput(multiline=False)
self.add_widget(self.username)
self.add_widget(Label(text='password'))
self.password = TextInput(password=True, multiline=False)
self.add_widget(self.password)
print self.username.text
class Login(App):
def build(self):
Window.borderless = True
return LoginScreen()
main.py
import classtest, gui
users = ['user_name1', 'user_name2', 'user_name3']
gui.Login().run()
for u in users:
test = classtest.Test(u) # this should update the user text field on the windows login automatically, but how?
In example, How can I update an element of the login window when an instance attribute value change?
Thanks many many times!
It won't update because... of a loop :P Kivy for each App().run() or a similar "run" command launches a loop and your:
for u in users:
test = classtest.Test(u)
is written after that loop. So basically it won't even execute while your app is running. Just put print('something') into that for loop and you'll see.
Example:
while True:
<do something>
<change a variable in that loop> # == nope
Which for you means that you need either:
write that into gui.py file or
set those things right in the classtest.py
The second option also depends when you use the class. If outside of the main loop, then you are in the same situation as right now, therefore - use that Test() inside of gui.py.
You won't be able to use any code after run() while the app is running. Maybe with some dirty trick, but that will bring you only troubles. The code you wrote could be used in some "cleaning", which you can also do in App.on_stop method(inside main loop).

PySide2 signal not catched between threads with objects

I'm working on a GUI application, developed in Python and its UI library : PySide2 (Qt wrapper for Python)
I have a heavy computation function I want to put on another thread in order to not freeze my UI. The Ui should show "Loading" and when the function is over, receive from it it's results and update the UI with it.
I've tried a lot of different codes, a lot of examples are working for others but not me, is it PySide2 fault ? (For example this is almost what I want to do : Updating GUI elements in MultiThreaded PyQT)
My code is :
class OtherThread(QThread):
def __init__(self):
QThread.__init__(self)
def run(self):
print 'Running......'
self.emit(SIGNAL("over(object)"), [(1,2,3), (2,3,4)])
#Slot(object)
def printHey( obj):
print 'Hey, I\'ve got an object ',
print obj
thr = OtherThread()
self.connect(thr,SIGNAL("over(object)"),printHey)
thr.start()
My code is working if I use primitives such as bool or int but not with object. I see 'Running....' but never the rest.
Hope someone can enlighten me
You can't define signals dynamically on a class instance. They have to be defined as class attributes. You should be using the new-style signals and slot syntax.
class OtherThread(QThread):
over = QtCore.Signal(object)
def run(self):
...
self.over.emit([(1,2,3), (2,3,4)])
class MyApp(QtCore.QObject)
def __init__(self):
super(MyApp, self).__init__()
self.thread = OtherThread(self)
self.thread.over.connect(self.on_over)
self.thread.start()
#QtCore.Slot(object)
def on_over(self, value):
print 'Thread Value', value

How do I tell a class method to wait until a signal from a QDialog class method is caught?

I have the following code:
class Functions(QObject):
mysig = Signal(filename)
def __init__(self, parent=None):
super(Functions, self).__init__(parent)
self.result = None
def showDialog(self, filename):
self.mysig.emit(filename)
def grabResult(self):
while not self.result:
time.sleep(5)
return result #this is the question
def setResult(self, result):
self.result = result
The other part of the code has this:
class Dialog(QDialog):
anotherSig = Signal(str)
fun = Functions()
def __init__(self, parent=None, filename=filename):
self.filename = filename
#Here it displays a picture based on the filename parameter
def okButtonClicked(self):
text = self.lineedit.text()
fun.setResult(text)
#Tried also this:
self.anotherSig.emit(text)
The Functions() class is called from a worker QThread (not shown here).
I guess my question is this: how do I tell my Functions class that the user has entered the the text and clicked the OK button? I tried connecting that anotherSig Signal, but when I try to do so, Qt complains about QPixmaps not being safe to be set from a different thread, and it doesn't work.
The method that I am using here "works", but I feel it's not very reliable. Plus, it only works when all of the relevant methods in the Functions class are #classmethod - this way, for some reason, it doesn't work. The setResult is called (I added a print statement to make sure), but the grabResult still shows self.result as None.
This code is not working because the call to showDialog is happening on the instantiation of a Functions object that is an attribute of what ever object is off on the other thread. Your fun in Dialog, which you set the result on, is a different instantiation.
To move the results back to the original Functions object I think you need to connect anotherSig of the Dialog object to the setResult function on the Functions object you want to get the results back.
Does something like this work (hard to test this with out a good bit of boiler plate).
class Functions(QObject):
mysig = Signal(filename,Functions)
def __init__(self, parent=None):
super(Functions, self).__init__(parent)
self.result = None
def showDialog(self, filename):
self.mysig.emit(filename,self)
def grabResult(self):
while not self.result:
time.sleep(5)
return result #this is the question
#QtCore.Slot(str)
def setResult(self, result):
self.result = result
def connection_fun(filename,fun):
d = Dialog(filename)
# what ever else you do in here
d.anotherSig.connect(fun.setResult))
Using time.sleep causes your application to freeze. One method for making your class wait is using QEventLoop like this:
loop = QEventLoop()
myDialog.mySignal.connect(loop.quit)
loop.exec_()

Is there a generic update notification/pub sub system for GTK?

I'm building a PyGTK application with several widgets that when changed, need to notify other widgets about the change. I would like to avoid code like this:
def on_entry_color_updated(self, widget):
self.paint_tools_panel.current_color_pane.update_color()
self.main_window.status_bar.update_color()
self.current_tool.get_brush().update_color()
And do something like this instead:
def on_entry_color_updated(self, widget):
self.update_notify('color-changed')
The status bar, current color pane and current tool would subscribe to that notification event and act accordingly. From what I can tell, the GObject signaling mechanism only allows me to register a callback on a particular widget, so each object that wants to receive a notification has to be aware of that widget.
Does GTK provide such a system or should I build it myself? Developers of Shotwell, a photo organization application for GNOME, had to build their own signaling mechanism, if I understand their design doc correctly. Searching here on SO didn't turn out any definitive answers.
Edit:
Clarification why I think GObject signaling is not what I need (or just a part of what I need). With GObject, I need to explicitly connect an object to another object, like so:
emitter.connect('custom-event', receiver.event_handler)
So in my application, I would have to do this:
class ColorPane(gtk.Something):
def __init__(self, application):
# init stuff goes here...
application.color_pallette.connect('color-changed', self.update_color)
def update_color(self, widget):
"""Show the new color."""
pass
class StatusBar(gtk.Something):
def __init__(self, application):
# init stuff goes here...
application.color_pallette.connect('color-changed', self.update_color)
def update_color(self, widget):
"""Show the new color name."""
pass
class Brush(gtk.Something):
def __init__(self, application):
# init stuff goes here...
application.color_pallette.connect('color-changed', self.update_color)
def update_color(self, widget):
"""Draw with new color."""
pass
In other words, I have to pass the application object or some other object that knows about the color_pallete to other objects in my application so that they connect to color_pallette signals. This is the kind of coupling that I want to avoid.
For one, you could create a custom subclass of GObject, which provides some custom signals. The following example is a slightly adapted version of the one given in the linked article:
import pygtk
pygtk.require('2.0')
import gobject
class Car(gobject.GObject):
__gsignals__ = {
'engine-started': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
'engine-stopped': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
}
def __init__(self):
gobject.GObject.__init__(self)
self._state = 0
def start(self):
if not self._state:
self._state = 1
self.emit('engine-started')
def stop(self):
if self._state:
self._state = 0
self.emit('engine-stopped')
gobject.type_register(Car)
def kill_switch(c):
def callback(*unused, **ignored):
c.stop()
return callback
def on_start(*unused, **ignored):
print "Started..."
def on_stop(*unused, **ignored):
print "Stopped..."
some_car = Car()
some_car.connect('engine-started', on_start)
some_car.connect('engine-started', kill_switch(some_car))
some_car.connect('engine-stopped', on_stop)
some_car.start()
Another approach would be to take advantage of one of the many event/signalling packages already on PyPI, for example:
Zope Event
Louie
PyDispatcher
Darts Events
Trellis
GObjects don't have to be widgets. For example, your application class can be a GObject which defines signals that other widgets connect to.
Also, I don't think you understood the Shotwell design document correctly. It looks to me like their signalling system is 100% GObject signalling system, just with particular guarantees about the order in which signals are handled. As they say in their design document, such things are possible in plain GObject, but Vala makes it easier to code it their way.

How do I shut down PyQt's QtApplication correctly?

I don't know the first thing about Qt, but I'm trying to be cheeky and borrow code from elsewhere (http://lateral.netmanagers.com.ar/weblog/posts/BB901.html#disqus_thread). ;)
I have a problem. When I run test() the first time, everything works swimmingly. However, when I run it the second time, I get nasty segfaults. I suspect that the problem is that I'm not ending the qt stuff correctly. What should I change about this program to make it work multiple times? Thanks in advance!
from PyQt4 import QtCore, QtGui, QtWebKit
import logging
logging.basicConfig(level=logging.DEBUG)
class Capturer(object):
"""A class to capture webpages as images"""
def __init__(self, url, filename, app):
self.url = url
self.app = app
self.filename = filename
self.saw_initial_layout = False
self.saw_document_complete = False
def loadFinishedSlot(self):
self.saw_document_complete = True
if self.saw_initial_layout and self.saw_document_complete:
self.doCapture()
def initialLayoutSlot(self):
self.saw_initial_layout = True
if self.saw_initial_layout and self.saw_document_complete:
self.doCapture()
def capture(self):
"""Captures url as an image to the file specified"""
self.wb = QtWebKit.QWebPage()
self.wb.mainFrame().setScrollBarPolicy(
QtCore.Qt.Horizontal, QtCore.Qt.ScrollBarAlwaysOff)
self.wb.mainFrame().setScrollBarPolicy(
QtCore.Qt.Vertical, QtCore.Qt.ScrollBarAlwaysOff)
self.wb.loadFinished.connect(self.loadFinishedSlot)
self.wb.mainFrame().initialLayoutCompleted.connect(
self.initialLayoutSlot)
logging.debug("Load %s", self.url)
self.wb.mainFrame().load(QtCore.QUrl(self.url))
def doCapture(self):
logging.debug("Beginning capture")
self.wb.setViewportSize(self.wb.mainFrame().contentsSize())
img = QtGui.QImage(self.wb.viewportSize(), QtGui.QImage.Format_ARGB32)
painter = QtGui.QPainter(img)
self.wb.mainFrame().render(painter)
painter.end()
img.save(self.filename)
self.app.quit()
def test():
"""Run a simple capture"""
app = QtGui.QApplication([])
c = Capturer("http://www.google.com", "google.png", app)
c.capture()
logging.debug("About to run exec_")
app.exec_()
DEBUG:root:Load http://www.google.com
QObject::connect: Cannot connect (null)::configurationAdded(QNetworkConfiguration) to QNetworkConfigurationManager::configurationAdded(QNetworkConfiguration)
QObject::connect: Cannot connect (null)::configurationRemoved(QNetworkConfiguration) to QNetworkConfigurationManager::configurationRemoved(QNetworkConfiguration)
QObject::connect: Cannot connect (null)::configurationUpdateComplete() to QNetworkConfigurationManager::updateCompleted()
QObject::connect: Cannot connect (null)::onlineStateChanged(bool) to QNetworkConfigurationManager::onlineStateChanged(bool)
QObject::connect: Cannot connect (null)::configurationChanged(QNetworkConfiguration) to QNetworkConfigurationManager::configurationChanged(QNetworkConfiguration)
Process Python segmentation fault (this last line is comes from emacs)
You need to handle the QApplication outside of the test functions, sort of like a singleton (it's actually appropriate here).
What you can do is to check if QtCore.qApp is something (or if QApplication.instance() returns None or something else) and only then create your qApp, otherwise, use the global one.
It will not be destroyed after your test() function since PyQt stores the app somewhere.
If you want to be sure it's handled correctly, just setup a lazily initialized singleton for it.
A QApplication should only be initialized once!
It can be used by as many Capture instances as you like, but you should start them in the mainloop.
See: https://doc.qt.io/qt-4.8/qapplication.html
You could also try "del app" after "app.exec_", but I am unsure about the results.
(Your original code runs fine on my system)
I would use urllib instead of webkit:
import urllib
class Capturer:
def capture(self, s_url, s_filename):
s_file_out, httpmessage = urllib.urlretrieve(s_url, s_filename, self.report)
def report(self, i_count, i_chunk, i_size):
print('retrived %5d of %5d bytes' % (i_count * i_chunk, i_size))
def test():
c = Capturer()
c.capture("http://www.google.com/google.png", "google1.png")
c.capture("http://www.google.com/google.png", "google2.png")
if __name__ == '__main__':
test()

Categories