Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 3 из 25 ~20 мин +50 XP

Виджеты: базовые

Окно научились открывать — пора его населить. Базовые fyne виджеты: Label для текста, Button для действий, Entry для ввода, Check и Select для выбора, ProgressBar для прогресса. У каждого — фабрика NewXxx, сеттер вместо прямого поля и один способ обновления через Refresh. Плюс правило, без которого первые формы выглядят застрявшими: меняешь виджет — вызывай сеттер.

Содержание урока · 13 разделов

Во втором уроке мы собрали окно: Resize, SetFixedSize, перехват закрытия, живой цикл событий. Только внутри него пока скучно — та же метка с кнопкой из hello world. Здесь мы пройдём по каталогу из шести элементов — это базовые fyne виджеты: Label, Button, Entry, Check и Select, плюс ProgressBar. Их хватит для подавляющего большинства внутренних утилит: панели управления, формы настроек, мониторы статусов. Если гуглить тот же список по-английски — fyne widgets — ты попадёшь на страницу explore/widgets в документации, где живут все элементы тулкита; сегодня мы берём из неё ядро. У урока есть сквозная идея, одна на все шесть виджетов: у каждого есть фабрика вида NewXxx, набор полей состояния и методы-сеттеры, через которые состояние меняют. Это правило сеттеров — самая важная привычка урока, и начнём мы именно с неё: без неё кнопка молча перестанет обновлять надпись, и ты будешь искать баг там, где его нет.

Правило сеттеров: поле меняет данные, сеттер меняет экран#

Вспомним анатомию из первого урока: виджет — это CanvasObject с поведением, а внешний вид вынесен в WidgetRenderer. Когда состояние виджета меняется, кто-то должен сказать рендереру «перерисуйся» — это делает Refresh(). Так вот, методы-сеттеры — SetText, SetChecked, SetValue, Disable — делают ровно две вещи: записывают новое значение в поле и вызывают Refresh. Поля у виджетов публичные, и компилятор не запретит тебе написать button.Text = "Готово" напрямую. Значение в поле запишется — а экран нет: рендерер о новом состоянии не узнал, Refresh никто не вызвал. Классический симптом: логика отработала, переменные верные, а интерфейс показывает старьё. Отсюда правило урока: меняешь виджет — вызывай сеттер, а не трогай поле. Прямое присваивание оправдано только в связке с ручным Refresh() — так делают внутри самих сеттеров и при массовом обновлении, но в обычном коде это лишний шанс забыть перерисовку. Читаемость тоже выигрывает: по вызову SetText видно намерение, по присваиванию поля — нет.

Label и Button: надпись, действие, счётчик

GO
package main

import (
        "fmt"

        "fyne.io/fyne/v2/app"
        "fyne.io/fyne/v2/container"
        "fyne.io/fyne/v2/widget"
)

func main() {
        myApp := app.New()
        window := myApp.NewWindow("Счётчик")

        count := 0
        label := widget.NewLabel("Кликов: 0")

        add := widget.NewButton("Плюс один", func() {
                count++
                label.SetText(fmt.Sprintf("Кликов: %d", count))
        })

        // главная кнопка формы: заметнее остальных
        add.SetImportance(widget.HighImportance)

        window.SetContent(container.NewVBox(
                label,
                add,
        ))
        window.ShowAndRun()
}

widget.NewLabel создаёт метку, widget.NewButton — кнопку: первый аргумент текст, второй — функция-обработчик, которая попадает в поле OnTapped. Внутри обработчика мы меняем надпись через label.SetText — сеттер, а не label.Text = ...: поэтому экран обновляется сам. SetImportance(widget.HighImportance) выделяет кнопку основным цветом темы — так помечают главное действие формы.

Button: OnTapped, Importance и запрет на клик#

Кнопка — самый востребованный из fyne виджетов, и запросы вроде fyne button ведут на неё же. Внутри у неё четыре главных поля: Text — надпись, Icon — иконка fyne.Resource, Importance — «важность» внешнего вида и OnTapped — функция, которую цикл событий вызовет по клику. Фабрика NewButton(label, tapped) заполняет Text и OnTapped одним вызовом; есть и NewButtonWithIcon для кнопки с иконкой, полезной в тулбарах. Importance появился в версии 1.4 и управляет заметностью: HighImportance красит кнопку в primary-цвет темы — главное действие; LowImportance наоборот убирает часть оформления — второстепенное; DangerImportance — для разрушительных действий вроде удаления, у него красная заливка. Менять важно только сеттером SetImportance по тому же правилу. Вторая способность кнопки — временный запрет: Disable() делает её серой и нечувствительной к кликам, Enable() возвращает; состояние читает Enabled(). Типовой сценарий: форма невалидна — кнопка «Сохранить» заблокирована, поле заполнили корректно — Enable. Нажатие заблокированной кнопки не вызывает OnTapped — обработчик просто не сработает.

