Forum Archive

Converting tkinter app to Pythonista UI

ChrisHare

So, I know I can't run tkinter in Pythonista. Not a big deal.

I have a running tkinter app that I would like to convert to the UI class. I like the date picker, UT I have a data source I would like to display I something like the date picker. Is there a widget to do this or has someone built one already? The current tkinter app uses a drop down, which would be ok, but I would prefer the date picker style.

cvp

@ChrisHare Use ObjectiveC UIPickerView, see an example here

It is an example with images, but, of course, you can do that with texts, like here

# coding: utf-8

from objc_util import ObjCInstance, c, ObjCClass, ns, create_objc_class, NSObject
from ctypes import c_void_p
import ui


# Data for both pickers
_data = [
    [str(x) for x in range(2000, 2040)],
    ['xxxx', 'yyyy', 'zzzz']
]

# ObjC classes
UIColor = ObjCClass('UIColor')
UIPickerView = ObjCClass('UIPickerView')
UIFont = ObjCClass('UIFont')
NSAttributedString = ObjCClass('NSAttributedString')


# Default attributes, no need to recreate them again and again
def _str_symbol(name):
    return ObjCInstance(c_void_p.in_dll(c, name))


_default_attributes = {
    _str_symbol('NSFontAttributeName'): UIFont.fontWithName_size_(ns('Courier'), 16),
    _str_symbol('NSForegroundColorAttributeName'): UIColor.redColor(),
    _str_symbol('NSBackgroundColorAttributeName'): UIColor.greenColor()
}


# Data source & delegate methods
def pickerView_attributedTitleForRow_forComponent_(self, cmd, picker_view, row, component):
    tag = ObjCInstance(picker_view).tag()
    return NSAttributedString.alloc().initWithString_attributes_(ns(_data[tag - 1][row]), ns(_default_attributes)).ptr


def pickerView_titleForRow_forComponent_(self, cmd, picker_view, row, component):
    tag = ObjCInstance(picker_view).tag()
    return ns(_data[tag - 1][row]).ptr


def pickerView_numberOfRowsInComponent_(self, cmd, picker_view, component):
    tag = ObjCInstance(picker_view).tag()
    return len(_data[tag - 1])


def numberOfComponentsInPickerView_(self, cmd, picker_view):
    return 1


def rowSize_forComponent_(self, cmd, picker_view, component):
    return 100


def pickerView_rowHeightForComponent_(self, cmd, picker_view, component):
    return 30


def pickerView_didSelectRow_inComponent_(self, cmd, picker_view, row, component):
    tag = ObjCInstance(picker_view).tag()
    print(f'Did select {_data[tag - 1][row]}')


methods = [
    numberOfComponentsInPickerView_, pickerView_numberOfRowsInComponent_,
    rowSize_forComponent_, pickerView_rowHeightForComponent_, pickerView_attributedTitleForRow_forComponent_,
    pickerView_didSelectRow_inComponent_
]

protocols = ['UIPickerViewDataSource', 'UIPickerViewDelegate']


UIPickerViewDataSourceAndDelegate = create_objc_class(
    'UIPickerViewDataSourceAndDelegate', NSObject, methods=methods, protocols=protocols
)


# UIPickerView wrapper which behaves like ui.View (in terms of init, layout, ...)
class UIPickerViewWrapper(ui.View):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._picker_view = UIPickerView.alloc().initWithFrame_(ObjCInstance(self).bounds()).autorelease()
        ObjCInstance(self).addSubview_(self._picker_view)

    def layout(self):
        self._picker_view.frame = ObjCInstance(self).bounds()

    @property
    def tag(self):
        return self._picker_view.tag()

    @tag.setter
    def tag(self, x):
        self._picker_view.setTag_(x)

    @property
    def delegate(self):
        return self._picker_view.delegate()

    @delegate.setter
    def delegate(self, x):
        self._picker_view.setDelegate_(x)

    @property
    def data_source(self):
        return self._picker_view.dataSource()

    @data_source.setter
    def data_source(self, x):
        self._picker_view.setDataSource_(x)


class MyView(ui.View):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        self.background_color = 'white'

        self.delegate_and_datasource = UIPickerViewDataSourceAndDelegate.alloc().init().autorelease()

        pv1 = UIPickerViewWrapper(frame=[100, 100, 200, 100])
        pv1.delegate = self.delegate_and_datasource
        pv1.data_source = self.delegate_and_datasource
        pv1.tag = 1
        self.add_subview(pv1)

        pv2 = UIPickerViewWrapper(frame=[100, 400, 200, 100])
        pv2.delegate = self.delegate_and_datasource
        pv2.data_source = self.delegate_and_datasource
        pv2.tag = 2
        self.add_subview(pv2)


if __name__ == '__main__':
    v = MyView()
    v.present('full_screen')
SmartGoat

@cvp and is there a way to do the opposite, like using pyui files with tkinter for a desktop (Windows) program ?
Cordially,
SmartGoat

cvp

@SmartGoat No idea at all, sorry

mikael

@SmartGoat, I would expect it to be feasible, as the Pythonista views are not overly exotic, but I do not think anyone has already built this for you.

cvp

@SmartGoat rename a .pyui file into .txt and edit it, you will see it is only a dictionary of keywords, and you could easily read it and build an UI

ccc

Even better to rename .pyui file to .json because that is what it is.