Русский
Русский
English
Статистика
Реклама

Widget

Обзор разработки дополнений для amoCRM, с использованием webHook и виджетов

09.03.2021 18:08:33 | Автор: admin

Содержание

  1. WebHook

  2. Виджет

  3. Техническая поддержка

  4. Итог

Мы не использовали все возможности разработки под amoCRM, ограничились приватным виджетом и webHook, поэтому ниже речь пойдет именно об этом

WebHook

К каждому аккаунту(на пробном только в течении 14 дней)можно установитьwebHook,документация подробно описывает процесс. Разработка каких-либо интеграций при этом не нужна.

В нашем случае было достаточно информации одобавлении сделки.

На сервере по указанному url в файле(в данном случаеindex.php)первым делом необходимо сырые POST данные преобразовать из json в массив php:

//если в сырых POST данных первый символ { значит это jsonif(strlen($sRawPost) > 0 && $sRawPost[0] == "{"){    $sDecode = json_decode($sRawPost, true);    if($sDecode !== null)         $_POST = $sDecode;}

ВgetпараметрыwebHookпри создании новой сделки ничего не приходит, а вpostпримерно следующее:

{    "leads": {        "add": [            {                "id": 4564454,                "name": "Название товара",                "status_id": 7534534,                "price" => 0,                "responsible_user_id": 453453453,                "last_modified": 1612007407,                "modified_user_id": 0,                "created_user_id": 0,                "date_create": 1612007407,                "pipeline_id": 4546445,                "tags": [                    {                        "id": 7899                        "name": tilda                    }                ]            }        ],        "account_id": 19277260        "custom_fields": [            {                "id": 448797,                "name": "name_field",                "code": "code_field",                "values": [                    {                        "value": "string"                    }                ]            }        ],        "created_at": 1612007407,        "updated_at": 1612007407    },    "account": [        {            "subdomain": "subdomain",            "id": 19217260,            "_links": [                "self": "https://subdomain.amocrm.ru"            ]        }    ]}

Очевидно чтоидентифицировать аккаунтиз которого была отправка запроса можно по ключуaccount, аleads["add"][0]["account_id"] == account["id"].

Вleads["add"][0]["tags"]находятсяспециальные метки, которые можно присвоить сделке, и по которым на стороне принимающего сервера можно как-то идентифицировать, в нашем случае нужен был тег со значениемtilda.

Но больший интерес представляетleads["add"][0]["custom_fields"]- этомассив произвольных полей сделки.

Изначально у сделки небольшой список полей и есть только "Основная" группа полей, в которую можнодобавить новые поля, а можнодобавить новую группу и в нее добавить новые поля- есть категоризация полей на стороне веб-интерфейса.

Для редактирования полей сделки нужно зайти в любую сделку или в интерфейс создания новой сделки, затем перейти во вкладку "Настроить".

Редактирование полей сделкиРедактирование полей сделки

Новое поле сделки может быть скрыто из веб-интерфейса для редактирования и может быть доступно только на стороне API.

Для работы с полями сделки на стороне сервера принимающего запрос можно так:

$aAdd = $_POST['leads']['add'][0]; //извлекаем имена полей$aNameCustomFields = array_column($aAdd['custom_fields'], 'name'); //здесь пишем проверку наличия нужных полей //получаем значение поля$idOrder = $aAdd['custom_fields'][array_search('ORDERID', $aNameCustomFields)]['values'][0]['value'];
Добавление нового поля сделкиДобавление нового поля сделки

А дальше все зависит от целей использования webHook :)

Изначально мы делали эту интеграцию для клиента на Tilda, так как у клиента было настроено получение заявок из Tilda в amoCRM. Нам необходимо было отслеживать факт поступления оплаты, однако в amoCRM не поступают такие данные ифакт оплаты не удалось выявить, даже в самом интерфейсе CRM

Виджет

Документация по разработке виджетовдостаточно подробная, и содержит все необходимые данные для работы. Даже есть пример виджетаздесь.

Для начала разработки виджета необходимозарегистрировать приложение, все просто.

Затем необходимо создатьструктуру виджетасостоящую из директорий и файлов.

Код виджета пишется наjavascript, шаблоны виджета наtwig, в js доступенjquery, есть возможность использованияcss

