Oracle对我们的期望是什么?

时间:2014-03-06 00:13:03

标签: java css javafx javafx-8

RESUME

有一段时间我一直在研究新的JavaFX技术,而且我在创建自定义控件方面遇到了一些障碍。我学习了如何使用CSS自定义控件,然后我遇到了使用SkinSkinBase自定义控件的情况。

看到这样的资源,很容易启动并完成具有视觉和特定功能的新控件的创建。但是,个性化,即JavaFX库上现有控件的可视化和功能性编辑变得更加复杂。在许多情况下,程序员被迫使用仅在private packages from Oracle(com.sun ...)中可用的资源,这将成为一种不好的做法,导致软件的生产无法维护。

想象一下我们想要自定义ScrollBar控件的示例。可以使用CSS完全改变其外观。但是,为此类控件添加新行为的愿望包括从ZERO创建一个新主题,而不重用ScrollBarSkin,因为它位于私有Oracle包中。这迫使程序员必须重新实现已经实现的逻辑,例如拇指的定位,值的更新,单击轨道时发生的事情,以及许多其他事情。为了创建ScrollBarSkin的子类型,我们看到有许多重要的方法被封装为不被覆盖,这使得你必须强制重新实现现有的逻辑。

至少看来,用于自定义控件的许多重要组件都被关在笼子里,导致您必须使用单一路径来访问它们(仍然有限)。

示例(从理论到实践)

为了说明我的意思并在本社区的这个问题的结论中强调,我们将简要地尝试自定义JavaFX包中存在的ScrollBar。我的目的是创建一个如下所示的滚动条:

LINK1& LINK2

关于滚动条的行为,在点击它的箭头时,它们应该移动一点,当释放鼠标按钮时返回到它们的位置。当您将鼠标移过拇指和箭头时,它们应该亮起。通过单击轨道或按下任何滚动条箭头,拇指必须以动画形式平稳移动,而不是突然移动。

所以让我们从实验开始吧。首先,让我们创建一个CSS文件,作为一些外观的定义:

.scroll-bar {
-fx-skin: "packageA.packageB.ScrollBarSkin2";

-fx-background-color: rgb(66,64,64);
-fx-border-color: rgb(96,96,98);
-fx-border-width: 1px;
}

.scroll-bar > .thumb {
-fx-background-insets: 5 0 5 0;
-fx-shape: "M0 0c3,0 7,0 10,0l0 6c-3,0 -7,0 -10,0l0 -6z";
}

.scroll-bar > .increment-button > .increment-arrow , 
.scroll-bar > .decrement-button > .decrement-arrow , 
.scroll-bar > .thumb {
-fx-background-color: rgb(254,254,254);
}

/* ------------------------------------------------------------ BUTTONS */

.scroll-bar:horizontal > .increment-button ,
.scroll-bar:horizontal > .decrement-button ,
.scroll-bar:vertical > .increment-button , 
.scroll-bar:vertical > .decrement-button {
-fx-padding: 4px;
}

.scroll-bar:horizontal > .increment-button:hover ,
.scroll-bar:horizontal > .decrement-button:hover ,
.scroll-bar:vertical > .increment-button:hover , 
.scroll-bar:vertical > .decrement-button:hover {
-fx-background-color: null;
}

/* ------------------------------------------------------------ BUTTONS SHAPES */

.scroll-bar:horizontal > .increment-button > .increment-arrow {
-fx-shape: "m -745.01097,-1519.0664 -156.95606,90.6186 -156.95607,90.6186 0,-181.2372 0,-181.2372 156.95608,90.6186 z";
}

.scroll-bar:horizontal > .decrement-button > .decrement-arrow {
-fx-shape: "m -1455.5694,-1550.495 153.1056,-88.3956 153.1056,-88.3955 0,176.7911 0,176.7911 -153.1056,-88.3955 z";
}

.scroll-bar:vertical > .increment-button > .increment-arrow {
-fx-shape: "m -1334.2856,-2204.9669 85.446,147.9968 85.446,147.9968 -170.892,0 -170.8921,0 85.446,-147.9968 z";
}

.scroll-bar:vertical > .decrement-button > .decrement-arrow {
-fx-shape: "m -1234.2856,-1096.134 -94.0582,-162.9135 -94.0582,-162.9136 188.1164,0 188.1163,0 -94.0582,162.9136 z";
}

