# JTable - Spaltenzellen nicht fokusierbar machen



## jherz (1. Apr 2009)

Hallo,

ich habe eine JTable mit Selektionskonfiguration
  setRowSelectionAllowed(true);
  setColumnSelectionAllowed(false);
In dieser Tabelle möchte ich verhindern dass Zellen in Spalten 1 und 2 den Fokus bekommen können. Also die gepunktete Linie weder per Mausklick noch per Tastaturbedienung erhalten können.

Irgendeine Idee wie sich das erreichen lässt? Ich hänge da seit drei Tagen dran und komme nicht dahin.

Grüße,
Jürgen


----------



## Ebenius (2. Apr 2009)

Ich dachte eigentlich schon, ich bekomme das ganze überhaupt nicht mehr hin. Das Hauptproblem besteht in der Navigation die natürlich in Actions gemacht wird die vom TableUI installiert werden. Alle Navigations-Aktionen (und das sind etliche) gehen davon aus, dass jede Zelle selektiert werden kann. Man müsste alle ersetzen um eine Einhundertprozentlösung zu erreichen... 

Außerdem habe ich den Ansatz getestet, einfach zwei Tabellen nebeneinander auf ein JPanel-Derivat zu legen, dieses auf dem Viewport eines JScrollPane zu installieren und alle Gemeinsamkeiten gleich zu setzen; also gleiche TableModel-Instanz, gleiche RowSorter-Instanz, etc. Dann könnte man der linken Tabelle ein ColumnModel geben das nur die nicht auswählbaren Spalten kennt und der rechten eines das die auswählbaren Spalten kennt. Funktioniert auch problemlos. Allerdings benötigt man dann ein drittes Column Model, das beide Column Models hintereinander hängt, bei moveColumn aufpasst, dass es die Columns aus dem ersten nicht ins zweite verschieben kann, usw. Dann muss man noch circa 250 Zeilen Quelltext aus der JTable-Klasse kopieren (configureEnclosingScrollPane() und unconfiger...) damit die JScrollPane auch so aussieht wie die *einer* Tabelle und von dem Panel *einen* JTableHeader bekommt. Und dann muss dieses JPanel-Derivat auch noch Scrollable implementieren. Das wär's dann auch schon. 

Nach einigen Tests und Gedankenspielen ist es dann wieder der Ansatz geworden den ich zuerst gehabt und fast verworfen hatte. Eine ColumnSelectionModel-Implementation die den LeadSelectionIndex prüft und verändert und eine ColumnModel-Implementation die das ColumnSelectionModel bzgl. der gesperrten Spalten aktualisieren kann. Ohne die Navigations-Aktionen anzufassen ist diese Lösung ebenfalls nicht einhundertprozentig. Wenn man bspw. die letzte Spalte sperrt, und mit TAB navigiert, dann gelangt man nicht in die nächste Zeile; das kann das *Column*SelectionModel ja auch nicht. Das SelectionModel rät eben nur, wohin es nun den LeadSelectionIndex verschieben soll, wenn der eigentlich gewünschte LeadIndex eine gesperrte Spalte trifft. Aber das Verhalten ist mindestens annehmbar. 

Die Klassennamen gefallen mir nicht sonderlich, die Nested Classes und Interfaces sollten aus der Testklasse raus, die Methodennamen sind auch nicht ganz in Ordnung und Javadoc ist auch zu spärlich gesäht... Aber für den Anfang ist's okay. Teste mal, ob die Lösung zu Deinem Problem passt und ob Du sie so oder ähnlich übernehmen willst.
[HIGHLIGHT="Java"]import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.beans.PropertyChangeEvent;
import java.util.*;

import javax.swing.*;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.TableColumn;

public class TableCellFocusTest {

  /**
   * Extension of the {@link DefaultTableColumnModel} to support denying focus
   * on specified columns. Note that the extension does not work if the
   * selection model is re-assigned an instance not implementing the
   * {@link ControlledLeadSelectionModel} interface.
   * 
   * @version $Revision$ as of $Date$
   * @author Sebastian Haufe
   */
  static class FocusSelectableColumnModel extends DefaultTableColumnModel {

