Найти в Дзене
ZDG

Графический интерфейс программы на языке C #9: Ползунки

Оглавление

В предыдущей части были сделаны основные состояния элементов и переходы между ними:

Но я слишком легкомысленно отнёсся к перетаскиванию, которое только перемещало окно. Позже вскрылся целый пласт задач, связанных с перетаскиванием.

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

Например, горизонтальный ползунок:

Аналогично – вертикальный. С его помощью можно вводить какие-то числовые значения, или использовать как скроллбар для прокрутки контента.

Есть и такие, названия которых не знаю, но вроде как это колёса прокрутки:

-2

Они при перетаскивании остаются на месте, но при этом крутятся вверх или вниз и тоже меняют какое-то значение.

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

-3

Всех их объединяет, а точнее, разъединяет, разнообразие способов использования драга.

Например, у горизонтального ползунка драг ограничен только по горизонтали, и кроме того, его движущаяся часть (собственно ползунок) не может выйти за пределы элемента. Когда курсор вышел за пределы элемента, драг останавливается (в некоторых вариантах – нет). Но вот у колеса прокрутки выход за пределы ничего не значит. Оно работает так, что чем дальше курсор отошёл от колеса, тем большее значение он накрутил. У ручки громкости драг может работать как в её пределах, так и вне её, но проблема в том, что он не линейный. Ручка вращается вокруг своего центра, поэтому алгоритм вычисления значения должен быть другой.

Я уж не говорю о том, что теперь надо различать, какой элемент может двигать всё окно, а какой только сам себя.

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

Поэтому я добавил в схему дополнительные пункты "обработать логику элемента в GUI" и "обработать логику элемента в контроллере". Я пометил их красным цветом:

-4

Чтобы не было путаницы, поясню дополнительно. Обработка логики элемента в GUI это то, как меняется внешний вид и положение элемента по законам GUI. То есть кнопки должны нажиматься, движки двигаться и т.д. Это чисто механическая обработка для некоторых типов элементов со сложным поведением.

А обработка логики элемента в контроллере это то, как данный конкретный элемент и данное конкретное событие влияют на состояние программы. Здесь нет никакой стандартной договорённости, потому что на любое событие любого элемента можно назначить абсолютно любую логику – что я дальше и покажу.

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

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

Горизонтальный ползунок

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

Добавлен перечислимый тип GUI_ITEM_HSLIDER:

-5

Структура элемента включает минимальное, максимальное и текущее значение:

-6

Кроме того, есть поле margin. Значение элемента рассчитывается исходя из позиции ползунка в прямоугольнике. Прямоугольник элемента задаётся как обычно, и расчёт значения делается как пропорция между минимальным и максимальным положением ползунка. Но его крайние положения не полностью совпадают с рамками прямоугольника, так как те могут иметь толщину:

-7

Вот за эту разницу и отвечает margin, чтобы вычисления были точными. Значение margin задавать не нужно, оно посчитается автоматически во время рендера с текущим контекстом.

С виду простой элемент таит в себе много неприятных для реализации особенностей. Интерактивен не только сам ползунок, но и вся полоска, где он может двигаться. Это порождает такие варианты:

Если кнопка мыши нажата в области полоски, где нет ползунка, то обычно делают перемещение ползунка ближе к месту клика (типа перелистывание контента на одну страницу). Но для ввода значений мне больше нравится, когда ползунок перепрыгивает ровно туда, где был клик. При этом он должен расположиться так, чтобы курсор мыши оказался в его центре. Но если мы кликнули близко к краю полосы прокрутки, ползунок не сможет переместиться дальше него, поэтому курсор мыши окажется не в его центре.

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

Уффф...

Ну и вот, например, как выглядит обработка логики GUI для нажатия на ползунок:

-8

Тест

Я добавил в окно три ползунка. Они будут управлять цветом заголовка окна. Один ползунок это R, другой G, и третий B. Принимают значения от 0 до 255.

-9

Сами ползунки работают, то есть их можно перемещать любым из вышеописанных способов. Теперь нужна обработка логики в контроллере. Ранее она работала только для события отжатия кнопки, но теперь требуется и для нажатия, и для перетаскивания.

-10

Ранее общий метод route_item() поделился на отдельные route_item_move(), route_item_down() и route_item_up().

К примеру, возникло событие нажатия кнопки мыши на ползунке. В соответствии с ним был вызван метод route_item_down(). Который определил, что кнопка была нажата в окне с идентификатором ID_WIN1, и маршрутизировал обработку уже в контроллер этого окна:

-11

Вариантов контроллера теперь тоже три: process_win1_move(), process_win1_down(), process_win1_up(). Мы попадаем в down:

-12

Контроллер применяет уже логику приложения: он знает, что в окне есть ползунки с определёнными ID, и если ID элемента совпадает с одним из них, то он поменяет цвет заголовка, у которого тоже есть известный контроллеру ID. Само изменение цвета вынесено в отдельную функцию, потому что та же самая логика будет работать для события перетаскивания ползунка:

-13

Здесь с помощью громоздкого кода отыскиваем по ID последовательно три элемента ползунков, берём их значения, соединяем их в RGB-цвет, затем получаем опять же по ID элемент заголовка, и устанавливаем ему цвет.

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

-14
-15

Ну и наверно пора закругляться на сегодня. Остальные двигунки допишу молча.

Видео (сразу и для следующей части):

Код, как всегда, на гитхабе:

GitHub - nandakoryaaa/gui

Читайте дальше:

Наука
7 млн интересуются