Хобрук: Ваш путь к мастерству в программировании

Автоматическое изменение размера фигуры, назначенной ячейке в качестве изображения в JavaFx

На работе я создал TableView, в котором определенные ячейки должны одновременно мигать одним цветом и другим. Это относительно легко сделать, используя Rectangles, FillTransitions и ParallelTransition, как показано в игрушечном примере ниже. После назначения прямоугольника FillTransition я устанавливаю графику TableCell в прямоугольник. Затем мне просто нужно было добавить/удалить FillTransition из ParallelTransition в зависимости от того, должна ли ячейка мигать.

Однако область, в которой у меня были большие трудности, заключалась в том, чтобы найти способ масштабировать прямоугольник до размера TableCell, содержащего его как графику. Проблема заключалась в том, что TableCell всегда изменял свой размер, чтобы между его границами и границами прямоугольника оставалось пустое пространство.

Мне пришлось решить это очень утомительным и окольным путем: мне пришлось вызвать setFixedCellSize, чтобы зафиксировать высоту ячейки таблицы до любой высоты моего прямоугольника, переместить прямоугольник вверх и влево методом проб и ошибок, вызвав его setTranslateX/ Y и установите минимальную ширину и минимальную высоту столбца немного меньше, чем ширина и высота моего прямоугольника. Это решило проблему, но я бы надеялся на что-то менее утомительное и раздражающее.

Я бы предположил, что этого можно избежать, выполнив одно или несколько из следующих действий с ячейкой:

  • Вызов setScaleShape(true)

  • Вызов setContentDisplay(ContentDisplay.GRAPHIC_ONLY)

  • Настройка стиля CSS ячейки для включения «-fx-scale-shape: true»

К сожалению, ни один из них не имел никакого заметного эффекта...

Мой вопрос состоит из трех частей:

  1. Есть ли лучший способ определить размер формы, назначенной в качестве изображения для ячейки, чтобы заполнить границы ячейки?

  2. Почему ни один из трех вышеперечисленных методов не имеет никакого эффекта в моем случае и какова их фактическая цель? Применяются ли они только для формы, назначенной с помощью setShape(), а не setGraphic()?

  3. Существуют ли какие-либо законные причины, по которым JavaFx не поддерживает установку предпочтительной ширины или высоты узлов, кроме тех, которые являются подклассами Region? Авторазмер кажется чем-то, что должно быть универсальным для всех узлов в иерархии, и кажется интуитивно понятным, что любой родительский узел должен иметь возможность при необходимости диктовать размер своих дочерних узлов.

import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javafx.animation.Animation;
import javafx.animation.FillTransition;
import javafx.animation.ParallelTransition;
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.Border;
import javafx.scene.layout.BorderStroke;
import javafx.scene.layout.BorderStrokeStyle;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;


public class FlashingPriorityTable extends Application {

  public static void main(String args[]) {
    FlashingPriorityTable.launch();
  }

  @Override
  public void start(Stage primaryStage) throws Exception {

    // periodically add prioritized items to an observable list
    final ObservableList<PItem> itemList = FXCollections.observableArrayList();
    class ItemAdder {
      private int state, count = 0; private final int states = 3;
      public synchronized void addItem() {
        state = count++ % states;
        PItem item;
        if(state == 0)
          item = new PItem(Priority.LOW, count, "bob saget");
        else if(state == 1)
          item = new PItem(Priority.MEDIUM, count, "use the force");
        else
          item = new PItem(Priority.HIGH, count, "one of us is in deep trouble");
        itemList.add(item);
      }
    };
    final ItemAdder itemAdder = new ItemAdder();
    Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
        () -> itemAdder.addItem(),
        0, // initial delay
        1, // period
        TimeUnit.SECONDS); // time unit

    // set up a table view bound to the observable list
    final TableColumn<PItem, Priority> priCol = new TableColumn<>("Priority");
    priCol.setCellValueFactory(new PropertyValueFactory<PItem, Priority>("priority"));
    priCol.setCellFactory((col) -> new PriorityCell()); // create a blinking cell
    priCol.setMinWidth(50);
    priCol.setMaxWidth(50);

    final TableColumn<PItem, Integer> indexCol = new TableColumn<>("Index");
    indexCol.setCellValueFactory(new PropertyValueFactory<PItem, Integer>("index"));
    indexCol.setCellFactory((col) -> makeBorderedTextCell());