    // -----------------------------------------------------------------------
    // Instance fields
    // -----------------------------------------------------------------------

    private final Set<Object> notFocusable =
          Collections.newSetFromMap(new WeakHashMap<Object, Boolean>());

    // -----------------------------------------------------------------------
    // Construction
    // -----------------------------------------------------------------------

    @Override
    protected ListSelectionModel createSelectionModel() {
      return new DefaultControlledLeadSelectionModel();
    }

    // -----------------------------------------------------------------------
    // Overridden Methods
    // -----------------------------------------------------------------------

    @Override
    public void moveColumn(int columnIndex, int newIndex) {
      super.moveColumn(columnIndex, newIndex);
      updateFocusableColumn(newIndex);
    }

    @Override
    public void addColumn(TableColumn column) {
      super.addColumn(column);
      updateFocusableColumn(getColumnIndex(column.getIdentifier()));
    }

    @Override
    public void removeColumn(TableColumn column) {
      super.removeColumn(column);
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
      super.propertyChange(evt);
      if ("identifier".equals(evt.getPropertyName())
            && notFocusable.remove(evt.getOldValue())) {
        notFocusable.add(evt.getNewValue());
        updateFocusableColumns();
      }
    }

    private void updateFocusableColumns() {
      final ListSelectionModel lsm = getSelectionModel();
      if (lsm instanceof ControlledLeadSelectionModel) {
        final ControlledLeadSelectionModel selModel =
              (ControlledLeadSelectionModel) lsm;
        final int columnCount = getColumnCount();
        for (int i = 0; i < columnCount; i++) {
          final TableColumn column = getColumn(i);
          final Object identifier = column.getIdentifier();
          selModel
                .setAcceptedLeadIndex(i, !notFocusable.contains(identifier));
          selModel.setSize(getColumnCount());
        }
      }
    }

    private void updateFocusableColumn(int index) {
      final ListSelectionModel lsm = getSelectionModel();
      if (lsm instanceof ControlledLeadSelectionModel) {
        final ControlledLeadSelectionModel selModel =
              (ControlledLeadSelectionModel) lsm;
        final TableColumn column = getColumn(index);
        final Object identifier = column.getIdentifier();
        selModel.setAcceptedLeadIndex(index, !notFocusable
              .contains(identifier));
        selModel.setSize(getColumnCount());
      }
    }

    // -----------------------------------------------------------------------
    // API extension
    // -----------------------------------------------------------------------

    /**
     * Set the given column focusable / unfocusable. If the column currently
     * has focus, removes the focus.
     * 
     * @param column the column
     * @param focusable {@code true} to allow focus, {@code false} to deny
     */
    public void setColumnFocusable(TableColumn column, boolean focusable) {
      final Object identifier = column.getIdentifier();
      final boolean changed;
      if (focusable) {
        changed = notFocusable.remove(identifier);
      } else {
        changed = notFocusable.add(identifier);
      }

      if (changed) {
        updateFocusableColumn(getColumnIndex(identifier));
      }
    }

    /**
     * Determines for the given column whether it is focusable.
     * 
     * @param column the column
     * @return {@code true} if allowed to be focused, {@code false} if denied
     */
    public boolean isColumnFocusable(TableColumn column) {
      return !notFocusable.contains(column.getIdentifier());
    }
  }

  /**
   * Selection Model extension to support blocking certain indices for lead
   * selection.
   * 
   * @version $Revision$ as of $Date$
   * @author Sebastian Haufe
   */
  interface ControlledLeadSelectionModel extends ListSelectionModel {

    /**
     * Returns whether the given index is accepted as lead selection index
     * within {@code this} selection model.
     * 
     * @param index the index to check
     * @return {@code true} if accepted
     */
    boolean isAcceptedLeadIndex(int index);

    /**
     * Sets whether the given index is accepted as lead selection index within
     * {@code this} selection model.
     * 
     * @param index the index
     * @param accepted
     */
    void setAcceptedLeadIndex(int index, boolean accepted);

    /**
     * Sets the size of the selection model. The size is necessary to
     * automatically determine an alternative lead index, if the original lead
     * index is not accepted by {@link #isAcceptedLeadIndex(int)}.
     * 
     * @param size the size
     */
    void setSize(int size);

