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

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

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

Урок 13 из 25 ~18 мин +50 XP

Диалоги и уведомления

Двенадцатый урок остановился в шаге от честного выхода: перехватчик закрытия умеет отклонить вкладку, но не умеет спросить пользователя. Спрашивает пакет dialog: запрос «fyne диалоги» — это dialog.ShowConfirm с колбэком bool, ShowInformation для уведомлений, ShowError для сбоев и файловый выбор NewFileOpen для импорта. Диалоги fyne не блокируют программу: show возвращается сразу, а ответ приходит в колбэк, когда пользователь решит; модальность блокирует лишь родительское окно. Здесь же выполняется обещание второго урока — выход с подтверждением через SetCloseIntercept — и вводится confirm dialog как главный инструмент необратимых действий.

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

Необратимое действие без вопроса — это баг, а не UX: удаление заметки, выход без сохранения, очистка архива. Fyne собирает все такие вопросы в одном пакете fyne.io/fyne/v2/dialog, и у него есть три кита. Информация: dialog.ShowInformation — окно с сообщением и одной кнопкой «понятно». Вопрос: dialog.ShowConfirm — сообщение и две кнопки, ответ приходит колбэком func(bool): true — подтверждение, false — отказ. Ошибка: dialog.ShowError — текст err с иконкой сбоя, чтобы пользователь увидел проблему, а не гадал по молчанию. Все три привязаны к родительскому окну — последний аргумент fyne.Window, без него fyne диалог не построить: оверлей поднимается именно над этим окном. Запросы «fyne диалоги» и «fyne dialog» ведут сюда — к этим трём вызовам. Сразу фиксируем главную механику, незнакомую пришедшим из блокирующих GUI: вызов show не ждёт ответа — он показывает оверлей и возвращает управление мгновенно, а твоя логика продолжается в колбэке после решения пользователя. Разберём это на коде.

Информация и подтверждение

GO
package main

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

func main() {
        myApp := app.New()
        window := myApp.NewWindow("Заметки")

        notes := []string{"молоко", "хлеб", "сыр"}
        list := widget.NewLabel("молоко, хлеб, сыр")

        info := widget.NewButton("О приложении", func() {
                dialog.ShowInformation("Заметки 2.0",
                        "Учебное приложение курса Fyne.", window)
        })
        wipe := widget.NewButton("Очистить всё", func() {
                dialog.ShowConfirm("Очистить всё",
                        "Удалить все заметки без возврата?",
                        func(ok bool) {
                                if ok {
                                        notes = nil
                                        list.SetText("")
                                }
                        }, window)
        })

        window.SetContent(container.NewVBox(list, info, wipe))
        window.ShowAndRun()
}

ShowInformation поднимает оверлей с текстом и одной кнопкой — колбэка у него нет, информация не требует ответа. ShowConfirm получает колбэк func(bool): нажатие подтверждения вызовет его с true и очистит срез, отказ — с false и ничего не сделает. Оба вызова короткие сокращения: под ними те же диалоги, которые создаются вручную через NewInformation и NewConfirm с последующим Show.

Что попадает в колбэк dialog.ShowConfirm?

Анатомия Dialog: show, hide и модальность#

Сокращения Show* скрывают двухшаговую механику. dialog.NewInformation, dialog.NewConfirm и dialog.NewError создают объект диалога и ничего не показывают; Show() выводит его на экран. Зачем разделять? Чтобы настроить: SetDismissText меняет текст нижней кнопки — «Понятно», «Закрыть», «Не сейчас»; SetOnClosed вешает уборку на момент закрытия диалога, когда она нужна независимо от ответа. Hide() убирает диалог программно — так делают диалоги с собственной логикой закрытия. И главное свойство: модальность. Диалог поднимается оверлеем над родительским окном и перехватывает весь ввод, пока открыт; кликнуть по кнопке под оверлеем нельзя. Но модальность блокирует только окно — не программу: run loop из второго урока крутится, таймеры тикают, горутины работают, и код после вызова show уже выполняется. Отсюда железное правило: логика продолжения пишется в колбэке, а не строкой после show. Кнопки и подписи локализуются под язык системы в свежих версиях — но рассчитывать на конкретные подписи не стоит: если текст кнопки важен, задавай его SetDismissText явно.

В каком порядке напечатается

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

fmt.Println("до")

dialog.ShowConfirm("Удалить", "Точно?",
        func(ok bool) {
                fmt.Println("ответ:", ok)
        }, window)

fmt.Println("после")
// пользователь нажимает «подтвердить» через три секунды
// что и в каком порядке напечатается?

