JavaFx:滑块 - 带颜色的双向移动
JavaFx: Slider - Bidirectional movement with color
背景:
我正在尝试在 JavaFx 的 TableView 中创建实时股票走势的视觉表示,如 2 个附加图像(绿色表示正,琥珀色负 & 值在左下角的日低点,值在右下角的日高点)。运动将由实时数据流控制。因此,如果滑动标记超过当天的开盘价,则需要能够随着颜色变化双向滑动
研究:
我总是提到这个很棒的 -> Link 但在这种情况下寻求指导,因为我不确定它是否适用于 Progress Bar 或 Slider。
如能就此提出任何建议,我们将不胜感激。
一个可能的解决方案是将两个 ProgressBar
打包成一个 HBox
。
以下不应被视为完整的实现,而是作为想法的演示:
import javafx.application.Application;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.binding.When;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import src.tests.BiDiProgressBar.Indicator;
public class BiDiProgressBar extends Application {
@Override public void start(Stage stage) {
final HBox indicator = new Indicator(60);
stage.setScene(new Scene(indicator));
stage.show();
//run test
Thread th = new Thread(new TestTask((Indicator)indicator));
th.setDaemon(true);
th.start();
}
class Indicator extends HBox{
//properties representing indicator min max, mid and current values
DoubleProperty minValue = new SimpleDoubleProperty(0);
DoubleProperty maxValue = new SimpleDoubleProperty(100);
DoubleProperty nominalValue = new SimpleDoubleProperty(50);
DoubleProperty currentlValue;
//properties representing full, neg and pos ranges
DoubleProperty rangeValue = new SimpleDoubleProperty();
DoubleProperty posRangeValue = new SimpleDoubleProperty();
DoubleProperty negRangeValue = new SimpleDoubleProperty();
private ProgressBar negProgBar, posProgBar;
private static final double indicatorWidth = 200; //in pixles
Indicator(double nomlValue){
super(0);
setNominalValue(nomlValue);
rangeValue.bind(maxValue.subtract(minValue));
negRangeValue.bind(nominalValue.subtract(minValue));
posRangeValue.bind(rangeValue.subtract(negRangeValue));
currentlValue = new SimpleDoubleProperty(nominalValue.get());
negProgBar = new ProgressBar();
negProgBar.rotateProperty().set(180); //rotate to progress from left to right
posProgBar = new ProgressBar();
setProgBarWidth();
setProgBarValue();
getChildren().addAll(negProgBar, posProgBar);
getStylesheets().add(getClass().getResource("css/indicator.css").toExternalForm());
negProgBar.getStyleClass().add("neg-bar");
posProgBar.getStyleClass().add("pos-bar");
}
private void setNominalValue(double nominalValue) {
nominalValue = withInRange(nominalValue);
this.nominalValue.set(nominalValue);
}
private void setProgBarWidth() {
double width = (indicatorWidth * nominalValue.get())/rangeValue.get();
negProgBar.setPrefWidth(width);
posProgBar.setPrefWidth(indicatorWidth-width);
}
private void setProgBarValue() {
final DoubleBinding posBinding = new When(currentlValue.greaterThan(nominalValue))
.then((currentlValue.subtract(nominalValue)).divide(posRangeValue))
.otherwise(0.);
final DoubleBinding negBinding = new When(currentlValue.lessThan(nominalValue))
.then((nominalValue.subtract(currentlValue)).divide(negRangeValue))
.otherwise(0.);
posProgBar.progressProperty().bind(posBinding);
negProgBar.progressProperty().bind(negBinding);
}
private double withInRange(double value) {
value = (value < minValue.get()) ? minValue.get() : value;
value = (value > maxValue.get()) ? maxValue.get() : value;
return value ;
}
public double getMin() {return minValue.get();}
public double getMax() {return maxValue.get();}
public double getNominal() {return nominalValue.get();}
public void setProgress(double value) {
currentlValue.set(withInRange(value));
}
}
public static void main(String[] args) { launch(args);}
}
class TestTask extends Task<Void>{
private Indicator indicator;
public TestTask(Indicator indicator) {
this.indicator = indicator;
}
@Override
protected Void call() throws Exception {
for(double value = indicator.getNominal();
value < (indicator.getMax() +5) ; //exceed max
value++) {
indicator.setProgress(value);
delay(100);
}
for(double value = indicator.getMax();
value > (indicator.getMin() -5 ) ; value--) {
indicator.setProgress(value);
delay(100);
}
return null;
}
private void delay(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException ex) { ex.printStackTrace(); }
}
}
结果如下所示:
两者ProgressBar
相交的点代表日开始值。
在最终实施中,在任何给定时间只有一个进度条处于活动状态。
indicator.css
.root { -fx-background-color: black; -fx-padding: 15; }
.progress-bar > .track {
-fx-text-box-border: white;
-fx-control-inner-background: lightgrey;
-fx-border-radius: 0px ;
-fx-background-radius: 0px ;
}
.pos-bar { -fx-accent: green; }
.neg-bar { -fx-accent: red; }
您可以使用滑块或进度条相当简单地完成此操作,使用 CSS 更改轨道的背景颜色。对于进度条,只需将进度条设为透明即可。
基本思路是使用线性"gradient"。如果起始值为x%
,当前值为y%
,加上y > x
,你需要颜色停止在
(default 0%, default x%, green x%, green y%, default y%, default 100%)
其中 default
是默认背景颜色。 (与红色替换绿色类似,如果 y < x
则 x
和 y
切换。)
这是一个 SSCCE:
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.Slider;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class BidirectionalSlider extends Application {
@Override
public void start(Stage primaryStage) {
double startingValue = 0.5 ;
Slider slider = new Slider(0, 1, startingValue);
slider.styleProperty().bind(Bindings.createStringBinding(() -> {
double min = slider.getMin();
double max = slider.getMax();
double value = slider.getValue() ;
return createSliderStyle(startingValue, min, max, value);
}, slider.valueProperty()));
ProgressBar progressBar1 = new ProgressBar(0.1);
ProgressBar progressBar2 = new ProgressBar(0.9);
progressBar1.styleProperty().bind(Bindings.createStringBinding(() ->
createSliderStyle(startingValue, 0.0, 1.0, progressBar1.getProgress()),
progressBar1.progressProperty()));
progressBar2.styleProperty().bind(Bindings.createStringBinding(() ->
createSliderStyle(startingValue, 0.0, 1.0, progressBar2.getProgress()),
progressBar2.progressProperty()));
VBox root = new VBox(5, slider, progressBar1, progressBar2);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 400, 400);
scene.getStylesheets().add("style.css");
primaryStage.setScene(scene);
primaryStage.show();
}
private String createSliderStyle(double startingValue, double min, double max, double value) {
StringBuilder gradient = new StringBuilder("-slider-track-color: ");
String defaultBG = "derive(-fx-control-inner-background, -5%) " ;
gradient.append("linear-gradient(to right, ").append(defaultBG).append("0%, ") ;
double valuePercent = 100.0 * (value - min) / (max - min);
double startingValuePercent = startingValue * 100.0;
if (valuePercent > startingValuePercent) {
gradient.append(defaultBG).append(startingValuePercent).append("%, ");
gradient.append("green ").append(startingValuePercent).append("%, ");
gradient.append("green ").append(valuePercent).append("%, ");
gradient.append(defaultBG).append(valuePercent).append("%, ");
gradient.append(defaultBG).append("100%); ");
} else {
gradient.append(defaultBG).append(valuePercent).append("%, ");
gradient.append("red ").append(valuePercent).append("%, ");
gradient.append("red ").append(startingValuePercent).append("%, ");
gradient.append(defaultBG).append(startingValuePercent).append("%, ");
gradient.append(defaultBG).append("100%); ");
}
return gradient.toString() ;
}
public static void main(String[] args) {
launch(args);
}
}
和基础 CSS 文件 (style.css
):
.slider, .progress-bar {
-slider-track-color: derive(-fx-control-inner-background, -5%) ;
}
.slider .track, .progress-bar .track {
-fx-background-color:
-fx-shadow-highlight-color,
linear-gradient(to bottom, derive(-fx-text-box-border, -10%), -fx-text-box-border),
-slider-track-color ;
}
.progress-bar .bar {
-fx-background-color: transparent ;
}
背景:
我正在尝试在 JavaFx 的 TableView 中创建实时股票走势的视觉表示,如 2 个附加图像(绿色表示正,琥珀色负 & 值在左下角的日低点,值在右下角的日高点)。运动将由实时数据流控制。因此,如果滑动标记超过当天的开盘价,则需要能够随着颜色变化双向滑动
研究:
我总是提到这个很棒的 -> Link 但在这种情况下寻求指导,因为我不确定它是否适用于 Progress Bar 或 Slider。
如能就此提出任何建议,我们将不胜感激。
一个可能的解决方案是将两个 ProgressBar
打包成一个 HBox
。
以下不应被视为完整的实现,而是作为想法的演示:
import javafx.application.Application;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.binding.When;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import src.tests.BiDiProgressBar.Indicator;
public class BiDiProgressBar extends Application {
@Override public void start(Stage stage) {
final HBox indicator = new Indicator(60);
stage.setScene(new Scene(indicator));
stage.show();
//run test
Thread th = new Thread(new TestTask((Indicator)indicator));
th.setDaemon(true);
th.start();
}
class Indicator extends HBox{
//properties representing indicator min max, mid and current values
DoubleProperty minValue = new SimpleDoubleProperty(0);
DoubleProperty maxValue = new SimpleDoubleProperty(100);
DoubleProperty nominalValue = new SimpleDoubleProperty(50);
DoubleProperty currentlValue;
//properties representing full, neg and pos ranges
DoubleProperty rangeValue = new SimpleDoubleProperty();
DoubleProperty posRangeValue = new SimpleDoubleProperty();
DoubleProperty negRangeValue = new SimpleDoubleProperty();
private ProgressBar negProgBar, posProgBar;
private static final double indicatorWidth = 200; //in pixles
Indicator(double nomlValue){
super(0);
setNominalValue(nomlValue);
rangeValue.bind(maxValue.subtract(minValue));
negRangeValue.bind(nominalValue.subtract(minValue));
posRangeValue.bind(rangeValue.subtract(negRangeValue));
currentlValue = new SimpleDoubleProperty(nominalValue.get());
negProgBar = new ProgressBar();
negProgBar.rotateProperty().set(180); //rotate to progress from left to right
posProgBar = new ProgressBar();
setProgBarWidth();
setProgBarValue();
getChildren().addAll(negProgBar, posProgBar);
getStylesheets().add(getClass().getResource("css/indicator.css").toExternalForm());
negProgBar.getStyleClass().add("neg-bar");
posProgBar.getStyleClass().add("pos-bar");
}
private void setNominalValue(double nominalValue) {
nominalValue = withInRange(nominalValue);
this.nominalValue.set(nominalValue);
}
private void setProgBarWidth() {
double width = (indicatorWidth * nominalValue.get())/rangeValue.get();
negProgBar.setPrefWidth(width);
posProgBar.setPrefWidth(indicatorWidth-width);
}
private void setProgBarValue() {
final DoubleBinding posBinding = new When(currentlValue.greaterThan(nominalValue))
.then((currentlValue.subtract(nominalValue)).divide(posRangeValue))
.otherwise(0.);
final DoubleBinding negBinding = new When(currentlValue.lessThan(nominalValue))
.then((nominalValue.subtract(currentlValue)).divide(negRangeValue))
.otherwise(0.);
posProgBar.progressProperty().bind(posBinding);
negProgBar.progressProperty().bind(negBinding);
}
private double withInRange(double value) {
value = (value < minValue.get()) ? minValue.get() : value;
value = (value > maxValue.get()) ? maxValue.get() : value;
return value ;
}
public double getMin() {return minValue.get();}
public double getMax() {return maxValue.get();}
public double getNominal() {return nominalValue.get();}
public void setProgress(double value) {
currentlValue.set(withInRange(value));
}
}
public static void main(String[] args) { launch(args);}
}
class TestTask extends Task<Void>{
private Indicator indicator;
public TestTask(Indicator indicator) {
this.indicator = indicator;
}
@Override
protected Void call() throws Exception {
for(double value = indicator.getNominal();
value < (indicator.getMax() +5) ; //exceed max
value++) {
indicator.setProgress(value);
delay(100);
}
for(double value = indicator.getMax();
value > (indicator.getMin() -5 ) ; value--) {
indicator.setProgress(value);
delay(100);
}
return null;
}
private void delay(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException ex) { ex.printStackTrace(); }
}
}
结果如下所示:
两者ProgressBar
相交的点代表日开始值。
在最终实施中,在任何给定时间只有一个进度条处于活动状态。
indicator.css
.root { -fx-background-color: black; -fx-padding: 15; }
.progress-bar > .track {
-fx-text-box-border: white;
-fx-control-inner-background: lightgrey;
-fx-border-radius: 0px ;
-fx-background-radius: 0px ;
}
.pos-bar { -fx-accent: green; }
.neg-bar { -fx-accent: red; }
您可以使用滑块或进度条相当简单地完成此操作,使用 CSS 更改轨道的背景颜色。对于进度条,只需将进度条设为透明即可。
基本思路是使用线性"gradient"。如果起始值为x%
,当前值为y%
,加上y > x
,你需要颜色停止在
(default 0%, default x%, green x%, green y%, default y%, default 100%)
其中 default
是默认背景颜色。 (与红色替换绿色类似,如果 y < x
则 x
和 y
切换。)
这是一个 SSCCE:
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.Slider;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class BidirectionalSlider extends Application {
@Override
public void start(Stage primaryStage) {
double startingValue = 0.5 ;
Slider slider = new Slider(0, 1, startingValue);
slider.styleProperty().bind(Bindings.createStringBinding(() -> {
double min = slider.getMin();
double max = slider.getMax();
double value = slider.getValue() ;
return createSliderStyle(startingValue, min, max, value);
}, slider.valueProperty()));
ProgressBar progressBar1 = new ProgressBar(0.1);
ProgressBar progressBar2 = new ProgressBar(0.9);
progressBar1.styleProperty().bind(Bindings.createStringBinding(() ->
createSliderStyle(startingValue, 0.0, 1.0, progressBar1.getProgress()),
progressBar1.progressProperty()));
progressBar2.styleProperty().bind(Bindings.createStringBinding(() ->
createSliderStyle(startingValue, 0.0, 1.0, progressBar2.getProgress()),
progressBar2.progressProperty()));
VBox root = new VBox(5, slider, progressBar1, progressBar2);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 400, 400);
scene.getStylesheets().add("style.css");
primaryStage.setScene(scene);
primaryStage.show();
}
private String createSliderStyle(double startingValue, double min, double max, double value) {
StringBuilder gradient = new StringBuilder("-slider-track-color: ");
String defaultBG = "derive(-fx-control-inner-background, -5%) " ;
gradient.append("linear-gradient(to right, ").append(defaultBG).append("0%, ") ;
double valuePercent = 100.0 * (value - min) / (max - min);
double startingValuePercent = startingValue * 100.0;
if (valuePercent > startingValuePercent) {
gradient.append(defaultBG).append(startingValuePercent).append("%, ");
gradient.append("green ").append(startingValuePercent).append("%, ");
gradient.append("green ").append(valuePercent).append("%, ");
gradient.append(defaultBG).append(valuePercent).append("%, ");
gradient.append(defaultBG).append("100%); ");
} else {
gradient.append(defaultBG).append(valuePercent).append("%, ");
gradient.append("red ").append(valuePercent).append("%, ");
gradient.append("red ").append(startingValuePercent).append("%, ");
gradient.append(defaultBG).append(startingValuePercent).append("%, ");
gradient.append(defaultBG).append("100%); ");
}
return gradient.toString() ;
}
public static void main(String[] args) {
launch(args);
}
}
和基础 CSS 文件 (style.css
):
.slider, .progress-bar {
-slider-track-color: derive(-fx-control-inner-background, -5%) ;
}
.slider .track, .progress-bar .track {
-fx-background-color:
-fx-shadow-highlight-color,
linear-gradient(to bottom, derive(-fx-text-box-border, -10%), -fx-text-box-border),
-slider-track-color ;
}
.progress-bar .bar {
-fx-background-color: transparent ;
}