    /**
     * Gets the size of the selection model.
     * 
     * @return the size
     * @see #setSize(int)
     */
    int getSize();
  }

  /**
   * Default implementation of the {@link ControlledLeadSelectionModel}
   * interface; automatically changing the lead index to an alternative index,
   * if the desired lead index is not accepted.
   * <p>
   * Note that the algorithm to determine the alternative lead index is a
   * trying to figure out the desired behavior from the, seen from the point
   * of view of a user navigating with the cursor keys.
   * 
   * @version $Revision$ as of $Date$
   * @author Sebastian Haufe
   */
  static class DefaultControlledLeadSelectionModel
    extends DefaultListSelectionModel implements ControlledLeadSelectionModel {

    private final BitSet leadBlocked = new BitSet();
    private int size = 0;

    // -----------------------------------------------------------------------
    // Implementing LeadSelectableSelectionModel
    // -----------------------------------------------------------------------

    public boolean isAcceptedLeadIndex(int index) {
      return !leadBlocked.get(index);
    }

    public void setAcceptedLeadIndex(int index, boolean accepted) {
      leadBlocked.set(index, !accepted);
      if (getLeadSelectionIndex() == index && !accepted) {
        setLeadSelectionIndex(getLeadSelectionIndex());
      }
    }

    public void setSize(int size) {
      this.size = size;
    }

    public int getSize() {
      return size;
    }

    // -----------------------------------------------------------------------
    // Fix Index according to lead selectable rules
    // -----------------------------------------------------------------------

    private int findAcceptableLeadIndex(int index) {
      if (index == -1) {
        return -1;
      }
      final int oldLead = getLeadSelectionIndex();
      final int dLead = index - oldLead;
      final int maxIndex = getSize() - 1;

      int result;
      switch (dLead) {
      // trivial, occurs on usual cursor navigation
      case 1:
        result = findForward(index, maxIndex);
        if (result == -1) {
          result = findForward(0, index);
        }
        break;
      case -1:
        result = findBackward(index, 0);
        if (result == -1) {
          result = findBackward(maxIndex, index);
        }
        break;
      default:
        if (oldLead == -1 || dLead >= 0) {
          result = findForward(index, maxIndex);
          if (result == -1) {
            result = findBackward(index, 0);
          }
        } else {
          result = findBackward(index, 0);
          if (result == -1) {
            result = findForward(index, maxIndex);
          }
        }
        break;
      }

      return result != -1 || oldLead == -1
            ? result
            : isAcceptedLeadIndex(oldLead) ? oldLead : -1;
    }

    private int findForward(int index, final int maxIndex) {
      for (int i = index; i <= maxIndex; i++) {
        if (isAcceptedLeadIndex(i)) {
          return i;
        }
      }

      return -1;
    }

    private int findBackward(int index, int minIndex) {
      for (int i = index; i >= minIndex; i--) {
        if (isAcceptedLeadIndex(i)) {
          return i;
        }
      }

      return -1;
    }

    // -----------------------------------------------------------------------
    // Overridden Methods
    // -----------------------------------------------------------------------

    @Override
    public void setLeadSelectionIndex(int index) {
      super.setLeadSelectionIndex(findAcceptableLeadIndex(index));
    }

    @Override
    public void moveLeadSelectionIndex(int leadIndex) {
      super.moveLeadSelectionIndex(findAcceptableLeadIndex(leadIndex));
    }

    @Override
    public void removeSelectionInterval(int index0, int index1) {
      super.removeSelectionInterval(index0, findAcceptableLeadIndex(index1));
    }

    @Override
    public void addSelectionInterval(int index0, int index1) {
      super.addSelectionInterval(index0, findAcceptableLeadIndex(index1));
    }

    @Override
    public void setSelectionInterval(int index0, int index1) {
      super.setSelectionInterval(index0, findAcceptableLeadIndex(index1));
    }

    @Override
    public void removeIndexInterval(int index0, int index1) {
      final int gap = index1 - index0 + 1;
      final int last = getSize() - gap;
      for (int i = index1; i <= last; i++) {
        leadBlocked.set(i, leadBlocked.get(i + gap));
      }
      size -= gap;

      super.removeIndexInterval(index0, index1);
    }

    @Override
    public void insertIndexInterval(int index, int length, boolean before) {
      final int insMinIndex = (before) ? index : index + 1;
      final int maxIndex = getSize() - 1;
      for (int i = maxIndex; i >= insMinIndex; i--) {
        leadBlocked.set(i + length, leadBlocked.get(i));
      }
      size += length;

      super.insertIndexInterval(index, length, before);
    }
  }