Написали button.Text = "Готово", переменная изменилась, но надпись на экране осталась старой. Почему?

Label: текст, стиль и выравнивание#

Label — метка, нередактируемый текст: статусы, подписи полей, результаты расчёта. Главный метод тебе уже знаком — SetText. Стилизация идёт через fyne.TextStyle: структура с флагами Bold, Italic, Monospace, BoldItalic — для моноширинных значений вроде ID и IP-адресов Monospace незаменим. Выравнивание задаётся fyne.TextAlign: Leading, Center, Trailing. Всё это одной фабрикой: widget.NewLabelWithStyle("Готово к работе", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) — и есть одноимённый метод SetTextStyle и SetTextAlignment, чтобы менять стиль по ходу работы. С версии 2.4 у Label появилось поле Importance — им помечают предупреждающий и приглушённый текст, например серым рисуют недоступные значения; и Truncation — что делать с длинной строкой: переносить или обрезать многоточием. Если текста в метке становится много и нужен цвет, абзацы и вставки — это территория RichText, разберём его в девятом уроке про текст и ввод. Пока правило простое: одна строка или абзац статуса — Label, всё сложнее — RichText.

Entry: ввод с обратной связью

GO
package main

import (
        "fmt"

        "fyne.io/fyne/v2/app"
        "fyne.io/fyne/v2/container"
        "fyne.io/fyne/v2/widget"
)

func main() {
        myApp := app.New()
        window := myApp.NewWindow("Вход")

        email := widget.NewEntry()
        email.SetPlaceHolder("you@example.com")

        email.OnChanged = func(text string) {
                fmt.Println("Печатает:", text)
        }

        password := widget.NewPasswordEntry()
        password.SetPlaceHolder("Пароль")

        login := widget.NewButton("Войти", func() {
                fmt.Println("Логин:", email.Text, "пароль:", password.Text)
        })

        window.SetContent(container.NewVBox(email, password, login))
        window.ShowAndRun()
}

NewEntry создаёт однострочное поле, SetPlaceHolder кладёт серую подсказку внутри пустого поля. OnChanged вызывается на каждое изменение текста, OnSubmitted — по Enter у однострочного. NewPasswordEntry — тот же Entry с маской ввода и кнопкой показать текст. Текущее значение читает поле Text, записывает — SetText.

Сколько раз сработает OnChanged?

Прочитайте код и выберите, что он выведет:

email := widget.NewEntry()
email.OnChanged = func(text string) {
        fmt.Println("Изменение:", text)
}
// пользователь вводит слово "go" посимвольно

Entry подробно: многострочность, пароли и путь к валидации#

Entry — рабочая лошадь ввода, и у неё несколько лиц. NewMultiLineEntry даёт многострочное поле для комментариев и заметок; у многострочного Enter вставляет перевод строки, а отправка срабатывает по Shift+Enter, если OnSubmitted задан. NewPasswordEntry прячет символы и добавляет кнопку «показать» — специально сочинять маску не нужно. Значение читается полем Text в любой момент, пишется сеттером SetText — и да, заполнить поле программно перед показом формы — нормальный приём. Подсказка PlaceHolder экономит подпись над коротким полем. Отдельная тема — контроль ввода: у Entry есть SetValidator, функция, которая получает текст и возвращает nil или ошибку; поле само покажет ошибку под строкой и заблокирует «сломанное» состояние. Полный разбор валидации — восемнадцатый урок, а сегодня достаточно знать, что место для проверки уже предусмотрено архитектурой. Ещё полезно: Disable делает поле недоступным (не редактируется), Enable возвращает — удобно для режимов «только просмотр».

Нужно блокировать кнопку «Далее», пока пароль короче восьми символов. Куда встать?

Check и Select: выбор пользователя

GO
package main

