Третий урок дал тебе widget.NewEntry: поле Text, PlaceHolder для подсказки, OnChanged на каждое нажатие. Теперь — то, что отличает уверенную работу от случайной. Во-первых, правило сеттеров: текст меняй через SetText — он обновит поле и сам вызовет Refresh; присвоение entry.Text = «строка» меняет данные, но экран узнает об этом только при случайной перерисовке, например после растягивания окна. Прямое присвоение имеет смысл лишь при массовой инициализации, когда Refresh всё равно зовёшь сам. Во-вторых, Disable и Enable: заблокированное поле серо и не принимает фокус — это честнее скрытого поля, пользователь видит, что данные есть, но менять их нельзя. В-третьих, пароль: widget.NewPasswordEntry возвращает тот же Entry с точками вместо символов — больше никаких отличий, все обработчики работают одинаково. И в-четвёртых: Entry — виджет с фокусом из шестого урока: он сам ловит TypedRune, двигает курсор, держит выделение и буфер обмена. Твоя работа — не вмешаться в механику, а правильно повесить колбэки: запрос «fyne entry» в поиске почти всегда про одну из этих мелочей.
Чем OnSubmitted отличается от OnChanged?
Многострочный Entry: перенос, высота, Enter#
Многострочный режим включается вызовом widget.NewMultiLineEntry — или полем MultiLine = true у обычного Entry. Дальше три настройки, которые превращают поле в удобную область редактирования. Wrapping задаёт перенос строк структурой fyne.TextWrap: флаги Word и Break означают «переноси по словам, а при нехватке места и посимвольно»; без wrapping длинная строка уедет за край поля. SetMinRowsVisible(n) резервирует высоту под n строк: поле из одного бледного ряда становится полноценным редактором, и окно не дёргается по мере ввода. Прокрутка включается сама, когда текст перестаёт помещаться — для совсем длинных текстов поле дополнительно оборачивают в container.NewScroll. Поведение Enter заслуживает отдельной строки: в однострочном поле Enter вызывает OnSubmitted; в многострочном Enter вставляет перевод строки, а «отправить» — Shift+Enter. Это частый источник неожиданностей: пользователь чата жмёт Enter, ожидая отправку, и получает новую строку. Подсказывай поведение плейсхолдером или подписью под полем — интерфейс не должен молчать о таких вещах.
Редактор заметки со счётчиком
GOpackage main
import (
"fmt"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
)
func main() {
myApp := app.New()
window := myApp.NewWindow("Заметка")
counter := widget.NewLabel("Символов: 0")
editor := widget.NewMultiLineEntry()
editor.PlaceHolder = "Пиши заметку... Shift+Enter — сохранить"
editor.Wrapping = fyne.TextWrap{Word: true, Break: true}
editor.SetMinRowsVisible(8)
editor.OnChanged = func(s string) {
counter.SetText(fmt.Sprintf("Символов: %d", len([]rune(s))))
}
editor.OnSubmitted = func(s string) {
counter.SetText("Заметка сохранена: " +
fmt.Sprintf("%d символов", len([]rune(s))))
}
window.SetContent(container.NewVBox(editor, counter))
window.ShowAndRun()
}Многострочное поле с переносом по словам и резервом на восемь строк: SetMinRowsVisible держит высоту стабильной, fyne.TextWrap из флагов Word и Break переносит длинные строки. OnChanged обновляет счётчик на каждое нажатие — строки здесь короткие, это допустимо; для тяжёлых вычислений дросселируй. OnSubmitted в многострочном поле ловит Shift+Enter как «сохранить». Счётчик меняется через SetText — сеттер сам просит перерисовку, Refresh не нужен.
Enter в многострочном поле
Прочитайте код и выберите, что он выведет:
entry := widget.NewMultiLineEntry()
entry.SetMinRowsVisible(4)
entry.OnSubmitted = func(s string) {
status.SetText("Отправлено: " + s)
}
// пользователь ввёл "строка один" и нажал EnterRichText: текст из сегментов#
widget.Label рисует одну строку одним стилем — текст, который хочется форматировать, просит widget.RichText. Модель сегментная: поле Segments хранит []widget.RichTextSegment, а каждый сегмент — кусок текста со своим стилем. Базовый тип — widget.TextSegment с полями Style и Text; стили — предопределённые переменные пакета: RichTextStyleStrong жирный, RichTextStyleEmphasis курсив, RichTextStyleCode моноширинный, RichTextStyleParagraph блочный абзац. Ключевое различие — флаг Inline в структуре RichTextStyle: инлайн-сегменты текут в одной строке, блочные начинают с новой. Стиль — это не только шрифт: ColorName берёт цвет из активной темы (theme.ColorNameError покрасит ошибки в красный палитры), SizeName задаёт размер, TextStyle собирает Bold, Italic и Monospace. Свой стиль — просто значение структуры: RichTextStyle{Inline: false, TextStyle: fyne.TextStyle{Bold: true}}. Ссылки делаются стилем RichTextStyleHyperlink с полем URL. Конструктор widget.NewRichText принимает сегменты списком; после любой правки Segments зови Refresh — RichText не редактируемый, это дисплей, а не поле ввода.
Отчёт из сегментов
GOpackage main
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
)
func main() {
myApp := app.New()
window := myApp.NewWindow("Отчёт")
report := widget.NewRichText(
&widget.TextSegment{
Style: widget.RichTextStyleParagraph,
Text: "Отчёт за неделю",
},
&widget.TextSegment{
Style: widget.RichTextStyleStrong,
Text: "Собрано 42 отгрузки",
},
&widget.TextSegment{
Style: widget.RichTextStyleParagraph,
Text: ", ошибок в документах нет.",
},
&widget.TextSegment{
Style: widget.RichTextStyle{
Inline: true,
ColorName: theme.ColorNameError,
TextStyle: fyne.TextStyle{Bold: true},
},
Text: " Одна посылка просрочена",
},
&widget.TextSegment{
Style: widget.RichTextStyleCode,
Text: " order-1024",
},
)
window.SetContent(container.NewVBox(report))
window.ShowAndRun()
}Первый сегмент блочный — заголовок с новой строки; дальше инлайны текут в абзаце: жирный, обычный, цветной и моноширинный куски склеиваются в одну строку. Собственный стиль собран структурой RichTextStyle с ColorName из темы — при смене палитры приложения цвет обновится вместе со всем интерфейсом. В реальном приложении Segments собирают программно из данных и зовут report.Refresh() — например, перекрашивая статус задачи.
Ты дописал новый сегмент в rt.Segments. Что нужно, чтобы он появился на экране?
Инлайн против блочного
Прочитайте код и выберите, что он выведет:
rt := widget.NewRichText(
&widget.TextSegment{
Style: widget.RichTextStyleStrong,
Text: "Первый",
},
&widget.TextSegment{
Style: widget.RichTextStyleParagraph,
Text: "Второй",
},
)TextGrid: моноширинная сетка#
RichText хорош для документов, но для логов, диффов и кода есть инструмент поточнее — widget.TextGrid. Это моноширинная сетка символов: текст хранится построчно, и каждая ячейка может нести собственный стиль. Создаётся widget.NewTextGrid(«строка\nстрока»), полностью перезаписывается SetText, к отдельным ячейкам добираешься SetStyle(строка, столбец, стиль), к диапазонам — SetStyleRange(строка, от, до, стиль); строки нумеруются с нуля. Готовые фабрики стилей: TextGridStyleForeground и TextGridStyleBackground красят в заданный цвет, TextGridStyleWhitespace подсвечивает пробелы и табы — удобно показывать «хвостовые» пробелы, как в редакторах кода. TextGrid не редактируемый и не умеет форматных абзацев — зато быстр: сетка рендерится как есть, без вёрстки сегментов, и тянет большие объёмы строк. Ниша ясна: просмотр логов с цветными уровнями, диффы файлов, ASCII-диаграммы, читалка кода в справке приложения. Редактора кода из коробки в тулките нет — Grid это только дисплей; ввод остаётся территорией Entry.
Лог сборки с цветными уровнями
GOpackage main
import (
"image/color"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
)
func main() {
myApp := app.New()
window := myApp.NewWindow("Лог сборки")
log := widget.NewTextGrid(
"INFO сборка начата\n" +
"WARN кеш устарел\n" +
"ERROR файл не найден",
)
log.SetStyle(1, 0, 3, widget.TextGridStyleForeground(
&color.NRGBA{R: 214, G: 158, B: 46, A: 255}))
log.SetStyle(2, 0, 4, widget.TextGridStyleForeground(
&color.NRGBA{R: 192, G: 57, B: 43, A: 255}))
window.SetContent(container.NewScroll(log))
window.ShowAndRun()
}Три строки лога одним TextGrid: уровни WARN и ERROR покрашены по ячейкам через SetStyle — строки нумеруются с нуля, диапазон от нулевого столбца покрывает слово целиком. TextGridStyleForeground принимает любой color.Color из седьмого урока. Длинный лог оберни в container.NewScroll, как здесь; если лог перечитывается целиком, проще собрать новый текст и вызвать SetText, а затем раскрасить заново.
Каталог: ввод и дисплейный текст#
| Инструмент | Что умеет | Типичная роль |
|---|---|---|
| widget.NewEntry | однострочный ввод: Text, PlaceHolder, OnChanged, OnSubmitted | имя, поиск, короткие значения |
| widget.NewMultiLineEntry | многострочный ввод: Wrapping, SetMinRowsVisible, Shift+Enter | заметки, описания, чат |
| widget.NewPasswordEntry | маскировка символов точками | пароли, ключи, токены |
| entry.SetText | задать текст: сеттер сам зовёт Refresh | инициализация, вставка из кода |
| widget.RichText | сегменты TextSegment со стилями: инлайн и блочные | отчёты, сообщения, статусы |
| RichTextStyle и ColorName | цвет и стиль из темы: Strong, Emphasis, Code, свой | акценты, ошибки, ссылки |
| widget.TextGrid | моноширинная сетка: SetStyle, SetStyleRange, whitespace | логи, диффы, код, ASCII |
| container.NewScroll | прокрутка длинного содержимого | большие тексты и логи |
Что из перечисленного пользователь может редактировать?
Собери поле редактора
Восстанови четыре пропуска: конструктор, подсказка, резерв высоты и обработчик отправки.
Путь одного символа
Расставь по порядку, что происходит от нажатия клавиши до OnSubmitted.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
Ввод из этого урока не показать в браузерной песочнице курса: клавиатуре и фокусу нужен настоящий экран. Собери примеры локально по второму уроку и попробуй всё руками: Enter против Shift+Enter, Wrapping на длинной строке, стили RichText на живом тексте.
Практика: поиск, чат, дросселирование#
Несколько сценариев, где эти детали экономят часы. Живой поиск: Entry с OnChanged фильтрует данные и обновляет список — но пересчитывать тяжёлый запрос на каждый символ расточительно; сохраняй последнее значение и срабатывай только на реальную перемену, а данные для отображения готовь заранее в срезе — приём целиком разберём в десятом уроке. Ввод больших текстов: многострочное поле с Wrapping по словам внутри container.NewScroll — стандартная схема заметок и комментариев. Форматированный чат: сообщения собираются в RichText из сегментов, стили ошибок и успеха берутся ColorName из темы — смена палитры не оставит их грязными, а время и номера можно дать моноширинным Code-сегментом. Подсветка синтаксиса в просмотрщике — это TextGrid плюс собственная раскраска по правилам языка. Если же нужен нестандартный контрол ввода с собственной отрисовкой — возвращайся к CreateRenderer из восьмого урока: Entry тоже однажды был собран именно так. Валидация ввода — отдельное ремесло, ей посвящён восемнадцатый урок; связывание полей с данными через binding — семнадцатый.
Что дальше: текст в большом приложении#
Сам Entry с обработчиками пришёл из третьего урока, фокус и клавиатура — из шестого, а форма с валидацией полей — из пятого. Собственный контрол ввода с нестандартной отрисовкой собирается по восьмому уроку, а кормить текстом длинные перечни научит десятый — там же пригодится живой поиск из этого урока. Полный маршрут из двадцати пяти уроков — на странице курса Fyne. Домашнее задание соберёт экран чата: RichText для истории, многострочный ввод для отправки и TextGrid для служебного лога — все три героя урока в одном окне.
Термины урока
Песочница урока
Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.
Виртуальный экран
HTML-модель виджет-дерева fyneЭкран собран: «Ввод текста»
Коротко: что запомнить
- Entry меняет текст через SetText — сеттер сам зовёт Refresh; прямое присвоение Text проявляет только при перерисовке.
- OnChanged срабатывает на каждое нажатие, OnSubmitted — на «отправку»: Enter в однострочном поле, Shift+Enter в многострочном.
- Многострочный ввод: NewMultiLineEntry, Wrapping с флагами Word и Break, SetMinRowsVisible для стабильной высоты.
- RichText собирается из сегментов TextSegment: инлайновые стили текут в строке, блочный Paragraph начинает новую.
- Цвета и размеры RichText берутся из темы по ColorName и SizeName; после правки Segments нужен Refresh.
- TextGrid — моноширинная сетка для логов и кода: SetStyle и SetStyleRange красят ячейки, whitespace подсвечивает пробелы.
- Редактируемое — только Entry; RichText и TextGrid — дисплейные виджеты.
Домашнее задание
Собери экран чата: сверху — RichText с историей из трёх последних сообщений, каждое собрано из сегментов (автор Strong, текст Paragraph, время Code), снизу — многострочный Entry с Wrapping и SetMinRowsVisible(4). Отправка по Shift+Enter: сообщение добавляется в Segments, RichText обновляется Refresh, поле очищается SetText. Добавь счётчик символов через OnChanged и предупреждение для сообщений длиннее двухсот символов — сегментом с ColorName темы Error. Затем сделай окно логов на TextGrid: генерируй строки со случайными уровнями INFO, WARN и ERROR, крась уровни TextGridStyleForeground в разные цвета, на отдельной строке-образце включи подсветку пробелов TextGridStyleWhitespace. В конце оберни чат в container.NewScroll и проверь, как ведёт себя прокрутка при длинных сообщениях, а также убедись, что Enter в поле ввода вставляет перевод строки, а не отправляет.
Личные заметкиПусто
Вопросы и ответы по уроку
Почему entry.Text = «строка» не обновил экран?
Присвоение меняет поле, но перерисовка происходит только по команде Refresh. Используй SetText — он сам просит кадр. Прямое присвоение оставь для инициализации до первого показа окна.
Как показать красный текст ошибки?
В RichText задай сегменту стиль с ColorName: theme.ColorNameError — цвет придёт из активной темы. У widget.Label цвет привязан к теме и напрямую не красится: либо RichText, либо canvas.Text из седьмого урока с ручным цветом.
TextGrid умеет редактирование?
Нет, это чистый дисплей: сетка символов со стилями ячеек. Ввод — территория Entry; редактор кода собирают как собственный контрол поверх TextGrid с TypedRune из шестого урока — задачка уровня кастомных виджетов восьмого урока.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Сравните с альтернативами
Практический вопрос после первых уроков: а почему именно эта библиотека? Мы разобрали конкурентов по одним критериям, с кодом одной задачи рядом и персональным подбором под ваши приоритеты.
Изучайте дальше
Следующая ступень учебного пути
Cobra: CLI-приложения на Go
Интерактивный курс Cobra — фреймворка для CLI на Go: команды, флаги, конфиги Viper, автодополнение. 25 уроков с примерами кода, тестами и итоговым приложением.
- Создавать CLI-приложения с деревом команд на Cobra
- Работать с аргументами, флагами и их привязкой к конфигу
- Управлять конфигурацией через Viper: файлы, env, приоритеты
- Писать хуки PreRun/PostRun и корректно обрабатывать ошибки