  // -------------------------------------------------------------------------
  // Program Entry Point
  // -------------------------------------------------------------------------

  /**
   * Test main method.
   * 
   * @param args ignored
   */
  public static void main(String[] args) {
    final JTable table1 = new JTable(20, 10);
    table1.setAutoCreateRowSorter(true);

    final FocusSelectableColumnModel columnModel =
          new FocusSelectableColumnModel();
    table1.setColumnModel(columnModel);
    table1.createDefaultColumnsFromModel();

    final DefaultTableCellRenderer renderer = new DefaultTableCellRenderer() {

      @Override
      public Component getTableCellRendererComponent(
            JTable table,
            Object value,
            boolean isSelected,
            boolean hasFocus,
            int row,
            int column) {
        super.getTableCellRendererComponent(table, value, isSelected,
              hasFocus, row, column);
        setBackground(Color.LIGHT_GRAY);
        return this;
      }
    };

    int[] blocked = { 0, 2, 8, 9 };
    for (int i : blocked) {
      final TableColumn col = columnModel.getColumn(i);
      columnModel.setColumnFocusable(col, false);
      col.setCellRenderer(renderer);
    }

    final JPanel mainPanel = new JPanel(new BorderLayout(6, 6));
    mainPanel.add(new JScrollPane(table1), BorderLayout.CENTER);

    final JFrame f = new JFrame("Table Cell Focus Fun");
    f.setContentPane(mainPanel);
    f.pack();
    f.setLocationRelativeTo(null);
    f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    f.setVisible(true);
  }
}[/HIGHLIGHT]
Gute Nacht.
Ebenius


----------



## jherz (2. Apr 2009)

Ebenius hat gesagt.:


> Ich dachte eigentlich schon, ich bekomme das ganze überhaupt nicht mehr hin.


Wow, da scheinst Du Dich ja wirklich mächtig reingehängt zu haben. Und das nur auf meine knapp spezifizierte Anfrage hin. Da muß ich sagen Hut ab und herzlichen Dank.



> Das Hauptproblem besteht in der Navigation die natürlich in Actions gemacht wird die vom TableUI installiert werden. Alle Navigations-Aktionen (und das sind etliche) gehen davon aus, dass jede Zelle selektiert werden kann. Man müsste alle ersetzen um eine Einhundertprozentlösung zu erreichen...


Ich habe schon befürchtet dass es da keine zentrale 

Außerdem habe ich den Ansatz getestet, einfach zwei Tabellen nebeneinander auf ein JPanel-Derivat zu legen [...] Das wär's dann auch schon. [/QUOTE]
Ach so, also nicht der Rede wert. Mannomann, mir ist schon beim Lesen schwindlig geworden. 



> Nach einigen Tests und Gedankenspielen ist es dann wieder der Ansatz geworden den ich zuerst gehabt und fast verworfen hatte. Eine ColumnSelectionModel-Implementation die den LeadSelectionIndex prüft und verändert und eine ColumnModel-Implementation die das ColumnSelectionModel bzgl. der gesperrten Spalten aktualisieren kann. Ohne die Navigations-Aktionen anzufassen ist diese Lösung ebenfalls nicht einhundertprozentig.


Ich habe in meinem Test jetzt keine Navigation gefunden die nicht funktioniert. Dabei kommen zwei wichtige Einschränkungen zum Tragen die ich in meiner Frage nicht erwähnt hatte weil ich Deinen Einsatz unterschätzt hatte.
1. Tab/Shift-Tab habe ich sowieso umkonfiguriert so dass es aus der Tabelle springt.
2. Mit Spalte 0 gibt es sowieso nur eine Spalte die überhaupt selektierbar sein soll, also fiele findAcceptableLeadIndex() in meinem Fall sowieso sehr simpel aus.

