2015-08-05 1 views
8

Когда я вызываю JTable#scrollRectToVisible, строка, которую я хочу показать, скрыта под заголовком в определенных ситуациях.JTable # scrollRectToVisible в сочетании с JSplitPlane показывает неправильную строку

Остальная часть этого вопроса имеет смысл только при использовании следующего кода. Это очень простая программа, которую я использую для иллюстрации проблемы. Он показывает интерфейс, содержащий JSplitPane, а в верхней части - некоторые кнопки управления, а нижняя часть содержит JTable, завернутый в JScrollPane (см. Скриншоты в нижней части этой публикации).

import java.awt.EventQueue; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 
import java.awt.event.ItemEvent; 
import java.awt.event.ItemListener; 

import javax.swing.*; 
import javax.swing.table.DefaultTableModel; 
import javax.swing.table.TableModel; 

public class DividerTest { 

    private final JSplitPane fSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); 
    private final JTable fTable; 
    private final JScrollPane fScrollPane; 

    private boolean fHideTable = false; 

    public DividerTest() { 
    fTable = new JTable(createTableModel(50)); 
    fScrollPane = new JScrollPane(fTable); 
    fSplitPane.setBottomComponent(fScrollPane); 
    fSplitPane.setTopComponent(createControlsPanel()); 
    fSplitPane.setDividerLocation(0.5); 
    } 

    private JPanel createControlsPanel(){ 
    JPanel result = new JPanel(); 
    result.setLayout(new BoxLayout(result, BoxLayout.PAGE_AXIS)); 

    final JCheckBox checkBox = new JCheckBox("Make table invisible before adjusting divider"); 
    checkBox.addItemListener(new ItemListener() { 
     @Override 
     public void itemStateChanged(ItemEvent e) { 
     fHideTable = checkBox.isSelected(); 
     } 
    }); 
    result.add(checkBox); 

    JButton upperRow = new JButton("Select row 10"); 
    upperRow.addActionListener(new ActionListener() { 
     @Override 
     public void actionPerformed(ActionEvent e) { 
     selectRowInTableAndScroll(10); 
     } 
    }); 
    result.add(upperRow); 

    JButton lowerRow = new JButton("Select row 45"); 
    lowerRow.addActionListener(new ActionListener() { 
     @Override 
     public void actionPerformed(ActionEvent e) { 
     selectRowInTableAndScroll(45); 
     } 
    }); 
    result.add(lowerRow); 

    JButton hideBottom = new JButton("Hide bottom"); 
    hideBottom.addActionListener(new ActionListener() { 
     @Override 
     public void actionPerformed(ActionEvent e) { 
     if (fHideTable) { 
      fScrollPane.setVisible(false); 
     } 
     fSplitPane.setDividerLocation(1.0); 
     } 
    }); 
    result.add(hideBottom); 

    JButton showBottom = new JButton("Show bottom"); 
    showBottom.addActionListener(new ActionListener() { 
     @Override 
     public void actionPerformed(ActionEvent e) { 
     fScrollPane.setVisible(true); 
     fSplitPane.setDividerLocation(0.5); 
     } 
    }); 
    result.add(showBottom); 

    return result; 
    } 

    private void selectRowInTableAndScroll(int aRowIndex){ 
    fTable.clearSelection(); 
    fTable.getSelectionModel().addSelectionInterval(aRowIndex, aRowIndex); 
    fTable.scrollRectToVisible(fTable.getCellRect(aRowIndex, 0, true)); 
    } 

    public JComponent getUI(){ 
    return fSplitPane; 
    } 

    private TableModel createTableModel(int aNumberOfRows){ 
    Object[][] data = new Object[aNumberOfRows][1]; 
    for(int i = 0; i < aNumberOfRows; i++){ 
     data[i] = new String[]{"Row" + i}; 
    } 
    return new DefaultTableModel(data, new String[]{"Column"}); 
    } 

    public static void main(String[] args) { 
    EventQueue.invokeLater(new Runnable() { 
     @Override 
     public void run() { 
     JFrame frame = new JFrame("Test frame"); 

     frame.getContentPane().add(new DividerTest().getUI()); 
     frame.pack(); 
     frame.setVisible(true); 
     frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); 
     } 
    }); 
    } 
} 

Нежелательное поведение

  • Выполнить код выше
  • Нажмите "Выбрать строку 10": строка 10 выбран и видимый
  • Нажмите "Выбрать строку 45": строка 45 выбирается и отображается
  • Нажмите кнопку «Скрыть нижний». Это отрегулирует делитель JSplitPane так, чтобы видна только верхняя панель.
  • Нажмите кнопку «Выбрать строку 10». Вы, конечно, ничего не видите, потому что таблица еще не виден
  • Нажмите кнопку «Показать нижнюю». Делитель настроен, но строка 10 скрыта под заголовком. Я ожидал, что он будет виден без необходимости прокрутки.

Wanted поведения

Повторите шаги с выше, но убедитесь, что установлен флажок «Сделать таблицу невидимой перед настройкой делителя». Это вызовет setVisible(false) на JScrollPane вокруг JTable, прежде чем спрятать нижнюю панель.

Делая это, в последнем шаге строка 10 будет видна как самая верхняя строка, что я и хочу. Я просто не хочу превращать scrollpane в невидимое: в моем реальном приложении делитель настраивается анимированным способом и, как таковой, вы хотите, чтобы таблица отображалась во время анимации.

