Best blog entry ever on GridBag
. Also excellent over-the-shoulder pairing sensation. Nice animation.
Thursday, January 12, 2006
madbean » Totally Gridbag
Monday, January 02, 2006
Getting JTable columns widths to fit the data
I've been stumped on a JTable problem for quite a while now: how to get the columns to fit the width of the data in the cells. Out of the box, JTable uses the same width for all columns in a table. This is very annoying for non-editable tables which just display data. I want my Java tables to looks similar to HTML tables which resize column widths according to the cell contents.
A likely solution is simply that I did not search Google well enough, but information was frustratingly rare to find for me.
I did, however, eventually stumble across the prepareRenderer(TableCellRenderer renderer, int row, int column):Component
method (and likewise prepareRenderer(TableCellEditor editor, int row, int column):Component
). A flash of insight illuminated the solution at once:
{ final TableCellRenderer renderer = getTableHeader() .getDefaultRenderer(); for (int i = 0; i < getColumnCount(); ++i) getColumnModel().getColumn(i).setPreferredWidth( renderer.getTableCellRendererComponent(this, getModel().getColumnName(i), false, false, 0, i) .getPreferredSize().width); } public Component prepareRenderer(final TableCellRenderer renderer, final int row, final int column) { final Component prepareRenderer = super .prepareRenderer(renderer, row, column); final TableColumn tableColumn = getColumnModel().getColumn(column); tableColumn.setPreferredWidth(max( prepareRenderer.getPreferredSize().width, tableColumn.getPreferredWidth())); return prepareRenderer; }
My solution has two parts. Addressing first prepareRenderer
: as the table is drawn, adjust the preferred width of a column to be at least as wide as the widest cell seen so far. This was 90% of my problem right there. Immediately I stopped having truncated cells, and the table adjusted gracefully to new cell data which did not fit the old column widths.
The second part is more obscure: the instance initializer. Although my cell contents were now always visible, the proportions among the columns did not look quite right: narrow columns were not narrow enough with excess slop on either side of column titles. So I instructed the table to shrink column widths down to just that necessary to show the title (prepareRenderer
will widen them again as needed for cell contents). Perfect!
What surprises me is that I was unable to find this or a similar solution anywhere in my Google searches. I can scarcely believe this is the first attempt at this approach. Hopefully Sun will include a solution in the JDK—this is not an uncommon JTable problem.
UPDATE: This topic continues to deliver: Alex Boosmeeuw posts with a different approach.
Sunday, January 01, 2006
More enum reinvention in Java
I posted earlier on reinventing the enum in Java for pre-JDK5 code. But I am also annoyed by one limitation in JDK5 enums, than they are not extendable at runtime or otherwise. So I copied the Enum
class from the JDK and modified it to support non-final
subclasses.
An example class and its extension from the unit tests, also demonstrating the abstract
feature present in the JDK enum
:
1 private abstract static class TestEnumA 2 extends ExtensibleEnum<TestEnumA> { 3 public static final TestEnumA A = new TestEnumA("A") { 4 void foo() { 5 } 6 }; 7 public static final TestEnumA B = new TestEnumA("B") { 8 void foo() { 9 } 10 }; 11 12 static { 13 new TestEnumA("C") { 14 void foo() { 15 } 16 }; 17 } 18 19 abstract void foo(); 20 21 public static TestEnumA[] values() { 22 return ExtensibleEnum.values(TestEnumA.class); 23 } 24 25 public static TestEnumA valueOf(final String name) { 26 return ExtensibleEnum.valueOf(TestEnumA.class, name); 27 } 28 29 protected TestEnumA(final String name) { 30 super(name); 31 } 32 } 33 34 private static abstract class TestEnumB 35 extends TestEnumA { 36 public static final TestEnumB D = new TestEnumB("D") { 37 void foo() { 38 } 39 }; 40 41 public static TestEnumB[] values() { 42 return ExtensibleEnum.values(TestEnumB.class); 43 } 44 45 public static TestEnumB valueOf(final String name) { 46 return ExtensibleEnum.valueOf(TestEnumB.class, name); 47 } 48 49 protected TestEnumB(final String name) { 50 super(name); 51 } 52 } 53 }
Note that if you ask for values()
from TestEnumA
, they include TestEnumB.D
—a TestEnumA
by subclassing.
Here is the base class. I am also trying out the "Code as HTML" plugin for IDEA—I hope the colorizing is not too much:
1 /* 2 * Copyright (c) 2006 B. K. Oxley (binkley) <binkley@alumni.rice.edu> under the 3 * terms of the Artistic License, including Clause 8. See 4 * http://www.opensource.org/licenses/artistic-license.php. 5 */ 6 7 import java.io.Serializable; 8 import java.lang.reflect.Array; 9 import java.util.ArrayList; 10 import java.util.EnumMap; 11 import java.util.HashMap; 12 import java.util.List; 13 import java.util.Map; 14 15 /** 16 * {@code ExtensibleEnum} <strong>needs documentation</strong>. 17 * 18 * @author <a href="mailto:binkley@alumni.rice.edu">B. K. Oxley (binkley)</a> 19 * @version $Id$ 20 * @todo {@code RuntimeExtensibleEnum} needs documentation 21 * @noinspection FinalizeDoesntCallSuperFinalize 22 * @since Dec 31, 2005 8:38:03 AM 23 */ 24 public abstract class ExtensibleEnum<E extends ExtensibleEnum<E>> 25 implements Comparable<E>, Serializable { 26 private static final Map<Class<? extends ExtensibleEnum<?>>, 27 Map<String, ? extends ExtensibleEnum<?>>> BY_NAME 28 = new HashMap<Class<? extends ExtensibleEnum<?>>, 29 Map<String, ? extends ExtensibleEnum<?>>>(4); 30 private static final Map<Class<? extends ExtensibleEnum<?>>, 31 List<? extends ExtensibleEnum<?>>> BY_ORDINAL 32 = new HashMap<Class<? extends ExtensibleEnum<?>>, 33 List<? extends ExtensibleEnum<?>>>(4); 34 /** 35 * The JDK5 enum has ordinals in stepwise order; this implementation relaxes 36 * the constraint to simply monotonically increasing. 37 * 38 * @noinspection StaticNonFinalField 39 */ 40 private static int ORDINAL; 41 42 /** 43 * The name of this enum constant, as declared in the enum declaration. Most 44 * programmers should use the {@link #toString} method rather than accessing 45 * this field. 46 */ 47 private final String name; 48 49 /** 50 * Returns the name of this enum constant, exactly as declared in its enum 51 * declaration. 52 * <p/> 53 * <b>Most programmers should use the {@link #toString} method in preference 54 * to this one, as the toString method may return a more user-friendly 55 * name.</b> This method is designed primarily for use in specialized 56 * situations where correctness depends on getting the exact name, which 57 * will not vary from release to release. 58 * 59 * @return the name of this enum constant 60 */ 61 public final String name() { 62 return name; 63 } 64 65 /** 66 * The ordinal of this enumeration constant (its position in the enum 67 * declaration, where the initial constant is assigned an ordinal of zero). 68 * <p/> 69 * Most programmers will have no use for this field. It is designed for use 70 * by sophisticated enum-based data structures, such as {@link 71 * java.util.EnumSet} and {@link EnumMap}. 72 */ 73 private final int ordinal = ORDINAL++; 74 75 /** 76 * Returns the ordinal of this enumeration constant (its position in its 77 * enum declaration, where the initial constant is assigned an ordinal of 78 * zero). 79 * <p/> 80 * Most programmers will have no use for this method. It is designed for 81 * use by sophisticated enum-based data structures, such as {@link 82 * java.util.EnumSet} and {@link EnumMap}. 83 * 84 * @return the ordinal of this enumeration constant 85 */ 86 public final int ordinal() { 87 return ordinal; 88 } 89 90 /** 91 * Sole constructor. Programmers cannot invoke this constructor. It is for 92 * use by code emitted by the compiler in response to enum type 93 * declarations. 94 * 95 * @param name - The name of this enum constant, which is the identifier 96 * used to declare it. 97 */ 98 protected ExtensibleEnum(final String name) { 99 if (null == name) 100 throw new NullPointerException("Missing name"); 101 102 this.name = name; 103 104 new EnumInstall(getDeclaringClass(), name, this).install(); 105 } 106 107 private static class EnumInstall<T extends ExtensibleEnum<T>, U extends T> { 108 private final Class<T> enumType; 109 private final String name; 110 private final U instance; 111 112 EnumInstall(final Class<T> enumType, final String name, 113 final U instance) { 114 this.enumType = enumType; 115 this.name = name; 116 this.instance = instance; 117 } 118 119 void install() { 120 if (!ExtensibleEnum.class.isAssignableFrom(enumType)) 121 return; 122 123 final EnumInstall<? super T, ? super U> parentInstall 124 = new EnumInstall(enumType.getSuperclass(), name, instance); 125 126 parentInstall.install(); 127 128 try { 129 putEnum(enumType, name, instance); 130 131 } catch (final IllegalArgumentException e) { 132 parentInstall.uninstall(); 133 134 throw e; 135 } 136 } 137 138 void uninstall() { 139 unputEnum(enumType, name); 140 } 141 } 142 143 private static <T extends ExtensibleEnum<T>, U extends T> void putEnum( 144 final Class<T> enumType, final String name, final U instance) { 145 if (!BY_NAME.containsKey(enumType)) { 146 BY_NAME.put(enumType, new HashMap<String, T>(4)); 147 BY_ORDINAL.put(enumType, new ArrayList<T>(4)); 148 } 149 150 final Map<String, T> nameMap = byName(enumType); 151 152 if (nameMap.containsKey(name)) 153 throw new IllegalArgumentException("Duplicate name: " + name); 154 155 nameMap.put(name, instance); 156 byOrdinal(enumType).add(instance); 157 } 158 159 private static <T extends ExtensibleEnum<T>> void unputEnum( 160 final Class<T> enumType, final String name) { 161 if (!BY_NAME.containsKey(enumType)) 162 return; 163 164 byName(enumType).remove(name); 165 166 final List<T> list = byOrdinal(enumType); 167 168 list.remove(list.size() - 1); 169 } 170 171 /** 172 * Returns the name of this enum constant, as contained in the declaration. 173 * This method may be overridden, though it typically isn't necessary or 174 * desirable. An enum type should override this method when a more 175 * "programmer-friendly" string form exists. 176 * 177 * @return the name of this enum constant 178 */ 179 @Override 180 public String toString() { 181 return name; 182 } 183 184 /** 185 * Returns true if the specified object is equal to this enum constant. 186 * 187 * @param other the object to be compared for equality with this object. 188 * 189 * @return true if the specified object is equal to this enum constant. 190 */ 191 @Override 192 public final boolean equals(final Object other) { 193 return this == other; 194 } 195 196 /** 197 * Returns a hash code for this enum constant. 198 * 199 * @return a hash code for this enum constant. 200 */ 201 @Override 202 public final int hashCode() { 203 return System.identityHashCode(this); 204 } 205 206 /** 207 * Throws CloneNotSupportedException. This guarantees that enums are never 208 * cloned, which is necessary to preserve their "singleton" status. 209 * 210 * @return (never returns) 211 */ 212 @Override 213 protected final Object clone() 214 throws CloneNotSupportedException { 215 throw new CloneNotSupportedException(); 216 } 217 218 /** 219 * Compares this enum with the specified object for order. Returns a 220 * negative integer, zero, or a positive integer as this object is less 221 * than, equal to, or greater than the specified object. 222 * <p/> 223 * ExtensibleEnum constants are only comparable to other enum constants of 224 * the same enum type. The natural order implemented by this method is the 225 * order in which the constants are declared. 226 * 227 * @noinspection ObjectEquality 228 */ 229 public final int compareTo(final E o) { 230 if (getClass() != o.getClass() // optimization 231 && getDeclaringClass() != o.getDeclaringClass()) 232 throw new ClassCastException(); 233 234 return new Integer(ordinal).compareTo(o.ordinal); 235 } 236 237 public static <T extends ExtensibleEnum> T[] values( 238 final Class<T> enumType) { 239 final List<T> values = byOrdinal(enumType); 240 241 return values.toArray((T[]) Array.newInstance(enumType, values.size())); 242 } 243 244 /** 245 * Returns the enum constant of the specified enum type with the specified 246 * name. The name must match exactly an identifier used to declare an enum 247 * constant in this type. (Extraneous whitespace characters are not 248 * permitted.) 249 * 250 * @param enumType the <tt>Class</tt> object of the enum type from which to 251 * return a constant 252 * @param name the name of the constant to return 253 * 254 * @return the enum constant of the specified enum type with the specified 255 * name 256 * 257 * @throws IllegalArgumentException if the specified enum type has no 258 * constant with the specified name, or the specified class object does not 259 * represent an enum type 260 * @throws NullPointerException if <tt>enumType</tt> or <tt>name</tt> is 261 * null 262 * @since 1.5 263 */ 264 public static <T extends ExtensibleEnum> T valueOf(final Class<T> enumType, 265 final String name) { 266 // IDEA says cast is useless; JDK5 javac says it is necessary 267 //noinspection RedundantCast 268 final T result = (T) byName(enumType).get(name); 269 270 if (null != result) 271 return result; 272 if (null == name) 273 throw new NullPointerException("Name is null"); 274 275 throw new IllegalArgumentException( 276 "No enum const " + enumType + '.' + name); 277 } 278 279 /** 280 * enum classes cannot have finalize methods. 281 */ 282 @Override 283 protected final void finalize() { } 284 285 protected Class<?> getDeclaringClass() { 286 final Class<?> enumType = getClass(); 287 288 return enumType.isAnonymousClass() 289 ? enumType.getSuperclass() 290 : enumType; 291 } 292 293 private static <T extends ExtensibleEnum<T>> Map<String, T> byName( 294 final Class<T> enumType) { 295 return (Map<String, T>) BY_NAME.get(enumType); 296 } 297 298 private static <T extends ExtensibleEnum<T>> List<T> byOrdinal( 299 final Class<T> enumType) { 300 return (List<T>) BY_ORDINAL.get(enumType); 301 } 302 }
Happy New Year!