Aber auch als Allgemeinlösung gefällt mir Deine Arbeit.

Übrigens apropos Navigation, laut BasicTableUI gibt es eine Action mit dem Key "clearSelection" der JTable.clearSelection() aufruft. Ich bin bisher noch nicht draufgekommen wie ich zu einem bekannten actionMapKey einen KeyStroke ermitteln kann. Geht das? Und unabhängig davon, weißt Du welcher Shortcut clearSelection in einer Tabelle ausführt?

Jürgen


----------



## Ebenius (2. Apr 2009)

jherz hat gesagt.:


> Aber auch als Allgemeinlösung gefällt mir Deine Arbeit.


Man sieht an meinem Code hoffentlich wenigstens in Ansätzen, dass er darauf vorbereitet ist, im Bedarfsfall mit geringem Aufwand zur allgemeinen Lösung zu mutieren. 



jherz hat gesagt.:


> [...] weißt Du welcher Shortcut clearSelection in einer Tabelle ausführt?


Hängt vom TableUI ab. Im Metal L&F (BasicTableUI) ist diese Aktion nicht per KeyBinding verfügbar. In meinen Tabellen hab ich die Aktion aber im Kontextmenü.



jherz hat gesagt.:


> Ich bin bisher noch nicht draufgekommen wie ich zu einem bekannten actionMapKey einen KeyStroke ermitteln kann. Geht das?


Natürlich, wenn auch nicht so performant und einfach wie andersherum (was ja der Standard-Anwendungsfall ist). Habe eben meine Utilities erweitert; schenk ich Dir.  [HIGHLIGHT="Java"]/**
 * Get the key strokes bound to the given action key from the given input
 * map. The parent input maps' bindings are not included.
 * 
 * @param im the input map to investigate
 * @param actionKey the action key bound
 * @return the array of keys, not {@code null}
 * @throws NullPointerException if the input map or the action key is
 *           {@code null}
 */
public static KeyStroke[] boundKeys(InputMap im, Object actionKey) {
  return boundKeys(im, actionKey, false);
}

/**
 * Convenience method getting one of the given component's input maps. Get
 * the key strokes bound to the given action key from that input map. The
 * parent input maps' bindings are not included.
 * 
 * @param component the component to get the input map from
 * @param condition the kind of input map to get
 * @param actionKey the action key bound
 * @return the array of keys, not {@code null}
 * @throws NullPointerException if the component or the action key is
 *           {@code null}
 * @see JComponent#getInputMap(int)
 */
public static KeyStroke[] boundKeys(
      JComponent component,
      int condition,
      Object actionKey) {
  return boundKeys(component.getInputMap(condition), actionKey, false);
}

/**
 * Get the key strokes bound to the given action key from the given input
 * map. The parent input maps' bindings are included.
 * 
 * @param im the input map to investigate
 * @param actionKey the action key bound
 * @return the array of keys, not {@code null}
 * @throws NullPointerException if the input map or the action key is
 *           {@code null}
 */
public static KeyStroke[] allBoundKeys(InputMap im, Object actionKey) {
  return boundKeys(im, actionKey, true);
}

/**
 * Convenience method getting one of the given component's input maps. Get
 * the key strokes bound to the given action key from that input map. The
 * parent input maps' bindings are included.
 * 
 * @param component the component to get the input map from
 * @param condition the kind of input map to get
 * @param actionKey the action key bound
 * @return the array of keys, not {@code null}
 * @throws NullPointerException if the component or the action key is
 *           {@code null}
 * @see JComponent#getInputMap(int)
 */
public static KeyStroke[] allBoundKeys(
      JComponent component,
      int condition,
      Object actionKey) {
  return boundKeys(component.getInputMap(condition), actionKey, true);
}

/**
 * Get the key strokes from the given input map, bound to the given action
 * key.
 * 
 * @param im the input map to investigate
 * @param actionKey the action key bound
 * @param includeParents whether or not to include all input map's parents
 * @return the array of keys, not {@code null}
 * @throws NullPointerException if the input map or the action key is
 *           {@code null}
 */
