Part 3 - Using fyne.io to select files
Go has an io library that enables a developer to access the host file system. Building a GUI application that interacts with the native file system requires the developer to try to make the user experience the same, or similar, across platforms. We want a user to be able to work with the application without having to learn multiple ways to respond to application prompts to open files. Fortunately, fyne.io provides a fairly robust cross-platform toolset with which to accomplish this task.
Background and Review
In my journey learning Go, I’ve structured my efforts around an actual programming challenge. My longtime interest in CPU design and instruction sets led me to base my learning around a CPU simulator. Specifically, I set myself the problem of adding a GUI front end to an existing terminal-based CPU simulator.
I started my project with an imaginary CPU I found on GitHub. That simple instruction set helped me get familiar with the basics of Go and fyne.io. Once I had the base dashboard developed, I searched for a simulator for a more complex CPU. Fortunately, there was a comprehensive simulator for the 6502 8-bit processor with associated assembler and disassembler that has been actively maintained. The 6502 was the processor on which the original Apple computer was based. I forked a copy of that and continued my learning. A summary of that effort is described in my second post here.
Using the new fork of a 6502 simulator, I copied over my dashboard package and began studying the code. I don't know how other developers study and learn someone else's code, but my approach has been to first scan the entire repository and get a brief idea of the structure and components of the application. I review the README, if there is one, and then I try to compile and run the application. I've found that getting the environment properly configured and correcting missing dependencies, through often frustrating, forces me to learn a lot about the application and how it is built.
If you are lucky, the original developer will have left a few test cases to run. These definitely help in understanding how to use the various packages in the application. If you aren't that lucky, then start at the top and find the main.go package and follow how it sets up and executes the rest of the application.
In the case of the 6502 simulator, I found the CPU and Host structures, the methods that operate on them, and focused on how the elements of the CPU were specified and stored. After some study, I learned that the Host object and associated methods were called upon from a terminal session to invoke individual instructions and return results. I just had to simulate the commands to the CPU that would be sent from the terminal and generate them using GUI buttons. Output was redirected from stdout to my GUI scrolling component. The well-designed 6502 simulator made it relatively easy to integrate my dashboard.
The only thing missing from the updated GUI-based application was a means for the user to select files to compile and load rather than have to enter them on the command line. That is where the file picker project starts.
The File Picker
Fyne provides a Dialog package for use in creating various dialogs a user may need to conduct with an application. One specific to our needs is the FileDialog. It opens a dialog with the user allowing navigation through the local file system to select a specific file. On pressing Save, that file info is provided to the callback function that the developer specifies in the call to the dialog. If there is no error, then the application can use the resulting file URI in further processing.
Let's look at the details of what happens. I wrote a function in my dashboard I called showFilePicker(w Fyne.Window). It takes as an argument the Fyne.Window object used by the dashboard. I created a File button on the dashboard and associated showFilePicker(w Fyne.Window) as the callback function the window invokes whenever the File button is pressed. I also created a Label widget to display and contain the selected file and path. I also save the URI returned by the picker for later use.
var fileButton *widget.Button
var selectedFile *widget.Label
var fileURI fyne.URI
fileButton = widget.NewButton("File", func() { showFilePicker(w) })
When the user presses the File button, showFilePicker is activated and immediately invokes the Fyne dialog.ShowFileOpen method. You must import the dialog package at the head of your file.
import (
"fyne.io/fyne/v2/dialog"
)
//...
// Show file picker and return selected file
func showFilePicker(w fyne.Window) {
dialog.ShowFileOpen(func(f fyne.URIReadCloser, err error) {
saveFile := "NoFileYet"
if err != nil {
dialog.ShowError(err, w)
return
}
if f == nil {
return
}
saveFile = f.URI().Path()
fileURI = f.URI()
selectedFile.SetText(saveFile)
}, w)
}
The Fyne window invokes showFilePicker() when the File button is pressed. When showFilePicker is running, the rest of the GUI is blocked until it returns. showFilePicker starts a modal dialog by calling dialog.ShowFileOpen. As a modal dialog, the dashboard is blocked until it returns. ShowFileOpen returns a Fyne.URIReadCloser, which has numerous useful methods, and an error object. If error is nil, then we can process the selected file object.
I place the file path into the label widget by calling selectedFile.SetText(saveFile). I also preserve the returned URI in a variable I prepared called fileURI. I will use that URI to determine what type of file has been selected. Is it a .asm, assembly language file, or a .bin, assembled 6502 binary file? The Assemble and Load button callback functions use the URI to check the extension before trying to complete their actions.
Conclusions
Sure, this is a primitive file selector. It doesn't maintain state to know where we are in a specific directory and to start there instead of the user's home. However, because it is modal and blocking the GUI, we want to handle the file selection as efficiently as possible and return control to the window so it can respond to other buttons and actions.
So, that is all for now. I'd like to enhance the picker to enable selection of multiple files so we can assemble a batch at once. Likewise, loading multiple binaries into different areas of memory could be quite handy.
Please feel free to comment and suggest improvements or other topics of interest to a learner of Go and GUI development. Thanks for reading.
Top comments (0)