Updating a widget more frequently than 30 minutes - android-widget

Lots of answers, none of which work.
Should you use a service or an alarm or a timer? All of the answers seem to just be a random guess.
In this example in onDisable what is the point of creating a new instance of AppWidgetAlarm and then calling stopAlarm() -- isn't the alarm still running on the long since lost instance that was created in onEnabled.
The alarm is started in onEnabled which is called only when a new widget is created. Therefore after a reboot the alarm will not be started -- a user would have to delete and re-create the widget.
package com.example.thisisnotawidget
import android.app.AlarmManager
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.widget.RemoteViews
import java.util.*
val ACTION_AUTO_UPDATE = "doodah"
// https://stackoverflow.com/questions/5476867/updating-app-widget-using-alarmmanager/14319020#14319020
class AppWidgetAlarm(private val context: Context) {
private val ALARM_ID = 0
private val INTERVAL_MILLIS = 500
fun startAlarm() {
val calendar = Calendar.getInstance()
calendar.add(Calendar.MILLISECOND, INTERVAL_MILLIS)
val alarmIntent = Intent(context, ThisIsTheWidget::class.java)
alarmIntent.action = ACTION_AUTO_UPDATE
val pendingIntent = PendingIntent.getBroadcast(
context,
ALARM_ID,
alarmIntent,
PendingIntent.FLAG_CANCEL_CURRENT
)
val alarmManager =
context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
// RTC does not wake the device up
alarmManager.setRepeating(
AlarmManager.RTC,
calendar.timeInMillis,
INTERVAL_MILLIS.toLong(),
pendingIntent
)
}
fun stopAlarm() {
val alarmIntent = Intent(ACTION_AUTO_UPDATE)
val pendingIntent = PendingIntent.getBroadcast(
context,
ALARM_ID,
alarmIntent,
PendingIntent.FLAG_CANCEL_CURRENT
)
val alarmManager =
context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmManager.cancel(pendingIntent)
}
}
/**
* Implementation of App Widget functionality.
*/
class ThisIsTheWidget : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
// Do the updating.
val thisWidget = ComponentName(context, ThisIsTheWidget::class.java)
val allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget)
for (widgetId in allWidgetIds) {
updateAppWidget(context, appWidgetManager, widgetId)
}
}
override fun onEnabled(context: Context) {
// Enter relevant functionality for when the first widget is created
// start alarm
val appWidgetAlarm = AppWidgetAlarm(context.applicationContext)
appWidgetAlarm.startAlarm()
}
override fun onDisabled(context: Context) {
// Enter relevant functionality for when the last widget is disabled
// stop alarm only if all widgets have been disabled
val appWidgetManager = AppWidgetManager.getInstance(context);
val thisWidget = ComponentName(context, ThisIsTheWidget::class.java)
val allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget)
if (allWidgetIds.count() == 0) {
// stop alarm
val appWidgetAlarm = AppWidgetAlarm(context.getApplicationContext());
appWidgetAlarm.stopAlarm();
}
}
}
internal fun updateAppWidget(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetId: Int
) {
val prefs = context.getSharedPreferences("W", 0)
val n:Int = prefs.getInt("W_" + appWidgetId.toString(), 0) + 1
val e = prefs.edit()
e.putInt("W_" + appWidgetId.toString(), n)
e.apply()
e.commit()
// Construct the RemoteViews object
val views = RemoteViews(context.packageName, R.layout.this_is_the_widget)
views.setTextViewText(R.id.appwidget_text, n.toString())
// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, views)
}

Related

Implementing a flutter plugin to wake screen when app is asleep