В директории виджета необходимо наличие файлаmanifest.json- файла конфигурации виджета,в документации есть подробное описание, аздесьописаны типы полей. Не забываем олокализацииi18n.

Стоит учесть что виджет подключается только к темобластям сайта, которые будут указаны вmanifest.json

В документации есть разделWEB SDKкоторый также посвящен созданию виджетов.

Не вижу смысла описывать процесс написания виджета, а также его возможности, так какдокументация исчерпывающая:)

Если виджет используетajax запросы со стороннего сервера(например как было у нас, виджет обращался к нашему серверу), то сервер должен отправлять заголовокAccess-Control-Allow-Origin: *:

header("Access-Control-Allow-Origin: *");

Разработка виджета осуществляется локально, на машине разработчика, тестирование виджета возможно только череззагрузку архива виджетана странице созданной интеграции.

Это не очень удобно, к тому же вmanifest.jsonкаждый разпри загрузке виджета нужно менять версиюwidget.version, иначе обновление виджета произойдет не сразу.

Техническая поддержка

Через чат amoCRM на всех страницах сайта CRM можно быстро получить ответы на многие вопросы. CRM платная для использования, но предоставляется бесплатный доступ на 14 дней. Однако,мы не собирались пользоваться самой CRM, а лишьпредоставлять нашу интеграцию. Возможность разработки виджета возможна только в течении 14 дней. После истечения периода, нам понадобилось продлить пробный период, обратившись в онлайн чат мы получили дополнительные 10 дней. Однако, позже через онлайн-чат удалось выяснить чтодля разработчиков публичных интеграций естьспециальный бесплатныйтехнический аккаунт. Также во время разработки нам потребовалось узнатьip адреса серверов amoCRM, с которых они присылают webHook на наш сервер, тех. поддержка через онлайн чат любезно их предоставила.На момент написания статьи, ip адреса серверов amoCRM не находятся в публичном доступе, узнать информацию о них можно через онлайн-чат на сайте.

Итог

В целом мне понравилось разработка для amoCRM, понятная и объемная документация с примерами, однако загрузка виджета доставляет определенные неудобства.

Подробнее..

Виджеты в iOS 14 возможности и ограничения

17.09.2020 14:13:09 | Автор: admin


В этом году для iOS-разработчиков появилось сразу несколько интересных возможностей посадить батарейку айфона улучшить пользовательский опыт, одна из таких новые виджеты. Пока мы все находимся в ожидании выхода релизной версии ОС, хотел бы поделиться опытом написания виджета для приложения Кошелёк и рассказать, с какими возможностями и ограничениями наша команда столкнулась на бета-версиях Xcode.

Начнем с определения виджетами называются представления, показывающие актуальную информацию без запуска основного мобильного приложения и всегда находящиеся под рукой у пользователя. Возможность их использовать уже есть в iOS (Today Extension), начиная с iOS 8, но мой сугубо личный опыт их использования довольно печален под них хоть и выделен специальный рабочий стол с виджетами, но я всё равно редко туда попадаю, привычка не выработалась.

Как итог, в iOS 14 мы видим перерождение виджетов, глубже интегрированных в экосистему, и более удобных для пользователя (в теории).



Работа с картами лояльности одна из основных функций нашего приложения Кошелёк. Периодически в отзывах в App Store появляются предложения от пользователей о возможности добавления виджета в Today. Пользователи, находясь у кассы, хотели бы как можно быстрее показать карту, получить скидку и убежать по своим делам, ведь промедление на любой квант времени вызывает те самые укоризненные взгляды в очереди. В нашем случае виджет может сэкономить несколько пользовательских действий для открытия карты, таким образом сделав операцию оплаты товара на кассе более быстрой. Магазины тоже будут благодарны меньше очередей на кассе.

В этом году Apple неожиданно для всех выпустила релиз iOS практически сразу после презентации, оставив разработчикам сутки на доработки своих приложений на Xcode GM, но мы оказались готовы к релизу, так как свой вариант виджета наша iOS-команда стала делать еще на бета-версиях Xcode. Сейчас виджет находится на ревью в App Store. Обновление устройств до новой iOS по статистике происходит довольно быстро; скорее всего, пользователи пойдут проверять, у каких приложений виджеты уже есть, найдут наш и будут счастливы.

