Forum Archive

Finding a more elegant way of communicating between views

superrune

Hi!

I am trying to get better in Python/Pythonista by doing a pixel paint app. I have some problems understanding communication between views, nested and otherwise, and I was hoping you might have some suggestion to how I can make my code more elegant.

My paint app is built up from views, there is an editor view at the "root" holding the image and tool buttons. What I want to do, is to open a file selector view on top of this, that does stuff with the editor view "below". For that I need the file view to access variables and functions that belong to the root editor.

Right now, I have nested everything. The editor functions are inside the editor class, and the file view is a function of the editor class. Also, the file view functions are nested inside the file view function itself. But this feels real messy to me, and I'd rather have the file window as a separate class that is outside of the editor. But I'm not sure how to let the communicate back to the editor!

I have made a super-simplified example of how my app works. Can anybody take a quick look and give me some hints?

#!python3
import ui
from glob import glob
from os.path import basename

class pixelEditor(ui.View):

    def fileWindow(self, sender):
        fileWindow = ui.View(frame=(100, 150, 300, 300), name='File window', border_width=2)
        imagefiles = [basename(x) for x in glob('*.*')]

        def loadAction(sender):
            # Dummy for the function that loads the image into the editor
            selectedFile = imagefiles[filelist.selected_row[1]]
            print ('Selected ' + selectedFile + ' from sender: ' + sender.name)
            # Sends the selected image to the pixel Editor
            self.subviews[1].text = selectedFile

        def fileSelected(sender):
            filePreview.background_color = 'red'

        filelistData = ui.ListDataSource(imagefiles)
        filelistData.delete_enabled=False

        filelist = ui.TableView(frame=(10, 10 ,150, 280), data_source=filelistData, name='filelist')
        filelist.row_height = 24
        filelist.action = fileSelected # Does not work...
        fileWindow.add_subview(filelist)

        filePreview = ui.ImageView(frame=(170,10,120,100))
        filePreview.background_color = 'black'
        fileWindow.add_subview(filePreview)

        loadButton = ui.Button(name='Load', frame=(170,120,64,32), title='Load')
        loadButton.background_color = 'white'
        loadButton.action = loadAction
        fileWindow.add_subview(loadButton)

        self.add_subview(fileWindow)
        print('File window opened.')


    def __init__(self, width=640, height=480):
        self.bg_color = 'grey'

        fileButton = ui.Button(name='File', frame=(10,80,64,32), title='File Window')
        fileButton.background_color = 'white'
        fileButton.action = self.fileWindow
        self.add_subview(fileButton)

        fileLabel = ui.Label(frame=(100, 80, 300, 32), font=('HelveticaNeue-Light', 32), text='___')
        fileLabel.name = 'File Label'
        self.add_subview(fileLabel)

        print(self.superview)


v = pixelEditor()
v.present('fullscreen')
mikael

@superrune, if I understand your question correctly:

The file picker view can be defined in a separate class and still communicate easily with the editor view via the superview attribute (after having been included in the view hierarchy, of course).

JonB

If you are trying to make reusable classes, you could consider using a delegate attribute, or menu items are defined with a title, target object and a method that gets called on the target. That way a menu can target the root, or target some low level component.

superrune

Thanks for answering! I’ve tried to restructure the script so that the file windows is a separate class, but it doesn’t behave quite as I expected. But views opens instantaneously, and the buttons don’t seem to work. Any pointers?