    final TableColumn<PItem, String> descriptionCol = new TableColumn<>("Description");
    descriptionCol.setCellValueFactory(new PropertyValueFactory<PItem, String>("description"));
    descriptionCol.setCellFactory((col) -> makeBorderedTextCell());
    descriptionCol.setMinWidth(300);

    final TableView<PItem> table = new TableView<>(itemList);
    table.getColumns().setAll(priCol, indexCol, descriptionCol);
    table.setFixedCellSize(25);

    // display the table view
    final Scene scene = new Scene(table);
    primaryStage.setScene(scene);
    primaryStage.show();
  }

  // render a simple cell text and border
  private <T> TableCell<PItem, T> makeBorderedTextCell() {
    return new TableCell<PItem, T>() {
      @Override protected void updateItem(T item, boolean empty) {
        super.updateItem(item, empty);
        if(item == null || empty) {
          setText(null);
        } else {
          setBorder(new Border(new BorderStroke(Color.GREEN, BorderStrokeStyle.SOLID, null, null)));
          setText(item.toString());
        }
      }
    };
  }

  /* for cells labeled as high priority, render an animation that blinks (also include a border) */
  public static class PriorityCell extends TableCell<PItem, Priority> {
    private static final ParallelTransition pt = new ParallelTransition();
    private final Rectangle rect = new Rectangle(49.5, 24);
    private final FillTransition animation = new FillTransition(Duration.millis(100), rect);
    public PriorityCell() {
      rect.setTranslateX(-2.75);
      rect.setTranslateY(-2.7);
      animation.setCycleCount(Animation.INDEFINITE); animation.setAutoReverse(true); }
    @Override
    protected void updateItem(Priority priority, boolean empty) {
      super.updateItem(priority, empty);
      if(priority == null || empty) {
        setGraphic(null);
        return;
      }
      setGraphic(rect);
      setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
      setBorder(new Border(new BorderStroke(Color.GREEN, BorderStrokeStyle.SOLID, null, null)));
      if(priority == Priority.HIGH) {
        if(!pt.getChildren().contains(animation)) {
          animation.setFromValue(Color.BLACK);
          animation.setToValue(priority.getColor());
          animation.setShape(rect);
          pt.getChildren().add(animation);
          pt.stop(); pt.play();
        }
      } else {
        if(pt.getChildren().contains(animation)) {
          pt.getChildren().remove(animation);
          pt.stop(); pt.play();
        }
        rect.setFill(priority.getColor());
      }
    }
  }

  /* an item that has a priority assigned to it */
  public static class PItem {
    private ObjectProperty<Priority> priority = new SimpleObjectProperty<>();
    private IntegerProperty index = new SimpleIntegerProperty();
    private StringProperty description = new SimpleStringProperty();

    public PItem(Priority priority, Integer index, String description) {
      setPriority(priority); setIndex(index); setDescription(description);
    }

    public void setPriority(Priority priority_) { priority.set(priority_); }
    public Priority getPriority() { return priority.get(); }

    public void setIndex(int index_) { index.set(index_); }
    public Integer getIndex() { return index.get(); }

    public void setDescription(String description_) { description.set(description_); }
    public String getDescription() { return description.get(); }
  }

  /* a priority */
  public enum Priority {
    HIGH(Color.RED), MEDIUM(Color.ORANGE), LOW(Color.BLUE);
    private final Color color;
    private Priority(Color color) { this.color = color; }
    public Color getColor() { return color; }
  }
}

31.01.2015

Ответы:


1

Касательно:

TableCell всегда будет изменять свой размер, чтобы между его границами и границами прямоугольника было пустое пространство.

Это связано с тем, что ячейка по умолчанию имеет отступ 2 пикселя, согласно modena.css:

.table-cell {
    -fx-padding: 0.166667em; /* 2px, plus border adds 1px */
    -fx-cell-size: 2.0em; /* 24 */
}

Один простой способ избавиться от этого пустого пространства — просто переопределить его:

@Override
protected void updateItem(Priority priority, boolean empty) {
    super.updateItem(priority, empty);
    ...
    setGraphic(rect);
    setStyle("-fx-padding: 0;");
    ...
}

Следующая проблема, которую вы также упомянули, — это автоматическое изменение размера. Согласно JavaDoc, для Node.isResizable():