В дальнейшем мы хотели бы добавить еще больше актуальной информации например, баланс, штрихкод, последние непрочитанные сообщения от партнёров и уведомления (например, что пользователям необходимо совершить действие подтвердить или активировать карту). На текущий момент результат выглядит следующим образом:



Добавление виджета в проект


Как и другие подобные дополнительные возможности, виджет добавляется как расширение (extension) к основному проекту. После добавления Xcode любезно генерирует код виджета и других основных классов. Вот тут нас ждала первая интересная особенность для нашего проекта этот код не компилировался, так как в одном из файлов автоматически подставлялся префикс в названиях класса (да-да, те самые Obj-C префиксы!), а в генерируемых файлах нет. Как говорится, не боги горшки обжигают, видимо, разные команды внутри Apple не договорились между собой. Будем надеяться, что к релизной версии исправят. Для того, чтобы настроить префикс своего проекта, в File Inspector основного таргета приложения заполните поле Class Prefix.

Для тех, кто следил за новинками WWDC, не секрет, что реализация виджетов возможна только с использованием SwiftUI. Интересный момент, что таким образом Apple форсит обновление на свои технологии: даже если основное приложение написано с использованием UIKit, то тут, будьте любезны, только SwiftUI. С другой стороны, это хорошая возможность попробовать новый фреймворк для написания фичи, в этом случае он удобно вписывается в процесс никаких изменений состояния, никакой навигации, требуется только задекларировать статичный UI. То есть вместе с новым фреймворком появились и новые ограничения, потому как старые виджеты в Today могут содержать больше логики и анимацию.

Одно из основных нововведений SwiftUI возможность предпросмотра без запуска на симуляторе или девайсе (preview). Классная вещь, но, к сожалению, на больших проектах (в нашем ~400K строк кода) работает крайне медленно даже на топовых макбуках, быстрее запустить на девайсе. Альтернатива такому способу иметь под рукой пустой проект или плейграунд для быстрого прототипирования.

Возможность для дебага также есть с помощью выделенной Xcode схемы. На симуляторе отладка работает нестабильно даже к версии Xcode 12 beta 6, поэтому лучше пожертвовать один из тестовых девайсов, обновить до iOS 14 и тестировать на нем. Будьте готовы, что эта часть и на релизных версиях будет работать не как ожидается.

Интерфейс


На выбор пользователю даются разные типы (WidgetFamily) виджетов трёх размеров small, medium, large.



Для регистрации необходимо явно указать поддерживаемые:
struct CardListWidget: Widget {    public var body: some WidgetConfiguration {        IntentConfiguration(kind: CardListWidgetKind,                            intent: DynamicMultiSelectionIntent.self,                            provider: CardListProvider()) { entry in            CardListEntryView(entry: entry)        }        .configurationDisplayName("Быстрый доступ")        .description("Карты, которые вы используете чаще всего")        .supportedFamilies([.systemSmall, .systemMedium])    }}

Мы с командой решили остановиться на small и medium выводить одну любимую карту для маленького виджета или 4 для medium.

Добавление виджета на рабочий стол происходит из центра управления, там пользователь выбирает необходимый ему вид:



Цвет кнопки Добавить виджет кастомизируем с помощью Assets.xcassets -> AccentColor, имя виджета с описанием тоже (пример кода выше).

Если уперлись в ограничение по количеству поддерживаемых видов, то можно расширить его с помощью WidgetBundle:

@mainstruct WalletBundle: WidgetBundle {    @WidgetBundleBuilder    var body: some Widget {        CardListWidget()        MySecondWidget()    }}

Так как виджет показывает слепок некоторого состояния, то единственная возможность для пользовательского интерактива это переход в основное приложение по нажатию на какой-то элемент или весь виджет. Никакой анимации, навигации и переходов на другие view. Но есть возможность прокинуть диплинку в основное приложение. При этом для small виджета зоной нажатия является вся область, и в этом случае используем widgetURL(_:) метод. Для medium и big доступны нажатия по view, и в этом нам поможет структура Link из SwiftUI.