Создали, но не показали

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

d := dialog.NewConfirm("Выход",
        "Выйти без сохранения?",
        func(ok bool) { if ok { window.Close() } },
        window)

fmt.Println("диалог создан")
// Show() никто не вызвал
// что увидит пользователь?

Обещание второго урока: выход с подтверждением#

Второй урок показал SetCloseIntercept и пообещал развёрнутый сценарий с настоящим диалогом подтверждения. Время платить. Перехватчик закрытия окна полностью берёт закрытие на себя: без вызова window.Close() окно не закроется. Собираем конвейер: пользователь жмёт крестик → SetCloseIntercept вызывает dialog.ShowConfirm → пользователь отвечает → в колбэке true закрываем окно явным window.Close(), в false просто отпускаем диалог — и окно остаётся жить. Обрати внимание на порядок: window.Close() внутри колбэка — не рекурсия и не конфликт с перехватчиком; перехватчик срабатывает на запрос закрытия, а Close() выполняет его по решению пользователя. Тот же конвейер встречается повсюду: удаление записи через dialog.ShowConfirm из двенадцатого урока, очистка корзины, выход из аккаунта. Общее правило оформления: короткий вопрос, честная кнопка отказа и никакой логики удаления вне колбэка — она будет выполнена в любом случае, включая отказ.

Выход без потери заметки

GO
package main

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

func main() {
        myApp := app.New()
        window := myApp.NewWindow("Редактор")

        editor := widget.NewMultiLineEntry()
        editor.SetPlaceHolder("Текст заметки...")

        window.SetCloseIntercept(func() {
                dialog.ShowConfirm("Выход",
                        "Заметка не сохранена. Всё равно выйти?",
                        func(ok bool) {
                                if ok {
                                        window.Close()
                                }
                        }, window)
        })

        window.SetContent(editor)
        window.ShowAndRun()
}

Исполнение обещания второго урока: крестик больше не закрывает окно напрямую — перехватчик задаёт confirm dialog, и только подтверждение приводит к window.Close(). Отказ снимает оверлей, редактор остаётся открыт. Хочешь не спрашивать каждый раз — проверяй editor.Text на пустоту в перехватчике и закрывай сразу, когда терять нечего.

SetCloseIntercept задан и вызывает ShowConfirm. Что нужно, чтобы окно всё-таки закрылось?

Форма и выбор файла внутри диалога#

Диалог — не обязательно вопрос с двумя кнопками. dialog.NewForm собирает полноценную форму из []*widget.FormItem, знакомых по пятому уроку: заголовок, тексты кнопок подтверждения и отмены, элементы формы и колбэк onSubmit, который вызовется по подтверждению после автоматической проверки валидации полей — то самое правило «невалидное поле блокирует submit». Это готовый экран «настройки импорта» или «новая категория» без отдельного окна. Вторая рабочая лошадка — файловые диалоги: dialog.NewFileOpen создаёт системный выбор файла, колбэк получает пару (fyne.URIReadCloser, error): дескриптор чтения с методом URI() или nil при отмене. Фильтр ставится picker.SetFilter(storage.NewExtensionFileFilter([]string{".txt"})) — в списке останутся только текстовые файлы. У URI читаются Scheme(), Name() и Extension() — схема, имя и расширение файла; полный путь даёт String(). Пара важностей: отмена выбора — это reader nil и err nil, не ошибка; а ошибка реального сбоя приходит во втором аргументе и заслуживает ShowError. Папки выбирает dialog.NewFolderOpen с колбэком (fyne.ListableURI, error) — приём тот же. Полное погружение в файловое хранилище — сохранение, папки, ListableURI — двадцать первый урок, здесь только открытие.

Импорт заметки из файла

GO
package main

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

func main() {
        myApp := app.New()
        window := myApp.NewWindow("Импорт")

        status := widget.NewLabel("Файл не выбран")
        editor := widget.NewMultiLineEntry()

        choose := widget.NewButton("Импорт из .txt...", func() {
                picker := dialog.NewFileOpen(func(reader fyne.URIReadCloser, err error) {
                        if err != nil {
                                dialog.ShowError(err, window)
                                return
                        }
                        if reader == nil {
                                status.SetText("Отмена")
                                return
                        }
                        defer reader.Close()
                        status.SetText("Выбрано: " + reader.URI().Name())
                }, window)
                picker.SetFilter(storage.NewExtensionFileFilter([]string{".txt"}))
                picker.Show()
        })

        window.SetContent(container.NewVBox(choose, status, editor))
        window.ShowAndRun()
}

