---
title: "Диалоги и уведомления"
description: "Диалоги в Fyne: dialog.ShowConfirm и ShowInformation, модальность и колбэки, форма в диалоге, выбор файла с фильтром, тосты и подтверждение выхода."
course: "Fyne: десктопные и мобильные GUI на Go"
url: "https://golangmaster.ru/courses/fyne/lessons/13-dialogi/"
published: "2026-09-19"
updated: "2026-09-21"
minutes: 18
---

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

*Курс «Fyne: десктопные и мобильные GUI на Go», урок 13 из 25. ~18 минут. Обновлено 21.09.2026. Источник: https://golangmaster.ru/courses/fyne/lessons/13-dialogi/*

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

Необратимое действие без вопроса — это баг, а не 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?

- Строка с текстом кнопки, которую нажал пользователь
- ✅ Логическое значение: true — подтверждение, false — отказ
- Индекс нажатой кнопки: 0 или 1
- Ошибка, если пользователь закрыл диалог не кнопкой

Контракт confirm dialog — func(bool): true на подтверждение, false на любой отказ. Ни строк, ни ошибок: развилка всего одна, и код в колбэке читается как if ok — путь подтверждения.

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

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

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

```go
fmt.Println("до")

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

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

**Что выведет программа?**

- до, затем ответ: true, затем после — show ждёт решения пользователя
- ✅ до, после, а ответ: true появится через три секунды, когда пользователь нажмёт кнопку
- Только до и после: колбэк ShowConfirm вызывается в другом окне и не печатает
- ответ: true, до, после — колбэк выполняется первым

ShowConfirm не блокирует: он показывает оверлей и мгновенно возвращается, поэтому «после» печатается сразу за «до». Колбэк сработает позже — когда пользователь примет решение. Это главный сдвиг мышления урока: диалог — не вопрос программе, а событие с отложенным ответом.

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

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

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

**Что выведет программа?**

- Диалог поверх окна: New* показывает диалог сразу после создания
- ✅ Пустое окно приложения — диалог создан, но на экране его нет
- Паника: диалог обязан быть показан до конца обработчика
- Диалог появится после первой смены вкладки

New* только собирают объект: настройки, кнопки, колбэки. На экране его нет, пока не вызван Show(). Сокращения ShowInformation, ShowConfirm и ShowError делают оба шага сразу — а двухшаговый путь оставляет окно для настройки через SetDismissText и SetOnClosed.

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

Второй урок показал 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. Что нужно, чтобы окно всё-таки закрылось?

- Ничего: после диалога окно закроется само
- ✅ Вызвать window.Close() в колбэке при подтверждении
- Вызвать dialog.Hide() — скрытие диалога закрывает и окно
- Вернуть true из перехватчика

Перехватчик подменяет закрытие целиком: автоматического закрытия больше нет. Решение пользователя приходит колбэком, и явный window.Close() при true — единственный путь завершить приложение. Отказ оставляет окно открытым.

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

Диалог — не обязательно вопрос с двумя кнопками. 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?

- Ошибка «cancelled» в err — проверяй err на nil
- ✅ reader nil и err nil — отмена не является ошибкой
- Пустой reader с нулевым URI
- Колбэк не вызовется вовсе

Отмена — штатный исход: reader nil, err nil, колбэк вызывается. Ошибка реального сбоя приходит в err и заслуживает ShowError. Поэтому проверка идёт в порядке: сначала err, потом nil-дескриптор, и только потом работа с файлом.

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

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

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

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

```go
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.___()
```

Подсказка: Подтверждение — dialog.ShowConfirm, точечное обновление строки — RefreshItem, выбор файла — NewFileOpen, URI читается методом URI(), показ требует Show().

Правильные ответы в пропусках: `ShowConfirm`, `RefreshItem`, `NewFileOpen`, `URI()`, `Show()`

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

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

Правильный порядок строк:

1. `клик по кнопке «Удалить» вызывает обработчик`
2. `dialog.ShowConfirm поднимает модальный оверлей — остальной ввод окна заблокирован`
3. `программа живёт дальше: run loop крутится, код после show уже выполнен`
4. `пользователь выбирает подтверждение — колбэк получает true`
5. `запись удаляется из среза по правилам десятого урока`
6. `RefreshItem перерисовывает список — оверлей снят, окно снова отвечает`

### Каталог 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+) | тост без кнопок и модальности | дешёвые подтверждения действий |

> **Честно о запуске кода.** Диалогам из этого урока нужен настоящий экран — в браузерной песочнице курса GUI не исполняется. Собери примеры по второму уроку и обязательно проверь три исхода подтверждения: обе кнопки и закрытие диалога без выбора — отказ не всегда кнопка, и логика удаления обязана оставаться внутри колбэка.

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

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

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

Диалоги закрыли пробел навигации из двенадцатого урока: теперь перехват закрытия вкладки и окна умеет спрашивать, импорт поднимает файловый выбор, а финальный менеджер задач получил все три канала сообщений. Опора урока — [второй урок](/courses/fyne/lessons/02-pervoe-okno/) с SetCloseIntercept и run loop, [десятый](/courses/fyne/lessons/10-spiski/) с RefreshItem, [пятый](/courses/fyne/lessons/05-grid-form/) с формами и валидацией, [двенадцатый](/courses/fyne/lessons/12-vkladki/) с DocTabs. Следующая тема — меню и тулбары: MainMenu с иерархией команд, Toolbar с кнопками-иконками и контекстные меню по правой кнопке из шестого урока; там появится и место для вызова наших диалогов — команды «Файл → Импорт» и «Правка → Очистить». Домашнее задание соберёт безопасную корзину заметок с подтверждением удаления и импортом из файла.

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

- **dialog.ShowConfirm** — Вопрос с двумя кнопками и колбэком func(bool): true — подтверждение, false — отказ. Сокращение для NewConfirm плюс Show; show не блокирует программу.
- **Модальность диалога** — Оверлей блокирует ввод родительского окна, но не программы: run loop крутится, код после show выполняется, ответ приходит в колбэк позже.
- **Двухшаговый путь New* + Show()** — NewConfirm и братья создают диалог без показа; Show выводит. Окно между ними — для SetDismissText и SetOnClosed. Сокращения Show* делают оба шага.
- **SetCloseIntercept и выход** — Перехватчик окна подменяет закрытие: диалог подтверждения, window.Close() в колбэке при true — исполнение обещания второго урока.
- **fyne.URIReadCloser** — Результат NewFileOpen: дескриптор чтения с методом URI() — Scheme, Name, Extension, String. Отмена — reader nil и err nil; ошибка приходит в err.
- **Тост против диалога** — widget.Toast (2.6+) — плашка без кнопок и модальности для дешёвых подтверждений. Диалоги — для решений с последствиями и ошибок, требующих внимания.

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

- Пакет 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 и колбэком и убедись, что она выполняется раньше ответа пользователя; если ждёшь обратного — перечитай предсказание про порядок вывода.

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

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

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

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

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

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

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