Link(destination: card.url) {  CardView(card: card)}

Финальный вид виджета двух размеров получился следующим:



При проектировании интерфейса виджета могут помочь следующие правила и требования (согласно гайдлайнам Apple):
  1. Сфокусируйте виджет на одной идее и проблеме, не пытайтесь повторить всю функциональность приложения.
  2. В зависимости от размера выводите больше информации, а не просто масштабируйте контент.
  3. Выводите динамическую информацию, которая может меняться в течение дня. Крайности в виде полностью статической информации и информации, меняющейся ежеминутно, не приветствуются.
  4. Виджет должен давать актуальную информацию пользователям, а не быть еще одним способом открыть приложение.

Внешний вид настроили. Следующий шаг выбрать, какие карты и каким образом показывать пользователю. Карт может быть явно больше четырёх. Рассмотрим несколько вариантов:
  1. Дать возможность пользователю выбирать карты. Кто, как не он, знает, какие карты важнее!
  2. Показывать последние использованные карты.
  3. Сделать более умный алгоритм, ориентируясь, например, на время и день недели и статистику (если пользователь по будням вечером ходит во фруктовую лавку у дома, а на выходных ездит в гипермаркет, то можно помочь пользователю в этом моменте и показывать нужную карту)

В рамках прототипа остановились на первом варианте, чтобы заодно попробовать возможность настраивать параметры прямо на виджете. Не нужно делать специальный экран внутри приложения. Правда, настолько ли пользователи, как говорится, experienced, чтобы найти эти настройки?

Пользовательские настройки виджета


Настройки формируются с помощью интентов (привет Андроид-разработчикам) при создании нового виджета файл интента добавляется в проект автоматически. Кодогенератор подготовит класс-наследник от INIntent, который является частью фреймворка SiriKit. В параметрах интента стоит магическая опция Intent is eligible for widgets. Доступны несколько типов параметров, можно настраивать свои подтипы. Так как данные в нашем случае это динамический список, то еще устанавливаем пункт Options are provided dynamically.

Для разных типов виджета настраиваем максимальное количество элементов в списке для small 1, для medium 4.
Этот тип интента используется виджетом как источник данных.



Далее настроенный класс интента необходимо поставить в конфигурацию IntentConfiguration.
struct CardListWidget: Widget {    public var body: some WidgetConfiguration {        IntentConfiguration(kind: WidgetConstants.widgetKind,                            intent: DynamicMultiSelectionIntent.self,                            provider: CardListProvider()) { entry in            CardListEntryView(entry: entry)        }        .configurationDisplayName("Быстрый доступ")        .description("Карты, которые вы используете чаще всего.")        .supportedFamilies([.systemSmall, .systemMedium])    }}

В случае, если пользовательские настройки не требуются, то есть альтернатива в виде класса StaticConfiguration, которая работает без указания интента.

Изменяемыми на экране настроек являются title и description.
Название у виджета должно помещаться на одну строку, иначе оно обрезается. При этом допустимая длина для экрана добавления и настроек виджета отличаются.
Примеры максимальной длины названия для некоторых устройств:

iPhone 11 Pro Max28 для настроек21 для меню добавленияiPhone 11 Pro25 для настроек19 для меню добавленияiPhone SE24 для настроек19 для меню добавления

Описание является многострочным. В случае очень длинного текста в настройках контент можно проскролить. А вот на экране добавления сначала сжимается превью у виджета, а потом с версткой происходит что-то страшное.



Также можно изменить цвет фона и значения параметров WidgetBackground и AccentColor по умолчанию они уже лежат в Assets. При необходимости их можно переименовать в конфигурации виджета в Build Settings у группы Asset Catalog Compiler Options в полях Widget Background Color Name и Global Accent Color Name соответственно.



Некоторые параметры могут быть скрыты (или показаны) в зависимости от выбранного значения в другом параметре через настройку Relationship.
Стоит отметить, что UI для редактирования параметра зависит от его типа. К примеру, если укажем Boolean, то мы увидим UISwitch, а если Integer, то тут у нас уже выбор из двух вариантов: ввод через UITextfield или пошаговое изменение через UIStepper.



Взаимодействие с основным приложением.


