2

I'm using tkiner ttk widgets and configuring everything with ttk.Style(). When I use the 'clam' theme, widgets like ttk.Entry() and ttk.Spinbox() have this default setting that when text is selected but the widget is not in focus anymore the background of the text is gray and the foreground is white. Here a visual example when the cursor is already in entry line, but text in spinbox still has some custom color. The coloring of the text goes back to unselected style only when text in other widget is selected

enter image description here

I've been searching for a long time how this can be changed. The closest I have found is that tk.Text() widget actually has this option of inactiveselectbackground (there isn't inactiveselectforeground tho). But that is not something that works for ttk.Entry() or ttk.Spinbox(). Also when using regular tk.Entry() or tk.Spinbox() or using the default theme for ttk widgets, the background and foreground does not change to a new color. I suppose the options still might be there, but they are not set to anything specific.

Getting back to the specific problem - does anyone know if it is possible to change color of background and foreground of text in ttk.Entry() or ttk.Spinbox() when the widget is not in focus anymore? Maybe some workaround to this problem?

Kārlis Rieksts
  • 169
  • 1
  • 1
  • 8

2 Answers2

1

To change the foreground and background when the widget is inactive (out in focus) then with the help of and binds we can configure the widgets in such a way that they will change their foreground and background when they lose focus and when gain focus back.

Practically we can first save original foreground and background values of that widget and then use it and callbacks.

Here I've made a class Entry which does exactly you want. I added inactivebackground and inactiveforeground configure options.

class Entry(tk.Entry):
    def __init__(self, master=None, **kw):
        self.inactivebackground = kw.pop('inactivebackground', 'white')
        self.inactiveforeground = kw.pop('inactiveforeground', 'black')
        super().__init__(master=master, **kw)
        self.org_bg = self['background']
        self.org_fg = self['foreground']
        self.bind('<FocusIn>', self._focusin, '+')
        self.bind('<FocusOut>', self._focusout, '+')
        self._focusout()

    def _focusout(self, evt=None):
        self['background'] = self.inactivebackground
        self['foreground'] = self.inactiveforeground
    
    def _focusin(self, evt=None):
        self['background'] = self.org_bg
        self['foreground'] = self.org_fg

Have a look at this example:-

import tkinter as tk

root = tk.Tk()

var = tk.StringVar(value="Hello! How are you doing! :)")

Entry(root, textvariable=var, inactivebackground='pink', 
      inactiveforeground='blue').pack()
Entry(root, textvariable=var, inactivebackground='orange', 
      inactiveforeground='red').pack()

root.mainloop()

Similarly, you can modify a Spinbox to do the same thing. Also just by replacing the inherited class tk.Entry with ttk.Entry will work with ttk style widgets as well but remember not everything is configurable directly with ttk style widgets.


Power of inheritance

There is one trick you can do to save time and space, by creating a support class that can be inherited along with the desired widget to have the same functionality.

class supportinactive(object):
    def __init__(self, inactivebackground, inactiveforeground):
        self.inactivebackground = inactivebackground
        self.inactiveforeground = inactiveforeground
        self.org_bg = self['background']
        self.org_fg = self['foreground']
        self.bind('<FocusIn>', self._focusin, '+')
        self.bind('<FocusOut>', self._focusout, '+')
        self._focusout()

    def _focusout(self, evt=None):
        self['background'] = self.inactivebackground
        self['foreground'] = self.inactiveforeground
    
    def _focusin(self, evt=None):
        self['background'] = self.org_bg
        self['foreground'] = self.org_fg

How to use it?

From the above supportinactive class we can add this functionality to widget like so

class Entry(tk.Entry, supportinactive):
    def __init__(self, master=None, **kw):
        inactivebg = kw.pop('inactivebackground', 'white')
        inactivefg = kw.pop('inactiveforeground', 'black')
        tk.Entry.__init__(self, master=master, **kw)
        supportinactive.__init__(self, inactivebg, inactivefg)

# Spinbox will have the same functionality too.
class Spinbox(tk.Spinbox, supportinactive):
    def __init__(self, master=None, **kw):
        inactivebg = kw.pop('inactivebackground', 'white')
        inactivefg = kw.pop('inactiveforeground', 'black')
        tk.Spinbox.__init__(self, master=master, **kw)
        supportinactive.__init__(self, inactivebg, inactivefg)

If you want to understand how this inheritance is working check out these answers:-

Saad
  • 3,340
  • 2
  • 10
  • 32
0

I'm posting as an answer, so I can add pictures and be specific about the configuration that is not changing. The above answer is a very nice approach to changing those configurations but will work only when a tk.Entry() widget is use. I'm using ttk.Entry() with 'clam' theme. And 'clam' theme is the one that introduces this additional configuration for background/foreground when text is selected and the widget goes out of focus. What the above-given answer modifies is:

  • background/foreground of unselected text when out of focus.

What I want to modify (get rid off):

  • background/foreground of the selected text when out of focus when using ttk.Entry and 'clam'.

Here I have applied the above answer when using ttk.Entry() and 'clam' as theme.

import tkinter as tk
from tkinter import ttk

root = tk.Tk()
root.geometry('200x100')

style = ttk.Style()
style.theme_use('clam')
style.configure('New.TEntry',
                fieldbackground='red',
                foreground='green',
                selectbackground='black',
                selectforeground='yellow')

class NewEntry(ttk.Entry):
    def __init__(self, parent, style):
        super().__init__(parent)
        self.style = style
        self['style'] = 'New.TEntry'

        self.bind('<FocusIn>', self._focusin, '+')
        self.bind('<FocusOut>', self._focusout, '+')

    def _focusout(self, evt=None):
        self.style.configure('New.TEntry',
                             fieldbackground='red',
                             foreground='green',
                             selectbackground='black',
                             selectforeground='yellow')

    def _focusin(self, evt=None):
        self.style.configure('New.TEntry',
                             fieldbackground='blue',
                             foreground='pink',
                             selectbackground='black',
                             selectforeground='yellow')


entry1 = NewEntry(root, style).pack()
entry2 = tk.Entry(root).pack()

root.mainloop()

Here is a walkthrough:

  • entry1 is in focus, no text is selected. Color properties set by fieldbackground and foreground.

enter image description here

  • entry1 is in focus, half of the text is selected. Color properties of selected text are defined by selectbackground and selectforeground

enter image description here

  • entry1 is out of focus. The cursor is in entry2. The color of the text that is still selected is governed by some unknown properties to me. selectbackground and selectforeground out of focus have effect only on the part that was not selected.

enter image description here

When new text is selected somewhere else - the colors will be defined as they should by selectbackground and selectforeground

As I mentioned in the original posting that tk.Text() for instance has an option inactiveselectbackground. So I believe somewhere in the setting something like that exists. It is just a question of how to gain access to that property.

P.S. This also made me wonder what is the easiest way to modify style within a class. I haven't seen anyone doing that. I figured passing the style inside the class would allow me to do it. Maybe there's a better way.

Kārlis Rieksts
  • 169
  • 1
  • 1
  • 8
  • Good answer but if you use ‘“New.TEntry”’ style name for multiple entries then it can collide with different entry widgets, it will be a good practice to have a unique name for each instance created of the ‘NewEntry’ class. You can fix that by replacing ‘“New.TEntry’” with ‘“%s.TEntry” %self’. – Saad Jun 24 '20 at 05:31