Forum Archive

Crossword/Codeword grid query

GaryGadget

Hi, I'm not used to coding with Python so using Pythonista to help learn the ways on my mobile device for convenience. I'm trying to rewrite a codeword solver I wrote in Visual Basic for Excel. I used the spreadsheet grid for the user interface but not sure of the best method to adopt in Pythonista. The idea is to draw a crossword type grid (codeword) which can be edited and updated for each time a letter Is substituted in the grid.
Not sure whether to use layers, user interface or separate tiles etc.

Still learning so any pointers or examples for crossword type interface coding help would be greatly appreciated.

Basically my program allows the user to see the effect of a particular letter when it is substituted in the grid. Thus helping to see if other areas of the grid look feasible. If not happy the then the operation can be corrected. Much quicker than pencil and paper apart from the initial set up of the grid.

Thanks for any help.

Regards
Gary

JonB

Are your grids a fixed size? Or dynamic?

Ui module is somewhat easier to use IMHO than scene. You could create an array of TableViews for each column. Or, you could draw an array of ui.TextFields for example.

brumm

I also think ui is the better choice. First you draw an black/white image and then make a matrix out of it. With this 0/1 matrix you draw a button matrix. Each button opens a popup menu where you can change the letter or word (button.name). Maybe you want to search for the pixel editor.

GaryGadget

Thanks for these chaps.
JonB my grids usually 13 or 15 cells depending on which newspaper/source for the puzzle. I suppose the cells could remain the same size as long as grid can be viewed clearly.

Brumm, I think I follow. A black or white background for the buttons running a matrix grid?

I'll have a study of the UI module. Can a UI be pinched/zoomed? Hope to view on iPhone as well as iPad you see.

Thanks again
Gary

brumm

I would recommend the ui.ScrollView for this.

JonB

Rather than a black or white image, you might consider just using bg_color to color the tiles. You might consider the action to be a function that takes sender, row, and col, but then you use functools partial to give it the proper one argument, so that you can easily have access to row and column info.

You could also do this with a grid of TextFields, if you want to be able to directly enter into the grid. Textfield delegate methods would be used to call your grid update functions, and limit each textfield to 1character.

ccc

Something to throw darts at... I did not find .bg_color to be the answer.

import console, ui

crossword_rows = 15
crossword_cols = 22
gap = 4 

class CrosswordView(ui.View):
    def __init__(self):
        self.present()
        min_dimension = min(self.width / crossword_cols, self.height / crossword_rows)
        #self.squares = []
        for i in xrange(crossword_cols):
            x = i * min_dimension
            for j in xrange(crossword_rows):
                y = j * min_dimension
                square = ui.TextField(frame = (x, y, min_dimension-gap, min_dimension-gap))
                square.alignment = ui.ALIGN_CENTER
                square.bg_color = 'black' # 'white' # 'grey' if (i+j) % 2 else 'white'
                square.delegate = self  # self.textfield_did_change() will be called
                square.name = '{} {}'.format(i,j)
                square.text = 'X'
                self.add_subview(square)
                #self.squares.append(square)

    def textfield_did_change(self, textfield):
        textfield.text = textfield.text[:1]  # no longer than 1 character
        x, y = textfield.name.split()
        fmt = 'x={}, y={}, text={}'
        console.hud_alert(fmt.format(x, y, textfield.text))

CrosswordView()
ccc

One step further by adding a crossword_entry which lets you specify row, column, direction, and length of a word...

 import collections, console, ui

crossword_rows = 15
crossword_cols = 22
gap = 4

crossword_entry = collections.namedtuple('crossword_entry',
                                         'row col direction length')
puzzle_entries = ( crossword_entry( 0, 0,'A',5),
                   crossword_entry( 5, 5,'D',7),
                   crossword_entry( 8, 3,'a',5),
                   crossword_entry(10,20,'d',4) )

def make_matrix(row_count, col_count, puzzle_entries):
    the_matrix = [[' ' for col in xrange(col_count)]
                       for row in xrange(row_count)]
    for the_entry in puzzle_entries:
        if the_entry.direction.upper() == 'A':  # across
            for i in xrange(the_entry.length):
                the_matrix[the_entry.row][the_entry.col+i] = 'X'
        else:                                   # down
            for i in xrange(the_entry.length):
                the_matrix[the_entry.row+i][the_entry.col] = 'X'
    return the_matrix