I am trying to turn on a screen once an alarm fires. I am using this
[package][1] for the alarm. However, after time doing research I have not come any closer to achieving waking the screen on alarm firing.
The solutions either use deprecated APIs or are only specific to code within some contexts like activities and fragments, which is not the case for a flutter plugin.
Here is what I have tried
-Implementing ActivityAware interface but the onAttachedToActivity() never seems to fire
-Changing activity settings of my main activity in android manifes
android:showWhenLocked="true"
android:turnScreenOn="true"
I am currently trying to get an activity within the plugin to add flags to it to turn the screen on
if (Build.VERSION.SDK_INT < 27)
activity!!.window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
else
activity!!.setTurnScreenOn(true)
The problem is the activity variable is always null because for some reason, onAttachedToActivity doesn't run.
I have created an activity within the native code but I don't know how to get a reference to that activity and use it to turn the flags on. Remember, my screen is turned off so this is possibly what is complicating the matter.
Here is where I create the activity:
private fun startMainActivity(context: Context) {
val pm = context.packageManager
val intent = pm.getLaunchIntentForPackage(context.packageName)
context.startActivity(intent)
turnScreenON()
}
How can I create this plugin to wake the screen when an alarm fires?
Here is my code for the android kotlin side:
package com.example.wake_screen
import android.app.Activity
import android.content.Context
import android.os.Build
import android.util.Log
import android.view.WindowManager
import androidx.annotation.NonNull
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
/** WakeScreenPlugin */
class WakeScreenPlugin: ActivityAware, FlutterPlugin, MethodCallHandler{
private lateinit var channel : MethodChannel
private lateinit var mContext: Context
private var activity: Activity? = null
override fun onAttachedToEngine(#NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "wake_screen")
channel.setMethodCallHandler(this)
mContext = flutterPluginBinding.applicationContext
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity
Log.d("We are attached",binding.lifecycle.toString())
}
override fun onDetachedFromActivity() {
//release resources
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
activity = binding.activity
}
override fun onDetachedFromActivityForConfigChanges() {
//release resources
}
override fun onMethodCall(#NonNull call: MethodCall, #NonNull result: Result) {
if (call.method == "getPlatformVersion") {
startMainActivity(mContext)
result.success("Android ${android.os.Build.VERSION.RELEASE}")
}
else {
result.notImplemented()
}
}
private fun turnScreenON(){
if (Build.VERSION.SDK_INT < 27)
activity!!.window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
else
activity!!.setTurnScreenOn(true)
}
private fun startMainActivity(context: Context) {
val pm = context.packageManager
val intent = pm.getLaunchIntentForPackage(context.packageName)
context.startActivity(intent)
turnScreenON()
}
override fun onDetachedFromEngine(#NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
}
Here's my code on the flutter side:
//Provide instruction to wake device after duration
void setAlarm() async {
await AndroidAlarmManager.oneShot(
const Duration(seconds: _duration),
0,
wakeTheScreen,
wakeup: true,
allowWhileIdle: true,
);
}
//Function invoking platform code
static Future<void> wakeTheScreen() async {
String? version = await WakeScreen().getPlatformVersion();
print(version);
}
In summary, How do I wake the screen up using a flutter plugin that needs an activity to work
[1]: https://pub.dev/packages/android_alarm_manager_plus

How to update/refresh a parameter in Flink application

I have a Flink application on AWS Kinesis Analytics service. I need to filter some values on a data stream based on a threshold. Also, I'm passing the threshold parameter using AWS Systems Manager Parameter Store service. For now, I got this:
In my Main class:
val threshold: Int = ssmParameter.getParameterRequest(ssmClient, "/kinesis/threshold").toInt
val kinesis_deserialization_schema = new KinesisDeserialization[ID]
val KinesisConsumer = new FlinkKinesisConsumer[ID](
"Data-Stream",
kinesis_deserialization_schema,
consumerProps
)
val KinesisSource = env.addSource(KinesisConsumer).name(s"Kinesis Data")
val valid_data = KinesisSource
.filter(new MyFilter[ID](threshold))
.name("FilterData")
.uid("FilterData")
Filter class:
import cl.mydata.InputData
import org.apache.flink.api.common.functions.FilterFunction
class MyFilter[ID <: InputData](
threshold: Int
) extends FilterFunction[ID] {
override def filter(value: ID): Boolean = {
value.myvalue > threshold
}
}
}
This works fine, the thing is that I need to update the threshold parameter every hour, because that value can be changed by my client.
Perhaps you can implement the ProcessingTimeCallback interface in the MyFilter class, which supports timer operations, and you can update the threshold in the onProcessingTime function
public class MyFilter extends FilterFunction<...> implements ProcessingTimeCallback {
int threshold;
#Override
public void open(Configuration parameters) throws Exception {
scheduler.scheduleAtFixedRate(this, 1, 1, TimeUnit.HOURS);
final long now = getProcessingTimeService().getCurrentProcessingTime();
getProcessingTimeService().registerTimer(now + 3600000, this);
}
#Override
public boolean filter(IN xxx) throws Exception {
return xxx > threshold;
}
#Override
public void onProcessingTime(long timestamp) throws Exception {
threshold = XXXX;
final long now = getProcessingTimeService().getCurrentProcessingTime();
getProcessingTimeService().registerTimer(now + 3600000, this);
}
}
You could turn the FilterFunction into a BroadcastProcessFunction, and broadcast new thresholds as they become available.

