I have a login window view, and I want to display a progress bar when I click/press enter button while slick is querying the password. If I change the visible attribute for the progress bar at the button actionEvent it doesn´t appear until after the query is done. Also I don't want the progress bar to be taking space while its invisible. Does anybody know how to do these things?
object SPM extends JFXApp {
/*
* Primary stage: Log in
* */
stage = new PrimaryStage {
// error message hidden label
val errorLabel = new Label()
errorLabel.textFill = Color.Red
errorLabel.font = Font.font("Helvetica", FontWeight.ExtraLight, 12)
val usernameField = new TextField {
promptText = "User"
maxWidth = 250
prefHeight = 35
}
val passwordField = new PasswordField() {
promptText = "Password"
maxWidth = 250
prefHeight = 35
}
val progressBar = new ProgressBar {
maxWidth = 300
visible = false
}
title = "Software Project Management"
scene = new Scene(800, 600) {
root = new VBox {
spacing = 10
padding = Insets(20)
alignment = Pos.Center
children = List(
new ImageView {
image = new Image(
this.getClass.getResourceAsStream("/images/logo.png"))
margin = Insets(0, 0, 20, 0)
},
new Label {
text = "Software Project Management"
font = Font.font("Helvetica", FontWeight.ExtraLight, 32)
},
new Label {
text = "Sign in to get started"
font = Font.font("Helvetica", FontWeight.Thin, 18)
},
errorLabel,
progressBar,
usernameField,
passwordField,
new Button {
text = "Enter"
defaultButton = true
prefHeight = 35
font = Font.font("Helvetica", FontWeight.Thin, 18)
maxWidth = 250
onAction = (ae: ActionEvent) => {
progressBar.visible = true
val password = Users.checkPassword(usernameField.text.value)
if (password != passwordField.text.value)
errorLabel.text = "Please re-enter your password"
else root = chooseProject
}
}
) // children
} // root
} // scene
Your Button.onAction handler is running on JavaFX application thread. The same that is used to update UI. When you run long running task you should run it on a separate thread it will help UI to react properly. The common way to do that is to use JavaFX Task. General pattern is like this:
// Define your task
val task = new javafx.concurrent.Task[T] {
override def call(): T = {
// Do your task and return result
// Executed off JavaFX Application thread
}
override def succeeded(): Unit = {
// Update UI to finish processing
// Executed on JavaFX Application thread
}
override def failed(): Unit = {
// Handle errors, if any
// Executed on JavaFX Application thread
}
}
// Run your task
val t = new Thread(task, "My Task")
t.setDaemon(true)
t.start()
```
Here is how it could look in your code:
root = new VBox { _root =>
...
onAction = (ae: ActionEvent) => {
progressBar.visible = true
_root.disable = true
// progressBar.visible = true
val task = new javafx.concurrent.Task[Boolean] {
override def call(): Boolean = {
println("Checking password... ")
Thread.sleep(3000)
println("Password checked. ")
// Assume password is correct
true
}
override def succeeded(): Unit = {
progressBar.visible = false
_root.disable = false
val passwordOK = get()
if (passwordOK) {
new Alert(AlertType.Information) {
headerText = "Password OK"
}.showAndWait()
} else {
new Alert(AlertType.Warning) {
headerText = "Invalid Password"
}.showAndWait()
}
}
override def failed(): Unit = {
println("failed")
progressBar.visible = false
_root.disable = false
}
}
val t = new Thread(task, "Password Task")
t.setDaemon(true)
t.start()
}
Related
How can a Snackbar be shown above a Dialog or AlertDialog in Jetpack Compose? Everything I have tried has resulted in the snack bar being below the scrim of the dialog not to mention the dialog itself.
According to Can I display material design Snackbar in dialog? it is possible in non-Compose Android by using a custom or special (like getDialog().getWindow().getDecorView()) view, but that isn't accessible from Compose I believe (at least not without a lot of effort).
I came up with a solution that mostly works. It uses the built-in Snackbar() composable for the rendering but handles the role of SnackbarHost() with a new function SnackbarInDialogContainer().
Usage example:
var error by remember { mutableStateOf<String?>(null) }
AlertDialog(
...
text = {
...
if (error !== null) {
SnackbarInDialogContainer(error, dismiss = { error = null }) {
Snackbar(it, Modifier.padding(WindowInsets.ime.asPaddingValues()))
}
}
}
...
)
It has the following limitations:
Has to be used in place within the dialog instead of at the top level
There is no host to queue messages, instead that has to be handled elsewhere if desired
Dismissal is done with a callback (i.e. { error = null} above) instead of automatically
Actions currently do nothing at all, but that could be fixed (I had no use for them, the code do include everything necessary to render the actions I believe, but none of the interaction).
This has built-in support for avoiding the IME (software keyboard), but you may still need to follow https://stackoverflow.com/a/73889690/582298 to make it fully work.
Code for the Composable:
#Composable
fun SnackbarInDialogContainer(
text: String,
actionLabel: String? = null,
duration: SnackbarDuration =
if (actionLabel == null) SnackbarDuration.Short else SnackbarDuration.Indefinite,
dismiss: () -> Unit,
content: #Composable (SnackbarData) -> Unit
) {
val snackbarData = remember {
SnackbarDataImpl(
SnackbarVisualsImpl(text, actionLabel, true, duration),
dismiss
)
}
val dur = getDuration(duration, actionLabel)
if (dur != Long.MAX_VALUE) {
LaunchedEffect(snackbarData) {
delay(dur)
snackbarData.dismiss()
}
}
val popupPosProvider by imeMonitor()
Popup(
popupPositionProvider = popupPosProvider,
properties = PopupProperties(clippingEnabled = false),
) {
content(snackbarData)
}
}
#Composable
private fun getDuration(duration: SnackbarDuration, actionLabel: String?): Long {
val accessibilityManager = LocalAccessibilityManager.current
return remember(duration, actionLabel, accessibilityManager) {
val orig = when (duration) {
SnackbarDuration.Short -> 4000L
SnackbarDuration.Long -> 10000L
SnackbarDuration.Indefinite -> Long.MAX_VALUE
}
accessibilityManager?.calculateRecommendedTimeoutMillis(
orig, containsIcons = true, containsText = true, containsControls = actionLabel != null
) ?: orig
}
}
/**
* Monitors the size of the IME (software keyboard) and provides an updating
* PopupPositionProvider.
*/
#Composable
private fun imeMonitor(): State<PopupPositionProvider> {
val provider = remember { mutableStateOf(ImePopupPositionProvider(0)) }
val context = LocalContext.current
val decorView = remember(context) { context.getActivity()?.window?.decorView }
if (decorView != null) {
val ime = remember { WindowInsetsCompat.Type.ime() }
val bottom = remember { MutableStateFlow(0) }
LaunchedEffect(Unit) {
while (true) {
bottom.value = ViewCompat.getRootWindowInsets(decorView)?.getInsets(ime)?.bottom ?: 0
delay(33)
}
}
LaunchedEffect(Unit) {
bottom.collect { provider.value = ImePopupPositionProvider(it) }
}
}
return provider
}
/**
* Places the popup at the bottom of the screen but above the keyboard.
* This assumes that the anchor for the popup is in the middle of the screen.
*/
private data class ImePopupPositionProvider(val imeSize: Int): PopupPositionProvider {
override fun calculatePosition(
anchorBounds: IntRect, windowSize: IntSize,
layoutDirection: LayoutDirection, popupContentSize: IntSize
) = IntOffset(
anchorBounds.left + (anchorBounds.width - popupContentSize.width) / 2, // centered on screen
anchorBounds.top + (anchorBounds.height - popupContentSize.height) / 2 + // centered on screen
(windowSize.height - imeSize) / 2 // move to the bottom of the screen
)
}
private fun Context.getActivity(): Activity? {
var currentContext = this
while (currentContext is ContextWrapper) {
if (currentContext is Activity) {
return currentContext
}
currentContext = currentContext.baseContext
}
return null
}
private data class SnackbarDataImpl(
override val visuals: SnackbarVisuals,
val onDismiss: () -> Unit,
) : SnackbarData {
override fun performAction() { /* TODO() */ }
override fun dismiss() { onDismiss() }
}
private data class SnackbarVisualsImpl(
override val message: String,
override val actionLabel: String?,
override val withDismissAction: Boolean,
override val duration: SnackbarDuration
) : SnackbarVisuals
This is a to-do list application that i made as a beginner. However, there is an error that says missing parameter type in this line of code:
todoTable.selectionModel().selectedItem.onChange(
(_, newValue) => showTodoDetails(Some(newValue))
)
I can't figure out what the problem is as I had no issue running with it earlier. And it also seems to be the only error that keeps me from finishing the to-do application program.
Below is the file that is having an error
package ch.makery.address.view
import ch.makery.address.model.Todo
import ch.makery.address.MainApp
import scalafx.scene.control.{Alert, Label, TableColumn, TableView}
import scalafxml.core.macros.sfxml
import scalafx.beans.property.StringProperty
import scala.util.{Failure, Success}
import scalafx.Includes._
import scalafx.event.ActionEvent
import scalafx.scene.control.Alert.AlertType
#sfxml
class TodoOverviewController(
private val todoTable : TableView[Todo],
private val titleColumn : TableColumn[Todo, String],
private val titleLabel : Label,
private val descriptionLabel : Label
) {
// initialize Table View display contents model
todoTable.items = MainApp.todoData
// initialize columns's cell values
titleColumn.cellValueFactory = {_.value.title}
showTodoDetails(None);
todoTable.selectionModel().selectedItem.onChange(
(_, newValue) => showTodoDetails(Some(newValue))
)
private def showTodoDetails (todo : Option[Todo]) = {
todo match {
case Some(todo) =>
// Fill the labels with info from the todo object.
titleLabel.text <== todo.title
descriptionLabel.text <== todo.description
case None =>
// todo is null, remove all the text.
titleLabel.text = ""
descriptionLabel.text = ""
}
}
def handleNewTodo(action : ActionEvent) = {
val todo = new Todo("")
val okClicked = MainApp.showTodoEditDialog(todo);
if (okClicked) {
todo.save() match {
case Success(x) =>
MainApp.todoData += todo
case Failure(e) =>
val alert = new Alert(Alert.AlertType.Warning) {
initOwner(MainApp.stage)
title = "Failed to Save"
headerText = "Database Error"
contentText = "Database problem filed to save changes"
}.showAndWait()
}
}
}
def handleEditTodo(action : ActionEvent) = {
val selectedTodo = todoTable.selectionModel().selectedItem.value
if (selectedTodo != null) {
val okClicked = MainApp.showTodoEditDialog(selectedTodo)
if (okClicked) {
selectedTodo.save() match {
case Success(x) =>
showTodoDetails(Some(selectedTodo))
case Failure(e) =>
val alert = new Alert(Alert.AlertType.Warning) {
initOwner(MainApp.stage)
title = "Failed to Save"
headerText = "Database Error"
contentText = "Database problem filed to save changes"
}.showAndWait()
}
}
} else {
// Nothing selected.
val alert = new Alert(Alert.AlertType.Warning){
initOwner(MainApp.stage)
title = "No Selection"
headerText = "No Todo Selected"
contentText = "Please select a todo in the table."
}.showAndWait()
}
}
def handleDeleteTodo(action : ActionEvent) = {
val selectedIndex = todoTable.selectionModel().selectedIndex.value
val selectedTodo = todoTable.selectionModel().selectedItem.value
if (selectedIndex >= 0) {
selectedTodo.save() match {
case Success(x) =>
todoTable.items().remove(selectedIndex);
case Failure(e) =>
val alert = new Alert(Alert.AlertType.Warning) {
initOwner(MainApp.stage)
title = "Failed to Save"
headerText = "Database Error"
contentText = "Database problem filed to save changes"
}.showAndWait()
}
} else {
// Nothing selected.
val alert = new Alert(AlertType.Warning){
initOwner(MainApp.stage)
title = "No Selection"
headerText = "No Todo Selected"
contentText = "Please select a todo in the table."
}.showAndWait()
}
}
}
The function passed to onChange takes 3 arguments, not 2, you should have
todoTable.selectionModel().selectedItem.onChange(
(_, _, newValue) => showTodoDetails(Some(newValue))
)
I want to chain title and description text centered with respect to image with chainStyle.Packed how to achieve this in jetpack compose.
when i use createVerticalChain() its create chain with respect to parent container that's not what i want, is there a way to achieve this?
Like suggested inside the documentation of createVerticalChain(), you can "Use constrain with the resulting VerticalChainReference to modify the top and bottom constraints of this chain.":
ConstraintLayout(modifier = Modifier.fillMaxSize()) {
val (box, text1, text2) = createRefs()
val chainRef = createVerticalChain(text1, text2, chainStyle = ChainStyle.Packed)
constrain(chainRef) {
top.linkTo(box.top)
bottom.linkTo(box.bottom)
}
Box(modifier = Modifier
.size(100.dp)
.padding(16.dp)
.background(color = Color.Red, shape = RoundedCornerShape(50.dp))
.constrainAs(box) {
start.linkTo(parent.start)
top.linkTo(parent.top)
}
)
Text("Line 1 goes here",
modifier = Modifier
.constrainAs(text1) {
start.linkTo(box.end)
end.linkTo(parent.end)
top.linkTo(box.top)
bottom.linkTo(text2.top)
width = Dimension.fillToConstraints
}
)
Text("Line 2 goes here",
modifier = Modifier
.constrainAs(text2) {
start.linkTo(box.end)
end.linkTo(parent.end)
top.linkTo(text1.bottom)
bottom.linkTo(box.bottom)
width = Dimension.fillToConstraints
}
)
}
There are two solutions. The first solution requires that the height of the content on the right of the circle is fixed, while in the second solution, it is not fixed but is bound by constraints:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startActivity(intent)
setContent {
Column(modifier = Modifier.fillMaxSize()) {
// First solution
Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
) {
Box(
modifier = Modifier
.size(100.dp)
.background(color = Color.Red, shape = RoundedCornerShape(50.dp))
)
Column(
modifier = Modifier
.height(100.dp)
.padding(start = 20.dp), verticalArrangement = Arrangement.Center
) {
Text("Line 1 goes here")
Text("Line 2 goes here")
}
}
Spacer(modifier = Modifier
.requiredHeight(30.dp)
.fillMaxWidth())
// Second solution
ConstraintLayout(modifier = Modifier.fillMaxWidth()) {
val (left, right) = createRefs()
Box(modifier = Modifier
.size(100.dp)
.background(color = Color.Red, shape = RoundedCornerShape(50.dp))
.constrainAs(left) {
start.linkTo(parent.start)
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
})
Column(
verticalArrangement = Arrangement.Center,
modifier = Modifier
.padding(start = 20.dp)
.constrainAs(right) {
start.linkTo(left.end)
top.linkTo(left.top)
end.linkTo(parent.end)
bottom.linkTo(left.bottom)
width = Dimension.fillToConstraints
height = Dimension.fillToConstraints
}) {
Text("Line 1 goes here")
Text("Line 2 goes here")
}
}
}
}
}
}
I have created a tableview with their components inside it, assigned cellValueFactory and have set the properties editable to true. Somewhere in my code, I have the following :
...
tableID.selectionModel().selectedItem.onChange(
(_, _, newValue) => col_uname.setCellFactory(TextFieldTableCell.forTableColumn());
...
With it, I managed to create to convert it to textfield and are allowed to type in it. However,after finishing typing, the text reversed back to previous text before the edit. What type/piece of code should I include make sure that the text is updated properly?
I've tried searching on google, but there's no explanation for it so far.
You should be able to edit table by, as you mentioned, editable = true and adding cell factory with a text field, for instance:
new TableColumn[Person, String] {
text = "First Name"
cellValueFactory = {_.value.firstName}
cellFactory = TextFieldTableCell.forTableColumn()
prefWidth = 180
}
The JavaFX Table View Tutorial also suggests using OnEditCommit. Not sure if that is really necessary. Here is a complete example that works without using OnEditCommit:
import scalafx.application.JFXApp
import scalafx.application.JFXApp.PrimaryStage
import scalafx.beans.property.StringProperty
import scalafx.collections.ObservableBuffer
import scalafx.event.ActionEvent
import scalafx.scene.Scene
import scalafx.scene.control.TableColumn._
import scalafx.scene.control.cell.TextFieldTableCell
import scalafx.scene.control.{Button, TableColumn, TableView}
import scalafx.scene.layout.VBox
object EditableTableView extends JFXApp {
class Person(firstName_ : String, lastName_ : String) {
val firstName = new StringProperty(this, "firstName", firstName_)
val lastName = new StringProperty(this, "lastName", lastName_)
firstName.onChange { (_, oldValue, newValue) => println(s"Value changed from `$oldValue` to `$newValue`") }
lastName.onChange { (_, oldValue, newValue) => println(s"Value changed from `$oldValue` to `$newValue`") }
override def toString = firstName() + " " + lastName()
}
val characters = ObservableBuffer[Person](
new Person("Peggy", "Sue"),
new Person("Rocky", "Raccoon")
)
stage = new PrimaryStage {
title = "Editable Table View"
scene = new Scene {
root = new VBox {
children = Seq(
new TableView[Person](characters) {
editable = true
columns ++= List(
new TableColumn[Person, String] {
text = "First Name"
cellValueFactory = {_.value.firstName}
cellFactory = TextFieldTableCell.forTableColumn()
prefWidth = 180
},
new TableColumn[Person, String]() {
text = "Last Name"
cellValueFactory = {_.value.lastName}
cellFactory = TextFieldTableCell.forTableColumn()
prefWidth = 180
}
)
},
new Button {
text = "Print content"
onAction = (ae: ActionEvent) => {
println("Characters:")
characters.foreach(println)
}
}
)
}
}
}
}
//Main.scala
/* imports */
object Main extends SimpleSwingApplication {
lazy val ui = new TabbedPane {
/* contents */
}
def top = new MainFrame {
/* contents */
}
override def startup(args: Array[String]) {
val t = top
val loginStatus = new Login(t).status
if (loginStatus == true) {
if (t.size == new Dimension(0, 0)) t.pack
t.visible = true
} else
quit
}
}
//Login.scala
class Login(owner: Window) extends Dialog(owner) {
import Login._
var status = true
contents = ui
listenTo(login) //login is a Button
reactions += {
case ButtonClicked(login) => {
if (/* login field is empty */)
status = false
else if (/* login info is correct */)
status = true
else /*login info is wrong*/
status = false
}
}
}
How can I have 'Main' to wait for 'Login' before displaying itself?
Use a CountDownLatch:
//Main.scala
/* imports */
object Main extends SimpleSwingApplication {
private val latch = new CountDownLatch(1)
lazy val ui = new TabbedPane {
/* contents */
}
def top = new MainFrame {
/* contents */
}
override def startup(args: Array[String]) {
val t = top
val loginDialog = new Login(t, latch)
latch.await
val loginStatus = loginWindow.status
if (loginStatus == true) {
if (t.size == new Dimension(0, 0)) { t.pack }
t.visible = true
} else
quit
}
}
//Login.scala
class Login(owner: Window, latch: CountDownLatch) extends Dialog(owner) {
import Login._
var status = true
contents = ui
listenTo(login) //login is a Button
reactions += {
case ButtonClicked(login) => {
latch.countdown
if (/* login field is empty */)
status = false
else if (/* login info is correct */)
status = true
else /*login info is wrong*/
status = false
}
}
}