class CrosswordView(ui.View):
    def __init__(self, puzzle_matrix):
        row_count = len(puzzle_matrix)
        col_count = len(puzzle_matrix[0])
        self.present()
        min_dimension = min(self.width / col_count, self.height / row_count)
        for row in xrange(row_count): 
            y = row * min_dimension
            for col in xrange(col_count):
                if not puzzle_matrix[row][col].strip():
                    continue
                x = col * min_dimension
                square = ui.TextField(frame = (x, y, min_dimension-gap, min_dimension-gap))
                square.alignment = ui.ALIGN_CENTER
                square.bg_color = 'blue' if (row+col) % 2 else 'white'
                square.delegate = self  # self.textfield_did_change() will be called
                square.text = 'X'
                square.name = '{} {}'.format(row, col)
                self.add_subview(square)

    def textfield_did_change(self, textfield):
        textfield.text = textfield.text[:1]  # no longer than 1 character
        row, col = textfield.name.split()
        fmt = 'row={}, col={}, text={}'
        console.hud_alert(fmt.format(row, col, textfield.text))

if __name__ == '__main__':
    the_matrix = make_matrix(crossword_rows, crossword_cols, puzzle_entries)
    #for row in the_matrix:  # print_matrix()
    #    print(''.join(col for col in row))
    CrosswordView(the_matrix)
GaryGadget

Thanks for this. I'll have a play 😄

GaryGadget

I've been having a play around and trying to populate a matrix with buttons but can't find much in the docs to help. I have got a button to swap from black to white at the touch of the screen but not sure how to populate with more than one button. When I try 2 they end up on top of each other. Dynamic matrix preferred so not using the builder. Can't see anything for the button class attributes relating to position/size or a constructor. Is there a view required for each object or view? Please can someone explain how it is done?

JonB

The frame property lets you set size and position in one go.
Or, you can set width, height, x and y separately.
Frame can be set in the constructor, as can the background color, many other parameters can not.

You basically have to do the math yourself to place on a grid. See ccc's example above, where he computes x and y for each column/row, and initializer the button using those values. His code seems like it would be a good jumping off point, and has a good interface for encoding the row,col inside the name.

GaryGadget

Thanks for the reply. I've managed this so far but not sure how to manage the scope for my Constant 'status'.
Once the 'Done' button is tapped 'status' should be set to 2 and then stop the black/white toggle code using the 'if' block. I think scope is the problem here but not sure how to cure it. Any help appreciated.
Also don't know how to show my code like others do on the message sorry.

import console, ui, time
crossword_rows = 4
crossword_cols = 4
gap = 1
status = 1

def button_tapped1(square):
#print 'status = '+ str(status)
tup = tuple([1.0,1.0,1.0,1.0]) # white values
if status==1:
#print 'status = '+ str(status)
if str(tup) == str(square.bg_color):
square.bg_color = 'black'
else:
square.bg_color = 'white'
elif status==2:
#button_tapped2()
pass

def button_tapped2(button):
#add square number
#print 'status = '+ str(status)
status = 2
button.title = 'ok'
#print 'status = '+ str(status)
pass

class CrosswordView(ui.View):
def init(self):
self.present(hide_title_bar=False )
self.background_color = (0, 1.0, 2.0, 0.4)
min_dimension = min(self.width / crossword_cols, self.height / crossword_rows)
#self.squares = []
for i in xrange(crossword_cols):
x = i * min_dimension
for j in xrange(crossword_rows):
time.sleep(.05)
y = j * min_dimension
square = ui.Button(frame = (x, y, min_dimension-gap, min_dimension-gap))
#square.alignment = #ui.ALIGN_CENTER
square.bg_color = 'white'
#square.title = 'X'
self.add_subview(square)
square.action = button_tapped1

    button = ui.Button(frame = (x-150, y+80, min_dimension*2, min_dimension*2))
    self.add_subview(button)
    button.title = 'Done'
    button.background_color = 'white'
    button.action = button_tapped2
    #print 'another visit'

CrosswordView()
print 'status = '+ str(status)

JonB

From help('SCOPING')

  If a name is bound in a block, it is a local variable of that block.