Двухшаговый путь в деле: NewFileOpen собирает диалог, SetFilter оставляет только .txt, Show открывает. Колбэк различает три исхода: ошибка уходит в ShowError, отмена — reader nil без ошибки, успех — читаем URI и не забываем Close через defer. Показ системного окна выбора — работа драйвера: на каждой платформе диалог выглядит по-своему, контракт колбэка общий.

Пользователь закрыл окно выбора файла кнопкой отмены. Что придёт в колбэк NewFileOpen?

Тосты и лёгкие уведомления#

Диалоги громкие: оверлей, модальность, кнопка. А «сохранено», «скопировано», «задача перенесена» — сообщения, на которые не нужен ответ и тем более блокировка. Для них с версии 2.6 в комплекте есть тост: widget.NewToast собирает всплывающую плашку, Show показывает её на пару секунд поверх интерфейса, без кнопок и без модальности — пользователь продолжает работать. До 2.6 тосты собирали руками: стек поверх содержимого, Label внутри, time.AfterFunc на три секунды вызывает Hide — рабочий приём, если целевая версия старше. Когда что выбирать: тост — для фактов, не требующих решения; ShowInformation — для сводок, которые пользователь обязан заметить и закрыть; ShowConfirm — для развилок с последствиями; ShowError — для сбоев с текстом ошибки. Ошибка дублирования каналов: если каждый чих программы всплывает диалогом, пользователь перестаёт их читать — держи диалоги для дорогих решений, тосты для дешёвых подтверждений. В финальном проекте так и разделено: перенос задачи — тост, удаление задач — confirm dialog, сбой сохранения — ShowError. И помни про фокус: тост не забирает фокус и не останавливает ввод, а диалог забирает — не открывай его в ответ на каждое действие пользователя, иначе через неделю он будет бить кнопки по памяти, не читая текст.

Собери подтверждение и импорт

Восстанови пять пропусков: сокращение подтверждения, точечное обновление списка, конструктор выбора файла, чтение URI и показ диалога.

dialog.("Удалить", "Удалить заметку?", func(ok bool) { if ok { notes = append(notes[:i], notes[i+1:]...) list.(widget.ListItemID(i)) } }, window) picker := dialog.(func(reader fyne.URIReadCloser, err error) { if err == nil && reader != nil { status.SetText("Файл: " + reader.().Name()) } }, window) picker.SetFilter(storage.NewExtensionFileFilter([]string{".txt"})) picker.()

Путь подтверждённого удаления

Расставь по порядку, что происходит от клика по кнопке «Удалить» до обновления списка.

Строки

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

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

Каталог API диалогов и уведомлений#

APIЧто делаетКогда звать
dialog.ShowInformation(title, msg, window)уведомление с одной кнопкойфакты, которые нужно заметить
dialog.ShowConfirm(title, msg, func(bool), window)вопрос с подтверждением и отказомнеобратимые действия
dialog.ShowError(err, window)ошибка с текстом err и иконкой сбоялюбой пойманный err, важный пользователю
New* + Show() + SetDismissText / SetOnClosedдвухшаговый путь с настройкойнужен свой текст кнопки или уборка
dialog.NewForm / ShowFormформа из FormItem внутри диалогамини-настройки без отдельного окна
dialog.NewFileOpen(callback, window)системный выбор файлаимпорт данных
picker.SetFilter(storage.NewExtensionFileFilter)фильтр по расширениямперед Show файлового диалога
fyne.URIReadCloser и URI().Name()дескриптор чтения и имя файлав колбэке после выбора
widget.Toast (2.6+)тост без кнопок и модальностидешёвые подтверждения действий

Диалоги в архитектуре приложения#

Последний обход по архитектуре. Диалог — не место для бизнес-логики: в колбэке живут одна-две строки, которые зовут методы твоих структур, а не разворачивают сценарии на сто строк; иначе confirm dialog превращается в нечитаемую лапшу замыканий. Повторные вызовы Show на одном диалоге допустимы: собери NewConfirm один раз при старте окна и переиспользуй — SetText и Show по мере надобности. Следи за наследованием фокуса: модальность перехватывает ввод всего окна, поэтому диалог поверх диалога не соберётся в очередь — второй вызов Show заменит картинку, и колбэки могут разминуться с ожиданиями. Проверяй сценарий «отказ» так же придирчиво, как «подтверждение»: предсказание выше показывало, что ответ приходит позже, — значит, и отказ приходит позже, а код между show и колбэком уже отработал. Наконец, чисти за собой SetOnClosed: если диалог открывал соединение или подписку, закрывай их там, а не в колбэке подтверждения — закрытие без кнопки тоже должно освобождать ресурсы. Дальше по курсу — меню и тулбары: у них своя механика команд, но правило «дорогие решения через диалог, дешёвые через тост» остаётся. Полный маршрут — на странице курса Fyne.