Связку настроили, осталось определить, откуда сам интент возьмет реальные данные. Мостик с основным приложением в этом случае файл в общей группе (App Groups). Основное приложение пишет, виджет читает.
Для получения URL к общей группе используется следующий метод:
FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: group.ru.yourcompany.yourawesomeapp)

Сохраняем всех кандидатов, так как они будут использоваться пользователем в настройках как словарь для выбора.
Далее операционная система должна узнать, что данные обновились, для этого вызываем:
WidgetCenter.shared.reloadAllTimelines()// Или WidgetCenter.shared.reloadTimelines(ofKind: "kind")

Так как вызов метода будет перезагружать контент виджета и весь таймлайн, используйте его, когда данные действительно обновились, чтобы лишний раз не нагружать систему.

Обновление данных


В целях бережного отношения к батарейке пользовательского девайса Apple продумали механизм обновления данных на виджете с использованием timeline механизма генерации слепков (snapshot). Напрямую разработчик не обновляет и не управляет view, но зато предоставляет расписание, руководствуясь которым, операционная система нарежет снапшотов в бэкграунде.
Обновление происходит по следующим событиям:
  1. Вызов используемого ранее WidgetCenter.shared.reloadAllTimelines()
  2. При добавлении виджета пользователем на рабочий стол
  3. При редактировании настроек.

Также в распоряжении разработчика три вида политик по обновлению таймлайнов (TimelineReloadPolicy):
atEnd обновление после показа последнего cнапшота
never обновление только в случае принудительного вызова
after(_:) обновление через определенный промежуток времени.

В нашем случае достаточно попросить систему сделать один снепшот до момента, пока данные карт не обновились в основном приложении:

struct CardListProvider: IntentTimelineProvider {    public typealias Intent = DynamicMultiSelectionIntent    public typealias Entry = CardListEntry    public func placeholder(in context: Context) -> Self.Entry {        return CardListEntry(date: Date(), cards: testData)    }    public func getSnapshot(for configuration: Self.Intent, in context: Self.Context, completion: @escaping (Self.Entry) -> Void) {        let entry = CardListEntry(date: Date(), cards: testData)        completion(entry)    }    public func getTimeline(for configuration: Self.Intent, in context: Self.Context, completion: @escaping (Timeline<Self.Entry>) -> Void) {        let cards: [WidgetCard]? = configuration.cards?.compactMap { card in            let id = card.identifier            let storedCards = SharedStorage.widgetRepository.restore()            return storedCards.first(where: { widgetCard in widgetCard.id == id })        }        let entry = CardListEntry(date: Date(), cards: cards ?? [])        let timeline = Timeline(entries: [entry], policy: .never)        completion(timeline)    }}struct CardListEntry: TimelineEntry {    public let date: Date    public let cards: [WidgetCard]}

Более гибкий вариант пригодился бы при использовании автоматического алгоритма подбора карточек в зависимости от дня недели и времени.

Отдельно стоит отметить показ виджета, если он находится в стэке из виджетов (Smart Stack). В этом случае для управления приоритетами мы можем воспользоваться двумя вариантами: Siri Suggestions или через установку значения relevance у TimelineEntry с типом TimelineEntryRelevance. TimelineEntryRelevance содержит два параметра:
score приоритет текущего снапшота относительно других снапшотов;
duration время, пока виджет остается актуальным и система может поставить его на верхнюю позицию в стэке.

Оба способа, а также возможности конфигурации виджета, были подробно рассмотрены на сессии WWDC.

Также необходимо рассказать о том, как поддерживать актуальное отображение даты и времени. Так как мы не можем регулярно обновлять содержимое виджета, то для компонента Text было добавлено несколько стилей. При использовании стиля система автоматически обновляет содержимое компонента, пока виджет находится на экране. Возможно, в будущем такой же подход распространится и на другие SwiftUI компоненты.

Text поддерживает следующие стили:
relative разница времени между текущей и заданной датами. Тут стоит отметить: если дата указана в будущем, то начинается обратный отсчет, а после показывается дата от момента достижения нуля. Такое же поведение будет и для следующих двух стилей;
offset аналогично предыдущему, но есть индикация в виде префикса с ;
timer аналог таймера;
date отображение даты;
time отображение времени.