How to use actionlistener in Scala Swing

I would like to make click counter in Scala Swing. I used for this ActionListener interface. But I don't know if I did it correctly. The program works, but I want to find out how to do this according to best practies. I will be very thankful for the answer how to do it correctly.
import javax.swing._
import java.awt._
import java.awt.event._
class UI extends JFrame {
var title_ : String = "Hello, Swing in Scala"
setTitle(title_)
val textArea = new JTextArea
var text : String = "Hello, Swing world in Scala!"
textArea.setText(text)
val scrollPane = new JScrollPane(textArea)
val panel = new JPanel
var text2 : String = "Click Here"
val button =new JButton(text2)
panel.add(button)
var clicks:Int = 0
def onClick(): Unit = {
clicks += 1
text = "Number of button clicks: " + clicks.toString
textArea.setText(text)
}
button.addActionListener(new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = onClick() })
getContentPane.add(scrollPane, BorderLayout.CENTER)
getContentPane.add(panel, BorderLayout.SOUTH)
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)
setSize(new Dimension(600, 400))
setLocationRelativeTo(null)
def display() {
setVisible(true)
}
}
object SwingExample extends App {
val ui = new UI
ui.display()
println("End of main function")
}
there are simple things, which improve code quality and maintainability.
it seems that text, text2 and title_ can remove and put that value directly into.
to shorten it, try this statement for the onClick
def onClick(): Unit = {
clicks += 1
textArea.setText(s"Number of button clicks: $clicks")
}
see String Interpolation.
you can shorten your ActionListener by
button.addActionListener(e => onClick())

JavaFX/ScalaFX & Clipboard: Cannot copy files?