Что дальше: меню и инструментальные панели#

Диалоги закрыли пробел навигации из двенадцатого урока: теперь перехват закрытия вкладки и окна умеет спрашивать, импорт поднимает файловый выбор, а финальный менеджер задач получил все три канала сообщений. Опора урока — второй урок с SetCloseIntercept и run loop, десятый с RefreshItem, пятый с формами и валидацией, двенадцатый с DocTabs. Следующая тема — меню и тулбары: MainMenu с иерархией команд, Toolbar с кнопками-иконками и контекстные меню по правой кнопке из шестого урока; там появится и место для вызова наших диалогов — команды «Файл → Импорт» и «Правка → Очистить». Домашнее задание соберёт безопасную корзину заметок с подтверждением удаления и импортом из файла.

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

Термин

dialog.ShowConfirm

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

Вопрос с двумя кнопками и колбэком func(bool): true — подтверждение, false — отказ. Сокращение для NewConfirm плюс Show; show не блокирует программу.

1 / 6

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

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

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

HTML-модель виджет-дерева fyne
Диалогиfyne · мок

Удалить файл?

Подтверждение

dialog.ShowConfirm показал бы модальное окно

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

Экран собран: «Диалоги»

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

  • Пакет dialog даёт три кита: ShowInformation — уведомление, ShowConfirm — вопрос с func(bool), ShowError — сбой с текстом err; все привязаны к родительскому окну.
  • Show не блокирует программу: оверлей поднимается, код после вызова выполняется, ответ приходит в колбэк — логика продолжения живёт только в колбэке.
  • Модальность блокирует окно, но не run loop; диалог поверх диалога не складывается в очередь — не строй сценарии из вложенных Show.
  • Двухшаговый путь New* + Show оставляет окно для SetDismissText и SetOnClosed; Hide закрывает программно.
  • SetCloseIntercept + ShowConfirm + window.Close() в колбэке — честный выход с подтверждением, обещанный вторым уроком.
  • Файловый выбор: NewFileOpen с колбэком (fyne.URIReadCloser, error), фильтр через storage.NewExtensionFileFilter, отмена — nil без ошибки; глубина — двадцать первый урок.
  • Тосты widget.Toast с 2.6 — для дешёвых подтверждений; диалоги — для дорогих решений и ошибок.

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

Собери корзину заметок: список из десятого урока, кнопка «Удалить» под выделенной строкой вызывает dialog.ShowConfirm, и только колбэк с true удаляет запись из среза и перерисовывает список RefreshItem-ом — код удаления вне колбэка считается багом. Добавь SetCloseIntercept на окно: при непустом тексте редактора — ShowConfirm с вопросом о выходе, при подтверждении window.Close. Кнопка «Импорт .txt» открывает NewFileOpen с фильтром storage.NewExtensionFileFilter: ошибка уходит в ShowError, отмена молча игнорируется, содержимое файла попадает в editor. Сведи каналы сообщений по правилу урока: успешный импорт — ShowInformation, сбой чтения — ShowError, удаление — confirm dialog. Ловушка для самопроверки: поставь печать между show и колбэком и убедись, что она выполняется раньше ответа пользователя; если ждёшь обратного — перечитай предсказание про порядок вывода.

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

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

ShowConfirm останавливает выполнение программы?

Нет: блокируется только ввод родительского окна — оверлей перехватывает клики. Программа живёт: run loop крутится, код после show выполняется сразу, а ответ пользователя приходит в колбэк, когда он примет решение.

Можно показать диалог без родительского окна?

Нет: последний аргумент каждого диалога — fyne.Window: fyne окно диалога всегда привязано к родителю, оверлей поднимается над ним. Всплывающие подсказки и легковесные плашки без окна собираются тостами или оверлеями из Stack, но настоящий диалог всегда привязан к окну.

Что вернёт колбэк, если пользователь закрыл confirm-диалог кнопкой отмены?

false — отказ выражается не только кнопкой: любое расставание с диалогом без подтверждения означает false. Поэтому код удаления пишется строго внутри ветки if ok, а не строкой после show.

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

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

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

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

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

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

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

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

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