2016-01-29 4 views
1

Имеет ли JavaFX ListView свойство, которое возвращает, какие строки в настоящее время отображаются в области просмотра? Или есть способ присвоить EventHandler определенной строке, которая отправляет событие, когда его позиция в списке изменяется (из-за того, что новые объекты добавляются в список или объекты, удаляемые из списка)?JavaFX ListView: получить позицию строки

В сущности, мне нужно знать координаты x, y выбранной строки, чтобы узнать, выбрана ли выбранная строка из текущей области обзора. Потому что, если это произойдет, мне нужно вызвать метод ListView.scrollTo(), чтобы вернуть фокус в выбранную строку. И эта ситуация может возникнуть, если пользователь выбирает строку, тогда программа добавляет в список больше объектов, которые будут вытеснять любые существующие объекты, в том числе выбранные, из области просмотра.

Я попытался добавить ListChangeListener в наблюдаемый список ниже, но, похоже, я не могу применить его к определенной строке. И этот ListChangeListener, по-видимому, указывает только, была ли строка добавлена ​​/ удалена/обновлена ​​/ заменена/перестановлена ​​и т. Д. Этот обработчик/слушатель событий, похоже, не дает координаты x, y конкретной строки и не дает некоторую sort boolean указывает, что строка находится в текущей области просмотра объекта ListView.