meaning, since you are trying to modify status, it is a local to button_tapped2. (I learned something today!)

Declare status as global in that function.

As an aside, for posting code to the forum, if works best if you insert a blank line, then a line containing three backticks ```, then your code, then the three backticks again.
On ios, backtick is found by long tapping the single quote, and selecting the leftmost item.
Or, go into settings app-> keyboard -> shortcuts, and add a shortcut.
I map ,,, to ```, allowing quick entry without having to go to the symbol page of the ipad keyboard

```
Like this
```
ccc

Putting the word python directly after the first three backticks will give you syntax highlighting for Python code.

@GaryGadget, it would be cool if you could create a GitHub repo of your code so that we could collaborate on it.

GaryGadget

JonB,
I had been trying Global but it wasn't working but since you confirmed my thoughts I succeed thanks. I didn't realise it had to be declared global in each block!

Here's my attempt so far as test code for setting up the matrix black/white and to include it in this message properly.

Thanks for your help guys.


import console, ui, time


crossword_rows = 4
crossword_cols = 4
gap = 1
status = 1

def button_tapped1(square):
    global status
    print 'status = '+ str(status)
    tup = tuple([1.0,1.0,1.0,1.0]) # white values
    if status==1:
            #print 'status = '+ str(status)
        if str(tup) == str(square.bg_color):
            square.bg_color = 'black'
        else:
            square.bg_color = 'white'
    elif status==2:
            #button_tapped2()
        pass 

def button_tapped2(button):
    #add square number
    global status
    print 'status = '+ str(status)
    status = 2 
    button.title = 'Colours set.'
    #print 'status = '+ str(status)
    #pass 

class CrosswordView(ui.View):
    def __init__(self):
        self.present(hide_title_bar=False   )
        self.background_color = (0, 1.0, 2.0, 0.4)
        min_dimension = min(self.width / crossword_cols, self.height / crossword_rows)
        #self.squares = []
        for i in xrange(crossword_cols):
            x = i * min_dimension
            for j in xrange(crossword_rows):
                time.sleep(.05)
                y = j * min_dimension
                square = ui.Button(frame = (x, y, min_dimension-gap, min_dimension-gap))
                #square.alignment = #ui.ALIGN_CENTER
                square.bg_color = 'white' 
                #square.title = 'X'
                self.add_subview(square)
                square.action = button_tapped1

        button = ui.Button(frame = (x-150, y+80, min_dimension*2, min_dimension*2))
        self.add_subview(button)
        button.title = 'Done'
        button.background_color = 'white'
        button.action = button_tapped2
        #print 'another visit'

CrosswordView()
#print 'status = '+ str(status)
brumm

Maybe this code help you:

import ui

class MakeButtonArray(object):

    def __init__(self):
        self.view = ui.View()
        self.view.name = 'Buttons'
        self.view.background_color = 'grey'
        self.view.present('fullscreen')
        self.edit = ui.TextField()
        self.edit.text = ''
        self.edit.center = (50,50)
        self.edit.width = self.view.width
        self.edit.height = 30
        self.edit.border_color = 'black'
        self.edit.border_width = 1
        self.edit.placeholder = 'single letter or whole word'
        self.edit.enabled = False 
        self.edit.delegate = self
        self.edit.clear_button_mode = 'while_editing'
        self.view.add_subview(self.edit)
        self.button_array = []
        self.word1 = 'TELEFON'
        for i in range(len(self.word1)):
            self.button_array.append(ui.Button(title=self.word1[i]))
            x = 100
            y = 100 + (i * 50)
            self.button_array[i].x = x
            self.button_array[i].y = y
            self.button_array[i].width = 50
            self.button_array[i].height = 50
            self.button_array[i].border_color = 'black'
            self.button_array[i].border_width = 1
            self.button_array[i].name = str(i)
            self.button_array[i].bg_color = 'white'
            self.button_array[i].action = self.button_array_action
            self.view.add_subview(self.button_array[i])
            i += 1

    def button_array_action(self, sender):
        self.edit.enabled = True
        self.edit.text = self.word1
        #self.edit.text = 'sender.name:' + sender.name + ' // "' + sender.title + '" // ' + 'sender.x:' + str(sender.x) + ' sender.y:' + str(sender.y)

    def textfield_did_end_editing(self,textfield):
        #print textfield.text
        self.word1 = textfield.text
        self.edit.enabled = False 
        self.edit.text = ''