Кроме этого, есть возможность отобразить промежуток времени между датами, просто указав интервал.

let components = DateComponents(minute: 10, second: 0) let futureDate = Calendar.current.date(byAdding: components, to: Date())! VStack {   Text(futureDate, style: .relative)      .multilineTextAlignment(.center)   Text(futureDate, style: .offset)      .multilineTextAlignment(.center)   Text(futureDate, style: .timer)      .multilineTextAlignment(.center)   Text(Date(), style: .date)       .multilineTextAlignment(.center)   Text(Date(), style: .time)      .multilineTextAlignment(.center)   Text(Date() ... futureDate)      .multilineTextAlignment(.center)}



Превью виджета


При первом отображении виджет будет открыт в режиме превью, для этого нам необходимо вернуть TimeLineEntry в методе placeholder(in:). В нашем случае это выглядит так:
func placeholder(in context: Context) -> Self.Entry {        return CardListEntry(date: Date(), cards: testData) }

После чего к view применяется модификатор redacted(reason:) с параметром placeholder. При этом элементы на виджете отображаются размытыми.



Мы можем отказаться от этого эффекта у части элементов, использовав unredacted() модификатор.
Также в документации сказано, что вызов метода placeholder(in:) происходит синхронно и результат должен вернуться максимально быстро, в отличие от getSnapshot(in:completion:) и getTimeline(in:completion:)

Скругление элементов


В гайдлайнах рекомендуется согласовать скругление у элементов со скруглением виджета, для этого в iOS 14 была добавлена структура ContainerRelativeShape, которая позволяет применить к view форму контейнера.

.clipShape(ContainerRelativeShape()) 

Поддержка Objective-C


В случае необходимости добавить в виджет код на Objective-C (например, у нас на нем написана генерация изображений штрихкодов) всё происходит стандартным способом через добавление Objective-C bridging header. Единственная проблема, с которой мы столкнулись при сборке Xcode перестал видеть автогенерируемые файлы интентов, поэтому мы также добавили их в bridging header:

#import "DynamicCardSelectionIntent.h"#import "CardSelectionIntent.h"#import "DynamicMultiSelectionIntent.h"

Размер приложения


Тестирование проводилось на Xcode 12 beta 6
Без виджета: 61.6 Мб
С виджетом: 62.2 Мб

Резюмирую основные моменты, которые рассмотрели в статье:
  1. Виджеты отличная возможность пощупать SwiftUI на практике. Добавляйте их в проект, даже если минимальная поддерживаемая версия ниже iOS 14.
  2. WidgetBundle используется для увеличения числа доступных виджетов, вот отличный пример как много различных виджетов имеет приложение ApolloReddit.
  3. Для добавления пользовательских настроек на самом виджете поможет IntentConfiguration или StaticConfiguration, если пользовательские настройки не нужны.
  4. Общая папка на файловой системе в общей группе App Groups поможет синхронизировать данные с основным приложением.
  5. На выбор разработчику предоставляется несколько политик обновления таймлайна (atEnd, never, after(_:)).

На этом тернистый путь разработки виджета на бета-версиях Xcode можно считать завершенным, остался один простой шаг пройти ревью в App Store.

Спасибо, что дочитали до конца, буду рад предложениям и комментариям. Пройдите, пожалуйста, небольшой опрос, посмотрим, насколько виджеты популярны среди пользователей и разработчиков.
Подробнее..

Реализация двойной панели инструментов в QT

06.03.2021 18:05:18 | Автор: admin

Ссылка на исходный код

Задача и требования

  • Создание двойного "тулбара" (панели инструментов), положение разделителя которого меняло бы соотношение полей сплиттераа.
    Иными словами разделитель должен перетягиваться мышкой.

  • Положение разделителя тулбаров так же должно зависеть от положения сплиттера.

  • При наведении курсора на разделитель тулбаров должен меняться тип курсора на горизонтальный QT::SizeHorCursor

  • При перетягивании разделителя курсор так же должен менять свой тип на горизонтальный

  • В целом данный тулбар должен соответствовать типичному представлению пользователя о данном типе тулбаров, это сделает интерфейс простым и понятным

  • Приложение должно давать возможность пользователю менять размер окна