```

!python3

import ui
from glob import glob
from os.path import basename

class fileWindow(ui.View):
def init(self, frame=(100, 150, 300, 300)):
self.name = 'File window'
self.border_width = 2

    imagefiles = [basename(x) for x in glob('*.*')]

    def loadAction(sender):
        selectedFile = imagefiles[filelist.selected_row[1]]
        print ('Selected ' + selectedFile + ' from sender: ' + sender.name)
        # Sends the selected image to the pixel Editor
        self.subviews[1].text = selectedFile

    def fileSelected(sender):
        # This does not work..
        filePreview.background_color = 'red'

    filelistData = ui.ListDataSource(imagefiles)
    filelistData.delete_enabled=False

    filelist = ui.TableView(frame=(10, 10 ,150, 280), data_source=filelistData, name='filelist')
    filelist.row_height = 24
    filelist.action = fileSelected # This does not work...
    self.add_subview(filelist)

    filePreview = ui.ImageView(frame=(170,10,120,100))
    filePreview.background_color = 'black'
    self.add_subview(filePreview)

    loadButton = ui.Button(name='Load', frame=(170,120,64,32), title='Load')
    loadButton.background_color = 'white'
    loadButton.action = loadAction
    self.add_subview(loadButton)

class pixelEditor(ui.View):
def init(self, width=640, height=480):
self.bg_color = 'grey'

    def openFileWindow():
        fv = fileWindow()
        fv.present()
        print('File window opened.')

    fileButton = ui.Button(name='File', frame=(10,80,64,32), title='File Window')
    fileButton.background_color = 'white'
    fileButton.action = openFileWindow()
    self.add_subview(fileButton)

    fileLabel = ui.Label(frame=(100, 80, 300, 32), font=('HelveticaNeue-Light', 32), text='___')
    fileLabel.name = 'File Label'
    self.add_subview(fileLabel)

    print(self.superview)

v = pixelEditor()
v.present('fullscreen')
```

mikael

@superrune, remove the parentheses from the end of

fileButton.action = openFileWindow()

Now you are calling the method (and immediately opening the window) and setting action to the value returned, which is None, and thus the button does nothing.

superrune

Thanks @mikael, for explaining this to me. That makes total sense. When I do that though, I get this error:
TypeError: openFileWindow() takes 0 positional arguments but 1 was given
Answers on stackoverflow mention that the error occurs when you forgot to add ‘self’ to the functions inside the class, but that doesn’t seem to make any change.

cvp

@superrune a button action needs sender as parameter to identify which object has been tapped

        def openFileWindow(sender):
superrune

I’m slowly moving closer to something that works! So I moved the open view function out of the class, and now opening the window works! So now I need to get these two windows to talk to each other. Inside the init of the file window I am printing the superview to make sure it’s actually a child, but superview returns none. So why isn’t the parent/child relationship set up, even though I am creating this as a sub view?

#!python3
import ui
from glob import glob
from os.path import basename

class fileWindow(ui.View):
    def __init__(self):
        self.frame=(100, 150, 300, 300)
        self.name = 'File window'
        self.border_width = 2
        print ('Loader superview:', self.superview)

        imagefiles = [basename(x) for x in glob('*.*')]

        def loadAction(sender):
            selectedFile = imagefiles[filelist.selected_row[1]]
            print ('Selected ' + selectedFile + ' from sender: ' + sender.name)
            # Sends the selected image to the pixel Editor
            self.superview.text = selectedFile

        def fileSelected(sender):
            # This does not work..
            filePreview.background_color = 'red'

        filelistData = ui.ListDataSource(imagefiles)
        filelistData.delete_enabled=False

        filelist = ui.TableView(frame=(10, 10 ,150, 280), data_source=filelistData, name='filelist')
        filelist.row_height = 24
        filelist.action = fileSelected # This does not work...
        self.add_subview(filelist)

        filePreview = ui.ImageView(frame=(170,10,120,100))
        filePreview.background_color = 'black'
        self.add_subview(filePreview)

        loadButton = ui.Button(name='Load', frame=(170,120,64,32), title='Load')
        loadButton.background_color = 'white'
        loadButton.action = loadAction
        self.add_subview(loadButton)

def openFileWindow(sender):
        fv = fileWindow()
        sender.add_subview(fv)
        fv.present()
        #fv = fileWindow()
        #v.present()
        print('File window opened.')