MakeButtonArray()
ccc

You only need the global status statement in those functions where you want to make a permanent change to its value:

zippy = 'zippy'

def read_only():
    print(zippy)       # zippy

def local_change():
    # print(zippy)     # would cause the next statement to throw an error
    zippy = 'pinhead'  # local with same name as global
    print(zippy)       # pinhead

def permanent_change():
    global zippy
    zippy = 'pinhead'
    print(zippy)       # pinhead

read_only()            # zippy
local_change()         # pinhead
print(zippy)           # zippy -- global was not changed
permanent_change()     # pinhead
print(zippy)           # pinhead -- global was changed

Read_only() demonstrates that even without the global statement, you still have read-only access to the global variable.

ccc

A reformulation of @brumm's code above to:

  1. Convert MakeButtonArray into a subclass of ui.View
  2. Add make_edit_text_field() and make_button() methods
  3. Use list comprehension with enumerate() to create button_array
  4. Use format() to create the commented out debug text
import ui

class MakeButtonArray(ui.View):
    def __init__(self):
        self.name = 'Buttons'
        self.background_color = 'grey'
        self.present('fullscreen')
        self.edit = self.make_edit_text_field()
        self.add_subview(self.edit)
        self.word1 = 'TELEFON'
        self.button_array = [self.make_button(i, c)
                             for i, c in enumerate(self.word1)]

    def make_edit_text_field(self):
        edit_tf = ui.TextField()
        edit_tf.center = (50, 50)
        edit_tf.width = self.width
        edit_tf.height = 30
        edit_tf.border_color = 'black'
        edit_tf.border_width = 1
        edit_tf.placeholder = 'single letter or whole word'
        edit_tf.enabled = False 
        edit_tf.delegate = self
        edit_tf.clear_button_mode = 'while_editing'
        return edit_tf

    def make_button(self, i, c):
        button = ui.Button(name=str(i), title=c)
        button.frame = (100, 100 + i * 50, 50, 50)
        button.bg_color = 'white'
        button.border_color = 'black'
        button.border_width = 1
        button.action = self.button_array_action
        self.add_subview(button)
        return button

    def button_array_action(self, sender):
        self.edit.enabled = True
        self.edit.text = self.word1
        #fmt = 'sender.name:{} // "{}" // sender.x:{} sender.y:{}'
        #self.edit.text = fmt.format(sender.name, sender.title,
        #                            sender.x, sender.y)

    def textfield_did_end_editing(self,textfield):
        #print textfield.text
        self.word1 = textfield.text
        self.edit.enabled = False 
        self.edit.text = ''

MakeButtonArray()
GaryGadget

Thanks for the code examples. I'm sure they will help.

GaryGadget

Hi chaps,
I'm still playing around with buttons for my codeword app. Struggling now with changing the view from one view to another. How is it done please or have I got the wrong end of the stick here?

The idea is to firstly select which cells are black. Click done. Then selecting a white cell should present my second class as a view to choose number values from a grid. Trouble is the grid becomes too small when I call it from anywhere but the line not indented!

import console, ui, time

crossword_rows = 4
crossword_cols = crossword_rows
gap = 1
tupWhite = tuple([1.0,1.0,1.0,1.0]) # white values
tupBlack = tuple([1.0,1.0,1.0,1.0]) # black
status = 1

def button_tapped1(square):
    global status
    #print 'status = '+ str(status)
    #tup = tuple([1.0,1.0,1.0,1.0]) # white values
    if status==1:

            #print 'status = '+ str(status)
        if str(tupWhite) == str(square.bg_color):
            square.bg_color = 'black'
        else:
            square.bg_color = 'white'
    elif status==2:
            #button_tapped2()

            edit_cell_number(square)
            print 'step 2'
        #pass 

def button_tapped2(button):
    #add square number
    global status
    #print 'status = '+ str(status)
    status = 2 
    button.title = 'Colours set.'
    #print 'status = '+ str(status)
    #pass 

def button_tapped3(self, square):
    pass

def edit_cell_number(square):
    #cyvle through 1 - 26 for cell number

    if str(tupWhite) == str(square.bg_color):
        #square.title = str(26)
        #NumberSelectionGrid()

        pass