Скриншоты

Нежелательные: строка 10 является невидимым после выполнения вышеуказанных шагов

Unwanted behavior screenshot

Требуются: строка 10 видна после выполнения вышеуказанных шагов

Wanted behavior screenshot

Environment

Я не думаю, что это будет иметь значение, но на всякий случай: я использую JDK7 на системе Linux.

+0

пожалуйста, Что выход, используя [Прямоугольник из JVievport и вместе с JTables row] (http://stackoverflow.com/a/7052751/714968), потому что игнорирует кеш красок, и что возвращает значение из Rectangle.contains(), потому что это может быть картина артефакта, так как в стороне разделитель должен быть завернутый в invokeLater во всех случаях (анимация, такая же, как требуется для анимации с/в JTree) – mKorbel

ответ

3

Это, кажется, вызвано способом, как JViewport обрабатывает scrollRectToVisible вызовы для случаев, когда его размер меньше желаемого прямоугольника. Он содержит (несколько нечеткий, но, вероятно, связанный) комментарий в JavaDocs:

Обратите внимание, что этот метод не будет прокручиваться за пределы допустимого окна просмотра; например, если contentRect больше, чем область просмотра, прокрутка будет ограничена границами окна просмотра.

Я не прошел полный код и не выполнил все математические операции и не проверял все случаи. Так предупреждение: Следующие объяснения содержат совершенно то же ручное размахивание. Но упрощенное описание того, что это означает для меня в данном конкретном случае:

Когда нижняя часть скрыта (путем установки расположения разделителя соответственно), то эта высота JScrollPane и ее JViewport равна 0. Теперь, когда запрашивается до scrollRectToVisible с прямоугольником, который имеет высоту 20 (для одной строки таблицы, например), то он заметит, что это не подходит. В зависимости от текущей позиции вида JViewport это может привести к тому, что видовое окно будет прокручиваться так, чтобы вид снизу нижнего этого прямоугольника.

(Вы можете наблюдать это: Перетащите местоположение делителя вручную, так что примерно половина из одной таблицы строк видно При нажатии на ссылке «Выбрать строку 45» кнопки, верхней половины строки будет. быть видимым. При нажатии на ссылку «Выбрать строку 10» кнопку, то ниже половина строки будет виден)

Один прагматичное решение здесь, что, казалось, работал для меня, чтобы убедиться, что он всегда будет прокручиваться так что видны верхний прямоугольника (даже если прямоугольник не совсем вписываются в видовое окно!).Как это:

private void selectRowInTableAndScroll(int aRowIndex) 
{ 
    fTable.clearSelection(); 
    fTable.getSelectionModel().addSelectionInterval(aRowIndex, aRowIndex); 

    Rectangle r = fTable.getCellRect(aRowIndex, 0, true); 
    r.height = Math.min(fScrollPane.getHeight(), r.height); 
    fTable.scrollRectToVisible(r); 
} 

Но я не могу обещать, что это будет иметь желаемый эффект для вас, когда анимация входит в игру ...

+0

(1+) хорошее наблюдение. Я фактически попытался установить высоту прямоугольника на высоту окна просмотра, что устранило проблему, когда панель прокрутки была скрыта, но, конечно же, она заставила выбранную строку всегда идти вверх, когда панель прокрутки была видна, At меньше всего я знаю, почему это сработало, когда панель прокрутки была скрыта, потому что высота прямоугольника соответствовала высоте окна просмотра. – camickr

+0

Было принято, но все еще задается вопросом, всегда ли это работает по желанию, даже когда задействована анимация .... – Marco13

2

Не совсем уверен, что делает scrollRectToVisible().

Возможно, вы сможете использовать метод JViewport.setViewPosition(...).

Rectangle r = fTable.getCellRect(aRowIndex, 0, true); 
Point p = new Point(r.x, r.y); 
fScrollPane.getViewport().setViewPosition(p); 

В этом случае выбранная строка всегда будет отображаться в верхней части окна просмотра (если возможно). Таким образом, окно просмотра всегда будет прокручиваться, если выбранная строка не указана вверху. Используя этот подход, если первая строка находится в верхней части окна просмотра, и вы выбираете 10-ю строку, окно просмотра будет прокручивать, чтобы отобразить 10-ю строку вверху.

Однако это поведение немного отличается от метода scrollRectToVisible(). При использовании метода scrollRectToVisible() окно просмотра, когда только прокручивается, когда прямоугольник не находится в видимой части окна просмотра. Используя этот подход, если первая строка находится в верхней части окна просмотра, и вы выбираете 10-ю строку, окно просмотра не будет прокручиваться, так как 10-я строка уже отображается в окне просмотра.

Не знаю, приемлемо ли это изменение функциональности или нет.

Примечание Если вы не хотите, чтобы видовом автоматически прокручивать при выборе строки, вы можете попробовать что-то вроде:

JViewport viewport = fScrollPane.getViewport(); 
Rectangle viewRect = viewport.getViewRect(); 
Rectangle r = fTable.getCellRect(aRowIndex, 0, true); 
Point p = new Point(r.x, r.y); 

if (! viewRect.contains(p)) 
    viewport.setViewPosition(p); 
Смежные вопросы