-
-
Save MaxMorais/5388218 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python | |
# -*- coding: UTF-8 -*- | |
#------------------------------------------------------------------------------- | |
# Name: maskedit.py | |
# Purpose: | |
# | |
# Author: Maxwell Morais (max.morais.dmm@gmail.com) | |
# | |
# Created: 10/04/2013 | |
# Copyright: (c) Maxwell Morais 2013 | |
# Licence: MIT | |
#------------------------------------------------------------------------------- | |
import sys | |
import re | |
import calendar | |
if sys.version[0]=='2': | |
import Tkinter as tk | |
import ttk | |
import tkFont | |
elif sys.version[0]=='3': | |
import tkinter as tk | |
import tkinter.font as tkFont | |
from tkinter import ttk | |
basestring = type(u'') | |
else: | |
print("Python version not supported, try 2 or 3 versions") | |
definitions = { | |
'9': '[0-9]', | |
'a': '[a-zA-Z]', | |
'x': '[a-zA-z0-9]' | |
} | |
class ToolTip(tk.Toplevel): | |
def __init__(self, root, **kw): | |
self.fields = { | |
'tooltip': None, | |
'variable': None, | |
'showdelay': 1, | |
'hidedelay': 10, | |
'root': root, | |
} | |
self.__hide_id = None | |
self.__show_id = None | |
for k in list(kw.keys()): | |
if k in self.fields: | |
self.fields[k]=kw.pop(k) | |
if not 'variable' in kw and not self.fields['variable']: | |
self.fields['variable']=tk.StringVar(root) | |
tk.Toplevel.__init__(self, root, **kw) | |
self.withdraw() | |
self.overrideredirect(True) | |
self.setup() | |
def setup(self): | |
self._tooltip() | |
self._bind() | |
self._grid() | |
def set_text(self, value): | |
self.fields['variable'].set(value) | |
def get_text(self): | |
return self.fields['variable'].get() | |
def _tooltip(self): | |
if not isinstance(self.fields.get('variable', None), tk.StringVar): | |
self.fields['variable'] = tk.StringVar(self) | |
tooltiptext = self.fields['tooltip'] or self.fields['variable'].get() | |
self.fields['variable'].set(tooltiptext) | |
self._widget = ttk.Label(self, | |
textvariable=self.fields['variable'], | |
#aspect=1000, | |
background='#ffffcc' | |
) | |
def _grid(self): | |
self._widget.grid(row=0, column=0) | |
def _bind(self): | |
self.fields['root'].bind('<Enter>', self._init_tooltip, True) | |
self.fields['root'].bind('<Leave>', self._hide_tooltip, True) | |
def _init_tooltip(self, event): | |
self.__show_id = self.after( | |
int(self.fields['showdelay']*1000), | |
self._show_tooltip | |
) | |
def _show_tooltip(self, event=None): | |
self.__show_id = None | |
x = self.fields['root'].winfo_rootx() | |
y = self.fields['root'].winfo_rooty() | |
height = self.fields['root'].winfo_height() | |
width = self.fields['root'].winfo_width() | |
lx = x + self.winfo_width() + 5 | |
if lx < self.fields['root'].winfo_screenwidth() - 5: | |
x += 5 | |
else: | |
x += self.fields['root'].winfo_screenwidth() - lx | |
ly = (y + height + self.winfo_height() + 5) | |
if ly < self.fields['root'].winfo_height() - 5: | |
y += height + 5 | |
else: | |
y += -(self.winfo_height()+5) | |
self.wm_geometry('+%d+%d'%(x,y)) | |
self.deiconify() | |
self.__hide_id = self.after( | |
int(self.fields['hidedelay'])*1000, | |
self._hide_tooltip | |
) | |
def _hide_tooltip(self, event): | |
if self.__show_id is not None: | |
self.after_cancel( self.__show_id ) | |
self.__show_id = None | |
if self.__hide_id is not None: | |
self.after_cancel( self.__hide_id ) | |
self.__hide_id = None | |
self.withdraw() | |
class FormWidget(ttk.Frame): | |
_fields= { | |
'field': None, | |
'label': None, | |
'help': None, | |
'variable': None, | |
'labelvariable': None, | |
'helpvariable': None | |
} | |
_factory = { | |
'variable': tk.StringVar, | |
'widget': tk.Entry | |
} | |
def __init__(self, root, **kw): | |
self.fields = self._fields.copy() | |
for k in list(kw.keys()): | |
if k in self.fields: | |
self.fields[k]=kw.pop(k) | |
if not 'variable' in kw: | |
self.fields['variable']=self._factory.get( | |
'variable', | |
tk.StringVar)(root) | |
ttk.Frame.__init__(self, root, **kw) | |
self.setup() | |
def setup(self): | |
self._label() | |
self._widget() | |
self._help() | |
self._grid() | |
def set(self, value): self.fields['variable'].set(value) | |
def get(self): return self.fields['variable'].get() | |
def set_label(self, value): self.fields['labelvariable'].set(value) | |
def get_label(self): return self.fields['labelvariable'].get() | |
def set_help(self, value): self.fields['helpvariable'].set(value) | |
def get_help(self): return self.fields['helpvariable'].get() | |
def _label(self): | |
labeltext = self.fields['label'] or self.fields['field']['label'] or '' | |
if not isinstance(self.fields.get('labelvariable', None), tk.StringVar): | |
self.fields['labelvariable'] = tk.StringVar(self) | |
self.fields['labelvariable'].set(labeltext) | |
self._wgt_label = ttk.Label( | |
self, | |
textvariable=self.fields['labelvariable'] | |
) | |
def _widget(self): | |
self._widget = self._factory.get('widget', ttk.Entry)( | |
self, | |
textvariable=self.fields['variable'] | |
) | |
def _help(self): | |
helptext = self.fields['help'] or '' | |
if not helptext: return | |
if not isinstance(self.fields.get('helpvariable', None), tk.StringVar): | |
self.fields['helpvariable'] = tk.StringVar(self) | |
self.fields['helpvariable'].set(helptext) | |
self._wgt_help = ToolTip( | |
self._widget, | |
variable=self.fields['helpvariable'] | |
) | |
def _grid(self): | |
self._wgt_label.grid(row=0, column=0) | |
self._widget.grid(row=1, column=0) | |
class StringWidget(ttk.Entry): | |
@property | |
def variable(self): | |
return self.fields['textvariable'] | |
class MaskedWidget(ttk.Entry): | |
def __init__(self, master, format_type, **kw): | |
self.fields = { | |
'type': format_type, | |
'mask': None, | |
'monetary': False, | |
'dec_places': 2, | |
'dec_sep': '.', | |
'tho_places': 3, | |
'tho_sep': ',', | |
'symbol': '', | |
'fmt_neg': '-%(symbol)s%(amount)s', | |
'fmt_pos': '%(symbol)s%(amount)s', | |
'placeholder': '_', | |
'textvariable': None, | |
} | |
if str(format_type).lower() == 'fixed': | |
assert 'mask' in kw, 'the fixed mask, is not present' | |
self.fields['mask'] = kw.pop('mask', '').lower() | |
for k in list(kw.keys()): | |
if k in self.fields: | |
if k!='textvariable': | |
self.fields[k]=kw.pop(k) | |
else: | |
self.fields[k]=kw[k] | |
if not 'textvariable' in kw: | |
self.fields['textvariable']=tk.StringVar(master) | |
kw['textvariable'] = self.fields['textvariable'] | |
ttk.Entry.__init__(self, master, **kw) | |
self.defs = definitions | |
self.tests = [] | |
self.partialPosition = None | |
self.firstNonMaskPosition = None | |
self.len = len(self.fields['mask']) | |
for i,c in enumerate(self.fields['mask'].lower()): | |
if c == '?': | |
self.len -= 1 | |
self.partialPosition = i | |
atom = self.defs.get(c, None) | |
self.tests.append(re.compile('(%s)'%atom) if atom else atom) | |
if not atom and self.firstNonMaskPosition==None: | |
self.firstNonMaskPosition = len(self.tests)-1 | |
self.writeBuffer() | |
if str(self.cget("state")).upper()!="DISABLED": | |
self.bind('<KeyPress>', self._onKeyPress, True) | |
self.bind('<KeyRelease>', lambda e: 'break', True) | |
self.bind('<FocusIn>', self._onFocusIn, True) | |
def clean(self): | |
self.fields['textvariable'].set('') | |
self.writeBuffer() | |
def clean_numeric(self, string): | |
if not isinstance(string, basestring): string = str(string) | |
string = string.replace(self.fields['symbol']+' ', '')\ | |
.replace(self.fields['tho_sep'], '')\ | |
.replace(self.fields['dec_sep'], '.') | |
if not '.' in string: | |
string = list(string) | |
string.insert(-2, '.') | |
string = ''.join(string) | |
return string.partition('.') | |
def fmt_numeric( self, amount): | |
temp = '00' if not '.' in str(amount) \ | |
else str(amount).split('.')[1] | |
l = [] | |
amount = amount.split('.')[0] | |
try: | |
minus = float(''.join(self.clean_numeric(amount)))<0 | |
except ValueError: | |
minus = 0 | |
if len(amount)> self.fields['tho_places']: | |
nn = amount[-self.fields['tho_places']:] | |
l.append(nn) | |
amount = amount[:len(amount)-self.fields['tho_places']] | |
while len(amount) > self.fields['tho_places']: | |
nn = amount[len(amount)-self.fields['tho_places']:] | |
l.insert(0, nn) | |
amount = amount[0:len(amount)-self.fields['tho_places']] | |
if len(''.join(self.clean_numeric(amount)))>0: l.insert(0, amount) | |
amount = self.fields['tho_sep'].join(l)+self.fields['dec_sep']+temp | |
if minus: | |
amount = self.fields['fmt_neg']%{ | |
'symbol':self.fields['symbol'], | |
'amount': amount | |
} | |
else: | |
amount = self.fields['fmt_pos']%{ | |
'symbol': (self.fields['symbol']+' ') if self.fields['symbol'] else '', | |
'amount': amount | |
} | |
return amount | |
def seekNext(self, pos): | |
if 0 <= pos+1<self.len: | |
if self.tests[pos+1]: | |
return pos+1 | |
return self.seekNext(pos+1) | |
return pos | |
def seekPrev(self, pos): | |
if 0 <= pos-1 < self.len: | |
if self.tests[pos-1]: | |
return pos-1 | |
return self.seekPrev(pos-1) | |
return pos | |
def shiftL(self, begin, end): | |
if begin < 0: return | |
for i in range(self.len): | |
j = self.seekNext(begin) | |
if self.tests[i]: | |
if j < self.len and self.tests[i].match(self.buffer[i]): | |
self.buffer[i] = self.buffer[j] | |
self.buffer[j] = self.fields['placeholder'] | |
else: | |
break | |
def shiftR(self, pos, c): | |
if pos in range(len(self.len)): | |
j = self.seekNext(pos) | |
t = self.buffer[pos] | |
if not t == c and j < self.len and t == self.fields['placeholder']: | |
self.buffer[pos] = c | |
def writeBuffer(self): | |
self.fields['textvariable'].set( | |
''.join( | |
filter( | |
lambda x: x!=None, | |
map( | |
lambda c, self=self: | |
(self.fields['placeholder'] | |
if self.defs.get(c, None) | |
else c) | |
if c!='?' else None, self.fields['mask']) | |
) | |
) | |
) | |
return self.get() | |
def _onFocusIn(self, event): | |
if self.len>0 and self.tests[0]: | |
self.icursor(0) | |
else: | |
self.icursor(self.seekNext(0)) | |
def _onKeyPress(self, event): | |
if event.keysym == 'Tab': | |
return | |
elif event.keysym == 'Escape': | |
if self.fields['type'] == 'fixed': | |
self.writeBuffer() | |
else: | |
self.delete(0, len(event.widget.get())) | |
widget = event.widget | |
val = widget.get() | |
idx = widget.index(tk.INSERT) | |
if event.keysym == 'Left': | |
if 0 <= idx < self.len: | |
if idx < self.firstNonMaskPosition: | |
return 'break' | |
elif not self.tests[idx]: | |
widget.icursor(self.seekPrev(idx)) | |
elif event.keysym == 'Right': | |
if 0 <= idx < self.len: | |
if idx >= self.len: | |
return 'break' | |
elif not self.tests[idx]: | |
widget.icursor(self.seekNext(idx)) | |
elif event.keysym == 'BackSpace' and self.fields['type'] != 'numeric': | |
def repl_or_stop(cls, wget, pos): | |
if 0 <= pos <= cls.len: | |
if not cls.tests[pos]: | |
pos = cls.seekPrev(pos) | |
cls._write_char(pos, cls.fields['placeholder'], -1) | |
return 'break' | |
repl_or_stop(self, widget, idx - 1) | |
return 'break' | |
else: | |
if self.fields['type'] == 'fixed': | |
if self._write_char(idx, event.char) == 'break': | |
return 'break' | |
elif self.fields['type'] == 'numeric' and event.char.isdigit(): | |
if val: | |
widget.delete(0, len(val)) | |
head, sep, tail = self.clean_numeric(val) | |
else: | |
head, sep, tail = '0', '.', '00' | |
if not head: | |
head = '0' | |
if len(tail) < 2: | |
tail = '0' + tail | |
if tail and len(tail + event.char) <= 2 and (int(tail+event.char))<99: | |
tail = tail[1:] + event.char | |
else: | |
if not int(head): | |
head = tail[0] if tail else '0' | |
else: | |
head += tail[0] | |
tail = tail[1:] + event.char | |
widget.insert(0, ''.join([head, sep, tail])) | |
return 'break' | |
elif self.fields['type'] == 'numeric' and event.keysym == 'BackSpace': | |
if val: | |
widget.delete(0, len(val)) | |
head, sep, tail = self.clean_numeric(val[:-1]) | |
else: | |
head, sep, tail = '0', '.', '00' | |
widget.insert(0, ''.join([head, sep, tail])) | |
return 'break' | |
else: | |
self.bell() | |
return 'break' | |
def insert(self, index, value): | |
if self.fields['type']=='numeric': | |
ttk.Entry.insert(self, index, self.fmt_numeric(value)) | |
else: | |
for c in str(value): | |
while (not self.tests[index] or not self.tests[index].match(c)): | |
index += 1 | |
self._write_char(index,c) | |
index += 1 | |
def _write_char(self, idx, char, direction=+1): | |
if 0<=idx<self.len and self.tests[idx]: | |
if char != self.fields['placeholder'] and not self.tests[idx].match(char): | |
self.bell() | |
return 'break' | |
self.delete(idx) | |
ttk.Entry.insert(self, idx, char) | |
if direction == +1: | |
if idx + 1 < self.len and not self.tests[idx+1]: | |
idx = self.seekNext(idx) | |
else: | |
idx += 1 | |
elif direction == -1 and \ | |
idx - 1 >= 0 and \ | |
not self.tests[idx]: | |
idx = self.seekPrev(idx) | |
self.icursor(idx) | |
return 'break' | |
else: | |
self.bell() | |
return 'break' | |
def get_calendar(locale, fwday): | |
# instantiate proper calendar class | |
if locale is None: | |
return calendar.TextCalendar(fwday) | |
else: | |
return calendar.LocaleTextCalendar(fwday, locale) | |
class Calendar(ttk.Frame): | |
# XXX ToDo: cget and configure | |
datetime = calendar.datetime.datetime | |
timedelta = calendar.datetime.timedelta | |
def __init__(self, master=None, **kw): | |
""" | |
WIDGET-SPECIFIC OPTIONS | |
locale, firstweekday, year, month, selectbackground, | |
selectforeground | |
""" | |
# remove custom options from kw before initializating ttk.Frame | |
fwday = kw.pop('firstweekday', calendar.SUNDAY) | |
year = kw.pop('year', self.datetime.now().year) | |
month = kw.pop('month', self.datetime.now().month) | |
locale = kw.pop('locale', None) | |
sel_bg = kw.pop('selectbackground', '#ecffc4') | |
sel_fg = kw.pop('selectforeground', '#05640e') | |
self._date = self.datetime(year, month, 1) | |
self._selection = None # no date selected | |
ttk.Frame.__init__(self, master, **kw) | |
self._cal = get_calendar(locale, fwday) | |
self.__setup_styles() # creates custom styles | |
self.__place_widgets() # pack/grid used widgets | |
self.__config_calendar() # adjust calendar columns and setup tags | |
# configure a canvas, and proper bindings, for selecting dates | |
self.__setup_selection(sel_bg, sel_fg) | |
# store items ids, used for insertion later | |
self._items = [self._calendar.insert('', 'end', values='') | |
for _ in range(6)] | |
# insert dates in the currently empty calendar | |
self._build_calendar() | |
# set the minimal size for the widget | |
self._calendar.bind('<Map>', self.__minsize) | |
def __setitem__(self, item, value): | |
if item in ('year', 'month'): | |
raise AttributeError("attribute '%s' is not writeable" % item) | |
elif item == 'selectbackground': | |
self._canvas['background'] = value | |
elif item == 'selectforeground': | |
self._canvas.itemconfigure(self._canvas.text, item=value) | |
else: | |
ttk.Frame.__setitem__(self, item, value) | |
def __getitem__(self, item): | |
if item in ('year', 'month'): | |
return getattr(self._date, item) | |
elif item == 'selectbackground': | |
return self._canvas['background'] | |
elif item == 'selectforeground': | |
return self._canvas.itemcget(self._canvas.text, 'fill') | |
else: | |
r = ttk.tclobjs_to_py({item: ttk.Frame.__getitem__(self, item)}) | |
return r[item] | |
def __setup_styles(self): | |
# custom ttk styles | |
style = ttk.Style(self.master) | |
arrow_layout = lambda dir: ( | |
[('Button.focus', {'children': [('Button.%sarrow' % dir, None)]})] | |
) | |
style.layout('L.TButton', arrow_layout('left')) | |
style.layout('R.TButton', arrow_layout('right')) | |
def __place_widgets(self): | |
# header frame and its widgets | |
hframe = ttk.Frame(self) | |
lbtn = ttk.Button(hframe, style='L.TButton', command=self._prev_month) | |
rbtn = ttk.Button(hframe, style='R.TButton', command=self._next_month) | |
self._header = ttk.Label(hframe, width=15, anchor='center') | |
# the calendar | |
self._calendar = ttk.Treeview(show='', selectmode='none', height=7) | |
# pack the widgets | |
hframe.pack(in_=self, side='top', pady=4, anchor='center') | |
lbtn.grid(in_=hframe) | |
self._header.grid(in_=hframe, column=1, row=0, padx=12) | |
rbtn.grid(in_=hframe, column=2, row=0) | |
self._calendar.pack(in_=self, expand=1, fill='both', side='bottom') | |
def __config_calendar(self): | |
cols = self._cal.formatweekheader(3).split() | |
self._calendar['columns'] = cols | |
self._calendar.tag_configure('header', background='grey90') | |
self._calendar.insert('', 'end', values=cols, tag='header') | |
# adjust its columns width | |
font = tkFont.Font() | |
maxwidth = max(font.measure(col) for col in cols) | |
for col in cols: | |
self._calendar.column(col, width=maxwidth, minwidth=maxwidth, | |
anchor='e') | |
def __setup_selection(self, sel_bg, sel_fg): | |
self._font = tkFont.Font() | |
self._canvas = canvas = tk.Canvas(self._calendar, | |
background=sel_bg, borderwidth=0, highlightthickness=0) | |
canvas.text = canvas.create_text(0, 0, fill=sel_fg, anchor='w') | |
canvas.bind('<ButtonPress-1>', lambda evt: canvas.place_forget()) | |
self._calendar.bind('<Configure>', lambda evt: canvas.place_forget()) | |
self._calendar.bind('<ButtonPress-1>', self._pressed) | |
def __minsize(self, evt): | |
width, height = self._calendar.master.geometry().split('x') | |
height = height[:height.index('+')] | |
self._calendar.master.minsize(width, height) | |
def _build_calendar(self): | |
year, month = self._date.year, self._date.month | |
# update header text (Month, YEAR) | |
header = self._cal.formatmonthname(year, month, 0) | |
self._header['text'] = header.title() | |
# update calendar shown dates | |
cal = self._cal.monthdayscalendar(year, month) | |
for indx, item in enumerate(self._items): | |
week = cal[indx] if indx < len(cal) else [] | |
fmt_week = [('%02d' % day) if day else '' for day in week] | |
self._calendar.item(item, values=fmt_week) | |
def _show_selection(self, text, bbox): | |
"""Configure canvas for a new selection.""" | |
x, y, width, height = bbox | |
textw = self._font.measure(text) | |
canvas = self._canvas | |
canvas.configure(width=width, height=height) | |
canvas.coords(canvas.text, width - textw, height / 2 - 1) | |
canvas.itemconfigure(canvas.text, text=text) | |
canvas.place(in_=self._calendar, x=x, y=y) | |
# Callbacks | |
def _pressed(self, evt): | |
"""Clicked somewhere in the calendar.""" | |
x, y, widget = evt.x, evt.y, evt.widget | |
item = widget.identify_row(y) | |
column = widget.identify_column(x) | |
if not column or not item in self._items: | |
# clicked in the weekdays row or just outside the columns | |
return | |
item_values = widget.item(item)['values'] | |
if not len(item_values): # row is empty for this month | |
return | |
text = item_values[int(column[1]) - 1] | |
if not text: # date is empty | |
return | |
bbox = widget.bbox(item, column) | |
if not bbox: # calendar not visible yet | |
return | |
# update and then show selection | |
text = '%02d' % text | |
self._selection = (text, item, column) | |
self._show_selection(text, bbox) | |
def _prev_month(self): | |
"""Updated calendar to show the previous month.""" | |
self._canvas.place_forget() | |
self._date = self._date - self.timedelta(days=1) | |
self._date = self.datetime(self._date.year, self._date.month, 1) | |
self._build_calendar() # reconstuct calendar | |
def _next_month(self): | |
"""Update calendar to show the next month.""" | |
self._canvas.place_forget() | |
year, month = self._date.year, self._date.month | |
self._date = self._date + self.timedelta( | |
days=calendar.monthrange(year, month)[1] + 1) | |
self._date = self.datetime(self._date.year, self._date.month, 1) | |
self._build_calendar() # reconstruct calendar | |
# Properties | |
@property | |
def selection(self): | |
"""Return a datetime representing the current selected date.""" | |
if not self._selection: | |
return None | |
year, month = self._date.year, self._date.month | |
return self.datetime(year, month, int(self._selection[0])) | |
def main(): | |
root = tk.Tk() | |
page = tk.Frame(root) | |
c = MaskedWidget(page, 'fixed', mask='+99 (99) 999-999-999', width=20) | |
c.insert(0, '6611122266647') | |
c.pack() | |
c = MaskedWidget(page, 'fixed', mask='99/99/9999') | |
c.insert(0, '29091991') | |
c.pack() | |
MaskedWidget(page, 'numeric').pack() | |
c = MaskedWidget(page, 'numeric', dec_sep=",", tho_sep='.', symbol="R$") | |
c.insert(0, '12659.96') | |
c.pack() | |
# Cleaning text | |
c = MaskedWidget(page, 'numeric', dec_sep=",", tho_sep='.', symbol="R$") | |
c.insert(0, '12659.96') | |
c.pack() | |
c.clean() | |
c = Calendar(page) | |
c.pack() | |
FormWidget(page, | |
label=u'Olá', | |
help='Seu Nome' | |
).pack() | |
page.pack() | |
root.mainloop() | |
if __name__ == '__main__': | |
main() |
Max, tudo bem?
Implementei o seu código em uma aplicação que estou desenvolvendo.
O único problema é que quando preciso limpar os campos usando o método ".delete(0,END)" ele limpa também a máscara inserida, e transforma o widget em um entry normal.
Alguma sugestão? Tentei usar o método ".select_range(0,END)" e depois ".select_clear()" mas não estão funcionando não sei porque. Eles nao executam nada.
Pesquisei várias alternativas e a que me deixou mais perto do que quero foi o seu código.
Desde já muito obrigado.
@bernardesrodrigo eu só vi sua mensagem agora, para limpar vc pode usar
widgets.fields['textvariable'].set("")
Max, poderia dar uma exemplificada melhor na dúvida do Bernardes, por favor!
Estava tentando aqui com o que você repassou - widgets.fields['textvariable'].set("") - porém não tá dando certo :/
@wesleygurgel vou implementar um método dedicado para isso, assim que estiver em ordem, te aviso, e ao @bernardesrodrigo também
Added clean
as dedicated method to cleanup the field
@wesleygurgel agora basta utilizar o método clean
para limpar o widget
@wesleygurgel agora basta utilizar o método
clean
para limpar o widget
@MaxMorais tu é brabo, obg de verdade
bom dia, estou com duvida em relação a atribuir a data selecionada no Calendar à uma variavel.
@vitortitz, qual a sua pergunta?
após fazer a seleção no calendário como faço para poder atribuir a data selecionada à uma variável? Desculpa a pergunta, sou novato em python
@vitortitz utilize algo assim no teu codigo
minha_var = meu_calendar.selection
@MaxMorais desculpa a demora, obrigado pela ajuda, irei fazer o teste.
@MaxMorais desculpa incomodar novamente, mas existe alguma forma de selecionar o ano tbm, sem ser voltando de mês em mês?
como faço para adicionar maskara cpf no Entry do Tkinter?
Me ajudou muito mesmo, parabéns pela sua iniciativa!!
Various improvements regarding the BackSpace key issue, and the formatting of numeric fields