class CrosswordView(ui.View):
    def __init__(self):
        self.present(hide_title_bar=False   )
        self.background_color = (0, 1.0, 2.0, 0.4)
        min_dimension = min(self.width / crossword_cols, self.height / crossword_rows)
        #self.squares = []
        for i in xrange(crossword_cols):
            x = i * min_dimension
            for j in xrange(crossword_rows):
                time.sleep(.05)
                y = j * min_dimension
                square = ui.Button(frame = (x, y, min_dimension-gap, min_dimension-gap))
                #square.alignment = #ui.ALIGN_CENTER
                square.bg_color = 'white' 
                #square.title = 'X'
                self.add_subview(square)
                square.action = button_tapped1

        #button = ui.Button(frame = (x-150, y+80, min_dimension*2, min_dimension*2))
        button = ui.Button(frame = (0,0, min_dimension*4, min_dimension*2))
        self.add_subview(button)
        button.title = 'Done'
        button.background_color = 'white'
        button.center = (self.width * 0.5, self.height -50)
        button.flex = 'LRTB'
        button.action = button_tapped2
        #print 'another visit'


class NumberSelectionGrid(ui.View):

  def __init__(self):
        self.present(hide_title_bar=False   )

        self.background_color = (0, 1.0, 2.0, 0.4)
        min_dimension = min(self.width / 5, self.height / 6)
        #self.squares = []
        squareNumber = 1
        for i in xrange(5):
            x = i * min_dimension
            for j in xrange(6):
                #time.sleep(.05)
                y = j * min_dimension
                square = ui.Button(frame = (x, y, min_dimension-gap, min_dimension-gap))
                #square.alignment = #ui.ALIGN_CENTER
                square.bg_color = 'white' 
                square.title = str(squareNumber)
                self.add_subview(square)
                squareNumber = squareNumber +1
                square.action = button_tapped3

CrosswordView()
NumberSelectionGrid()
#NumberSelectionGridd().present(hide_title_bar=False)
#NumberSelectionGrid().send_to_back()
#print 'status = '+ str(status)
JonB

So, the number selection grid is supposed to popup, you do something, then you see the crossword grid again?

If so, you might want to present as a 'popover' view.
You need to set width and height of the NumberSelectionGrid, otherwise it may be tiny.

Alternatively, you can add the number selection grid as a subview to your root view, setting width and height to match root view. In that case, don't call present at all, you add_subview root then bring_to_front Your init would need to take width and height parameters. As an example of this method, see this input alert replacement. In that case, init doesn't set up the sizes, input_alert sets the size to match superview (it has to be added to root already), and brings to front. send_to_back or bring the other view, or set hidden in order to hide it. Don't mess around with trying to make a modal dialog like this, as things have to be done carefully to avoid freezes, instead your two views should interact via callbacks.

GaryGadget

Spot on. Thanks for that JonB. I've been having a look at the subview approach because I want it on iPhone (no popover iI believe). Managed to get it to work after a lot of trial and error.
Once I'm happy with how all the parts work I will rewrite because at this stage I am taking baby steps with the GUI programming within Pythonista so am really great full for all your help.

ccc
tupWhite = tuple([1.0,1.0,1.0,1.0]) # white values
tupBlack = tuple([1.0,1.0,1.0,1.0]) # black

# [ ... ]

        if str(tupWhite) == str(square.bg_color):
            square.bg_color = 'black'
        else:
            square.bg_color = 'white'

A few questions on the above snippets of code:

  1. Do you really want the same values for both white and black?
  2. Did you know that (1.0, 1.0, 1.0, 1.0) == tuple([1.0,1.0,1.0,1.0])? Is there some advantage to the second over the first?
  3. Why convert both values to strings each time you compare them? Why not just compare the tuples?
square.bg_color = 'black' if square.bg_color == tupWhite else 'white'
GaryGadget

Well spotted. Yes it is a mistake. I was getting errors when trying to use 'white' in equality tests so I used the tuple values instead. I couldn't remember what the values for black were but didn't implement it in the end. I'll reuse your line when I rewrite, once I know how all the parts work. The main obstacles are my inexperience with GUI's & Classes but also trying to learn how to use these techniques on my iPhone in my spare time. I am really impressed with the shear power of Pythonista so far though. Bit fiddly editing on the phone at times though. Thanks for your help.