class pixelEditor(ui.View): 
    def __init__(self, width=640, height=480):
        self.bg_color = 'grey'

        fileButton = ui.Button(name='File', frame=(10,80,64,32), title='File Window')
        fileButton.background_color = 'white'
        fileButton.action = openFileWindow
        self.add_subview(fileButton)

        fileLabel = ui.Label(frame=(100, 80, 300, 32), font=('HelveticaNeue-Light', 32), text='___')
        fileLabel.name = 'File Label'
        self.add_subview(fileLabel)

        print(self.superview)


v = pixelEditor()
v.present('fullscreen')
superrune

I tried changing self to sender in the function that opens the window, but still the parent view is returned as None.

cvp

@superrune normal way is this, remark action = self.xxxxx and def xxx(self,sender) at same level as the def init
```

class pixelEditor(ui.View):
def init(self, width=640, height=480):
self.bg_color = 'grey'

    fileButton = ui.Button(name='File', frame=(10,80,64,32), title='File Window')
    fileButton.background_color = 'white'
    fileButton.action = self.openFileWindow
    self.add_subview(fileButton)

    fileLabel = ui.Label(frame=(100, 80, 300, 32), font=('HelveticaNeue-Light', 32), text='___')
    fileLabel.name = 'File Label'
    self.add_subview(fileLabel)

    print(self.superview)

def openFileWindow(self, sender):
    fv = fileWindow()
    fv.present()
    print('File window opened.')```
superrune

When I do that, I get the error message 'pixelEditor' object has no attribute 'openFileWindow'

Moving the definition before the action assignment doesnt change anything either.

cvp

@superrune did you remark the new indentation of OpenFileWindow

It became a method of the class...

cvp

@superrune The other problem, of the superview, is that you use self.superview in the init.
The class is not yet really created. Try this and see where I use the self.superview

#!python3
import ui
from glob import glob
from os.path import basename

class fileWindow(ui.View):
    def __init__(self):
        self.frame=(100, 150, 300, 300)
        self.name = 'File window'
        self.border_width = 2
        #print ('Loader superview:', self.superview)

        imagefiles = [basename(x) for x in glob('*.*')]


        filelistData = ui.ListDataSource(imagefiles)
        filelistData.delete_enabled=False

        filelist = ui.TableView(frame=(10, 10 ,150, 280), data_source=filelistData, name='filelist')
        filelist.row_height = 24
        filelist.action = self.fileSelected # This does not work...
        self.add_subview(filelist)

        filePreview = ui.ImageView(frame=(170,10,120,100))
        filePreview.background_color = 'black'
        self.add_subview(filePreview)

        loadButton = ui.Button(name='Load', frame=(170,120,64,32), title='Load')
        loadButton.background_color = 'white'
        loadButton.action = self.loadAction
        self.add_subview(loadButton)

        ui.delay(self.x,0.01)
    def x(self):
        print ('Loader superview title = ', self.superview.title)

    def loadAction(self,sender):
        selectedFile = imagefiles[filelist.selected_row[1]]
        print ('Selected ' + selectedFile + ' from sender: ' + sender.name)
        # Sends the selected image to the pixel Editor
        self.superview.text = selectedFile

    def fileSelected(self,sender):
        # This does not work..
        filePreview.background_color = 'red'

class pixelEditor(ui.View): 
    def __init__(self, width=640, height=480):
        self.bg_color = 'grey'

        fileButton = ui.Button(name='File', frame=(10,80,64,32), title='File Window')
        fileButton.background_color = 'white'
        fileButton.action = self.openFileWindow
        self.add_subview(fileButton)

        fileLabel = ui.Label(frame=(100, 80, 300, 32), font=('HelveticaNeue-Light', 32), text='___')
        fileLabel.name = 'File Label'
        self.add_subview(fileLabel)

        print(self.superview)

    def openFileWindow(self,sender):
        fv = fileWindow()
        sender.add_subview(fv)
        fv.present()
        #fv = fileWindow()
        #v.present()
        print('File window opened.')



v = pixelEditor()
v.present('fullscreen')
superrune

Thanks, that also worked. I had the sub-window opening OK a couple steps back as well. But the next problem still remains, though, even in this version.

