Клавиатуры и View¶
Клавиатуры¶
Через add_button¶
import vkflow as vf
@app.command("меню")
async def menu(ctx: vf.Context):
kb = vf.Keyboard(inline=True)
kb.add_button("Кнопка 1", color="positive")
kb.add_button("Кнопка 2", color="negative")
kb.row() # Новый ряд (алиас для new_line())
kb.add_button("Во всю ширину", color="primary")
await ctx.send("Выберите:", keyboard=kb)
Через конструктор¶
from vkflow import Keyboard, Button
kb = Keyboard(
Button.text("Кнопка 1").positive(),
Button.text("Кнопка 2").negative(),
..., # Ellipsis = новый ряд
Button.text("Во всю ширину").primary(),
inline=True,
)
Через add + new_line (цепочка)¶
kb = vf.Keyboard(inline=True)
kb.add(Button.text("A").positive()).add(Button.text("B").negative())
kb.new_line()
kb.add(Button.text("C").primary())
Цвета кнопок¶
| Цвет | Метод | Вид |
|---|---|---|
"primary" |
.primary() |
Синяя |
"secondary" |
.secondary() |
Белая (по умолчанию) |
"positive" |
.positive() |
Зелёная |
"negative" |
.negative() |
Красная |
Типы кнопок¶
from vkflow import Button
# Текстовая (обычная)
Button.text("Текст")
# Callback (не отправляет сообщение, только событие)
Button.callback("Нажми", payload={"action": "click"})
# Открыть ссылку
Button.open_link("Перейти", link="https://vk.com")
# Открыть приложение VK Mini App
Button.open_app("Открыть", app_id=123456, owner_id=-100, hash="...")
# Геолокация
Button.location()
Обычная (не inline) клавиатура¶
kb = vf.Keyboard(one_time=True) # one_time=True -скрыть после нажатия
kb.add_button("Привет")
kb.add_button("Пока")
await ctx.send("Выбери:", keyboard=kb)
Убрать клавиатуру¶
Payload у кнопок¶
# Через add_button
kb.add_button("Товар 1", color="positive", payload={"item_id": 1})
# Через Button
Button.text("Товар 1", payload={"item_id": 1}).positive()
Обработка payload¶
@app.on_clicked_button()
async def handle_click(ctx: vf.NewMessage, item_id: int):
"""Вызывается при нажатии кнопки с payload {"command": "handle_click", "args": {"item_id": 1}}"""
return f"Выбран товар: {item_id}"
Интерактивные View¶
View -класс с callback-кнопками и автоматическим управлением состоянием. Кнопки View используют Button.callback и обрабатываются через VK Callback API (message_event).
Создание View¶
from vkflow.ui.view import View, button
class ConfirmView(View):
def __init__(self):
super().__init__(timeout=60, inline=True)
self.result = None
@button(label="Да", color="positive")
async def yes(self, interaction: vf.CallbackButtonPressed):
self.result = True
await interaction.show_snackbar("Принято!")
self.stop()
@button(label="Нет", color="negative")
async def no(self, interaction: vf.CallbackButtonPressed):
self.result = False
await interaction.show_snackbar("Отменено")
self.stop()
async def on_timeout(self):
"""Вызывается при истечении таймаута"""
pass
Использование View¶
@app.command("подтвердить")
async def confirm(ctx: vf.Context):
view = ConfirmView()
await ctx.send("Вы уверены?", view=view)
timed_out = await view.wait()
if not timed_out:
await ctx.send(f"Ответ: {'Да' if view.result else 'Нет'}")
else:
await ctx.send("Время вышло!")
Параметры View¶
class MyView(View):
def __init__(self):
super().__init__(
timeout=180, # Таймаут в секундах (None = бесконечный)
inline=True, # Inline-клавиатура
)
Параметры кнопок¶
@button(
label="Текст", # Текст на кнопке
emoji="🎉", # Эмодзи перед текстом
custom_id="my_btn", # Уникальный ID (по умолчанию = имя метода)
color="positive", # Цвет: positive/negative/primary/secondary
row=0, # Номер ряда (None = авто)
disabled=False, # Неактивная кнопка
)
async def my_button(self, interaction):
...
Проверка пользователя¶
class PrivateView(View):
def __init__(self, owner_id: int):
super().__init__(timeout=120, inline=True)
self.owner_id = owner_id
async def interaction_check(self, interaction: vf.CallbackButtonPressed) -> bool:
"""Только владелец может нажимать кнопки"""
return interaction.msg.user_id == self.owner_id
Управление View¶
view = MyView()
view.stop() # Остановить View
view.refresh() # Сбросить таймер таймаута
view.is_finished() # Завершён ли View
view.is_dispatching() # Обрабатывается ли сейчас нажатие
view.is_persistent() # True если timeout=None
View с FSM¶
View может интегрироваться с FSM для управления состояниями через кнопки:
from vkflow.app.fsm import MemoryStorage
class OrderView(View):
fsm_storage = MemoryStorage() # Атрибут класса
@button(label="Подтвердить", color="positive")
async def confirm(self, ctx, fsm):
# fsm автоматически инжектируется из fsm_storage
data = await fsm.finish()
await ctx.show_snackbar(f"Заказ: {data}")
self.stop()
@button(label="Отмена", color="negative")
async def cancel(self, ctx, fsm):
await fsm.clear()
await ctx.show_snackbar("Отменено")
self.stop()
Пример: Калькулятор¶
class CalculatorView(View):
def __init__(self):
super().__init__(timeout=120, inline=True)
self.value = 0
@button(label="+1", color="positive", row=0)
async def add_one(self, interaction):
self.value += 1
await interaction.show_snackbar(f"Значение: {self.value}")
@button(label="-1", color="negative", row=0)
async def sub_one(self, interaction):
self.value -= 1
await interaction.show_snackbar(f"Значение: {self.value}")
@button(label="Сброс", color="primary", row=1)
async def reset(self, interaction):
self.value = 0
await interaction.show_snackbar("Сброшено!")
@button(label="Готово", color="secondary", row=1)
async def done(self, interaction):
await interaction.show_snackbar(f"Итого: {self.value}")
self.stop()
Карусели¶
from vkflow import Carousel, Element
@app.command("магазин")
async def shop(ctx: vf.Context):
carousel = Carousel()
carousel.add_element(
Element(
title="Товар 1",
description="Описание товара",
photo_id="-123_456",
buttons=[Button.text("Купить", payload={"item": 1}).positive()],
)
)
carousel.add_element(
Element(
title="Товар 2",
description="Другой товар",
photo_id="-123_789",
buttons=[Button.text("Купить", payload={"item": 2}).positive()],
)
)
await ctx.send("Наш магазин:", carousel=carousel)