拖动后,ScrollPane内容变得模糊

时间:2014-09-29 10:59:07

标签: java javafx-8

JavaFX 8.0有这个bug,我不知道如何解决它 示例:http://i.imgur.com/tavh6XA.png

如果我拖动ScrollPane,其内容会变得模糊,但如果我将其拖回,则内容会恢复其清晰度。如果我不修改coords,内容看起来很不错。

有人解决了这个问题吗?

示例代码:

@Override
public void start(Stage stage) {
            Pane pane = new Pane();

    Scene scene = new Scene(pane, 500, 500);
    pane.prefWidthProperty().bind(scene.widthProperty());
    pane.prefHeightProperty().bind(scene.heightProperty());

    ScrollPane scroll = new ScrollPane();

    // Center ScrollPane
    scroll.layoutXProperty().bind(pane.widthProperty().divide(2).subtract(scroll.widthProperty().divide(2)));
    scroll.layoutYProperty().bind(pane.heightProperty().divide(2).subtract(scroll.heightProperty().divide(2)));
    scroll.setPrefSize(200, 200);

    ListView<String> list = new ListView<>();
    scroll.setContent(list);

    list.getItems().addAll("Resize the window", "and see how this list", "becomes blurry");

    // Label indicates x, y coords of ScrolPane and their ratio.
    TextField size = new TextField();
    size.setPrefWidth(500);
    size.textProperty().bind(
            scroll.layoutXProperty()
            .asString("x: %s; ").concat(
                    scroll.layoutYProperty()
                    .asString("y: %s; ")));

    pane.getChildren().addAll(size, scroll);

    stage.setScene(scene);
    stage.show();
}

5 个答案:

答案 0 :(得分:5)

我尝试了thebradness的答案,但我得到java.lang.reflect.InaccessibleObjectException(Java 10)可能是因为Java Platform Module System。我没有寻找使反射工作的解决方法,而是寻求另一种方法来实现它。

检查ScrollPaneSkin内部代码后,我注意到viewport css类被赋予viewRect参数。所以我可以通过CSS选择器Node#lookup而不是通过反射来获取它。

这是一个更简单,更易读的解决方案(并且在JAVA 9+中工作):

public void fixBlurryText(ScrollPane scrollPane) {
    StackPane stackPane = (StackPane) scrollPane.lookup("StackPane .viewport");
    stackPane.setCache(false);
}

希望它会帮助别人。

答案 1 :(得分:3)

我检查了内部皮肤代码并找到了解决方法,但并不漂亮。问题出在 com.sun.javafx.scene.control.skin.ScrollPaneSkin 中,它调用viewRect.setCache(true)。禁用缓存似乎可以解决问题。

PS:在TitledPane中,cache属性也设置为true,自JavaFX 8以来也存在同样的问题。

<强> ScrollPane2.java

<!-- language: lang-java -->

import javafx.scene.control.ScrollPane;
import javafx.scene.control.Skin;

public class ScrollPane2 extends ScrollPane
{
    @Override
    protected Skin<?> createDefaultSkin()
    {
        return new ScrollPaneSkin2(this);
    }
}

<强> ScrollPaneSkin2.java

<!-- language: lang-java -->
import java.lang.reflect.Field;

import javafx.scene.control.ScrollPane;
import javafx.scene.layout.StackPane;

import com.sun.javafx.scene.control.skin.ScrollPaneSkin;

class ScrollPaneSkin2 extends ScrollPaneSkin
{
    private static Field viewRectField;

    static
    {
        try
        {
            viewRectField = ScrollPaneSkin.class.getDeclaredField("viewRect");
            viewRectField.setAccessible(true);
        }
        catch (ReflectiveOperationException e)
        {
            throw new RuntimeException(e);
        }
    }

    private StackPane viewRect;

    public ScrollPaneSkin2(final ScrollPane scrollpane)
    {
        super(scrollpane);
        try
        {
            this.viewRect = (StackPane) viewRectField.get(this);
        }
        catch (ReflectiveOperationException e)
        {
            throw new RuntimeException(e);
        }
        this.viewRect.setCache(false);
    }
}

可能会降低性能以禁用缓存,但我没有注意到任何减速。

答案 2 :(得分:3)

import com.sun.javafx.scene.control.skin.ScrollPaneSkin;
import java.lang.reflect.Field;
import javafx.scene.Node;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.StackPane;

public class FieldUtils {

