kkelchev Posted March 12 Posted March 12 Hi How to do quick search with keyboard in TuniListBox. Thanks Kamen Quote
WSIINNDA Posted March 13 Posted March 13 11 hours ago, kkelchev said: Hi How to do quick search with keyboard in TuniListBox. Thanks Kamen // Функция, которая выполняется после рендеринга компонента TUniListBox function afterrender(sender, eOpts) { var me = sender; // Проверяем, не было ли уже добавлено поле поиска if (me._searchFieldAdded) { return; } me._searchFieldAdded = true; // Находим основной элемент boundlist var boundlist = me.el; // Находим контейнер для элементов списка и сам список var listWrap = boundlist.down('.x-boundlist-list-ct'); var listEl = boundlist.down('.x-list-plain'); // Отключаем прокрутку в listWrap (чтобы не было двойной прокрутки) if (listWrap) { listWrap.setStyle('overflow', 'hidden'); } // Создаем контейнер для поиска с кнопкой очистки var searchContainer = Ext.DomHelper.createDom({ tag: 'div', cls: 'x-boundlist-search-container', style: 'border-bottom: 1px solid #d0d0d0; background: #f5f5f5; padding: 8px; width: 100%; box-sizing: border-box; flex-shrink: 0;', children: [{ tag: 'div', style: 'position: relative; width: 100%;', children: [{ tag: 'input', type: 'text', id: me.getId() + '_searchField', placeholder: 'Поиск...', style: 'width: 100%; padding: 8px 30px 8px 8px; box-sizing: border-box; border: 1px solid #c0c0c0; border-radius: 4px; outline: none; font-size: 14px;' }, { tag: 'span', id: me.getId() + '_clearBtn', style: 'position: absolute; right: 8px; top: 50%; transform: translateY(-50%); cursor: pointer; font-size: 20px; color: #999; display: none; line-height: 1;', html: '×' // Символ "×" для кнопки очистки }] }] }); // Вставляем контейнер поиска перед списком элементов if (listEl) { listWrap.dom.insertBefore(searchContainer, listEl.dom); } else { listWrap.dom.insertBefore(searchContainer, listWrap.dom.firstChild); } var searchField = Ext.get(me.getId() + '_searchField'); var clearBtn = Ext.get(me.getId() + '_clearBtn'); // Добавляем CSS стили для исправления различных проблем var style = document.createElement('style'); style.textContent = ` .x-boundlist-item:focus, .x-boundlist-item:active { outline: none !important; } .x-boundlist-item { outline: none !important; } .x-boundlist-floating { outline: none !important; overflow: hidden !important; } .x-boundlist-list-ct { overflow: hidden !important; display: flex !important; flex-direction: column !important; padding: 0 !important; height: 100% !important; } .x-list-plain { overflow-y: auto !important; overflow-x: hidden !important; width: 100% !important; box-sizing: border-box !important; flex: 1 !important; margin: 0 !important; } .x-boundlist-item { width: 100% !important; box-sizing: border-box !important; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .x-boundlist-search-container { width: 100%; box-sizing: border-box; flex-shrink: 0; } `; document.head.appendChild(style); // Функция фильтрации списка var filterList = function() { var searchText = searchField.dom.value.toLowerCase().trim(); // Показываем/скрываем кнопку очистки if (searchText.length > 0) { clearBtn.setStyle('display', 'block'); } else { clearBtn.setStyle('display', 'none'); } // Получаем все элементы списка var items = listEl ? listEl.query('.x-boundlist-item') : []; // Если поле поиска пустое, показываем все элементы if (searchText === '') { items.forEach(function(item) { item.style.display = ''; }); return; } // Фильтруем элементы по тексту items.forEach(function(item) { var itemText = (item.textContent || item.innerText).toLowerCase(); if (itemText.indexOf(searchText) > -1) { item.style.display = ''; } else { item.style.display = 'none'; } }); }; // Обработчик ввода текста для фильтрации searchField.on('keyup', filterList); // Обработчик клика по кнопке очистки clearBtn.on('click', function() { searchField.dom.value = ''; clearBtn.setStyle('display', 'none'); filterList(); searchField.focus(); }); // Устанавливаем фокус на поле поиска setTimeout(function() { if (searchField) { searchField.focus(); } }, 200); // Функция точной настройки высоты var adjustHeight = function() { setTimeout(function() { if (!me.rendered) return; var searchHeight = Ext.fly(searchContainer).getHeight() || 49; var totalHeight = me.getHeight(); // Учитываем толщину границы при расчете высоты var borderWidth = 0; var boundlistStyle = window.getComputedStyle(boundlist.dom); if (boundlistStyle.borderWidth) { borderWidth = parseInt(boundlistStyle.borderTopWidth) + parseInt(boundlistStyle.borderBottomWidth); } var availableHeight = totalHeight - borderWidth; var listHeight = availableHeight - searchHeight; // Настраиваем высоту listWrap if (listWrap) { listWrap.setStyle('height', availableHeight + 'px'); listWrap.setStyle('maxHeight', availableHeight + 'px'); } // Настраиваем высоту listEl if (listEl) { listEl.setStyle('height', listHeight + 'px'); listEl.setStyle('maxHeight', listHeight + 'px'); listEl.setStyle('overflow-y', 'auto'); listEl.setStyle('overflow-x', 'hidden'); } // Убеждаемся, что нет пустого пространства boundlist.setStyle('overflow', 'hidden'); }, 50); }; // Выполняем настройку высоты после полного рендеринга setTimeout(adjustHeight, 250); me.on('resize', adjustHeight); // Перенастраиваем высоту при клике me.on('click', function() { setTimeout(adjustHeight, 10); }); // Перенастраиваем высоту после полного отображения setTimeout(adjustHeight, 500); } This is very important. procedure TMainForm.UniListBox1Click(Sender: TObject); begin if UniListBox1.ItemIndex <> -1 then Caption := UniListBox1.Items.Strings[UniListBox1.ItemIndex]; end; 1 Quote
kkelchev Posted March 13 Author Posted March 13 Спасибо ещё раз. Подтверждено. Работает идеально. 1 Quote
WSIINNDA Posted March 13 Posted March 13 1 hour ago, kkelchev said: Спасибо ещё раз. Подтверждено. Работает идеально. Пожалуйста, используйте этот код, я исправил автоматический фокус. function afterrender(sender, eOpts) { var me = sender; // Проверяем, не было ли уже добавлено поле поиска if (me._searchFieldAdded) { return; } me._searchFieldAdded = true; // Находим основной элемент boundlist var boundlist = me.el; // Находим контейнер для элементов списка и сам список var listWrap = boundlist.down('.x-boundlist-list-ct'); var listEl = boundlist.down('.x-list-plain'); // Отключаем прокрутку в listWrap if (listWrap) { listWrap.setStyle('overflow', 'hidden'); } // Создаем уникальные ID для элементов var searchFieldId = me.getId() + '_searchField'; var clearBtnId = me.getId() + '_clearBtn'; // Создаем контейнер для поиска с кнопкой очистки var searchContainer = Ext.DomHelper.createDom({ tag: 'div', cls: 'x-boundlist-search-container', style: 'border-bottom: 1px solid #d0d0d0; background: #f5f5f5; padding: 8px; width: 100%; box-sizing: border-box; flex-shrink: 0;', children: [{ tag: 'div', style: 'position: relative; width: 100%;', children: [{ tag: 'input', type: 'text', id: searchFieldId, placeholder: 'Поиск...', style: 'width: 100%; padding: 5px 30px 5px 8px; box-sizing: border-box; border: 1px solid #c0c0c0; border-radius: 4px; outline: none; font-size: 13px;' }, { tag: 'span', id: clearBtnId, style: 'position: absolute; right: 8px; top: 50%; transform: translateY(-50%); cursor: pointer; font-size: 20px; color: #999; display: none; line-height: 1;', html: '×' // Символ крестика для кнопки очистки }] }] }); // Вставляем контейнер поиска в начало списка if (listEl) { listWrap.dom.insertBefore(searchContainer, listEl.dom); } else { listWrap.dom.insertBefore(searchContainer, listWrap.dom.firstChild); } // Получаем ссылки на созданные DOM-элементы var searchField = document.getElementById(searchFieldId); var clearBtn = document.getElementById(clearBtnId); // Если элементы не найдены, выходим из функции if (!searchField || !clearBtn) return; // Добавляем CSS стили для правильного отображения компонентов var style = document.createElement('style'); style.textContent = ` /* Убираем фокус с элементов списка */ .x-boundlist-item:focus, .x-boundlist-item:active { outline: none !important; } /* Настройки контейнера списка */ .x-boundlist-floating { outline: none !important; overflow: hidden !important; } /* Настройки контейнера с элементами */ .x-boundlist-list-ct { overflow: hidden !important; display: flex !important; flex-direction: column !important; padding: 0 !important; height: 100% !important; } /* Настройки списка элементов */ .x-list-plain { overflow-y: auto !important; overflow-x: hidden !important; width: 100% !important; box-sizing: border-box !important; flex: 1 !important; margin: 0 !important; } /* Контейнер поиска */ .x-boundlist-search-container { width: 100%; box-sizing: border-box; flex-shrink: 0; } /* Убираем подсветку фокуса у boundlist */ .x-boundlist:focus { outline: none !important; } `; document.head.appendChild(style); /** * Функция фильтрации элементов списка * Скрывает элементы, не соответствующие тексту поиска */ var filterList = function() { var searchText = searchField.value.toLowerCase().trim(); // Показываем или скрываем кнопку очистки if (searchText.length > 0) { clearBtn.style.display = 'block'; } else { clearBtn.style.display = 'none'; } // Получаем все элементы списка var items = listEl ? listEl.query('.x-boundlist-item') : []; // Если строка поиска пуста, показываем все элементы if (searchText === '') { items.forEach(function(item) { item.style.display = ''; }); return; } // Фильтруем элементы по тексту items.forEach(function(item) { var itemText = (item.textContent || item.innerText).toLowerCase(); if (itemText.indexOf(searchText) > -1) { item.style.display = ''; // Показываем, если текст совпадает } else { item.style.display = 'none'; // Скрываем, если не совпадает } }); }; // Предотвращаем всплытие событий для поля ввода // Используем capturing phase, чтобы перехватить события до их обработки ExtJS searchField.addEventListener('mousedown', function(e) { e.stopPropagation(); // Останавливаем всплытие события }, true); // Обработчик клика на поле поиска searchField.addEventListener('click', function(e) { e.stopPropagation(); // Останавливаем всплытие this.focus(); // Устанавливаем фокус на поле }, true); // Основной обработчик ввода текста для фильтрации searchField.addEventListener('input', filterList); // Дополнительный обработчик для надежности searchField.addEventListener('keyup', filterList); // Обработчик для кнопки очистки поля поиска clearBtn.addEventListener('mousedown', function(e) { e.stopPropagation(); // Останавливаем всплытие e.preventDefault(); // Отменяем стандартное поведение searchField.value = ''; // Очищаем поле clearBtn.style.display = 'none'; // Скрываем кнопку очистки filterList(); // Обновляем фильтрацию (показываем все элементы) searchField.focus(); // Возвращаем фокус на поле поиска }, true); // Глобальный обработчик для всего boundlist // Предотвращает закрытие списка при клике на поле поиска или кнопку очистки boundlist.dom.addEventListener('mousedown', function(e) { // Если клик был по полю ввода или кнопке очистки, ничего не делаем // Позволяем событиям обрабатываться нормально if (e.target === searchField || e.target === clearBtn || searchField.contains(e.target) || clearBtn.contains(e.target)) { return; } }, true); /** * Функция принудительной установки фокуса на поле поиска * Использует рекурсивные попытки с задержкой * @param {number} attempts - количество попыток */ var forceFocus = function(attempts) { if (!attempts) attempts = 0; setTimeout(function() { if (searchField && document.body.contains(searchField)) { // Проверяем, видим ли элемент и доступен ли он var style = window.getComputedStyle(searchField); if (style.display !== 'none' && style.visibility !== 'hidden' && !searchField.disabled) { try { searchField.focus(); // Пытаемся установить фокус // Проверяем, удалось ли установить фокус setTimeout(function() { if (document.activeElement !== searchField && attempts < 10) { forceFocus(attempts + 1); // Повторяем попытку } }, 50); } catch(e) { if (attempts < 10) { forceFocus(attempts + 1); // Повторяем при ошибке } } } else if (attempts < 10) { forceFocus(attempts + 1); // Повторяем, если элемент невидим } } }, 100); }; // Запускаем несколько попыток установки фокуса с разными задержками setTimeout(function() { forceFocus(0); }, 200); setTimeout(function() { forceFocus(0); }, 500); setTimeout(function() { forceFocus(0); }, 1000); /** * Функция точной настройки высоты компонентов * Пересчитывает высоту списка с учетом поля поиска */ var adjustHeight = function() { setTimeout(function() { if (!me.rendered) return; // Получаем высоту поля поиска var searchHeight = searchContainer.offsetHeight || 49; var totalHeight = me.getHeight(); // Общая высота boundlist // Учитываем толщину границы var borderWidth = 0; var boundlistStyle = window.getComputedStyle(boundlist.dom); if (boundlistStyle.borderWidth) { borderWidth = parseInt(boundlistStyle.borderTopWidth) + parseInt(boundlistStyle.borderBottomWidth); } // Доступная высота для контейнера списка var availableHeight = totalHeight - borderWidth; // Высота для списка элементов (общая минус поле поиска) var listHeight = availableHeight - searchHeight; // Устанавливаем высоту контейнера списка if (listWrap) { listWrap.dom.style.height = availableHeight + 'px'; listWrap.dom.style.maxHeight = availableHeight + 'px'; } // Устанавливаем высоту для списка элементов if (listEl) { listEl.dom.style.height = listHeight + 'px'; listEl.dom.style.maxHeight = listHeight + 'px'; listEl.dom.style.overflowY = 'auto'; listEl.dom.style.overflowX = 'hidden'; } // Отключаем прокрутку основного контейнера boundlist.dom.style.overflow = 'hidden'; }, 50); }; // Первоначальная настройка высоты setTimeout(adjustHeight, 250); // Используем ResizeObserver для отслеживания изменения размеров var resizeObserver = new ResizeObserver(function() { adjustHeight(); // Пересчитываем высоту при изменении размеров }); if (boundlist.dom) { resizeObserver.observe(boundlist.dom); // Начинаем наблюдение } // Обработчики событий списка // При раскрытии списка me.on('expand', function() { setTimeout(function() { adjustHeight(); // Корректируем высоту forceFocus(0); // Пытаемся установить фокус }, 150); }); // При показе списка me.on('show', function() { setTimeout(function() { adjustHeight(); // Корректируем высоту forceFocus(0); // Пытаемся установить фокус }, 150); }); } Quote
Sherzod Posted March 13 Posted March 13 14 hours ago, kkelchev said: How to do quick search with keyboard in TuniListBox. I thought you meant something like Windows Explorer behavior, where typing characters moves the selection to the first matching folder or file. Quote
Sherzod Posted March 17 Posted March 17 On 3/13/2026 at 2:35 PM, Sherzod said: I thought you meant something like Windows Explorer behavior, where typing characters moves the selection to the first matching folder or file. Quote
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.