I have the same problem mentioned here : Not being able to edit NSTextField on NSPopover even though Editable behavior is set. The solution seems to be to override the canBecomeKeyWindow of NSWindow. I am trying to do the same thing in PyObjC, but I am getting an error Python signature doesn't match implied objective-C signature.
In the following code, if I comment out canBecomeKeyWindow_(), then the app runs as expected, but I am not able to click and edit the textfields.
# from Cocoa import *
from AppKit import NSWindowController, NSApplication, NSApp, NSMaxYEdge, NSImage, NSStatusBar, NSMenu, NSMenuItem, NSVariableStatusItemLength, NSRect
from Cocoa import objc
from Foundation import NSUserNotification, NSUserNotificationCenter, NSObject
from PyObjCTools import AppHelper
import webbrowser
import subprocess
import os
global popover
class TestApp(NSApplication):
def finishLaunching(self):
# Make statusbar item
statusbar = NSStatusBar.systemStatusBar()
self.statusitem = statusbar.statusItemWithLength_(NSVariableStatusItemLength)
self.icon = NSImage.alloc().initByReferencingFile_('app-icon.png')
self.icon.setScalesWhenResized_(True)
self.icon.setSize_((20, 20))
self.statusitem.setImage_(self.icon)
self.statusitem.setHighlightMode_(1)
# make the menu
self.menubarMenu = NSMenu.alloc().init()
self.menuItem = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_('Login', 'loginCallback:', '')
self.menubarMenu.addItem_(self.menuItem)
self.quit = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_('Quit', 'terminate:', '')
self.menubarMenu.addItem_(self.quit)
# add menu to statusitem
self.statusitem.setMenu_(self.menubarMenu)
def loginCallback_(self, notification):
# Initiate the contrller with a XIB
viewController = SimpleXibDemoController.alloc().initWithWindowNibName_("Login")
# Show the window
viewController.showWindow_(viewController)
rect = self.statusitem.valueForKey_('button').frame()
viewController.popover.showRelativeToRect_ofView_preferredEdge_(rect, self.statusitem.valueForKey_('button'), NSMaxYEdge)
class SimpleXibDemoController(NSWindowController):
popover = objc.IBOutlet()
counterTextField = objc.IBOutlet()
username_field = objc.IBOutlet()
password_field = objc.IBOutlet()
submit_button = objc.IBOutlet()
def canBecomeKeyWindow_(self):
return 1
def windowDidLoad(self):
NSWindowController.windowDidLoad(self)
#objc.IBAction
def submit_(self, sender):
username = self.username_field.stringValue()
password = self.password_field.stringValue()
self.updateDisplay(username + ' ' + password)
def updateDisplay(self, value):
self.counterTextField.setStringValue_(value)
if __name__ == "__main__":
app = TestApp.sharedApplication()
icon = NSImage.alloc().initByReferencingFile_('app-icon.png')
app.setApplicationIconImage_(icon)
AppHelper.runEventLoop()
It looks like you're adding an underscore where you shouldn't. The PyObjC bridge will translate it into a colon. Besides that, the corresponding Python boolean value should be True. Thus, the correct function would look like this:
def canBecomeKeyWindow(self):
return True
Related
I am developing an EEL project, and I needed to create a file dialog on the python side in order to preprocess data before sending it to javascript.
I tried to use tk.filedialog.askopenfilename, but that somehow froze the javascript event loop.
I found an answer on StackOverflow that used wxpython to create a non-blocking file picker. However, when I run the code below, the file picker always starts minimized.
However, once you use the file picker once, it works perfectly the second time.
Any help appreciated.
import base64
import json
from tkinter import Tk
Tk().withdraw()
from tkinter.filedialog import askopenfilename
import PIL.Image
import eel
import numpy as np
import wx
# Reusable wxpython App instance for the creation of non-blocking popup dialogs
app=wx.App(None)
eel.init("public")
def encode(bts):
return base64.b64encode(bts)
def array_to_json(array):
return json.dumps({
"shape": list(array.shape),
"dtype": str(array.dtype),
"data":list(np.ravel(array).astype(float)) # not efficient but quite clean
})
#eel.expose
def load_image(path):
return array_to_json(np.asarray(PIL.Image.open(path)))
#eel.expose
def pick_image():
# return askopenfilename()
""" --- Adapted from https://stackoverflow.com/a/59177064/5166365"""
style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST | wx.STAY_ON_TOP | wx.DIALOG_NO_PARENT | wx.MAXIMIZE
dialog = wx.FileDialog(None, "Open File", wildcard="*", style=style)
dialog.Iconize(False)
dialog.Maximize()
dialog.Raise()
path = ""
if dialog.ShowModal() == wx.ID_OK:
path = dialog.GetPath()
else:
path = ""
return path
""" --- """
eel.start("index.html")
I was able to get it working with the following code. I used a regular window instead of a wx.Dialog or similar class.
class FancyFilePickerApplication:
def __init__(self):
self.app = wx.App()
self.frame = wx.Frame(None,title="Fancy File Picker")
self.build_ui()
##private
def build_ui(self):
self.vertical_sizer = wx.BoxSizer(wx.VERTICAL)
self.control_panel = wx.Panel(self.frame,wx.ID_ANY)
self.horizontal_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.control_panel.SetSizer(self.horizontal_sizer)
self.frame.SetSizer(self.vertical_sizer)
self.dir_ctrl = wx.GenericDirCtrl(self.frame,wx.ID_ANY,wx.EmptyString,wx.DefaultPosition,wx.Size(400,300))
self.vertical_sizer.Add(self.dir_ctrl,wx.SizerFlags().Expand())
self.vertical_sizer.Add(self.control_panel,wx.SizerFlags().Expand())
self.scale_factor_label = wx.StaticText(self.control_panel,wx.ID_ANY,"Scale Factor: ")
self.scale_factor_textbox = wx.TextCtrl(self.control_panel,wx.ID_ANY)
self.open_button = wx.Button(self.control_panel,wx.ID_ANY,"Open")
self.horizontal_sizer.Add(self.scale_factor_label,wx.SizerFlags().Expand())
self.horizontal_sizer.Add(self.scale_factor_textbox,wx.SizerFlags().Expand())
self.horizontal_sizer.Add(self.open_button,wx.SizerFlags().Expand())
self.open_button.Bind(wx.EVT_BUTTON,lambda evt:self.submit())
self.frame.Bind(wx.EVT_CLOSE,self.cancel)
def open(self, file_picked_callback):
self.file_picked_callback = file_picked_callback
self.frame.Fit()
self.frame.Center()
self.frame.Show()
self.frame.Raise()
self.frame.ToggleWindowStyle(wx.STAY_ON_TOP)
self.app.MainLoop()
##private
def submit(self):
filepath =self.dir_ctrl.GetFilePath()
scale_factor_text = self.scale_factor_textbox.GetValue()
scale_factor = 1.0 if not scale_factor_text.strip() else float(scale_factor_text)
self.file_picked_callback(filepath,scale_factor)
self.frame.Destroy()
##private
def cancel(self,evt):
self.file_picked_callback("",0)
self.frame.Destroy()
I am pretty new to python and this is the first time I use tkinter so I hope someone can help me to find the right direction.
Basically this is what I would like to achieve:
I retrieve from an XML 2 lists (APPs, IDs);
The APP List will be shown in a Dropdown menu;
The APP selection in the Dropdown menu will call the APP status using its ID.
I can't get the last point work, basically I think I understand why (I have no matching between the two lists or a function to match them, and the selection calls automatically the last ID of second list) but I am to the best of my knowledge not able to solve it.
import requests
import xml.etree.ElementTree as ET
import tkinter as tk
APP_OPTIONS = []
ID_OPTIONS = []
session = requests.Session()
session.auth = ('USER', 'PW')
applications = session.get('https://getapplicationslist.myurl.com/application/')
applications_xml = applications.content
root = ET.fromstring(applications_xml)
for application in root.findall('application'):
app_name = application.find('name').text
app_id = application.find('id').text
APP_OPTIONS.append(app_name)
ID_OPTIONS.append(app_id)
def appcall(*args):
app_status = session.get('https://getapplicationstatus.myurl.com?Id=' + app_id)
status_xml = app_status.content
root = ET.fromstring(status_xml)
for appStatus in root.findall('appStatus'):
status = appStatus.find('status').text
print(status)
root = tk.Tk()
root.title('Application List')
root.geometry("300x200")
var =tk.StringVar(root)
var.set('Choose an Application')
var.trace('w', appcall)
dropDownMenu = tk.OptionMenu(root, var, *APP_OPTIONS)
dropDownMenu.pack()
root.mainloop()
print('End Request')
As mentioned in my comment, the issue is your app_id in appcall does not change. You need to get the corresponding ID from the ID_OPTIONS instead.
def appcall(*args):
app_id = ID_OPTIONS[APP_OPTIONS.index(var.get())] # Add this line
app_status = session.get('https://getapplicationstatus.myurl.com?Id=' + app_id)
...
The app_id is now set to the ID_OPTIONS of the same index based on the app_name (since the insertion order is the same).
However, a better approach would be to initialize your options as a dictionary instead:
# instead of APP_OPTIONS / ID_OPTIONS, create:
apps = {}
...
for application in root.findall('application'):
app_name = application.find('name').text
app_id = application.find('id').text
# add to dictionary here:
apps[app_name] = app_id
def appcall(*args):
# Change the app_id to apps.get(var.get())
app_status = session.get('https://getapplicationstatus.myurl.com?Id=' + apps.get(var.get())
...
See how much simpler it is to recall the same reference?
If you are feeling comfortable about the language, you might even opt for a dictionary comprehension:
...
root = ET.fromstring(applications_xml)
app_id = {application.find('name').text: application.find('id').text for application in root.findall('application')}
...
I'm using Kivy example code to get file path from two different files.
My goal is to use the file path to open and manipulate data from the file.
My problem is to pass the file path into the open file command in the test function below.
Here is my Code:
from kivy.app import App
from kivy.uix.button import Button
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
import re
import pandas as pd
class DropFile(Button):
def __init__(self, **kwargs):
super(DropFile, self).__init__(**kwargs)
# get app instance to add function from widget
app = App.get_running_app()
# add function to the list
app.drops.append(self.on_dropfile)
def on_dropfile(self, widget, path):
# a function catching a dropped file
# if it's dropped in the widget's area
if self.collide_point(*Window.mouse_pos):
self.text = path
def test(self):
minimum_wage = open(**FILE PATH HERE**)
LinesToString = ''
for line in minimum_wage:
LinesToString += line
patFinder = re.compile('\d{5}\s+\d{5,9}')
findPat = re.findall(patFinder, LinesToString)
empno_list = []
pattern = '(\d{5})\s+(\d{5})'
for string in findPat:
match = re.search(pattern, string)
empno = match.group(2)
empno_list.append(empno)
MinimumWage = pd.DataFrame({'EMPNO': empno_list})
MinimumWage.set_index('EMPNO')
print MinimumWage.head()
print MinimumWage.shape
class DropApp(App):
def build(self):
# set an empty list that will be later populated
# with functions from widgets themselves
self.drops = []
# bind handling function to 'on_dropfile'
Window.bind(on_dropfile=self.handledrops)
box = BoxLayout(orientation='vertical')
top_label = Label(text='Data manipulation', font_size=45)
box.add_widget(top_label)
run_button = Button(text='Run', size_hint=(1, 0.5))
run_button.bind(on_press=DropFile.test)
box.add_widget(run_button)
two_buttons = BoxLayout(orientation='horizontal')
dropleft = DropFile(text='Drag & Drop File here')
# dropright = DropFile(text='right')
two_buttons.add_widget(dropleft)
# two_buttons.add_widget(dropright)
box.add_widget(two_buttons)
return box
def handledrops(self, *args):
# this will execute each function from list with arguments from
# Window.on_dropfile
#
# make sure `Window.on_dropfile` works on your system first,
# otherwise the example won't work at all
for func in self.drops:
func(*args)
DropApp().run()
Thanks
You can call test() method at the last line of on_dropfile() e.g.:
def on_dropfile(self, widget, path):
# a function catching a dropped file
# if it's dropped in the widget's area
if self.collide_point(*Window.mouse_pos):
self.text = path
self.test(path)
def test(self, path):
minimum_wage = open(path)
LinesToString = ''
...
or launch already from the existing thing e.g. if you run test() separately from the on_dropfile() function and you won't change self.text property after changing the text:
def on_dropfile(self, widget, path):
# a function catching a dropped file
# if it's dropped in the widget's area
if self.collide_point(*Window.mouse_pos):
self.text = path # path is assigned to self.text <--
def test(self):
minimum_wage = open(self.text) # <-- and you can use it
LinesToString = ''
...
Or at the end of on_dropfile put it into a separate variable and use that in open().
I've been trying to run this simple test just to get the feel of how to import methods from another script, but I have two problems:
the whole program runs instead of just importing and calling, the methods, when I need them too.
I'm getting this error:
Traceback (most recent call last):
File "C:\Users\devilboy 4\Documents\Visual Studio 2013\Projects\classvariablesOnANewScript\classvariablesOnANewScript\classvariablesOnANewScript.py", line 1, in <module>
from logInScreen import getUser, getPass
ImportError: cannot import name getUser
This is the script I'm importing to:
from logInScreen import getUser, getPass
class hello:
def show(self):
usr = getUser()
ps = getPass()
str(usr)
str(ps)
h = hello()
h.show()
This is what's on logInScreen.py :
from tkinter import *
import os
import tkinter.messagebox
#check si lo entrado es correcto
class checkValidation:
fail = 0
user = "hi user"
password = "nice pass"
#valida el user y el pass
def fullVali(self, name, passwd):
if name == "" or name == " ":
tkinter.messagebox.showinfo( "Error","Dejo el usuario en blanco")
self.fail+= 1
elif name != "UmetSeg":
tkinter.messagebox.showinfo( "Error","Usuario incorrecto")
self.fail+= 1
else :
self.user = name
tkinter.messagebox.showinfo( "ok","dude" + name)
if passwd == "" or passwd == " ":
tkinter.messagebox.showinfo( "Error","Dejo la password en blanco")
self.fail+= 1
elif passwd != "SegUmet":
tkinter.messagebox.showinfo( "Error","Password incorrecto")
self.fail+= 1
else:
self.password = passwd
tkinter.messagebox.showinfo( "ok","dude" + passwd)
form.destroy()
#open another py script
#os.system("mainPage3.py 1")
return
# no deja pasar parametros por command en el boton a menos que se por lambda, so corre #este metodo para
#correr el metodo de validar
def callVali(self):
user = usrIn.get()
self.fullVali(usrIn.get(), passIn.get())
return
def getUser(self):
return self.user
def getPass(self):
return self.password
vali = checkValidation()
form = Tk()
form.title("LogIn")
form.geometry("300x320+300+200")
#User txtBox
usrIn = Entry(form, textvariable = None, width = 30)
usrIn.place(x = 60, y = 140)
user = usrIn.get()
#Passwd txtBox
passIn = Entry(form, textvariable = None, width = 30)
passIn.place(x = 60, y = 200)
#Username Label
usrLblVal = StringVar()
usrLblVal.set("User name")
usrLbl = Label(form, textvariable = usrLblVal )
usrLbl.place(x = 120, y = 115)
#Passwrd label
passLblVal = StringVar()
passLblVal.set("Password")
passLbl = Label(form, textvariable = passLblVal )
passLbl.place(x = 120, y = 175)
#Login btn
btn = Button(form, text = "Entrar", width = 10, command = vali.callVali)
btn.place(x = 110, y = 250)
form.mainloop()
I hope I got the indentation right, kinda off a pain going through every line and spacing 4 times till it's right. I apologize for the Spanish, just ignore all the comments lol
You are attempting to import methods from within a class in your LogInScreen.py file. The methods getUser and getPass are bound methods that belong to the class checkValidation, You need to instead import the class and make the call on your class instead
from LogInScreen import checkValidation
validation = checkValidation()
validation.getUser()
validation.getPass()
As a simple illustration consider the following example involving two files:
file_to_import_from.py (Analogous to your logInScreen.py)
class MyClass:
def my_class_bound_method(self):
print "hello I a method belonging to MyClass"
def my_method_not_belonging_to_a_class():
print "Hello, I am a method that does not belong to a class"
file_to_import_to.py (Analogous to the script you are importing to)
from file_to_import_from import my_method_not_belonging_to_a_class
my_method_not_belonging_to_a_class()
from file_to_import_from import MyClass
x = MyClass()
x.my_class_bound_method()
from file_to_import_from import my_class_bound_method
my_class_bound_method()
And see Output
Hello, I am a method that does not belong to a class
hello I a method belonging to MyClass
Traceback (most recent call last):
File "C:\Users\Joe\Desktop\Python\import_test2.py", line 10, in <module>
from file_to_import_from import my_class_bound_method
ImportError: cannot import name my_class_bound_method
As you can see, the first two calls worked, but the second time the error you are facing arose, this is because the method my_class_bound_method exists as a method within the class MyClass.
EDIT:
In response to your comment, to avoid running the whole file, surround the code in your 'LogInScreen.py' in an if statement which checks if the file being evaluated by the interpreter is the main file being run.
from tkinter import *
import os
import tkinter.messagebox
#check si lo entrado es correcto
class checkValidation:
# CODE FROM YOUR CLASS OMITTED FOR BREVITY SAKE
# NOTHING BEYOUND THIS IF STATEMENT WILL RUN WHEN THE FILE IS IMPORTED
if __name__ == "__main__":
vali = checkValidation()
# MORE CODE OMITTED FOR BREVITY
form.mainloop()
The if statement I added here checks a special environment variable that python creates when it interprets a file, which is __name__, when the file is the file that you are directly running: python file_i_am_running.py the variable __name__ is set the the string "__main__" to indicate it is the main method, when it is not the file being run, and maybe one being imported as in your case, the variable is set to the module name, i.e. file_to_import.
Change to this:
from logInScreen import checkValidation
And then, use:
check = checkValidation()
usr = check.getUser()
ps = check.getPass()
in general, i want connect to the database selected by users.
i using 2 module, dblogin.py and xconn.py
dblogin.py is the gui for user to setting the desired dataname, and xconn.py is the connection to the postgresql
the problem is i can't get the value of dbedit in dblogin.py
how i can fix it?
thanks b4 for the answer.... Gbu all......
Regards,
ide
dblogin.py
class dblog(QDialog):
def __init__(self):
super(dblog, self).__init__()
self.dblabel = QLabel('Database Name')
self.dbedit = QLineEdit('')
#create button
...
#set layout in grid
#action for button
self.connect(self.connectbutton, SIGNAL('clicked()'),self.connectaction)
def connectaction(self):
self._data = self.dbedit.text()
if self._data == '':
_msg = QMessageBox.information(self,'information','Nama Database harus diisi !',QMessageBox.Ok)
self.dbedit.setFocus()
else:
try:
xconn.getconn()
_msg = QMessageBox.information(self,'information','Tunggu, Check database struktur!',QMessageBox.Ok)
except:
_msg = QMessageBox.information(self,'information','Database tidak ditemukan !',QMessageBox.Ok)
xconn.py
import psycopg2
import dblogin
def getconn():
_host = '127.0.0.1'
_user = 'postgres'
_pass = 'xxx'
_data = dblogin.dblog.getdb()
conn = psycopg2.connect(database=_data, user=_user, password=_pass, host=_host)
return conn
Your QDialog class should begin with capiatlized letters class DBLog. You can use the standardbuttons:
self.buttonBox = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel)
To set the text from the QLineEdit as return value, reimplement the accept method:
self.buttonBox.accepted.connect(self.accept)
def accept(self):
self._data = self.dbedit.text()
self.done(1)
Then in xconn create an instance od DBLog and only use the Dialog to get this value. Then from xconn.py do something like this:
dblog = DBLog() # create an instance for your dialog
if dblog:
_data = dblog._data
else:
Dialog not accepted