private static KeyStroke[] boundKeys(
      InputMap im,
      Object actionKey,
      boolean includeParents) {

  /* special handling for first match, as many actions are mapped to one
     key stroke only */
  KeyStroke firstMatch = null;
  Collection<KeyStroke> matches = null;
  do {
    final KeyStroke[] keys = im.keys();
    if (keys == null) {
      continue;
    }
    for (final KeyStroke keyStroke : keys) {
      final Object ak = im.get(keyStroke);
      if (actionKey == ak || actionKey != null && actionKey.equals(ak)) {
        if (firstMatch == null) {
          firstMatch = keyStroke;
        } else if (matches == null) {
          matches = new HashSet<KeyStroke>(7);
          matches.add(firstMatch);
          matches.add(keyStroke);
        } else {
          matches.add(keyStroke);
        }
      }
    }
  } while (includeParents && (im = im.getParent()) != null);

  return firstMatch == null ? new KeyStroke[0] : matches == null
        ? new KeyStroke[] { firstMatch }
        : matches.toArray(new KeyStroke[matches.size()]);
}[/HIGHLIGHT]
Ebenius


----------



## jherz (9. Apr 2009)

Ebenius hat gesagt.:


> Natürlich, wenn auch nicht so performant und einfach wie andersherum (was ja der Standard-Anwendungsfall ist). Habe eben meine Utilities erweitert; schenk ich Dir.



Vielen Dank. Das muß nicht performant sein, ist ja nur zum Nachsehen beim Programmieren.

Gruß,
Jürgen


----------



## André Uhres (17. Apr 2009)

jherz hat gesagt.:


> In dieser Tabelle möchte ich verhindern dass Zellen in Spalten 1 und 2 den Fokus bekommen können. Also die gepunktete Linie weder per Mausklick noch per Tastaturbedienung erhalten können.




```
table = new JTable() {
    private Border TRANSPARENT_BORDER = new LineBorder(new Color(0, 0, 0, 0));
    @Override
    public Component prepareRenderer(
            final TableCellRenderer renderer, final int row, final int column) {
        Component c = super.prepareRenderer(renderer, row, column);
        if (column < 2) {
            ((JComponent) c).setBorder(TRANSPARENT_BORDER);
        }
        return c;
    }
};
```


----------



## Ebenius (17. Apr 2009)

André, damit bekommst Du aber ein Problem, wenn Du in der Tabelle navigierst. Du kannst in die "blinden" Zellen navigieren, hast kein Feedback mehr über den Fokus. Das ist kurz gesagt pfuiba. 

[size=-2]... ganz davon abgesehen, dass es nicht in jedem L&F sinnvoll aussehen wird ...[/size]

Ebenius


----------



## André Uhres (17. Apr 2009)

Ebenius hat gesagt.:


> Du kannst in die "blinden" Zellen navigieren


Ich weiss, aber im Originalpost ist Navigation kein Thema, nur die Darstellung. Allerdings ist es gewöhnlich umgekehrt.


----------



## Ebenius (17. Apr 2009)

André Uhres hat gesagt.:


> Ich weiss, aber im Originalpost ist Navigation kein Thema, nur die Darstellung. Allerdings ist es gewöhnlich umgekehrt.


Damit hast Du zwar Recht... Das Problem ist aber, dass die Anforderung des Entwicklers nicht viel mit den Erwartungen des Benutzers an ein GUI zu tun haben. Ich baue gute GUIs, da ist mir die ausdrückliche Anforderung des Entwicklers eher egal. 

BTW: Den Rand mit _0,0,0,0_ zu initialisieren sieht zumindest mit Metal-L&F komisch aus. Da sollte der Rand _1,1,1,1_ haben. Mit Nimbus brauchst Du auf der LINE_START-Seite (also links bei LTR, rechts bei RTL) mehr als ein Pixel; 2 oder 3, weiß nicht genau. Geschickter wäre es also, die Insets der vom Original-Renderer erzeugten Renderer-Komponente zu nehmen und damit einen EmptyBorder zu initialisieren.

Ebenius