GaryGadget

I'm trying to get the buttons to switch between views. How can I pass the size to child_view so it fills the screen like its parent?

Thanks in advance
Gary

import ui

# test of view interchanging

def button1_tapped(sender):
    child_view = SecondClass()
    parent_view.add_subview(child_view)


def button2_tapped(sender):
    print 'button2 tapped'

class FirstClass(ui.View):

    def __init__(self):
        self.present()
        self.background_color = 'black'
        button1 = ui.Button(frame = (1, 1, self.width, self.height))
        print 'parent_view started at ', self.width
        button1.title = 'First Class'
        self.add_subview(button1)
        button1.action = button1_tapped


class SecondClass (ui.View):

    def __init__(self):
        print 'parent_view now = ', parent_view.width
        self.background_color = 'white'
        button2 =ui.Button(frame = (1, 1, parent_view.width, parent_view.height))
        button2.title = 'Second Class'
        parent_view.add_subview(button2)
        button2.action = button2_tapped



parent_view = FirstClass()
ccc
import ui

# test of view interchanging

def button1_tapped(sender): 
    root_view = sender.superview.superview
    for subview in root_view.subviews:
        root_view.remove_subview(subview)
    root_view.add_subview(SecondClass(root_view.bounds))

def button2_tapped(sender):
    print 'button2 tapped'

class FirstClass(ui.View):
    def __init__(self, in_frame):
        self.frame = in_frame
        self.background_color = 'black'
        button1 = ui.Button(frame = (1, 1, self.width, self.height))
        print 'FirstClass frame is ', self.frame
        button1.title = 'First Class'
        self.add_subview(button1)
        button1.action = button1_tapped

class SecondClass(ui.View):
    def __init__(self, in_frame):
        self.frame = in_frame
        print 'SecondClass frame is ', self.frame
        self.background_color = 'white'
        button2 =ui.Button(frame = (1, 1, self.width, self.height))
        button2.title = 'Second Class'
        self.add_subview(button2)
        button2.action = button2_tapped

parent_view = ui.View()
parent_view.present()
parent_view.add_subview(FirstClass(parent_view.bounds))
JonB

Do you really need to delete the original view? If you are just swapping back and forth, bring_to_front or send_to_back work just fine.

Also, I would consider adding flex so that if you change orientation, your views get resized (custom layout the would resize your button grid, etc)

ccc

@JonB, Great ideas! That simplifies the button_tapped() logic:

import ui

def button_tapped(sender):
    sender.send_to_back()

parent_view = ui.View()
parent_view.hidden = True
buttons = [ui.Button(), ui.Button(), ui.Button(), ui.Button()]
for i, b in enumerate(buttons):
    b.bg_color = 'white'
    b.action = button_tapped
    b.flex = 'WH'
    b.frame = (25, 25, 200, 200)
    b.name = b.title = 'Button {}'.format(i)
    parent_view.add_subview(b)
    b.send_to_back()  # start with FIFO order
parent_view.present()
parent_view.hidden = False
GaryGadget

Thanks guys,

I'm just trying to understand how all of the parts will work for my codeword GUI. Once I grasp that I'll be able to put them all together in a neater version.

Basically I wanted to swap between my 3 main views: Codeword grid, number selection grid , and letter selection grid. But keep coming up against beginner type problems with my code. In this latest problem it was getting the SecondClass object (a selection grid) to appear full screen and pass a value back.

I'll look more closely later at your posts but just wanted to thank you all for you prompt and helpful responses. It always helps speed things up but believe me I have spent ages on each stage just trying to get what looks like only a few lines of code.

As I said before 'Baby Steps' for me but learning is satisfying!

Regards
Gary

JonB

Slightly off topic, perhaps, but here is a TabbedView, which has a tab bar, and lets you swap your main view between the different tabs. If your are literally swapping back and forth, this might be what you want, for instance I could imagine having clues on one tab, the crossword grid on another, and the key or quotation or whatever on another. Your use case might be more complex, but this might be one place to start.

Blah, backticks are behaving strange, here's a gist
https://gist.github.com/fcadaffff4be09c4ec78

GaryGadget