I do a print ('Loader superview:', self.superview) when I init the fileWindow, to see that there is a parent view I can put the selected file into, but that still returns None. Is there a way to get the new view properly assigned as a child of the first pixelEditor view?

edit: I see you have made a new function that prints the superview, but it returns fileWindow. So the windows parent is itself? Why is the superview not pixelEditor?

Thanks for taking the time to help me out!!

cvp

@superrune I'll check your question but you also have another error

        selectedFile = self['filelist'].data_source.items[self['filelist'].selected_row[1]]
cvp

@superrune You made a mistake: the superview is a button and my print in my new little x function is the title of the button, not the class.
You named your button 'File Window'....

cvp

@superrune Then, change in OpenFileWindow, set the FileWindow as a child of self which is the pixeleditor

    def openFileWindow(self,sender):
        fv = fileWindow()
        self.add_subview(fv)

And in def x():

    def x(self):
        print ('Loader superview type = ', type(self.superview))
cvp

@superrune other big problem, when you present the FileWindow, the PixelEditor is no more presented, thus try without presenting it:

    def openFileWindow(self,sender):
        fv = fileWindow()
        self.add_subview(fv)
        #fv.present()
        #fv = fileWindow()
        #v.present()
        print('File window opened.')
cvp

@superrune And last little problem, to "send" the selected file name to PixelEditor, use something like

        # Sends the selected image to the pixel Editor
        self.superview.name = selectedFile

and you will see the name of the file on the title bar of the PixelEditor View.

superrune

@cvp said:

print ('Loader superview type = ', type(self.superview))

Thanks,

I still have to do a fv.present() inside the openFileWindow function, right? The window will not show up otherwise...

print ('Loader superview type = ', type(self.superview)) now returns a NoneType, though.

I still want to put the selected file name into fileLabel.text - how would you go about doing that?

cvp

@superrune some answers written before your last post, isn't it?

cvp

@superrune file name in label

        #self.superview.name = selectedFile
        self.superview['File Label'].text = selectedFile
cvp

@superrune other problem

    def fileSelected(self,sender):
        # This does not work..
        filePreview.background_color = 'red'

does not work because filePreview does not exist

cvp

@superrune TableView does not have an action...I'll show you how to do, wait

cvp

@superrune this

        filelist.delegate = self
        #filelist.action = self.fileSelected # This does not work...

and this instead your fileSelected

    def tableview_did_select(self, tableview, section, row):
        # Called when a row was selected.
        self['filePreview'].background_color = 'red'

    #def fileSelected(self,sender):
        # This does not work..
        #self['filePreview'].background_color = 'red'
superrune

Cool, now most of the script seems to work as it should. Thanks again!

I changed the text assignment to this, and now it appears where I intended:

        # Sends the selected image to the pixel Editor
        self.superview['File Label'].text = selectedFile 

filePreview should exist though, should it not? I created it as an imageview a couple lines up, so is should be within the scope of the file window. Looks more like the function isnt called at all when I press the different table lines.