----------



## André Uhres (17. Apr 2009)

Ebenius hat gesagt.:


> Damit hast Du zwar Recht... Das Problem ist aber, dass die Anforderung des Entwicklers nicht viel mit den Erwartungen des Benutzers an ein GUI zu tun haben. Ich baue gute GUIs, da ist mir die ausdrückliche Anforderung des Entwicklers eher egal.
> 
> BTW: Den Rand mit _0,0,0,0_ zu initialisieren sieht zumindest mit Metal-L&F komisch aus. Da sollte der Rand _1,1,1,1_ haben. Mit Nimbus brauchst Du auf der LINE_START-Seite (also links bei LTR, rechts bei RTL) mehr als ein Pixel; 2 oder 3, weiß nicht genau. Geschickter wäre es also, die Insets der vom Original-Renderer erzeugten Renderer-Komponente zu nehmen und damit einen EmptyBorder zu initialisieren.
> 
> Ebenius


Grundsätzlich stimme ich dir zu. Man könnte aber auch sagen: Das Problem ist aber, dass die Anforderung des Benutzers nicht viel mit den Erwartungen des Entwicklers an ein GUI zu tun haben.
Beim Rand habe ich nicht die Grösse visiert, sondern die Farbe.


----------



## Ebenius (17. Apr 2009)

André Uhres hat gesagt.:


> Grundsätzlich stimme ich dir zu. Man könnte aber auch sagen: Das Problem ist aber, dass die Anforderung des Benutzers nicht viel mit den Erwartungen des Entwicklers an ein GUI zu tun haben.


Ein einfaches "Du hast Recht" hätte genügt. 



André Uhres hat gesagt.:


> Beim Rand habe ich nicht die Grösse visiert, sondern die Farbe.


Stimmt, hab ich zu flüchtig hingesehen. In dem Fall stimmt's für Metal-L&F, für Nimbus aber nicht.

Ebenius


----------



## André Uhres (17. Apr 2009)

Ebenius hat gesagt.:


> Ein einfaches "Du hast Recht" hätte genügt.
> Stimmt, hab ich zu flüchtig hingesehen. In dem Fall stimmt's für Metal-L&F, für Nimbus aber nicht.


Manchmal ist das Leben eben kompliziert.
Daß Nimbus keine Transparenz unterstützt, scheint mir wieder so ein Kuriosum zu sein.


----------



## Ebenius (17. Apr 2009)

André Uhres hat gesagt.:


> Daß Nimbus keine Transparenz unterstützt, scheint mir wieder so ein Kuriosum zu sein.


Ne, das hast Du falsch verstanden. Nimbus hat im Standard-TableCellRenderer einfach links mehr Platz als ein Pixel. Deswegen passt die 1-Pixel-Breite des LineBorders nicht. Alpha-Kanal funktioniert genauso wie bei Metal.

Ebenius


----------



## André Uhres (17. Apr 2009)

Ebenius hat gesagt.:


> Ne, das hast Du falsch verstanden. Nimbus hat im Standard-TableCellRenderer einfach links mehr Platz als ein Pixel. Deswegen passt die 1-Pixel-Breite des LineBorders nicht. Alpha-Kanal funktioniert genauso wie bei Metal.


Ach so, naja ich halte eh nicht so viel von all den exotischen Lafs. Sie machen meist mehr Probleme als sie nützen.


----------



## Ebenius (17. Apr 2009)

André Uhres hat gesagt.:


> Ach so, naja ich halte eh nicht so viel von all den exotischen Lafs. Sie machen meist mehr Probleme als sie nützen.


Der Trick ist, von Anfang an an die Exoten zu denken. Ein Mac-Java hat von haus aus nix anderes als das entsprechende Mac-L&F. Wenn man von vornherein seine Apps gegen die üblichen Verdächtigen testet, dann geht's mit den anderen um so wahrscheinlicher auch klar.

Ebenius


----------



## André Uhres (17. Apr 2009)

Ebenius hat gesagt.:


> Der Trick ist, von Anfang an an die Exoten zu denken.


Das überlass ich dann den Pferden, die haben grössere Köpfe, hi.. hahaha.


----------