Copying files does not work:
def toClipboard(selectedLinesOnly: Boolean = false): Unit = {
val clipboard = Clipboard.systemClipboard
val content = new ClipboardContent
val items: Iterable[FileRecord] = selectedLinesOnly match {
case true => tableView.selectionModel.value.selectedItems.toSeq
case false => tableView.items.value
}
val files = items.map(_.file)
println(s"COPY $files")
content.putFiles(files.toSeq)
clipboard.content = content
}
Output: [info] COPY [SFX][/tmp/test/a.txt, /tmp/test/b.txt]
No files to paste.
def toClipboard(selectedLinesOnly: Boolean = false): Unit = {
val clipboard = Clipboard.systemClipboard
val content = new ClipboardContent
val items: Iterable[FileRecord] = selectedLinesOnly match {
case true => tableView.selectionModel.value.selectedItems.toSeq
case false => tableView.items.value
}
val files = items.map(_.file.getPath)
println(s"COPY $files")
content.putFilesByPath(files.toSeq)
clipboard.content = content
}
Output: [info] COPY [SFX][/tmp/test/a.txt, /tmp/test/b.txt]
No files to paste.
def toClipboard(selectedLinesOnly: Boolean = false): Unit = {
val clipboard = Clipboard.systemClipboard
val content = new ClipboardContent
val items: Iterable[FileRecord] = selectedLinesOnly match {
case true => tableView.selectionModel.value.selectedItems.toSeq
case false => tableView.items.value
}
val files = items.map("file://" + _.file.getPath)
println(s"COPY $files")
content.putFilesByPath(files.toSeq)
clipboard.content = content
}
Output: [info] COPY [SFX][file:///tmp/test/a.txt, file:///tmp/test/b.txt]
No files to paste.
But copying the paths to the string clipboard is possible:
def toClipboard(selectedLinesOnly: Boolean = false): Unit = {
val clipboard = Clipboard.systemClipboard
val content = new ClipboardContent
val items: Iterable[FileRecord] = selectedLinesOnly match {
case true => tableView.selectionModel.value.selectedItems.toSeq
case false => tableView.items.value
}
val files = items.map(_.file.getPath)
println(s"COPY $files")
content.putString(files.mkString(" "))
clipboard.content = content
}
Now this is in my clipboard: "/tmp/test/a.txt /tmp/test/b.txt"
But I need it in the form of files, not a string.
How can I make copying files work in my application?
I am working with OpenJFX 8 on Ubuntu.
ClipBoard doesn't have the functionality like FileUtils.copyDirectoryToDirectory and FileUtils.moveDirectoryToDirectory (aka copy or cut). Clipboard can give only path or general data. This features can be possible using Drag and Drop by Dragboard.
Dragboard of JavaFX:
During the drag-and-drop gesture, various types of data can be
transferred such as text, images, URLs, files, bytes, and strings.
The javafx.scene.input.DragEvent class is the basic class used to
implement the drag-and-drop gesture. For more information on
particular methods and other classes in the javafx.scene.input
package, see the API documentation.
For more, you can go through this tutorial: Drag-and-Drop Feature in JavaFX Applications
Dragboard JavaFX code sample: HelloDragAndDrop.java
package hellodraganddrop;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.*;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.stage.Stage;
/**
* Demonstrates a drag-and-drop feature.
*/
public class HelloDragAndDrop extends Application {
#Override public void start(Stage stage) {
stage.setTitle("Hello Drag And Drop");
Group root = new Group();
Scene scene = new Scene(root, 400, 200);
scene.setFill(Color.LIGHTGREEN);
final Text source = new Text(50, 100, "DRAG ME");
source.setScaleX(2.0);
source.setScaleY(2.0);
final Text target = new Text(250, 100, "DROP HERE");
target.setScaleX(2.0);
target.setScaleY(2.0);
source.setOnDragDetected(new EventHandler <MouseEvent>() {
public void handle(MouseEvent event) {
/* drag was detected, start drag-and-drop gesture*/
System.out.println("onDragDetected");
/* allow any transfer mode */
Dragboard db = source.startDragAndDrop(TransferMode.ANY);
/* put a string on dragboard */
ClipboardContent content = new ClipboardContent();
content.putString(source.getText());
db.setContent(content);
event.consume();
}
});
target.setOnDragOver(new EventHandler <DragEvent>() {
public void handle(DragEvent event) {
/* data is dragged over the target */
System.out.println("onDragOver");
/* accept it only if it is not dragged from the same node
* and if it has a string data */
if (event.getGestureSource() != target &&
event.getDragboard().hasString()) {
/* allow for both copying and moving, whatever user chooses */
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
}
event.consume();
}
});
target.setOnDragEntered(new EventHandler <DragEvent>() {
public void handle(DragEvent event) {
/* the drag-and-drop gesture entered the target */
System.out.println("onDragEntered");
/* show to the user that it is an actual gesture target */
if (event.getGestureSource() != target &&
event.getDragboard().hasString()) {
target.setFill(Color.GREEN);
}
event.consume();
}
});
target.setOnDragExited(new EventHandler <DragEvent>() {
public void handle(DragEvent event) {
/* mouse moved away, remove the graphical cues */
target.setFill(Color.BLACK);
event.consume();
}
});
target.setOnDragDropped(new EventHandler <DragEvent>() {
public void handle(DragEvent event) {
/* data dropped */
System.out.println("onDragDropped");
/* if there is a string data on dragboard, read it and use it */
Dragboard db = event.getDragboard();
boolean success = false;
if (db.hasString()) {
target.setText(db.getString());
success = true;
}
/* let the source know whether the string was successfully
* transferred and used */
event.setDropCompleted(success);
event.consume();
}
});
source.setOnDragDone(new EventHandler <DragEvent>() {
public void handle(DragEvent event) {
/* the drag-and-drop gesture ended */
System.out.println("onDragDone");
/* if the data was successfully moved, clear it */
if (event.getTransferMode() == TransferMode.MOVE) {
source.setText("");
}
event.consume();
}
});
root.getChildren().add(source);
root.getChildren().add(target);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
Actually Java have some ways to copy file: 4 Ways to Copy File in Java
Resource Link:
How do I properly handle file copy/cut & paste in javafx?
My solution which I tested successfully with Ubuntu:
FilesTransferable.scala
package myapp.clipboard
import java.awt.datatransfer._
case class FilesTransferable(files: Iterable[String]) extends Transferable {
val clipboardString: String = "copy\n" + files.map(path => s"file://$path").mkString("\n")
val dataFlavor = new DataFlavor("x-special/gnome-copied-files")
def getTransferDataFlavors(): Array[DataFlavor] = {
Seq(dataFlavor).toArray
}
def isDataFlavorSupported(flavor: DataFlavor): Boolean = {
dataFlavor.equals(flavor)
}
#throws(classOf[UnsupportedFlavorException])
#throws(classOf[java.io.IOException])
def getTransferData(flavor: DataFlavor): Object = {
if(flavor.getRepresentationClass() == classOf[java.io.InputStream]) {
new java.io.ByteArrayInputStream(clipboardString.getBytes())
} else {
return null;
}
}
}
Clipboard.scala
package myapp.clipboard
import java.awt.Toolkit
import java.awt.datatransfer._
object Clipboard {
def toClipboard(
transferable: Transferable,
lostOwnershipCallback: (Clipboard, Transferable) => Unit =
{ (clipboard: Clipboard, contents: Transferable) => Unit }
): Unit = {
Toolkit.getDefaultToolkit.getSystemClipboard.setContents(
transferable,
new ClipboardOwner {
def lostOwnership(clipboard: Clipboard, contents: Transferable): Unit = {
lostOwnershipCallback(clipboard, contents)
}
}
)
}
}
Quite a lot code for such a basic functionality (and still not OS independent).
Usage Example:
val transferable = FilesTransferable(filePaths)
myapp.clipboard.Clipboard.toClipboard(transferable, { (cb, contents) =>
println(s"lost clipboard ownership (clipboard: $cb)")
})
If it is true that there still do not exist any solutions which are part of Java-AWT/Swing/JavaFX/Scala-Swing/ScalaFX (actually, I still can't believe it), my plan is to build an easy-to-use clipboard library which supports at least OS X, Ubuntu and Windows.

Updating the color of rows of a TableView consumes too much CPU

I am making an application that receives alerts.
An alert can have 4 possible states:
Unresolved_New_0
Unresolved_New_1
Unresolved_Old
Resolved
When an alert is received, it is in Unresolved_New_0 state. For 10 seconds, every 0.5s the state changes from Unresolved_New_0 to Unresolved_New_1 and vice-versa. Depending on state I, set a different background color to the table row (so that it flashes, for 10s).
When the 10s pass, the alert transitions to Unresolved_Old state. This causes its color to stop changing.
To implement this, I have a ScheduledThreadPoolExecutor that I use to submit an implementation of Runnable that for some time executes a runnable using Platform.runLater.
static class FxTask extends Runnable {
/**
*
* #param runnableDuring Runnable to be run while the task is active (run on the JavaFX application thread).
* #param runnableAfter Runnable to be run after the given duration is elapsed (run on the JavaFX application thread).
* #param duration Duration to run this task for.
* #param unit Time unit.
*/
public static FxTask create(final Runnable runnableDuring, final Runnable runnableAfter, final long duration, final TimeUnit unit) {
return new FxTask(runnableDuring, runnableAfter, duration, unit);
}
#Override
public void run() {
if (System.nanoTime() - mTimeStarted >= mTimeUnit.toNanos(mDuration) )
{
cancel();
Platform.runLater(mRunnableAfter);
}
else
Platform.runLater(mRunnableDuring);
}
private FxTask(final Runnable during, final Runnable after, final long duration, final TimeUnit unit) {
mRunnableDuring = during;
mRunnableAfter = after;
mDuration = duration;
mTimeUnit = unit;
mTimeStarted = System.nanoTime();
}
private final Runnable mRunnableDuring;
private final Runnable mRunnableAfter;
private final long mDuration;
private final TimeUnit mTimeUnit;
private final long mTimeStarted;
}
And I schedule Alerts using that Runnable as follows:
final Alert alert = new Alert(...);
scheduler.scheduleAtFixedRate(FxTask.create(
() -> {
switch (alert.alertStateProperty().get()) {
case UNRESOLVED_NEW_0:
alert.alertStateProperty().set(Alert.State.UNRESOLVED_NEW_1);
refreshTable(mAlertsTable);
break;
case UNRESOLVED_NEW_1:
alert.alertStateProperty().set(Alert.State.UNRESOLVED_NEW_0);
refreshTable(mAlertsTable);
break;
}
},
() -> { // This is run at the end
if (equalsAny(alert.alertStateProperty().get(), Alert.State.UNRESOLVED_NEW_0, SpreadAlert.State.UNRESOLVED_NEW_1)) {
alert.alertStateProperty().set(Alert.State.UNRESOLVED_OLD);
refreshTable(mAlertsTable);
}
},
10, TimeUnit.SECONDS), 0, 500, TimeUnit.MILLISECONDS
);
Note: alertStateProperty() is not shown on the TableView (it is not bound to any of its columns).
So in order to force JavaFx to redraw, I have to use refreshTable(), which unfortunately redraws the whole table (?).
public static <T> void refreshTable(final TableView<T> table) {
table.getColumns().get(0).setVisible(false);
table.getColumns().get(0).setVisible(true);
}
The problem is that even if I create a small number of Alerts at the same time, CPU usage goes very high: from 20% to 84% sometimes, averaging at about 40%. When the 10s pass for all alerts, CPU consumptions returns to 0%. If I comment out refreshTable(), CPU stays near 0%, which indicates that it is the problem.
Why is so much CPU being used? (I have 8 cores by the way).
Is there another way to redraw just a single row without redrawing the whole table?
I even tried a 'hacky' method -- changing all values of the Alerts and then resetting them back to cause JavaFx to detect the change and redraw, but CPU was again at the same levels.
Probably the most efficient way to change the color of a table row is to use a table row factory, have the table row it creates observe the appropriate property, and update one or more CSS PseudoClass states as appropriate. Then just define the colors in an external css file.
Here's a standalone version of the application you described. I just used a Timeline to perform the "flashing new alerts", which is less code; but use the executor as you have it if you prefer. The key idea here is the table row factory, and the pseudoclass state it manipulates by observing the property. On my system, if I fill the entire table with new (flashing) rows, the CPU doesn't exceed about 35% (percentage of one core), which seems perfectly acceptable.
Note that PseudoClass was introduced in Java 8. In earlier versions of JavaFX you can achieve the same by manipulating the style classes instead, though you have to be careful not to duplicate any style classes as they are stored as a List. Anecdotally, the pseudoclass approach is more efficient.
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener.Change;
import javafx.css.PseudoClass;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.stage.Stage;
import javafx.util.Duration;
public class AlertTableDemo extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Alert> table = new TableView<>();
table.getColumns().add(createColumn("Name", Alert::nameProperty));
table.getColumns().add(createColumn("Value", Alert::valueProperty));
TableColumn<Alert, Alert> resolveCol =
createColumn("Resolve", ReadOnlyObjectWrapper<Alert>::new);
resolveCol.setCellFactory(this::createResolveCell);
table.getColumns().add(resolveCol);
// just need a wrapper really, don't need the atomicity...
AtomicInteger alertCount = new AtomicInteger();
Random rng = new Random();
Button newAlertButton = new Button("New Alert");
newAlertButton.setOnAction( event ->
table.getItems().add(new Alert("Alert "+alertCount.incrementAndGet(),
rng.nextInt(20)+1)));
// set psuedo-classes on table rows depending on alert state:
table.setRowFactory(tView -> {
TableRow<Alert> row = new TableRow<>();
ChangeListener<Alert.State> listener = (obs, oldState, newState) ->
updateTableRowPseudoClassState(row, row.getItem().getState());
row.itemProperty().addListener((obs, oldAlert, newAlert) -> {
if (oldAlert != null) {
oldAlert.stateProperty().removeListener(listener);
}
if (newAlert == null) {
clearTableRowPseudoClassState(row);
} else {
updateTableRowPseudoClassState(row, row.getItem().getState());
newAlert.stateProperty().addListener(listener);
}
});
return row ;
});
// flash new alerts:
table.getItems().addListener((Change<? extends Alert> change) -> {
while (change.next()) {
if (change.wasAdded()) {
List<? extends Alert> newAlerts =
new ArrayList<>(change.getAddedSubList());
flashAlerts(newAlerts);
}
}
});
HBox controls = new HBox(5, newAlertButton);
controls.setPadding(new Insets(10));
controls.setAlignment(Pos.CENTER);
BorderPane root = new BorderPane(table, null, null, controls, null);
Scene scene = new Scene(root, 800, 600);
scene.getStylesheets().add(
getClass().getResource("alert-table.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
private void flashAlerts(List<? extends Alert> newAlerts) {
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(0.5),
event -> {
for (Alert newAlert : newAlerts) {
if (newAlert.getState()==Alert.State.UNRESOLVED_NEW_0) {
newAlert.setState(Alert.State.UNRESOLVED_NEW_1);
} else if (newAlert.getState() == Alert.State.UNRESOLVED_NEW_1){
newAlert.setState(Alert.State.UNRESOLVED_NEW_0);
}
}
}));
timeline.setOnFinished(event -> {
for (Alert newAlert : newAlerts) {
if (newAlert.getState() != Alert.State.RESOLVED) {
newAlert.setState(Alert.State.UNRESOLVED_OLD);
}
}
});
timeline.setCycleCount(20);
timeline.play();
}
private void clearTableRowPseudoClassState(Node node) {
node.pseudoClassStateChanged(PseudoClass.getPseudoClass("unresolved-new"), false);
node.pseudoClassStateChanged(PseudoClass.getPseudoClass("unresolved-new-alt"), false);
node.pseudoClassStateChanged(PseudoClass.getPseudoClass("unresolved-old"), false);
node.pseudoClassStateChanged(PseudoClass.getPseudoClass("resolved"), false);
}
private void updateTableRowPseudoClassState(Node node, Alert.State state) {
node.pseudoClassStateChanged(PseudoClass.getPseudoClass("unresolved-new"),
state==Alert.State.UNRESOLVED_NEW_0);
node.pseudoClassStateChanged(PseudoClass.getPseudoClass("unresolved-new-alt"),
state==Alert.State.UNRESOLVED_NEW_1);
node.pseudoClassStateChanged(PseudoClass.getPseudoClass("unresolved-old"),
state==Alert.State.UNRESOLVED_OLD);
node.pseudoClassStateChanged(PseudoClass.getPseudoClass("resolved"),
state==Alert.State.RESOLVED);
}
private TableCell<Alert, Alert> createResolveCell(TableColumn<Alert, Alert> col) {
TableCell<Alert, Alert> cell = new TableCell<>();
Button resolveButton = new Button("Resolve");
resolveButton.setOnAction(event ->
cell.getItem().setState(Alert.State.RESOLVED));
cell.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
cell.setAlignment(Pos.CENTER);
cell.graphicProperty().bind(
Bindings.when(cell.emptyProperty())
.then((Node)null)
.otherwise(resolveButton));
return cell ;
}
private <S, T> TableColumn<S, T> createColumn(String title,
Function<S, ObservableValue<T>> propertyMapper) {
TableColumn<S,T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> propertyMapper.apply(cellData.getValue()));
col.setMinWidth(Region.USE_PREF_SIZE);
col.setPrefWidth(150);
return col ;
}
public static class Alert {
public enum State {
UNRESOLVED_NEW_0, UNRESOLVED_NEW_1, UNRESOLVED_OLD, RESOLVED
}
private final ObjectProperty<State> state = new SimpleObjectProperty<>();
private final StringProperty name = new SimpleStringProperty();
private final IntegerProperty value = new SimpleIntegerProperty();
public final ObjectProperty<State> stateProperty() {
return this.state;
}
public final AlertTableDemo.Alert.State getState() {
return this.stateProperty().get();
}
public final void setState(final AlertTableDemo.Alert.State state) {
this.stateProperty().set(state);
}
public final StringProperty nameProperty() {
return this.name;
}
public final java.lang.String getName() {
return this.nameProperty().get();
}
public final void setName(final java.lang.String name) {
this.nameProperty().set(name);
}
public final IntegerProperty valueProperty() {
return this.value;
}
public final int getValue() {
return this.valueProperty().get();
}
public final void setValue(final int value) {
this.valueProperty().set(value);
}
public Alert(String name, int value) {
setName(name);
setValue(value);
setState(State.UNRESOLVED_NEW_0);
}
}
public static void main(String[] args) {
launch(args);
}
}
alert-table.css:
.table-row-cell:resolved {
-fx-background: green ;
}
.table-row-cell:unresolved-old {
-fx-background: red ;
}
.table-row-cell:unresolved-new {
-fx-background: blue ;
}
.table-row-cell:unresolved-new-alt {
-fx-background: yellow ;
}