javaswingpropertychangelistener

Updating callers of PropertyChangeListener in Java + Swing


I'm working with Swing and PropertyChangeListener to manage theme changes in an application. Listeners are notified when the theme property changes in the ThemeManager class. However, I need a mechanism to ensure all the callers of the ThemeChangeListener update their behavior appropriately when the theme changes.

For example if I have 2 ButtonGroup that changes the theme, but they aren't synced.

// ... existing code for ThemeManager, ThemeManagerFrame, and ThemeChangeListener ...
public class ThemeManagerFrame extends JFrame implements PropertyChangeListener {
  // ... existing code ...

  @Override
  public void propertyChange(PropertyChangeEvent evt) {
    if ("theme".equals(evt.getPropertyName())) {
      String newTheme = (String) evt.getNewValue();
      // Need to identify the specific component that triggered the change (e.g., settingsLightThemeRadioButton)
      System.out.println("Theme changed by: (Unable to determine specific component)");
      // ... existing code to update UI ...
    }
  }
}

Within the listener's propertyChange method, I want to be able to identify the specific Swing component that triggered the theme change notification.


Solution

  • In one of the comments you clarified your question/need:

    I want to bind several components to one bean

    So with that in mind: I'd suggest the following.

    In this example the "one bean" we're binding to is the JFrame's root pane table of client properties. (You could use any other bean, but this is already available to us and will manager the getters/settings/listeners for us.)

    import javax.swing.*;
    import java.awt.*;
    
    public class ThemeFrame extends JFrame {
    
        private static final String PROPERTY_THEME = "theme";
    
        enum Theme { LIGHT, DARK };
    
        public static void main(String[] arsg) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    ThemeFrame f = new ThemeFrame();
                    f.pack();
                    f.setVisible(true);
                }
            });
        }
    
        JRadioButton settingsLightThemeRadioButton = new JRadioButton("Light Theme");
        JRadioButton settingsDarkThemeRadioButton = new JRadioButton("Dark Theme");
        ButtonGroup buttonGroup = new ButtonGroup();
    
        public ThemeFrame() {
            buttonGroup.add(settingsLightThemeRadioButton);
            buttonGroup.add(settingsDarkThemeRadioButton);
            getContentPane().setLayout(new FlowLayout());
            getContentPane().add(settingsLightThemeRadioButton);
            getContentPane().add(settingsDarkThemeRadioButton);
    
            settingsLightThemeRadioButton.addActionListener(e -> getRootPane().putClientProperty(PROPERTY_THEME, Theme.LIGHT));
            settingsDarkThemeRadioButton.addActionListener(e -> getRootPane().putClientProperty(PROPERTY_THEME, Theme.DARK));
    
            getRootPane().addPropertyChangeListener(PROPERTY_THEME, evt -> changeTheme(getContentPane(), (Theme) evt.getNewValue()));
    
            // at this point: neither button is selected, and the "theme" property is undefined. Click one button
            // to initialize everything:
            settingsLightThemeRadioButton.doClick();
        }
    
        /**
         * This recursively iterates over every component in your window and gives you the chance to modify it
         */
        private void changeTheme(Component component, Theme theme) {
            // this is going to iterate over everything. Here's a sample of what changing the theme might mean:
            if (Theme.LIGHT.equals(theme)) {
                component.setBackground(Color.white);
                component.setForeground(Color.black);
            } else {
                component.setBackground(Color.black);
                component.setForeground(Color.white);
            }
    
            // this iterates over all descendant components
            if (component instanceof Container container) {
                for (Component child : container.getComponents()) {
                    changeTheme(child, theme);
                }
            }
        }
    }