import (
        "log"

        "fyne.io/fyne/v2/app"
        "fyne.io/fyne/v2/container"
        "fyne.io/fyne/v2/widget"
)

func main() {
        myApp := app.New()
        window := myApp.NewWindow("Настройки")

        notify := widget.NewCheck("Присылать уведомления", func(on bool) {
                log.Println("Уведомления:", on)
        })

        theme := widget.NewSelect(
                []string{"Системная", "Светлая", "Тёмная"},
                func(value string) {
                        log.Println("Тема:", value)
                },
        )

        window.SetContent(container.NewVBox(
                notify,
                theme,
        ))
        window.ShowAndRun()
}

NewCheck получает подпись и обработчик func(bool) — он вызывается при каждом переключении. NewSelect получает список опций и обработчик func(string). При старте чекбокс снят, а селект показывает подсказку-плейсхолдер: ни одна опция ещё не выбрана.

Check и RadioGroup: булевы вопросы#

Check — ответ на булев вопрос: включить уведомления, согласиться с условиями, добавить в избранное. Фабрика NewCheck(label, changed) принимает подпись и функцию func(bool); состояние читает поле Checked, програмно ставит сеттер SetChecked — например, чтобы восстановить сохранённые настройки при старте. Обработчик получает новое значение, поэтому внутри него не нужно читать Checked: аргумент и есть текущее состояние. Когда вариантов больше двух, а выбрать нужно один, берут RadioGroup — круги с точкой: NewRadioGroup([]string{"Email", "SMS"}, func(value string)) — обработчик получает выбранную строку. Нужен выбор нескольких из многих — есть CheckGroup, многовыборочный родственник RadioGroup с SetSelected для списка отмеченных. Вариантов становится много и хочется их редактировать на ходу — у RadioGroup и Select есть SetOptions, заменяющая список опций целиком. И помни каталог: чекбокс — про «да/нет», радиокруги — про «ровно один», и не выворачивай их местами, пользователи читают форму именно так.

Select: список с одним выбором#

Select — выпадающий список для выбора одного значения из известного набора: тема, город, приоритет задачи. Фабрика NewSelect(options, changed): первый аргумент — слайс строк, второй — обработчик func(string), который получит выбранное значение. Текущий выбор лежит в поле Selected, програмно ставится SetSelected — так восстанавливают сохранённую опцию при запуске, а ClearSelected сбрасывает выбор до плейсхолдера. Пока ничего не выбрано, Select показывает подсказку: по умолчанию это «(Select one)», а поле PlaceHolder позволяет поставить своё, например «выберите тему». Обработчик срабатывает и на выбор опции, и на сброс — получая пустую строку, учитывай это. Менять список опций на лету умеет SetOptions — удобно для зависимых списков: выбрали страну, подгрузили города. Когда список должен и подсказывать, и принимать произвольный ввод, есть гибрид SelectEntry — селект с редактируемым полем; разберём его вместе с остальным вводом в девятом уроке. Для «да/нет» Select не нужен — это работа Check.

ProgressBar: индикатор прогресса

GO
package main

import (
        "fyne.io/fyne/v2/app"
        "fyne.io/fyne/v2/container"
        "fyne.io/fyne/v2/widget"
)

func main() {
        myApp := app.New()
        window := myApp.NewWindow("Прогресс")

        bar := widget.NewProgressBar() // Min=0, Max=1
        bar.SetValue(0.25)             // 25%

        // длительность неизвестна: "что-то происходит"
        spin := widget.NewProgressBarInfinite()

        window.SetContent(container.NewVBox(
                bar,
                spin,
        ))
        window.ShowAndRun()
}

NewProgressBar создаёт полосу с Min 0 и Max 1; SetValue принимает долю и рисует процент. ProgressBarInfinite не знает значений — полоса ездит туда-сюда, показывая занятость. Оба обновляются сеттером: SetValue под капотом вызывает Refresh, заодно пересчитывая надпись.

ProgressBar подробно: доли, диапазоны и TextFormatter#

