Open virogu opened 3 years ago
I have tried to use the JFileChooser() like this. It did achieve the effect I wanted. But it will be very stuck when it is displayed, and it will take a long time for the file selection box to pop up.
`
val jfc = JFileChooser()
val path = preferences[prefKey, ""]
if (path.isNotEmpty() && File(path).exists()) {
jfc.currentDirectory = File(path)
} else {
jfc.currentDirectory = projectRootPath
}
jfc.fileSelectionMode = fileChooserType
jfc.fileFilter = object : FileFilter() {
override fun accept(f: File): Boolean {
return when (fileChooserType) {
JFileChooser.FILES_ONLY -> {
f.isDirectory || f.name.endsWith(filesFilter)
}
JFileChooser.FILES_AND_DIRECTORIES -> {
f.isDirectory || (f.isFile && f.name.endsWith(filesFilter))
}
JFileChooser.DIRECTORIES_ONLY -> {
f.isDirectory
}
else -> {
false
}
}
}
override fun getDescription(): String {
return description
}
}
val status = jfc.showOpenDialog(null)
`
Hello,
I found myself with the same lens just a few days ago. After asking the question (and getting a link to one of the examples that uses jswing dialogs as an answer) I wrote this function :
class Dialog {
enum class Mode { LOAD, SAVE }
}
@OptIn(DelicateCoroutinesApi::class)
@Composable
fun WindowScope.DialogFile(
mode:Dialog.Mode = Dialog.Mode.LOAD,
title:String = "Choisissez un fichier",
extensions:List<FileNameExtensionFilter> = listOf(),
onResult: (files: List<File>) -> Unit
) {
DisposableEffect(Unit) {
val job = GlobalScope.launch(Dispatchers.Swing) {
val fileChooser = JFileChooser()
fileChooser.dialogTitle = title
fileChooser.isMultiSelectionEnabled = mode == Dialog.Mode.LOAD
fileChooser.isAcceptAllFileFilterUsed = extensions.isEmpty()
extensions.forEach { fileChooser.addChoosableFileFilter(it) }
var returned:Int;
if(mode == Dialog.Mode.LOAD) {
returned = fileChooser.showOpenDialog(window)
}
else {
returned = fileChooser.showSaveDialog(window)
}
onResult(when(returned) {
JFileChooser.APPROVE_OPTION -> {
if(mode == Dialog.Mode.LOAD) {
val files = fileChooser.selectedFiles.filter { it.canRead() }
if(files.isNotEmpty()) {
files
}
else {
listOf()
}
} else {
if(!fileChooser.fileFilter.accept(fileChooser.selectedFile)) {
val ext = (fileChooser.fileFilter as FileNameExtensionFilter).extensions[0]
fileChooser.selectedFile = File(fileChooser.selectedFile.absolutePath + ".$ext")
}
listOf(fileChooser.selectedFile)
}
};
else -> listOf();
})
}
onDispose {
job.cancel()
}
}
}
However, as it is @Composable
, it is not possible to use it in a callback (like the onClick function of a button for example).
Usage (in this example I use a window to retrieve one or more PDF files) :
DialogFile(mode = Dialog.Mode.LOAD, title = "Your dialog title", extensions = listOf(FileNameExtensionFilter("PDF Files", "pdf"))) { files ->
// files is a List<File> object
}
In LOAD mode, you can select several documents, as long as they exist. It returns a list of File objects corresponding to all the selected files and accessible in reading.
In SAVE mode, you can either choose a file that exists or type in the name you want. If the file does not have an extension that matches the currently selected filter, the extension is added to the entered name. It returns a list of a File object corresponding to the selected file (existing or not). If you want to check if this file exists before overwriting it, you have to do this check yourself using the canRead() method of the File class for example.
And to make your dialog box look native (and this is true for all the swing dialog boxes I've tested so far), just add this at the beginning of your main function :
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName())
@virogu does it work for you?
Hello,
I found myself with the same lens just a few days ago. After asking the question (and getting a link to one of the examples that uses jswing dialogs as an answer) I wrote this function :
class Dialog { enum class Mode { LOAD, SAVE } } @OptIn(DelicateCoroutinesApi::class) @Composable fun WindowScope.DialogFile( mode:Dialog.Mode = Dialog.Mode.LOAD, title:String = "Choisissez un fichier", extensions:List<FileNameExtensionFilter> = listOf(), onResult: (files: List<File>) -> Unit ) { DisposableEffect(Unit) { val job = GlobalScope.launch(Dispatchers.Swing) { val fileChooser = JFileChooser() fileChooser.dialogTitle = title fileChooser.isMultiSelectionEnabled = mode == Dialog.Mode.LOAD fileChooser.isAcceptAllFileFilterUsed = extensions.isEmpty() extensions.forEach { fileChooser.addChoosableFileFilter(it) } var returned:Int; if(mode == Dialog.Mode.LOAD) { returned = fileChooser.showOpenDialog(window) } else { returned = fileChooser.showSaveDialog(window) } onResult(when(returned) { JFileChooser.APPROVE_OPTION -> { if(mode == Dialog.Mode.LOAD) { val files = fileChooser.selectedFiles.filter { it.canRead() } if(files.isNotEmpty()) { files } else { listOf() } } else { if(!fileChooser.fileFilter.accept(fileChooser.selectedFile)) { val ext = (fileChooser.fileFilter as FileNameExtensionFilter).extensions[0] fileChooser.selectedFile = File(fileChooser.selectedFile.absolutePath + ".$ext") } listOf(fileChooser.selectedFile) } }; else -> listOf(); }) } onDispose { job.cancel() } } }
However, as it is
@Composable
, it is not possible to use it in a callback (like the onClick function of a button for example).Usage (in this example I use a window to retrieve one or more PDF files) :
DialogFile(mode = Dialog.Mode.LOAD, title = "Your dialog title", extensions = listOf(FileNameExtensionFilter("PDF Files", "pdf"))) { files -> // files is a List<File> object }
In LOAD mode, you can select several documents, as long as they exist. It returns a list of File objects corresponding to all the selected files and accessible in reading.
In SAVE mode, you can either choose a file that exists or type in the name you want. If the file does not have an extension that matches the currently selected filter, the extension is added to the entered name. It returns a list of a File object corresponding to the selected file (existing or not). If you want to check if this file exists before overwriting it, you have to do this check yourself using the canRead() method of the File class for example.
And to make your dialog box look native (and this is true for all the swing dialog boxes I've tested so far), just add this at the beginning of your main function :
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName())
Thanks for your advice The main problem I encountered when using JFileChooser() was lag. I needed to use setCurrentDirectory() to set a default open path. Setting this would cause the dialog box to pop up for a long time, especially when there are many files in the folder.
Indeed, JFileChooser tends to lag a bit, mainly on Windows - but file explorer in general tends to lag on my computer too. I used it for my first application to allow the selection of PDF files (my application is used to merge PDFs which can be of different page formats). I also tested it on macOS and two Linux (Fedora and Debian), I had less lags (I had lags, but it's on virtual machines and it wasn't unusual compared to other actions I did on these machines).
I think any FileChooser application will lag in a huge folder, because it needs to read all files before displaying due to sort order.
I think any FileChooser application will lag in a huge folder, because it needs to read all files before displaying due to sort order.
I have changed my computer system to Win10, and now it won’t be so lagging when it opens. Before that, it was Win11. I guess JFileChooser() will be more lagging on win11.
It's true that I hadn't thought about it until now, but on my Windows (also 10), the JFileChooser opens in my My Documents folder, which contains almost 500 items (folders or files) not counting what the folders inside contain, whereas on my virtual machines, of course, I have a lot less stuff (the file chooser opens in the user's folder, which contains about ten folders at most).
So that explains it, even if browsing on Windows, in a sub-folder of My Documents, which contains only a few folders, is also quite slow after all. I think that the list of files of the folders already browsed is maybe kept somewhere in memory, which would explain why a folder that doesn't contain many subfolders and files lags so much after arriving from a folder that contained so many items.
I use it like this:
@Composable
fun FileChooserDialog(
title: String,
onResult: (result: File) -> Unit
) {
val fileChooser = JFileChooser(FileSystemView.getFileSystemView())
fileChooser.currentDirectory = File(System.getProperty("user.dir"))
fileChooser.dialogTitle = title
fileChooser.fileSelectionMode = JFileChooser.FILES_AND_DIRECTORIES
fileChooser.isAcceptAllFileFilterUsed = true
fileChooser.selectedFile = null
fileChooser.currentDirectory = null
if (fileChooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
val file = fileChooser.selectedFile
println("choose file or folder is: $file")
onResult(file)
} else {
// onResult(java.io.File(""))
println("No Selection ")
}
}
@Genie23 if you can't use it in a callback, I don't understand how I can even use it at all (probably I'm missing something fundamental about compose here)
@Genie23 if you can't use it in a callback, I don't understand how I can even use it at all (probably I'm missing something fundamental about compose here)
@IARI It's rather hacky, but based on https://github.com/JetBrains/compose-jb/issues/1003#issuecomment-1001187264 you can do something like
@Composable
@Preview
fun App() {
var text by remember { mutableStateOf("select file") }
var selectedFileString by remember { mutableStateOf("") }
MaterialTheme {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(onClick = {
selectedFileString = fileChooserDialog(text)
}) {
Text(text)
}
Text(text = selectedFileString.ifBlank { "no file selected" })
}
}
}
fun main() = application {
Window(onCloseRequest = ::exitApplication) {
App()
}
}
fun fileChooserDialog(
title: String?
): String {
val fileChooser = JFileChooser(FileSystemView.getFileSystemView())
fileChooser.currentDirectory = File(System.getProperty("user.dir"))
fileChooser.dialogTitle = title
fileChooser.fileSelectionMode = JFileChooser.FILES_AND_DIRECTORIES
fileChooser.isAcceptAllFileFilterUsed = true
fileChooser.selectedFile = null
fileChooser.currentDirectory = null
val file = if (fileChooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
fileChooser.selectedFile.toString()
} else {
""
}
return file
}
I've implemented a rudimentary file chooser in compose, that serves my purpose. It's a work-in-progress, though, but is there any chance that we might see a standard-compose file chooser in the future?
I found a way to use native dialogs in compose - https://github.com/fourlastor/nla-editor/pull/17 this PR uses LWJGL's native folder dialogs implementation to show the user a native file picker
It seems that this error has nothing to do with "JFileChooser". " java.lang.IllegalStateException: Already resumed, but proposed with update kotlin.Unit" This error is thrown out by the ”suspendCancellableCoroutine“ method,It usually occurs when repeatedly calls the resume method or call the "resume" method after the scope has been canceled, like this
suspend fun test() = suspendCancellableCoroutine<String> { it.resume("") it.resume("") // Maybe throw this error }
some code in 'CancellableContinuationImpl'
`
private fun resumeImpl(
proposedUpdate: Any?,
resumeMode: Int,
onCancellation: ((cause: Throwable) -> Unit)? = null
) {
_state.loop { state ->
when (state) {
is NotCompleted -> {
val update = resumedState(state, proposedUpdate, resumeMode, onCancellation, idempotent = null)
if (!_state.compareAndSet(state, update)) return@loop // retry on cas failure
detachChildIfNonResuable()
dispatchResume(resumeMode) // dispatch resume, but it might get cancelled in process
return // done
}
is CancelledContinuation -> {
/*
Racy exceptions will be lost, too. */ if (state.makeResumed()) { // check if trying to resume one (otherwise error) // call onCancellation onCancellation?.let { callOnCancellation(it, state.cause) } return // done } } } alreadyResumedError(proposedUpdate) // otherwise, an error (second resume attempt) } }
private fun alreadyResumedError(proposedUpdate: Any?): Nothing { error("Already resumed, but proposed with update $proposedUpdate") }
` ”at androidx.compose.runtime.BroadcastFrameClock$FrameAwaiter.resume(BroadcastFrameClock.kt:42)“
It may be a problem with the compose itself
Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.
How to use a FileChooser() in Compose.
The FileDialog() function can not do what i want, it can not select a dictionary or select file wih a filter