// Copyright (C) 2015 Kazuhiro Fujieda // // This program is part of KancolleSniffer. // // KancolleSniffer is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, see . using System; using System.Drawing; using System.Runtime.InteropServices; using System.Windows.Forms; using static System.Math; namespace KancolleSniffer { public class SwipeScrollify { private readonly MouseFilter _filter; public SwipeScrollify() { _filter = new MouseFilter(); Application.AddMessageFilter(_filter); } public void AddPanel(Panel panel) { var handler = new PanelHandler(panel); _filter.MouseDown += handler.MouseDown; _filter.MouseMove += handler.MouseMove; _filter.MouseUp += handler.MouseUp; } public void AddTreeView(TreeView treeView) { var handler = new TreeViewHandler(treeView); _filter.MouseDown += handler.MouseDown; _filter.MouseMove += handler.MouseMove; _filter.MouseUp += handler.MouseUp; } private class MouseFilter : IMessageFilter { public delegate void MouseHandler(IntPtr handle, ref bool handled); public event MouseHandler MouseMove , MouseDown , MouseUp; // ReSharper disable InconsistentNaming private const int WM_MOUSEMOVE = 0x0200; private const int WM_LBUTTONDOWN = 0x0201; private const int WM_LBUTTONUP = 0x0202; // ReSharper restore InconsistentNaming public bool PreFilterMessage(ref Message m) { var handled = false; switch (m.Msg) { case WM_LBUTTONDOWN: MouseDown?.Invoke(m.HWnd, ref handled); break; case WM_MOUSEMOVE: MouseMove?.Invoke(m.HWnd, ref handled); break; case WM_LBUTTONUP: MouseUp?.Invoke(m.HWnd, ref handled); break; } return handled; } } private class PanelHandler { private readonly Panel _panel; private bool _touch; private Point _mouseStart; private Point _panelStart; public PanelHandler(Panel panel) { _panel = panel; } public void MouseDown(IntPtr handle, ref bool handled) { if (!_mouseStart.IsEmpty) return; if (!_panel.RectangleToScreen(_panel.ClientRectangle).Contains(Control.MousePosition)) return; var found = false; for (var control = Control.FromHandle(handle); control != null; control = control.Parent) { if (control != _panel) continue; found = true; break; } if (!found) return; _mouseStart = Control.MousePosition; _panelStart = _panel.AutoScrollPosition; } public void MouseMove(IntPtr handle, ref bool handled) { if (_mouseStart.IsEmpty) return; var cur = Control.MousePosition; var dx = cur.X - _mouseStart.X; var dy = cur.Y - _mouseStart.Y; if (_touch) _panel.AutoScrollPosition = new Point(-_panelStart.X - dx, -_panelStart.Y - dy); else if (Abs(dx) > 5 || Abs(dy) > 5) _touch = true; } public void MouseUp(IntPtr handle, ref bool handled) { if (_touch && !_panelStart.IsEmpty && _panelStart != _panel.AutoScrollPosition) handled = true; _touch = false; _mouseStart = _panelStart = Point.Empty; } } private class TreeViewHandler { private readonly TreeView _treeView; private bool _touch; private Point _mouseStart; private Point _panelStart; [DllImport("user32.dll")] private static extern int GetScrollPos(IntPtr hWnd, int nBar); [DllImport("user32.dll")] private static extern int SetScrollPos(IntPtr hWnd, int nBar, int nPos, bool bRedraw); // ReSharper disable InconsistentNaming private const int GWL_STYLE = -16; private const int WS_HSCROLL = 0x00100000; private const int WS_VSCROLL = 0x00200000; // ReSharper restore InconsistentNaming [DllImport("user32.dll")] private static extern int GetWindowLong(IntPtr hWnd, int nIndex); public TreeViewHandler(TreeView treeView) { _treeView = treeView; } public void MouseDown(IntPtr handle, ref bool handled) { if (!_mouseStart.IsEmpty) return; var control = Control.FromHandle(handle); if (control == null || control != _treeView) return; if (!_treeView.RectangleToScreen(_treeView.ClientRectangle).Contains(Control.MousePosition)) return; var loc = _treeView.HitTest(_treeView.PointToClient(Control.MousePosition)).Location; // アイテムをクリックするとWM_LBUTTONUPが来ないので避ける if (loc == TreeViewHitTestLocations.Label || loc == TreeViewHitTestLocations.Image) return; _mouseStart = Control.MousePosition; _panelStart = ScrollPosition(); } public void MouseMove(IntPtr handle, ref bool handled) { if (_mouseStart.IsEmpty) return; if (!_treeView.RectangleToScreen(_treeView.ClientRectangle).Contains(Control.MousePosition)) { // TreeViewではうまく動かないので外に出たら止める _touch = false; _mouseStart = _panelStart = Point.Empty; return; } var cur = Control.MousePosition; var dx = cur.X - _mouseStart.X; var dy = cur.Y - _mouseStart.Y; var style = GetWindowLong(_treeView.Handle, GWL_STYLE); if (_touch) { _treeView.BeginUpdate(); if ((style & WS_HSCROLL) != 0) SetScrollPos(_treeView.Handle, 0, _panelStart.X - dx, true); if ((style & WS_VSCROLL) != 0) SetScrollPos(_treeView.Handle, 1, _panelStart.Y - dy / _treeView.ItemHeight, true); _treeView.EndUpdate(); handled = true; } else if (Abs(dx) > 5 || Abs(dy) > 5) _touch = true; } public void MouseUp(IntPtr handle, ref bool handled) { if (_touch && !_panelStart.IsEmpty && _panelStart != ScrollPosition()) handled = true; _touch = false; _mouseStart = _panelStart = Point.Empty; } private Point ScrollPosition() { return new Point(GetScrollPos(_treeView.Handle, 0), GetScrollPos(_treeView.Handle, 1)); } } } }