ProgressBar показывает известную долю завершённости. По умолчанию диапазон от 0 до 1: SetValue(0.25) — четверть, SetValue(1) — всё; надпись процента рисует сам виджет. Диапазон настраивается полями Min и Max: загрузили файл на 20 мегабайт — поставь Max = 20 и вызывай SetValue с мегабайтами, виджет сам переведёт в проценты. Но помни правило сеттеров: Value тоже поле, однако присваивание напрямую не перерисует полосу — только SetValue. Подпись по умолчанию — проценты; свою форматирование даёт поле TextFormatter (с версии 1.4): функция, возвращающая строку, которая пересчитывается при каждом обновлении значения, — например «3 из 12 файлов». Полезная оговорка честности: если длительность неизвестна, не выдумывай проценты — возьми ProgressBarInfinite, бесконечно движущуюся полосу занятости: SetValue для неё не имеет смысла, остановить индикатор можно Hide или удалением из контейнера. Прогресс из горутины обновляй только через fyne.Do из двадцатого урока — SetValue из чужого потока небезопасен.

Какой процент покажет полоса?

Прочитайте код и выберите, что он выведет:

bar := widget.NewProgressBar()
bar.Max = 200
bar.SetValue(100)

Каталог урока: шесть виджетов и их словарь#

ВиджетФабрикаСеттеры и событияЗадача
LabelNewLabel, NewLabelWithStyleSetText, SetTextStyleПоказать текст, статус, подпись
ButtonNewButton, NewButtonWithIconSetText, SetImportance, Disable/Enable, OnTappedДействие по клику
EntryNewEntry, NewMultiLineEntry, NewPasswordEntrySetText, SetPlaceHolder, OnChanged, OnSubmittedВвод текста
CheckNewCheckSetChecked, Checked, обработчик func(bool)Булев выбор «да/нет»
RadioGroupNewRadioGroupSetSelected, SetOptions, обработчик func(string)Один вариант из многих
SelectNewSelectSetSelected, SetOptions, ClearSelected, PlaceHolderВыбор строки из списка
ProgressBarNewProgressBarSetValue, Min/Max, TextFormatterИзвестная доля работы
ProgressBarInfiniteNewProgressBarInfiniteHide/показ, без SetValueЗанятость без процента

Какой виджет когда: сценарии выбора#

Пройдём по типовым ситуациям. Статус операции («Файл сохранён», «Ошибка сети») — Label, при необходимости с Monospace для значений. Единственное главное действие формы — Button с HighImportance; разрушительное вроде «Удалить всё» — DangerImportance; вторичные действия — обычные или LowImportance. Ввод любых данных от человека — Entry: однострочное для полей, многострочное для текстов, парольное для секретов; подсказку в пустое поле ставь PlaceHolder, а не первой строкой значения. Переключатель режима «вкл/выкл» — Check. Выбор одной опции из трёх-пяти известных — Select; если опции — режимы с видимой альтернативой, RadioGroup покажет все варианты сразу без клика. Отчёт о ходе долгой операции — ProgressBar с честным диапазоном, а если честного процента нет — ProgressBarInfinite. Проверка ввода на корректность — это не про виджеты выбора: у Entry есть валидатор, и это восемнадцатый урок. А собирают все эти элементы вместе не в окне напрямую, а в контейнерах — этим занимается следующий, четвёртый урок.

Собери форму подписки

Восстанови четыре пропуска: метка, поле с подсказкой, чекбокс и процент прогресса.

email := widget.("E-mail") // метка над полем input := widget.NewEntry() input.("you@example.com") // подсказка в пустом поле agree := widget.("Согласен с рассылкой", func(on bool) { log.Println(on) }) // булев выбор bar := widget.NewProgressBar() bar.(0.5) // половина пути

Путь клика по кнопке

Собери цепочку от нажатия до перерисованного экрана — она повторяется у каждого виджета урока.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Копирование файлов занимает от 5 секунд до получаса, точный процент неизвестен. Какой индикатор честнее?

Что дальше#

Шесть виджетов из каталога — это стройматериал, но в окне он должен стоять по местам: колонкой, строкой, с отступами. Этим займётся четвёртый урок про контейнеры и компоновку Box — NewVBox, NewHBox, спейсеры и правило минимальных размеров. Дальше — пятый урок 05-grid-form с сетками и Form, который соберёт поля и подписи в готовую форму с валидацией. Если что-то из словаря урока забыл — вернись к установке и первому окну за каркасом запуска или к дебютному уроку курса за анатомией Widget/Renderer. Полная программа из двадцати пяти уроков — на странице курса Fyne. Домашнее задание ниже собирает всё в один файл — мини-настройки приложения, которые мы ещё вернём и подключим к хранилищу preferences в девятнадцатом уроке.