Thanks to you all for your help so far. I have made some progress after the last post, if only a small step. I have now managed to get to grips a bit with switching 'views' at the touch of a button based on ccc's idea thanks.

The idea is still to switch between 3 different class types for my codeword, number grid and letter grid. This I have some idea of how I will tackle it but can somebody please explain the use of the 'frame' command in the context of switching between views. i.e. I don't know how to size or position my grids to fill the screen on iPhone/iPad. I have tried different ways from what I have seen but none of them seem to work. In this particular use my classes will be the grids made up of buttons that I want to fill the screen as best they fit.

Oh and I now have a bluetooth keyboard connected to my iPhone so it is much better for typing the lines of code in.

Thanks
Gary


import ui, console

def button_tapped(sender):
    sender.superview.send_to_back()
    print sender.name

class FirstClass(ui.View):
    def __init__(self):
        self.background_color = 'blue'
        button = ui.Button(frame = (0, 0, 1, 1), title = 'Class 1')
        button.name = button.title
        button.bg_color = 'white'
        button.flex  = 'WH'
        self.add_subview(button)
        button.action = button_tapped

class SecondClass(ui.View):
    def __init__(self):
        self.background_color = 'red'
        button = ui.Button(frame = (0, 0, 1, 1), title = 'Class 2')
        button.name = button.title
        button.bg_color = 'white'
        button.flex = 'WH'
        self.add_subview(button)
        button.action = button_tapped

parent_view = ui.View()
grids = [FirstClass(), SecondClass()]
for i, b in enumerate(grids):
    #b.frame = (0, 0, parent_view.width, parent_view.height)
    b.flex = 'WH'
    parent_view.add_subview(b)
    b.send_to_back()

parent_view.present('full_screen')

ccc

The first issue that you are facing is that before parent_view.present() is called, the values for parent_view.bounds and parent_view.frame are both set to (0.0, 0.0, 100.0, 100.0). It is only after parent_view.present() that they accurately refect screen size. This means that using .bounds, .frame, .center before .present() does not lead to the desired result. It is for this reason that increasingly I use the following approach:

parent_view = ui.View()
parent_view.hidden = True   # hide your work
parent_view.present()
# create, set up, add subviews using parent_view.bounds, parent_view.center, parent_view.frame, etc.
parent_view.hidden = False. # show your work

The second problem you face is that in order to lay your buttons out in a grid, your class needs to know its .frame size. In the current code, that is not set until after .__init__() has already run. Two alternative approaches: pass the frame size (parent_view.bounds) into the .__init__() method or create a separate layout_buttons() method that you call after the line b.frame = parent_view.bounds has been executed. Personally, I like the first approach to solving this issue.

import ui

def button_tapped(sender):
    sender.superview.send_to_back()
    print sender.name

def make_button(in_title='Untitled'):
    button = ui.Button(title=in_title)
    button.action = button_tapped
    button.bg_color = 'white'
    button.flex = 'TLBR'
    button.name = button.title
    return button

class FirstClass(ui.View):
    def __init__(self, in_frame):
        self.frame = in_frame
        self.bg_color = 'blue'
        self.flex = 'WH'
        button = make_button('Class 1')
        button.center = self.center
        self.add_subview(button)

class SecondClass(ui.View):
    def __init__(self, in_frame):
        self.frame = in_frame
        self.bg_color = 'red'
        self.flex = 'WH'
        button = make_button('Class 2')
        button.center = self.center
        self.add_subview(button)

parent_view = ui.View()
parent_view.hidden = True
print(parent_view.bounds, parent_view.frame)
parent_view.present()
print(parent_view.bounds, parent_view.frame)
grids = [FirstClass(parent_view.bounds),
         SecondClass(parent_view.bounds)]
for grid in grids:
    parent_view.add_subview(grid)
    grid.send_to_back()
parent_view.hidden = False
JonB

If you define a custom layout() method, which calls the layout_buttons method, you can add the subview before presenting, and set flex='wh', then when you present the parent view! layout gets called and the buttons get laid out. This also has the advantage of handling device orientation changes.

ccc

Oh yes... That is a much better approach!! Thx

import ui
class MyView(ui.View):
    def __init__(self):
        print('__init__()')
    def layout(self):
        print('layout()')
MyView().present()