Here is how it looks right now:
```#!python3
import ui
from glob import glob
from os.path import basename

class fileWindow(ui.View):
def init(self):
self.frame=(100, 150, 300, 300)
self.name = 'File window'
self.border_width = 2
print ('Loader superview at init:', self.superview)

    imagefiles = [basename(x) for x in glob('*.*')]


    filelistData = ui.ListDataSource(imagefiles)
    filelistData.delete_enabled=False

    filelist = ui.TableView(frame=(10, 10 ,150, 280), data_source=filelistData, name='filelist')
    filelist.row_height = 24
    filelist.action = self.fileSelected # This does not work...
    self.add_subview(filelist)

    filePreview = ui.ImageView(frame=(170,10,120,100))
    filePreview.background_color = 'black'
    self.add_subview(filePreview)

    loadButton = ui.Button(name='Load', frame=(170,120,64,32), title='Load')
    loadButton.background_color = 'white'
    loadButton.action = self.loadAction
    self.add_subview(loadButton)

    ui.delay(self.x,0.01)

def x(self):
    print ('Loader superview type = ', type(self.superview))

def loadAction(self,sender):
    selectedFile = self['filelist'].data_source.items[self['filelist'].selected_row[1]]
    print ('Selected ' + selectedFile + ' from sender: ' + sender.name)
    # Sends the selected image to the pixel Editor
    self.superview['File Label'].text = selectedFile

def fileSelected(self,sender):
    # This does not work..
    print('Gonk!!')
    filePreview.background_color = 'red'

class pixelEditor(ui.View):
def init(self, width=640, height=480):
self.bg_color = 'grey'

    fileButton = ui.Button(name='File', frame=(10,80,64,32), title='File:')
    fileButton.background_color = 'white'
    fileButton.action = self.openFileWindow
    self.add_subview(fileButton)

    fileLabel = ui.Label(frame=(100, 80, 300, 32), font=('HelveticaNeue-Light', 32), text='___')
    fileLabel.name = 'File Label'
    self.add_subview(fileLabel)

    print(self.superview)

def openFileWindow(self, sender):
    fv = fileWindow()
    self.add_subview(fv)
    #fv.present()
    print('File window opened.')

v = pixelEditor()
v.present('fullscreen')
```

superrune

Cool, saw your answer now!! I keep replying to fast :)

cvp

@superrune to see images like jpg, ping, gif,

Put in init

        filePreview.content_mode = ui.CONTENT_SCALE_ASPECT_FIT

And in selection

    def tableview_did_select(self, tableview, section, row):
        # Called when a row was selected.
        try:
            fil = tableview.data_source.items[row]
            self['filePreview'].background_color = 'red'
            self['filePreview'].image = ui.Image.named(fil)
        except Exception as e:
            # not an image
            #print(e)
            pass
cvp

@superrune Of course, these 3 lines may be commented, they have been added only to show you that self.superview is known only after init...

        ui.delay(self.x,0.01)        
    def x(self):
        print ('Loader superview type = ', type(self.superview))
cvp

@superrune If you want a preview of a lot of files types, not only images, do

from os.path import basename,abspath
import sys

and

        filePreview = ui.ImageView(frame=(170,10,120,100))
        filePreview.name = 'filePreview'
        filePreview.background_color = 'black'
        filePreview.content_mode = ui.CONTENT_SCALE_ASPECT_FIT
        self.add_subview(filePreview)

        textview = ui.TextView(name='textview')
        textview.frame = filePreview.frame
        self.add_subview(textview)

        webview = ui.WebView(name='webview')
        webview.frame = filePreview.frame
        self.add_subview(webview)

and

    def tableview_did_select(self, tableview, section, row):
        # Called when a row was selected.
        try:
            fil = tableview.data_source.items[row]
            i = fil.rfind('.')
            ext = fil.lower()[i:]
            if ext in ['.txt','.dat','.py','.md']:
                with open(fil,'r',encoding='utf-8') as f:
                    t = f.read()
                self['textview'].text = t
                self['textview'].bring_to_front()
            elif ext in ['.png','.jpg','.jpeg','.bmp','.tif','.tiff']:
                self['filePreview'].image = ui.Image.named(fil)
                self['filePreview'].bring_to_front()
            elif ext in ['.gif','.htm','.html','.webarchive','.pdf','.mov','.mp3','.wav','.m4a','.mp4','.avi','.doc','.docx','.xls','.xlsx','.pps','.ppt','.gmap']:
                f = sys.argv[0]         # folder of script
                i = fil.rfind('/')
                self['webview'].load_url(abspath(f[:i+1]+fil))
                self['webview'].bring_to_front()
            else:
                self['filePreview'].background_color = 'red'
                self['filePreview'].bring_to_front()
        except Exception as e:
            # not an image
            print(e)
            pass
superrune

Thanks @cvp! This is great!

cvp

@superrune 👍
Don't hesitate for any new question

You have to know that there is a FilePicker from omz and other ones....