Термины урока

Термин

Правило сеттеров

нажмите, чтобы перевернуть
Определение

Состояние виджета меняют методами SetText/SetChecked/SetValue — они записывают значение и вызывают Refresh. Прямое присваивание поля меняет данные, но не перерисовывает экран.

1 / 6

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.

Виртуальный экран

HTML-модель виджет-дерева fyne
Счётчикfyne · мок

Кликов: 0

Журнал событий

Экран собран: «Счётчик»

Коротко: что запомнить

  • Шесть базовых виджетов покрывают большинство форм: Label для текста, Button для действий, Entry для ввода, Check и Select для выбора, ProgressBar для прогресса.
  • Правило сеттеров: SetText, SetChecked, SetValue, Disable записывают значение и вызывают Refresh; прямое присваивание публичного поля перерисовки не даёт.
  • Button: OnTapped — обработчик клика, Importance (с 1.4) выделяет главное действие, Disable/Enable временно запрещают нажатия.
  • Entry: OnChanged срабатывает посимвольно, OnSubmitted — по Enter; многострочный NewMultiLineEntry и NewPasswordEntry — готовые варианты ввода; валидатор появится в 18-м уроке.
  • Check — булев выбор (NewCheck с func(bool)), Select — выбор строки (Selected, SetSelected, SetOptions, PlaceHolder «(Select one)» по умолчанию), RadioGroup — один вариант из многих.
  • ProgressBar рисует долю от Min..Max (по умолчанию 0..1) через SetValue, текст — TextFormatter; без известного процента честнее NewProgressBarInfinite.

Домашнее задание

Собери окно «настройки приложения»: Label-заголовок с Bold-стилем, Entry для имени пользователя с PlaceHolder, NewPasswordEntry для токена, Check «запускать при старте» и Select с тремя вариантами темы. Добавь ProgressBar и кнопку «Сохранить» с HighImportance: по клику она должна Disable, напечатать значения полей и вернуть Enable через пару секунд по таймеру time.AfterFunc из горутины — но трогать виджеты внутри горутины пока не смей, только печатай в консоль (fyne.Do — тема двадцатого урока). Поэкспериментируй: закомментируй SetImportance, попробуй button.Text = "..." вместо SetText и наблюдай, что изменится на экране, а что нет.

Личные заметкиПусто
Хранятся только в вашем браузере (Fyne · Виджеты: базовые)

Вопросы и ответы по уроку

Почему виджет не обновился, когда я поменял его поле напрямую?

Поля у виджетов публичные, поэтому присваивание компилируется, но Refresh никто не вызвал — рендерер рисует старое состояние. Используй сеттеры: SetText вместо присваивания Text, SetValue вместо Value, SetChecked вместо Checked — они обновляют экран сами.

Чем OnChanged отличается от OnSubmitted у Entry?

OnChanged вызывается на каждое промежуточное изменение текста — посимвольно при вводе. OnSubmitted срабатывает по нажатию Enter: у однострочного поля — сразу, у многострочного — Shift+Enter. Проверки «на каждый символ» живёт в OnChanged, финальная отправка — в OnSubmitted.

Что показывает Select, пока ничего не выбрано?

Плейсхолдер. По умолчанию это «(Select one)», свой текст ставится в поле PlaceHolder. Сброс выбора через ClearSelected или SetSelected с пустой строкой возвращает плейсхолдер и вызывает обработчик с пустой строкой — учитывай это в коде.

Термины урока

Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.

Сравните с альтернативами

Практический вопрос после первых уроков: а почему именно эта библиотека? Мы разобрали конкурентов по одним критериям, с кодом одной задачи рядом и персональным подбором под ваши приоритеты.

Следующая ступень учебного пути

Cobra25 уроков~10 чпрактика25 уроков уже доступны

Cobra: CLI-приложения на Go

Интерактивный курс Cobra — фреймворка для CLI на Go: команды, флаги, конфиги Viper, автодополнение. 25 уроков с примерами кода, тестами и итоговым приложением.

  • Создавать CLI-приложения с деревом команд на Cobra
  • Работать с аргументами, флагами и их привязкой к конфигу
  • Управлять конфигурацией через Viper: файлы, env, приоритеты
  • Писать хуки PreRun/PostRun и корректно обрабатывать ошибки