    public static void fixBlurryText(Node node) {
        try {
            Field field = ScrollPaneSkin.class.getDeclaredField("viewRect");
            field.setAccessible(true);

            ScrollPane scrollPane = (ScrollPane) node.lookup(".scroll-pane");

            StackPane stackPane = (StackPane) field.get(scrollPane.getSkin());
            stackPane.setCache(false);

        } catch (NoSuchFieldException | SecurityException |  IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}
  • 感谢AqD搞清楚这一点。
  • 我用AqD的答案写了这个小实用程序。我遇到的问题是PopOver中的TextAreas。所以这只是在节点深处找到stackPane并关闭缓存。
  • 注意:因为这个问题只发生在PopOver中,所以我必须在显示PopOver之后调用此方法(因为否则该节点尚不存在,也没有任何内容)。
  • 如果您遇到WebView问题,请忘掉它。 WebView没有滚动窗格(据我所知,没有使用setCache的字段),我不知道如何解决这个问题。
  • 希望这可以帮助某人,即使这个问题来自3年前。

    blurry text before and after

答案 3 :(得分:2)

如果我将以下样式应用于ScrollPane,则模糊消失。

.scroll-pane {
       -fx-background-color: -fx-box-border,-fx-background;
       -fx-background-insets: 0, 1;
       -fx-padding: 1.0;
}

这只是来自jfxrt.jar的caspian.css的风格。

我怀疑“-fx-background-insets:-1.4,0,1;”来自“.scroll-pane:focused”负责非整数偏移。因此导致模糊。所以我从ScrollPane的完整里海风格回来了。删除背景不明朗后,一切都保持清醒。我尽可能地减少了修改代码并得到了上面提出的解决方案。

答案 4 :(得分:1)

推荐方法 - 使用内置布局管理器+调整提示

除非您需要,否则我建议不要为节点设置布局值。相反,如果您使用标准布局容器,那么它(应该)默认将任何非整数布局坐标捕捉到整数值,从而允许系统的干净布局,而不会由导致文本和线条的分数布局值的抗锯齿引起的潜在模糊不直接在屏幕像素网格上对齐。

没有模糊性的示例代码

当我在我的机器上运行一个修改过的样本时,它不通过绑定执行布局,而是仅使用布局管理器进行布局,我没有观察到模糊值(win 7,JavaFX 8u20)。我也不需要修改默认节点缓存提示。

notblurry

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;

public class NonBlurryPane extends Application {

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

    @Override
    public void start(Stage stage) {
        VBox pane = new VBox();

        pane.setPrefSize(500, 500);

        Scene scene = new Scene(pane);

        ScrollPane scroll = new ScrollPane();
        StackPane scrollContainer = new StackPane(scroll);
        VBox.setVgrow(scrollContainer, Priority.ALWAYS);

        // Center ScrollPane
        scroll.setMinSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
        scroll.setPrefSize(200, 200);
        scroll.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);

        ListView<String> list = new ListView<>();
        scroll.setContent(list);

        list.getItems().addAll("Resize the window", "and see how this list", "does NOT become blurry");

        // Label indicates x, y coords of ScrolPane and their ratio.
        TextField size = new TextField();
        size.setMaxWidth(Double.MAX_VALUE);
        size.setMinHeight(Region.USE_PREF_SIZE);
        size.textProperty().bind(
                scroll.layoutXProperty()
                        .asString("x: %s; ").concat(
                        scroll.layoutYProperty()
                                .asString("y: %s; ")).concat(
                        scroll.layoutXProperty().divide(scroll.layoutYProperty())
                                .asString("x/y: %s; ")).concat(
                        scroll.layoutYProperty().divide(scroll.layoutXProperty())
                                .asString("y/x: %s")));

        pane.getChildren().addAll(size, scrollContainer);

        stage.setScene(scene);
        stage.show();
    }
}
  

我还注意到默认布局管理器将双重坐标转为整数坐标。是否可以使数字绑定以这种方式工作?

绑定整数坐标

整数坐标有点简化。有时您需要x.5的坐标(有关详细信息,请参阅How to draw a crisp, opaque hairline in JavaFX 2.2?)。这是一个你不需要担心标准布局窗格的细节,因为它们默认会处理干净的对齐。

Java 8的标准绑定例程中没有任何类型的floor或round函数。您可以使用失效侦听器或更改侦听器并根据它们进行更新(而不是绑定),或者您可以开发自定义绑定到适当的价值。有关这些选项的更多信息,请参阅Oracle JavaFX属性教程的"Exploring Observable, ObservableValue, InvalidationListener, and ChangeListener" and "Using the Low-Level Binding API"部分。

布局提示

进行布局的首选方法是(从最简单到更复杂):

  1. 使用布局管理器。
  2. 绑定或更改侦听器(通常)可以进行非常简单的布局调整。
  3. 在父级中覆盖layoutChildren()以适用于任何适度自定义和复杂的内容。
  4. layoutChildren()

    内置的JavaFX控件和布局管理器中的大多数自定义布局计算都是通过覆盖layoutChildren()来处理的,而不是通过绑定来处理的。您可以查看JavaFX source code并了解如何实施这些内容。这对于理解如何使用layoutChildren是必要的(因为我没有在任何地方看到它完整记录)。覆盖layoutChildren有点先进和棘手做得好。要从布局算法中获得可预测的质量结果,您需要手动考虑填充插入,像素捕捉,准确测量子组件的大小等内容。