Skip to main content

JComboBox with Disabled Items

Recently, I was working on a project in Java and needed to have a combo box, but with certain items in the list disabled (e.g. gray and non-selectable). At first, I simply set a custom renderer for the combo box which checked if the item was disabled. That, however, did not prevent the items from being selected. Thus, I set about to find a viable solution. There are plenty of solutions out there, but none seemed to work exactly the way I wanted. In the end, I ended up subclassing JComboBox to provide the functionality of disabling individual items. Here is my result, in under 100 lines:

import java.awt.Component;
import java.util.ArrayList;

import javax.swing.JComboBox;
import javax.swing.JList;
import javax.swing.plaf.basic.BasicComboBoxRenderer;

public class PartialDisableComboBox extends JComboBox {
 private static final long serialVersionUID = -1690671707274328126L;
 
 private ArrayList<boolean> itemsState = new ArrayList<boolean>();
 
 public PartialDisableComboBox() {
  super();
  this.setRenderer(new BasicComboBoxRenderer() {
   private static final long serialVersionUID = -2774241371293899669L;
   @Override
   public Component getListCellRendererComponent(JList list, Object value, 
     int index, boolean isSelected, boolean cellHasFocus) {
    Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
    boolean disabled = index >= 0 && index < itemsState.size() && !itemsState.get(index);
    c.setEnabled(!disabled);
    c.setFocusable(!disabled);
    return c;
   }
  });
 }
 
 @Override
 public void addItem(Object item) {
  this.addItem(item, true);
 }
 
 public void addItem(Object item, boolean enabled) {
  super.addItem(item);
  itemsState.add(enabled);
 }
 
 @Override
 public void insertItemAt(Object item, int index) {
  this.insertItemAt(item, index, true);
 }

 public void insertItemAt(Object item, int index, boolean enabled) {
  super.insertItemAt(item, index);
  itemsState.add(index, enabled);
 }
 
 @Override
 public void removeAllItems() {
  super.removeAllItems();
  itemsState.clear();
 }
 
 @Override
 public void removeItemAt(int index) {
  if (index < 0 || index >= itemsState.size()) throw new IllegalArgumentException("Item Index out of Bounds!");
  super.removeItemAt(index);
  itemsState.remove(index);
 }
 
 @Override
 public void removeItem(Object item) {
  for (int q = 0; q < this.getItemCount(); q++) {
   if (this.getItemAt(q) == item) itemsState.remove(q);
  }
  super.removeItem(item);
 }
 
 @Override
 public void setSelectedIndex(int index) {
  if (index < 0 || index >= itemsState.size()) throw new IllegalArgumentException("Item Index out of Bounds!");
  if (itemsState.get(index)) super.setSelectedIndex(index);
 }
 
 public void setItemEnabled(int index, boolean enabled) {
  if (index < 0 || index >= itemsState.size()) throw new IllegalArgumentException("Item Index out of Bounds!");
  itemsState.set(index, enabled);
 }
 
 public boolean isItemEnabled(int index) {
  if (index < 0 || index >= itemsState.size()) throw new IllegalArgumentException("Item Index out of Bounds!");
  return itemsState.get(index);
 }
}

As you can see, most of the code is pretty straightforward, adding and removing elements from the JComboBox and the list of item states. The custom renderer is still used to provide the L&F for the disabled items, but the key to having the disabled items non-selectable is the setSelectedIndex(int) method. There are also plenty of index range checks thrown in there, especially in the custom renderer, as the combo box calls that method with index = -1 when rendering the combo box in its "closed" state (without the dropdown).

Of course, I want give credit to some other blogs that helped lay the groundwork and point me in the right direction, but unfortunately I don't remember their names. If I find them again, I'll be sure to include them in this post.

As always, please let me know if you find this useful, or if you find any bugs or possible improvements.

Comments

  1. This comment has been removed by the author.

    ReplyDelete
  2. This is working great (just had to make a quick change from boolean to Boolean for private ArrayList (maybe a Java7 thing?). Just what I needed. Thanks.

    ReplyDelete

Post a Comment

Popular posts from this blog

Linux on XPS 15 9550/9560 with TB16 Dock [Update:3/29]

Finally got a laptop to replace my fat tower at work - Dell XPS 15 9560. I was allowed to choose which one I wanted and chose the XPS for its Linux support since Dell ships developer edition XPS's running Ubuntu so I figured Linux support would be better than other manufacturers. At first they got me the model with the 4K screen but my monitors are 2K and multi-dpi support in Linux is virtually non-existent and even hi-dpi support on its own is pretty terrible. So I got it exchanged for the model with the regular 1080p screen (which happened to also be the updated 9560 model), which works much better. I'm very glad to report that pretty much everything works, including the TB16 desktop dock, with just a bit of settings tweaking. This post is to help anybody considering getting this setup or looking for help getting things working. For now, I am running Kubuntu 16.04 with KDE Neon installed.

List of things I explicitly tested and work:
WiFi, BluetoothThunderbolt charging from T…

Drawing Dashed Lines on an HTML5 Canvas

The canvas element in HTML is great, but has one strange shortcoming: it cannot draw dashed lines (natively). However, dashed lines seem like a pretty common thing to draw, which only highlights the problem.

Looking around, I've noticed several solutions to this problem. Some use trig, and others use their own libraries that must be imported. So in the end, I decided to create my own method.

This code will add the function to all canvas elements, both those already on the page, and any that are dynamically added later.

Here is the code:
CanvasRenderingContext2D.prototype.dashedLine = function(x1, y1, x2, y2, dashLen) { if (dashLen == undefined) dashLen = 2; this.beginPath(); this.moveTo(x1, y1); var dX = x2 - x1; var dY = y2 - y1; var dashes = Math.floor(Math.sqrt(dX * dX + dY * dY) / dashLen); var dashX = dX / dashes; var dashY = dY / dashes; var q = 0; while (q++ < dashes) { x1 += dashX; y1 += dashY; this[q …

Listening for Window Resize events with Prototype

Recently, I came across an issue I had with full-screen (more specifically, full-viewport) canvas drawing. It all worked fine, except that the canvas dimensions must be specified in pixels, as opposed to percentages. This means you can't just set the canvas' width and height to "100%" and be done with it...

The dimensions were automatically set when the page loaded, but if the user resized the browser window after that, the canvas would stay the same size, and would be either too big or too small. Thus, the canvas must be resized every time the browser window is resized.

Prototype makes all of this easy. All that's necessary is to attach a listener to the onresize event of the window object, and then use document.viewport.getDimensions() to determine the new width and height.

Here's some sample code:

Event.observe(window, "resize", function() { var width = document.viewport.getWidth(); var height = document.viewport.getHeight(); var dims …