观察我们的CSS文件,我们可以看到我们选择了现有的三种方法之一将我们的控件连接到我们的皮肤,我们在CSS文件中使用了-fx-skin属性的定义。现在我们需要链接我们用我们的控件创建的CSS文件。这是在Java代码中完成的,我们只需将CSS样式表设置为我们的控件:

scrollBar.getStylesheets().setAll(this.getClass().getResource("scroll-bar-style.css").toExternalForm());
  

注意:要运行该示例,您需要拥有一个带有main的测试类   在场景图中创建和放置ScrollBar的方法。

我们已将控件链接到Skin,但我们还没有真正创建Skin。创建或编辑控件时会考虑控件本身(即扩展Control的对象)被视为MVC pattern, existing in JavaFX模型的一部分。控制和可视化的一部分最初分为两部分(不知道为什么原因)。一个称为皮肤,另一个称为行为。两者都是JavaFX中的现有接口,其中Skin表示可视化部分,而Behavior是Control的一部分。不幸的是(不知道为什么!)行为被认为是JavaFX包的私有部分,因此JavaFX的用户开发人员被推到处理Skin中可视化和控制的部分,这将是最初为Visualization设置的部分。说完所有这些奇怪的事情之后,让我们创建我们的Skin类:

// Public packages.
import javafx.animation.Animation;
import javafx.animation.TranslateTransition;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.geometry.Orientation;
import javafx.scene.control.ScrollBar;
import javafx.scene.effect.DropShadow;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.util.Duration;

// ... And here we have a private package.
import com.sun.javafx.scene.control.skin.ScrollBarSkin;