Если этот метод возвращает true, то родитель изменит размер узла (в идеале в пределах его диапазона размеров), вызвав node.resize(width,height) во время прохода компоновки. Все регионы, элементы управления и WebView являются классами с изменяемым размером, которые зависят от родительского изменения их размера во время макета после применения всей информации о размерах и стилях CSS. Если этот метод возвращает false, то родитель не может изменить его размер во время компоновки (resize() не работает) и должен вернуть свои layoutBounds для минимального, предпочтительного и максимального размеров. Размер группы, текста и всех фигур нельзя изменить, и, следовательно, приложение определяет их размер, устанавливая соответствующие свойства (например, ширину/высоту для прямоугольника, текст в тексте и т. д.). Узлы, размеры которых не изменяются, по-прежнему могут быть перемещены во время компоновки.

Понятно, что размер Rectangle нельзя изменить, но это не значит, что вы не можете изменить его размер: если макет не делает этого за вас, вам нужно позаботиться об этом.

Таким образом, одним из простых решений может быть привязка размеров прямоугольника к размерам ячейки (минус 2 пикселя для границ ячейки):

private final Rectangle rect = new Rectangle();

@Override
protected void updateItem(Priority priority, boolean empty) {
    super.updateItem(priority, empty);
    if(priority == null || empty) {
      setGraphic(null);
      return;
    }
    setGraphic(rect);
    setStyle("-fx-padding: 0;");
    rect.widthProperty().bind(widthProperty().subtract(2));
    rect.heightProperty().bind(heightProperty().subtract(2));
    ...
}

Обратите внимание, что вам не нужно будет переводить прямоугольник, и вам не нужно будет фиксировать размер ячейки или ширину столбца, если только вы не хотите придать ему фиксированный размер.

Также обратите внимание, что setShape() предназначено для изменения формы ячейки, которая по умолчанию уже является прямоугольником.

Это может ответить на ваши первые два вопроса. Что касается третьего, иногда вы хотите, чтобы узлы всегда изменяли размер... но если бы это было так, у нас была бы противоположная проблема, пытаясь сохранить их ограниченными...

31.01.2015
  • Будучи новичком в javafx, я не был знаком с modena.css до того, как вы упомянули об этом. Это определенно хороший файл, о котором нужно знать. Просто делать то, что вы сказали, действительно помогло! В сочетании с пониманием CSS Javafx, безусловно, очень мощен, даже если некоторые API иногда могут показаться немного ограниченными! Если я не получу другого ответа завтра, я отмечу ваш как ответ. 01.02.2015
  • Новые материалы

    Введение в контекст React
    В этом посте мы поговорим о Context API, который был представлен в React 16, и о том, как вы можете их использовать. Что такое контекст? Глядя на определение из react docs , оно..

    Шлюз с лицензией OSS, совместимый с Apollo Federation v2, появится в WunderGraph
    Сегодня мы рады сообщить, что мы сотрудничаем с поддерживаемой YC Tailor Technologies, Inc. для внедрения Apollo Federation v2. Реализация будет лицензирована MIT (Engine) и Apache 2.0..

    Это оно
    Ну, я официально уволился с работы! На этой неделе я буду лихорадочно выполнять последние требования Думающего , чтобы я мог сосредоточиться на поиске работы. Что именно это значит?..

    7 полезных библиотек JavaScript, которые вы должны использовать в своем следующем проекте
    Усильте свою разработку JavaScript Есть поговорка «Не нужно изобретать велосипед». Библиотеки — лучший тому пример. Это поможет вам написать сложные и трудоемкие функции простым способом...

    Базовое руководство по переносу концепций обучения в глубокое обучение
    Обзор По мере того, как машинное обучение становится все более мощным и продвинутым, модели, обеспечивающие эту расширенную возможность, становятся все больше и начинают требовать огромного..

    C в C.R.U.D с использованием React-Redux
    Если вы использовали React, возможно, вы знакомы с головной болью, связанной с обратным потоком данных. Передача состояния реквизитам от родительских компонентов к дочерним компонентам может..

    5 обязательных элементов современного инструмента конвейера данных
    В цифровом мире предприятия используют конвейеры данных для перемещения, преобразования и хранения огромных объемов данных. Эти конвейеры составляют основу бизнес-аналитики и играют..