A first window app

suggest change

Let's build a simple app that shows a list of files in a list view and has a search box for filtering results:

Full source is here with the most relevant code in file_manager.go.

The app is the simplest file manager. It shows the files in the directory, allows navigating file system and limiting files shown to only those matching text entered in the edit control.

Showing all files in directory:

Showing only files matching go:

Defining window layout

Let's define FileManager struct responsible for managing of the window:

// FileManager manages file manager window
type FileManager struct {
	window         *walk.MainWindow
	fileFilterEdit *walk.LineEdit
	dirLabel       *walk.Label
	filesListBox   *walk.ListBox
	// ... more fields
}

The window consists of 3 widgets, arranged in a vertical list:

Walk has 2 layers:

Here's a declarative layout of the above window:

var fm FileManager
def := declarative.MainWindow{
	AssignTo: &fm.window,
	Title:    "File Manageer",
	MinSize:  declarative.Size{Width: 240, Height: 200},
	Size:     declarative.Size{Width: 320, Height: 400},
	Layout:   declarative.VBox{},
	Children: []declarative.Widget{
		declarative.LineEdit{
			AssignTo:      &fm.fileFilterEdit,
			Visible:       true,
			CueBanner:     "Enter file filter",
			OnTextChanged: fm.onFilterChanged,
			OnKeyUp: func(key walk.Key) {
				if key == walk.KeyEscape {
					fm.fileFilterEdit.SetText("")
				}
			},
		},
		declarative.Label{
			AssignTo: &fm.dirLabel,
			Visible:  true,
			Text:     "Directory:",
		},
		declarative.ListBox{
			AssignTo:        &fm.filesListBox,
			Visible:         true,
			OnItemActivated: fm.onFileClicked,
		},
	},
}

Each widget has a corresponding declarative definition e.g. declarative.LineEdit corresponds to walk.LineEdit.

A declarative.MainWindow corresponds to top-level window.

Some widgets, like MainWindow can have children (which are also widgets). For those widgets Layout specifies how children are arranged inside the parent. We use a VBox layout which arranges widgets in a vertical list.

Creating a window

To create window and its widgets we call:

err := def.Create()
if err != nil {
	return nil, err
}

This creates actual widgets based on declarative definition. During creation we can set initial properties on the widget and provide handlers for events generated by widgets.

AssignTo will set a given variable to a created widget, which allows us to manipulate the real widget in the future.

Visible allows to set the initial visibility state of the widget.

Text specifies initial text of Label widget.

CueBanner is a text displayed in LineEdit when its text is empty and the control doesn't have focus.

OnTextChanged is an event handler that will be called when text in LineEdit changes.

OnItemActivated is an event handler that will be called when user double-clicks on a list item.

Running event loop

Windows GUI is event based. After we create a window, we need to run event loop which ensures processing of window messages:

_ = fm.window.Run()

Event loop finishes when the user closes the window.

Further execution of program logic happens as a result of responding to events generated by the user interacting with the widgets.

Thread-safe access to widget via synchronization

On Windows only the thread that created a given window / control can safely operate on this window / control. In most cases it's the main thread of the application, called UI thread.

Walk inherits this limitation. Calling any method on a widget needs to be done on UI thread. If the code runs in a goroutine and manipulates widgets, it must be executed on the right thread. Use widget.Synchronize(fn) to schedule arbitrary function to be executed on UI thread.

Crashes are often a result of not observing that rule.

Handling events

Some widgets broadcast events based on user interaction. We can provide functions to be called when an event happens.

LineEdit widget broadcasts OnTextChanged event when a text in edit widget changes. In our application we retrieve the text and use it to filter list of files to only show those that match the text:

func (fm *FileManager) onFilterChanged() {
	filter := fm.fileFilterEdit.Text()
	fm.applyFileFilter(filter)
}

ListBox widget broadcasts OnItemActivated event when a list item is activated e.g. with mouse double-click. We can retrieve the item selected with:

idx := fm.filesListBox.CurrentIndex()

In our case we use it to navigate the directory hierarchy when user double-clicks a directory name.

Setting the content of a label

A Label is a simple widget that displays a line of text, which we can set with:

fm.dirLabel.SetText(s)

Setting the content of ListBox

Some widgets, like ListBox or TreeView show more complicated content like a list or tree of items.

For ListBox the content is abstracted as ListModel. It's an interface that you implement to provide information about the list data.

Walk handles simple cases for us. For example we can use a slice of strings []string{} as a model:

model := []string{"item1", "item2"}
listBox.SetModel(model)

Application logic

You can study the rest of the code to see how to tie different pieces together.

The high-level description is:

Compiling as a windows application

Compiling the program as a valid Windows application is a bit more involved than a regular Go program.

For modern Windows applications you need a manifest. You can re-use this manifest for most apps.

In short it declares that the application is compatible with all Windows versions and that it opts in to modern look of controls by asking for version 6 of common controls library.

A manifest must be embedded into an .exe as a resource.

To achieve that:

The argument ldflags="-H windowsgui" to go build passes -H windowsgui flag to the linker.

This flag marks executable as a Windows GUI application (as opposed to the default console application).

If you don't embed the manifest in the application, it might fail to start in one of two ways:

Feedback about page:

Feedback:
Optional: your email if you want me to get back to you:



Table Of Contents