Реализация

Первый шаг

Создаем Qt Widgets application. Основы cpp и h файлов, с которыми мы работаем, сгенерируются без нашего участия.

Object Inspector

Выполнение задачи необходимо начать с построения структуры интерфейса.

Для корректного отображения виджета toolbar необходима хотя бы одна иконка, расположенная на нем. Иконку можете выбрать на свой вкус.

Параметры виджетов

У виджетов MainWindow, centralwidget и у обоих тулбаров задаем параметр Mouse tracking, как true. Это позволит перехватывать события нажатия мыши, отпускания мыши и передвижения мыши над тулбарами.

У обоих списков, что лежат в сплиттере, задаем параметр минимальной ширины. В моем случае этот параметр равен 200 пикселям. У сплиттера задаем параметр childrenCollapsible, как false. Эти действия необходимы для того, чтобы не было возможности "схлопнуть" одну из секций сплиттера или увести тулбар за пределы экрана. Ниже покажу: как это связано с реализацией cpp файла.

У тулбаров нужно установить параметр movable, как false, чтобы убрать дефолтное задание размеров тулбаров пользователем.

MainWindow.h

private:    Ui::MainWindow *ui;    int timerId = 0;    bool toolbar_dragged = false;

Под Ui::MainWindow *ui создаем две переменные: timerId и toolbar_dragged.
timerId необходима для хранения времени таймера, которое мы будем использовать.
toolbar_dragged определяет: тянет ли пользователь за разделитель

public:    MainWindow(QWidget *parent = nullptr);    ~MainWindow();private slots:    void on_splitter_splitterMoved(int pos, int index, bool windowResized  = true);    void mouseMoveEvent(QMouseEvent *event);    void mouseReleaseEvent(QMouseEvent *e);    void mousePressEvent(QMouseEvent *event);    void timerEvent(QTimerEvent *event);    void resizeEvent(QResizeEvent* event);

Задаем слоты и указываем входной параметр слота on_splitter_splitterMoved windowResized, как параметр, по умолчанию заданный, как true. В resizeEvent, событии изменения размера окна, будет вызываться on_splitter_splitterMoved при помощи emit. Позиция сплиттера при изменении окна меняется, потому и должна меняться позиция разделителя тулбаров. Но в случае изменения размера окна перетягивания разделителя не происходит, toolbar_dragged == false. Потому для случая, когда окно меняет размер, необходимо данное входное условие. Подробнее об этом в cpp файле.

MainWindow.cpp

В файл необходимо включить две библиотеки:

QMouseEvent нужна для обработки сообщений мыши
QWidget необходима для работы с виджетами приложения

#include <QMouseEvent>#include <QWidget>

Рассмотрим конструктор главного окна:

centralWidget()->layout()->setContentsMargins убирает отступы от границ окна, созданные добавлением layout-а, как центрального виджета.
setSpacing(2) нужен потому, что иконки на этом тулбаре не передают событие передвижения мыши в MainWindow, Отступ в 2 пикселя вполне достаточен, чтобы избавиться от этой проблемы.