observableList.addListener(new ListChangeListener<Object>() 
{ 
    @override 
    public void onChanged(ListChangeListener.Change<? extends Object> c) 
     { 
      while(c.next()) 
      { 
      if(c.wasAdded()) 
      { 
      } 
      else if(c.wasRemoved()) 
      { 
      } 
      ..... 
      } 
     } 
    } 

Я также вижу, что получение ListViews.getSelectionModel().getSelectedItem().getObject().getLayoutX() or LayoutY() по какой-то причине всегда возвращает 0,0 при х-у координат.

Единственная вещь, которая, как представляется, дает мне правильные координаты x-y, - это когда я нажимаю на строку и инициируется обратный вызов OnMouseClickEvent (MouseEvent event). Событие возвращает позицию x, y, где его щелкнул. Но, к сожалению, это не дает мне позицию x-y выбранной строки, если ListView динамически добавляет новые объекты в верхнюю часть списка, а позиция выбранной строки изменяется.

Любые идеи были бы весьма признательны.

+0

Нет API для того, что вы ищете. Третья партийная библиотека Томаса Микулы [Беглый] (https: // github.com/TomasMikula/Flowless) предлагает некоторую функциональность, похожую на то, что вы ищете (я думаю). –

+1

Обратите внимание, что в большинстве ваших вопросов вы путаете данные с представлением данных. Например: «Я попытался добавить« ListChangeListener »в« ObservableList »... этот обработчик событий, похоже, не дает координаты x, y конкретной строки». Дело в том, что элементы в вашем списке не имеют координат (* cells * имеют координаты, а не элементы): элемент может даже не иметь никакой соответствующей ячейки или какого-либо интерфейса. См. Документацию ['Cell] (http://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/Cell.html). Ничто из этого не предотвращает API 'getVisibleRowIndices()'; однако его просто нет –

+0

См. также http://stackoverflow.com/questions/18965871/how-to-get-position-of-an-item-in-listview-in-javafx –

ответ

1

Существует, насколько мне известно, API для этого. Добавление этой функции немного сложно, из-за механизма повторного использования соты (описано в Cell documentation).

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

Вот пример этого. Я поместил основную функциональность в обычную фабрику ячеек и выставил наблюдаемую карту. В качестве примера я создал ListView<Integer> со 100 элементами, а для демонстрации функциональности создан второй вид списка, содержимое которого - это элементы, у которых есть полностью отображаемые ячейки. Во втором представлении списка отображаются оба элемента и их границы (переведены в координаты в виде списка, содержащем ячейки).

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

import java.util.function.Function; 
import java.util.stream.Collectors; 

import javafx.application.Application; 
import javafx.beans.binding.Bindings; 
import javafx.beans.value.ChangeListener; 
import javafx.collections.FXCollections; 
import javafx.collections.MapChangeListener.Change; 
import javafx.collections.ObservableMap; 
import javafx.geometry.Bounds; 
import javafx.scene.Scene; 
import javafx.scene.control.ListCell; 
import javafx.scene.control.ListView; 
import javafx.scene.layout.HBox; 
import javafx.stage.Stage; 
import javafx.util.Callback; 

public class TrackCellsInListView extends Application { 

    @Override 
    public void start(Stage primaryStage) { 

     // main list view: 
     ListView<Integer> listView = new ListView<>(); 
     for (int i = 1 ; i <= 100; i++) { 
      listView.getItems().add(i); 
     } 

     // create a cell factory that tracks items which have cells and the bounds of their cells: 
     TrackingListCellFactory<Integer> cellFactory = new TrackingListCellFactory<>(i -> "Item "+i); 
     listView.setCellFactory(cellFactory); 

     // map from items with cells to bounds of the cells (in scene coordinates): 
     ObservableMap<Integer, Bounds> boundsByItem = cellFactory.getBoundsByItem(); 

     // list view which will display which items have cells that are completely displayed 
     // (i.e. whose bounds are completely contained in the list view bounds): 

     ListView<Integer> visibleCells = new ListView<>(); 

     // cell factory for second list cell displays item and its bounds (translated to 
     // list view coordinates): 
     visibleCells.setCellFactory(lv -> { 
      ListCell<Integer> cell = new ListCell<>(); 
      cell.textProperty().bind(Bindings.createStringBinding( 
       () -> { 
        if (cell.getItem()==null) { 
         return null ; 
        } 
        Bounds b = boundsByItem.get(cell.getItem()); 
        if (b == null) { 
         return null ; 
        } 
        Bounds bounds = listView.sceneToLocal(b); 
        return String.format("%d: [%.1f, %.1f, %.1f, %.1f]", cell.getItem(), 
          bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMaxY()); 
       }, cell.itemProperty(), boundsByItem)); 
      return cell ; 
     }); 

     // keep list of items in second list view up to date by observing map: 
     boundsByItem.addListener((Change<? extends Integer, ? extends Bounds> c) -> { 
      Bounds listBounds = listView.localToScene(listView.getBoundsInLocal()); 
      visibleCells.getItems().setAll(
        boundsByItem.keySet().stream() 
        .filter(s -> listBounds.contains(boundsByItem.get(s))) 
        .sorted() 
        .collect(Collectors.toList())); 
      System.out.println(); 
     }); 

     // usual UI setup: 
     Scene scene = new Scene(new HBox(5, listView, visibleCells)); 
     primaryStage.setScene(scene); 
     primaryStage.show(); 
    } 

    private static class TrackingListCellFactory<T> implements Callback<ListView<T>, ListCell<T>> { 

     // function for mapping item to text to display: 
     private Function<T,String> textFunction ; 

     // map items which have cells to bounds of those cell in scene coordinates: 
     private ObservableMap<T, Bounds> boundsByItem = FXCollections.observableHashMap(); 

     TrackingListCellFactory(Function<T,String> textFunction) { 
      this.textFunction = textFunction ; 
     } 

     // default text function just calls toString(): 
     TrackingListCellFactory() { 
      this(T::toString); 
     } 

     public ObservableMap<T, Bounds> getBoundsByItem() { 
      return boundsByItem ; 
     } 


     @Override 
     public ListCell<T> call(ListView<T> param) { 

      //create cell that displays text according to textFunction: 
      ListCell<T> cell = new ListCell<T>() { 
       @Override 
       protected void updateItem(T item, boolean empty) { 
        super.updateItem(item, empty); 
        setText(item == null ? null : textFunction.apply(item)); 
       } 
      }; 

      // add and remove from map when cell is reused for different item: 
      cell.itemProperty().addListener((obs, oldItem, newItem) -> { 
       if (oldItem != null) { 
        boundsByItem.remove(oldItem); 
       } 
       if (newItem != null) { 
        boundsByItem.put(newItem, cell.localToScene(cell.getBoundsInLocal())); 
       } 
      }); 

      // update map when bounds of item change 
      ChangeListener<Object> boundsChangeHandler = (obs, oldValue, newValue) -> { 
       T item = cell.getItem() ; 
       if (item != null) { 
        boundsByItem.put(item, cell.localToScene(cell.getBoundsInLocal())); 
       } 
      }; 

      // must update either if cell changes bounds, or if cell moves within scene (e.g.by scrolling): 
      cell.boundsInLocalProperty().addListener(boundsChangeHandler); 
      cell.localToSceneTransformProperty().addListener(boundsChangeHandler); 

      return cell; 
     } 

    } 

    public static void main(String[] args) { 
     launch(args); 
    } 
} 
Смежные вопросы