public class ScrollBarSkin2 extends ScrollBarSkin
{
// #########################################################################################################
//                                                                                                 INSTANCES
// #########################################################################################################

// SUBSTRUCTURES

private StackPane thumb;
private StackPane track;
private Region incButton;
private Region decButton;
private Region incArrow;
private Region decArrow;

// EFFECTS

private DropShadow drop_thumb;
private DropShadow drop_inc;
private DropShadow drop_dec;

// ANIMATIONS

private BindableTransition aniTran_thumbDrop;
private BindableTransition aniTran_incDrop;
private BindableTransition aniTran_decDrop;

private TranslateTransition aniTran_setaInc;
private TranslateTransition aniTran_setaDec;

// #########################################################################################################
//                                                                                              CONSTRUCTORS
// #########################################################################################################

public ScrollBarSkin2(ScrollBar scrollbar) 
{
    super(scrollbar);

    this.thumb = (StackPane) this.getSkinnable().lookup(".thumb");
    this.track = (StackPane) this.getSkinnable().lookup(".track");  
    this.incButton = (Region) this.getSkinnable().lookup(".increment-button");
    this.decButton = (Region) this.getSkinnable().lookup(".decrement-button");
    this.incArrow = (Region) this.getSkinnable().lookup(".increment-arrow");
    this.decArrow = (Region) this.getSkinnable().lookup(".decrement-arrow");

    this.configureSubstructures();
    this.addEvents();
}

/** Sets the substructures obtained.*/
protected void configureSubstructures()
{
    // ####################
    //        THUMB
    // ####################

    this.drop_thumb = new DropShadow();
    this.drop_thumb.setRadius(0);
    this.drop_thumb.setColor(Color.WHITE);

    this.thumb.setEffect(this.drop_thumb);
    this.aniTran_thumbDrop = new BindableTransition(Duration.millis(250));

    // ####################
    //   INCREMENT BUTTON
    // ####################

    this.drop_inc = new DropShadow();
    this.drop_inc.setRadius(0);
    this.drop_inc.setColor(Color.WHITE);

    this.incArrow.setEffect(this.drop_inc);
    this.aniTran_incDrop = new BindableTransition(Duration.millis(250));

    // ####################
    //   DECREMENT BUTTON
    // ####################

    this.drop_dec = new DropShadow();
    this.drop_dec.setRadius(0);
    this.drop_dec.setColor(Color.WHITE);

    this.decArrow.setEffect(this.drop_dec);
    this.aniTran_decDrop = new BindableTransition(Duration.millis(250));

    // ####################
    //        ARROWS
    // ####################

    this.aniTran_setaInc = new TranslateTransition(Duration.millis(100) , this.incArrow);
    this.aniTran_setaDec = new TranslateTransition(Duration.millis(100) , this.decArrow);

    if(this.getSkinnable().getOrientation() == Orientation.HORIZONTAL)
    {
        this.aniTran_setaInc.setFromX(this.incArrow.getLayoutX());
        this.aniTran_setaInc.setToX(this.incArrow.getLayoutX() + 2);

        this.aniTran_setaDec.setFromX(this.incArrow.getLayoutX());
        this.aniTran_setaDec.setToX(this.incArrow.getLayoutX() - 2);
    }
    else if(this.getSkinnable().getOrientation() == Orientation.VERTICAL)
    {
        this.aniTran_setaInc.setFromY(this.incArrow.getLayoutY());
        this.aniTran_setaInc.setToY(this.incArrow.getLayoutY() - 2);

        this.aniTran_setaDec.setFromY(this.incArrow.getLayoutY());
        this.aniTran_setaDec.setToY(this.incArrow.getLayoutY() + 2);
    }
}

/** Adds events animations. Here we also have the logic part.*/
protected void addEvents()
{
    // ####################
    //        THUMB
    // ####################

    thumb.addEventHandler(MouseEvent.MOUSE_ENTERED , new EventHandler<MouseEvent>() 
    {
        @Override public void handle(MouseEvent e) 
        {
            if(aniTran_thumbDrop.getStatus() != Animation.Status.RUNNING)
            {
                aniTran_thumbDrop.setRate(1);
                aniTran_thumbDrop.play();
            }
            else
            {
                aniTran_thumbDrop.setRate(1);
            }
        }
    });

    thumb.addEventHandler(MouseEvent.MOUSE_EXITED , new EventHandler<MouseEvent>() 
    {
        @Override public void handle(MouseEvent e) 
        {
            if(aniTran_thumbDrop.getStatus() != Animation.Status.RUNNING)
            {
                aniTran_thumbDrop.setRate(-1);
                aniTran_thumbDrop.play();
            }
            else
            {
                aniTran_thumbDrop.setRate(-1);
            }
        }
    });

    this.aniTran_thumbDrop.fractionProperty().addListener(new ChangeListener<Number>()
    {
        @Override public void changed(ObservableValue<? extends Number> observable , Number oldValue , Number newValue) 
        {
            drop_thumb.setRadius(4 * newValue.doubleValue());
        }
    });

    // ####################
    //   INCREMENT BUTTON
    // ####################

    incButton.addEventHandler(MouseEvent.MOUSE_ENTERED , new EventHandler<MouseEvent>() 
    {
        @Override public void handle(MouseEvent e) 
        {
            if(aniTran_incDrop.getStatus() != Animation.Status.RUNNING)
            {
                aniTran_incDrop.setRate(1);
                aniTran_incDrop.play();
            }
            else
            {
                aniTran_incDrop.setRate(1);
            }
        }
    });

    incButton.addEventHandler(MouseEvent.MOUSE_EXITED , new EventHandler<MouseEvent>() 
    {
        @Override public void handle(MouseEvent e) 
        {
            if(aniTran_incDrop.getStatus() != Animation.Status.RUNNING)
            {
                aniTran_incDrop.setRate(-1);
                aniTran_incDrop.play();
            }
            else
            {
                aniTran_incDrop.setRate(-1);
            }
        }
    });

    this.aniTran_incDrop.fractionProperty().addListener(new ChangeListener<Number>()
    {
        @Override public void changed(ObservableValue<? extends Number> observable , Number oldValue , Number newValue) 
        {
            drop_inc.setRadius(4 * newValue.doubleValue());
        }
    });

    // ####################
    //   DECREMENT BUTTON
    // ####################

    decButton.addEventHandler(MouseEvent.MOUSE_ENTERED , new EventHandler<MouseEvent>() 
    {
        @Override public void handle(MouseEvent e) 
        {
            if(aniTran_decDrop.getStatus() != Animation.Status.RUNNING)
            {
                aniTran_decDrop.setRate(1);
                aniTran_decDrop.play();
            }
            else
            {
                aniTran_decDrop.setRate(1);
            }
        }
    });

    decButton.addEventHandler(MouseEvent.MOUSE_EXITED , new EventHandler<MouseEvent>() 
    {
        @Override public void handle(MouseEvent e) 
        {
            if(aniTran_decDrop.getStatus() != Animation.Status.RUNNING)
            {
                aniTran_decDrop.setRate(-1);
                aniTran_decDrop.play();
            }
            else
            {
                aniTran_decDrop.setRate(-1);
            }
        }
    });

    this.aniTran_decDrop.fractionProperty().addListener(new ChangeListener<Number>()
    {
        @Override public void changed(ObservableValue<? extends Number> observable , Number oldValue , Number newValue) 
        {
            drop_dec.setRadius(4 * newValue.doubleValue());
        }
    });

    // ####################
    //   INCREMENT ARROW
    // ####################

    this.incButton.addEventHandler(MouseEvent.MOUSE_PRESSED , new EventHandler<MouseEvent>()
    {
        @Override public void handle(MouseEvent event)
        {
            if(aniTran_setaInc.getStatus() != Animation.Status.RUNNING)
            {
                aniTran_setaInc.setRate(1);
                aniTran_setaInc.play();
            }
            else
            {
                aniTran_setaInc.setRate(1);
            }
        }
    });

    this.incButton.addEventHandler(MouseEvent.MOUSE_RELEASED , new EventHandler<MouseEvent>()
    {
        @Override public void handle(MouseEvent event)
        {
            if(aniTran_setaInc.getStatus() != Animation.Status.RUNNING)
            {
                aniTran_setaInc.setRate(-1);
                aniTran_setaInc.play();
            }
            else
            {
                aniTran_setaInc.setRate(-1);
            }
        }
    });

    // ####################
    //   DECREMENT ARROW
    // ####################

    this.decButton.addEventHandler(MouseEvent.MOUSE_PRESSED , new EventHandler<MouseEvent>()
    {
        @Override public void handle(MouseEvent event)
        {
            if(aniTran_setaDec.getStatus() != Animation.Status.RUNNING)
            {
                aniTran_setaDec.setRate(1);
                aniTran_setaDec.play();
            }
            else
            {
                aniTran_setaDec.setRate(1);
            }
        }
    });

    this.decButton.addEventHandler(MouseEvent.MOUSE_RELEASED , new EventHandler<MouseEvent>()
    {
        @Override public void handle(MouseEvent event)
        {
            if(aniTran_setaDec.getStatus() != Animation.Status.RUNNING)
            {
                aniTran_setaDec.setRate(-1);
                aniTran_setaDec.play();
            }
            else
            {
                aniTran_setaDec.setRate(-1);
            }
        }
    });

    // ####################
    //        TRACK
    // ####################

    this.getSkinnable().valueProperty().addListener(new InvalidationListener()
    {
        @Override public void invalidated(Observable observable) 
        {
            System.out.println("ScrollBar value is invalid: " + getSkinnable().getValue());
        }
    });

    this.getSkinnable().valueProperty().addListener(new ChangeListener<Number>()
    {
        @Override public void changed(ObservableValue<? extends Number> observable , Number oldValue , Number newValue) 
        {
            System.out.printf("ScrollBar value changed! - [OLD: %f , NEW: %f] %n" , 
                    oldValue.doubleValue() , newValue.doubleValue() );
        }
    });

    this.track.addEventHandler(MouseEvent.MOUSE_PRESSED , new EventHandler<MouseEvent>()
    {
        @Override public void handle(MouseEvent event)
        {
            System.out.println("Track pressed!");
        }
    });

    ChangeListener<Number> listenerThumb = new ChangeListener<Number>()
    {
        @Override public void changed(ObservableValue<? extends Number> observable , Number oldValue, Number newValue) 
        {
            System.out.println("Thumb moved!");
        }
    };

    thumb.layoutXProperty().addListener(listenerThumb);
    thumb.layoutYProperty().addListener(listenerThumb);
    thumb.translateXProperty().addListener(listenerThumb);
    thumb.translateYProperty().addListener(listenerThumb);
}

@Override protected void handleControlPropertyChanged(String p) 
{
    System.out.println("Beginning.: " + p);

    super.handleControlPropertyChanged(p);

    System.out.println("End: " + p);
}
}