MainWindow::MainWindow(QWidget *parent)    : QMainWindow(parent)    , ui(new Ui::MainWindow){    ui->setupUi(this);    centralWidget()->layout()->setContentsMargins(0, 0, 0, 0);    //this is nessesary    this->ui->toolBar_2->layout()->setSpacing(2);}

Функция on_splitter_splitterMoved задает фиксированный размер левого тулбара. Второй тулбар автоматически подстраивается под размер первого. Данный размер можно задавать многократно, что мы и делаем. Обычными resize и move методами это сделать нельзя.

В случае, если размер тулбара становится меньше, чем минимальный размер первого списка, или начинает прижимать второй список к левому краю окна так, что размер второго списка меньше минимального, изменение размера первого тулбара, а следовательно и положения разделителя между тулбарами - не происходит. Это необходимо, не смотря на то, что в QT дизайнере уже заданы минимальные параметры ширины для списков.

void MainWindow::on_splitter_splitterMoved(int pos, int /*index*/, bool windowResized){    if (((pos>this->ui->listWidget->minimumSize().width()) &&        (pos<(this->width() - this->ui->listWidget_2->minimumSize().width()))) || windowResized)    {        this->ui->toolBar->setMaximumSize(pos, ui->toolBar->rect().height());        this->ui->toolBar->setMinimumSize(pos, ui->toolBar->rect().height());    }}

Функция mouseReleaseEvent вызывается, когда пользователь отпускает мышь. После этого нужно приветси курсор к типу Qt::ArrowCursor и задать соответствующую переменную toolbar_dragged, как false.

void MainWindow::mouseReleaseEvent(QMouseEvent* /*e*/){    if (toolbar_dragged)    {        toolbar_dragged = false;        this->setCursor(Qt::ArrowCursor);    }}

mousePressEvent перехватывает событие нажатия мыши пользователем.

При срабатывании данной функции, если мышь находится на промежутке +-20 пикселей от разделителя сплиттера, на высоте тулбаров, toolbar_dragged становится true, а тип курсора меняется на соответствующий перетягиванию.

void MainWindow::mousePressEvent(QMouseEvent *event){    QList<int> currentSizes = this->ui->splitter->sizes();    if ((this->ui->toolBar_2->underMouse()) &&        (event->buttons() == Qt::LeftButton) &&        (event->pos().x() < (currentSizes[0]+20)))    {        this->ui->toolBar_2->movableChanged(true);        toolbar_dragged = true;        this->setCursor(Qt::SizeHorCursor);    }    else if ((this->ui->toolBar->underMouse()) &&             (event->buttons() == Qt::LeftButton) &&             (event->pos().x() > (currentSizes[0]-20)))    {        this->ui->toolBar->movableChanged(true);        toolbar_dragged = true;        this->setCursor(Qt::SizeHorCursor);    }}

mouseMoveEvent вызывается при перемещении мыши. Если происходит перетягивание разделителя (toolbar_dragged), то вызывает функцию on_splitter_splitterMoved для изменения размера тулбара, предварительно поменяв размеры сплиттера.

В противном случае, если перетягивания не происходит, но мышь находится на промежутке -2 +5 пикселей от разделителя сплиттера, на высоте тулбаров, с отступами от горизонтальных границ в два пикселя, то тип курсора меняется на SizeHorCursor. Если эти отступы не сделать, то мышь не будет менять тип с SizeHorCursor на ArrowCursor, даже если задать Mouse tracking параметр, как true, всем другим виджетам.

void MainWindow::mouseMoveEvent(QMouseEvent* event){    QList<int> currentSizes = this->ui->splitter->sizes();    if (toolbar_dragged)    {        QList<int> currentSizes = this->ui->splitter->sizes();        currentSizes[0] = event->pos().x();        currentSizes[1] = this->width() - event->pos().x();        this->ui->splitter->setSizes(currentSizes);        emit on_splitter_splitterMoved(event->pos().x(), 1, false);    }    else if ((event->pos().y() > (2+this->ui->toolBar->y())) &&             (event->pos().y() < (this->ui->toolBar->height()-2+this->ui->toolBar->y())) &&             (event->pos().x() < (currentSizes[0]+5)) &&             (event->pos().x() > (currentSizes[0]-10)))    {        this->setCursor(Qt::SizeHorCursor);    }    else    {        this->setCursor(Qt::ArrowCursor);    }}

resizeEvent - событие изменения размера окна. В этом событии нельзя вызвать on_splitter_splitterMoved, потому необходимо сделать таймер, который будет "выходить за пределы" resizeEvent, работать вне ее.

void MainWindow::resizeEvent(QResizeEvent* event){        if (timerId)        {            killTimer(timerId);            timerId = 0;        }        // delay beetween ends of resize and your action        timerId = startTimer(1);    QMainWindow::resizeEvent(event);}

timerEvent меняет размеры тулбара через on_splitter_splitterMoved. При изменении размера окна, положение разделителя сплиттера определяется автоматически

void MainWindow::timerEvent(QTimerEvent *event){    QList<int> currentSizes = this->ui->splitter->sizes();    this->ui->toolBar_2->adjustSize();    emit on_splitter_splitterMoved(currentSizes[0], 1,true);    killTimer(event->timerId());    timerId = 0;}
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru