Тема: Как включить прокрутку колеса мыши в обычных контролах Silverlight 2
Введение
В настоящий момент Silverlight не поддерживает обработку событий скролла мыши. Тем не менее, вы можете прикрепить событие для перехвата движения колесика мыши через объект HtmlPage, найти контрол (элемент) находящийся под указателем мыши с помощью класса VisualTreeHelper, и прокрутить область контрола (DataGrid, Combobox, ScrollViewer, TreeView, и т.п.) через вызов API.
События колесика мыши
В сети можно найти несколько примеров того, как перехватить события колесика мыши. Следующий фрагмент кода демонстрирует это очень кратко. Вы можете найти полную версию этого примера тут (спасибо Майку Сноу).
public TestPage()
{
InitializeComponent();
HtmlPage.Window.AttachEvent("DOMMouseScroll", OnMouseWheel);
HtmlPage.Window.AttachEvent("onmousewheel", OnMouseWheel);
HtmlPage.Document.AttachEvent("onmousewheel", OnMouseWheel);
}
private void OnMouseWheel(object sender, HtmlEventArgs args)
{
// code goes here…
}Скроллинг с UI Automation API
Следующим шагом мы должны программно реализовать прокрутку для различных типов контроллов, таких как Combobox, Treeview, DataGrid, ItemsControl, ScrollViewer, и т.д. Как раз тут и возникает вопрос, как мы будем программно прокручивать эти контролы без реализации специальных производных от них классов. Причем поддержка прокрутки для DataGrid к сожалению не реализуется через ScrollViewer. В место этого DataGrid предоставляет метод ScrollIntoView, который мало чем полезен в нашей задаче.
Поиграв с Silverlight UI Automation API (UIA), я обнаружил очень интересный интерфейс IScrollProvider. Этот интерфейс доступен через UIA и позволяет нам прокручивать контрол необычным образом. UIA интерфейсы предназначаются для автоматизации доступа внешних приложений, например во время тестирования интерфейса, но так же могут использоваться во время исполнения в пределах одного приложения.
// get automation peer
AutomationPeer automationPeer = FrameworkElementAutomationPeer.FromElement(element);
if (automationPeer == null)
{
// create automation peer for element
automationPeer = FrameworkElementAutomationPeer.CreatePeerForElement(element);
}
// try to get scroll provider
IScrollProvider scrollProvider = automationPeer.GetPattern(PatternInterface.Scroll) as IScrollProvider;
if (scrollProvider != null)
{
scrollProvider.Scroll(horizontalScrollAmount, verticalScrollAmount);
}И это все!
Подключение события скрола мыши к контролам
Теперь остается решить, как мы направим событие поворота скролла мыши к нужному контролу. Конечно вы можете дописать приведенный выше код к каждому контролу, который должен реагировать на скроллинг мыши, и включать/выключать эту функцию, отслеживая MouseEnter/MouseLeave для каждого из этих контролов. Но при таком подходе будет задействовано много лишнего кода и потребуется дополнительный контроль. Поэтому мы будем использовать метод FindElementsInHostCoordinates, представленного классом VisualTreeHelper. Этот метод возвращает список элементов типа UIElement расположенных под заданной точкой (текущий позицией указателя мыши).
Наш класс MouseWheel (смотри исходинки) всегда знает текущую позицию мыши через подписку на событие MouseMove для корневого UIElement.
Вот сокращенный фрагмент кода:
// пробегаем по всем элементам, находящимся под текущей позицией указателя мыши
IEnumerable<UIElement> elements = VisualTreeHelper.FindElementsInHostCoordinates(currentPoint, s_rootElement);
foreach (UIElement element in elements)
{
// получаем автоматизированный пир (если он уже создан для этого контрола)
AutomationPeer automationPeer = FrameworkElementAutomationPeer.FromElement(element);
if (automationPeer == null)
{
// создаем новый пир для элемента
automationPeer = FrameworkElementAutomationPeer.CreatePeerForElement(element);
}
// некоторые элементы могут вернуть null, если для них пир автоматизации не реализован
if (automationPeer != null)
{
// пытаемся получить провайдер скрола
IScrollProvider scrollProvider = automationPeer.GetPattern(PatternInterface.Scroll) as IScrollProvider;
if (scrollProvider != null)
{
if (scrollProvider.VerticallyScrollable)
{
scrollProvider.Scroll(ScrollAmount.NoAmount, scrollAmount);
// прекращаем дальнейший поиск в коллекции UIElement’ов
break;
}
// тут не нужно разрывать цикл, т.к. могут быть вложенные контролы требующие обработки
//break;
}
}
}Другие моменты
При нажатой клавише Cntrl должен происходить горизонтальный скроллинг:
// horizontal scrolling?
bool ctrlKey = (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control;
if (scrollProvider.HorizontallyScrollable && ctrlKey)
{
scrollProvider.Scroll(scrollAmount, ScrollAmount.NoAmount);
}Итоги
UIA (UI Automation API) предоставляет несложное решение проблемы скроллинга без расширения текущих контролов, и VisualTreeHelper дает возможность отправлять события поворота колесика мыши к нужному контролу.
Большинство контролов имеющих область прокрутки снабжены IScrollProvider интерфейсом, но TextBox, к сожалению, - нет. Я продолжаю поиск других путей подключений скроллинга к контролу TextBox.
(ссылка на оригинал статьи с исходным кодом: How to enable mouse wheel scrolling in Silverlight without extending controls)