如您所见,我们坚持创建ScrollBarSkin的子类型,这是JavaFX(com.sun ...)包的私有实现。我还要说明我借用了BindableTransition类:

import javafx.animation.Transition;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.util.Duration;

/**
* A simple Transition thats fraction property can be bound to any other
* properties.
* 
* @author hendrikebbers
* 
*/
public class BindableTransition extends Transition {

private DoubleProperty fraction;

public BindableTransition(Duration duration) {
    fraction = new SimpleDoubleProperty();
    setCycleDuration(duration);
}

@Override
protected final void interpolate(double frac) {
    fraction.set(frac);
}

public ReadOnlyDoubleProperty fractionProperty() {
    return fraction;
}
}

这是我从AquaFX开始的课程,所以我对它的创作没有任何赞誉。正如班级本身所写的原作者是hendrikebbers。我要感谢他/她和AquaFX团队提供的源代码,没有这些源代码,我就会迷失。

如您所见,ScrollBarSkin2具有addEvents方法,该方法的任务是将某些事件从皮肤添加到某些组件。很大程度上,动画在那里处理。当用户单击轨道或箭头按钮尝试使拇指平滑移动时,会出现此代码中最严重的问题。我只是无法实现这样的行为,因为我不知道该怎么做。我试图覆盖handleControlPropertyChanged方法(来自BehaviorSkinBase),试图创建一个正确的positionThumb方法。不幸的是,这是不可能的,因为我需要一些ScrollBarSkin属性才能正确定位拇指。例如,这些属性可以是trackPos和trackLenght,保留用于计算并仅在ScrollBarSkin中使用。

结论

我可以得出结论,我不知道还能做什么。甲骨文提供限制其使用的JavaFX技术是非常令人讨厌的(至少这是出现的情况)。似乎没有关于更高级自定义的JavaFX study site的文档。关于在互联网上存在的JavaFX中编辑和创建自定义控件的文档谈论了通过CSS进行的简单定制,而没有提到我们如何在皮肤中实现更高级的功能(例如子结构的动画)。这让我很沮丧,毕竟我看到这项技术的巨大潜力(与Swing无法比拟),但仍无法使用此功能。

至多,我觉得自己无能为力,不知道该做什么。我希望负责开发JavaFX和文档的人能够理解我们出现的某些限制。关于这个主题的现有书籍已经过时,如果没有,他们除了现有的内容here之外也没有更多的内容。

问题

我不知道在哪里谈论这些事情,但我希望有人告诉我,我以错误的方式自定义我的控件,并且我必须纠正一些代码行任何理由。毕竟,JavaFX 8将于3月18日全面推出。有人请告诉我我做错了什么。如果我没有做错任何事情,需要做些什么来通知JavaFX的开发人员?

感谢您的关注。

修改

有些人并不真正理解我想在这里展示的内容。我想证明,显然需要,因为JavaFX API的那部分变得公开。我说是因为我无法创建一个简单的自定义滚动条。所以我猜对方。是否真的需要公开部分API?就在那时我终于可以问:

我是否以错误的方式进行控件自定义?如果我是,有人可以纠正我吗?

这很简单。这里巨大的文字帖子证明我想要表明,A加B,我必须来这里问这个问题,对某些人来说这可能是一个愚蠢的事情。 换句话说 ...我演示了如何自定义滚动条(我的问题的源代码)。然后我问:&#34;嘿,有人可以帮我吗?谢谢你#34;我来不教如何自定义组件,来表明我是如何做的(显然这似乎是错误的方式)。

尽管已经接受了用户的回答,但我的疑虑仍然存在。无论如何,I put the same question in the Oracle JavaFX community,如同消化。不幸的是,事情需要时间在那里工作(我知道绝大多数人都在忙着工作)。

1 个答案:

答案 0 :(得分:1)

你问:

  

如果我没有做错任何事情,需要做些什么来通知JavaFX的开发人员?

来自community section on the JavaFX home page(强调我的):

  

OTN JavaFX Forum是发布,回答和审核的好地方   与JavaFX相关的问题。

     

Jira bug tracking system是您要举报的地方   JavaFX的问题,或提交功能请求。说明如何   提交错误报告或功能请求可在FAQ section

中找到

你问:

  

Oracle对我们的期望是什么?

我认为他们希望您通过传统的联系渠道,开发人员会阅读并考虑这些渠道。

你的帖子写得很好(呃......可能......至少它的格式很好),但是你可能希望把它分成更小,更小的问题或者总结并提供所要求的详细信息。为了更容易解析和回应您的疑虑。