Можно ли использовать колесо мыши при перетаскивании?

В WinForms после вызова DoDragDrop для начала перетаскивания элемента элементы управления больше не прокручиваются с помощью колесика мыши, а событие элемента управления MouseWheel больше не вызывается до тех пор, пока пользователь не удалит то, что он перетаскивает.

Есть ли способ заставить колесо мыши работать при перетаскивании?


person BlueRaja - Danny Pflughoeft    schedule 27.01.2011    source источник
comment
Эй, я не уверен в этом, так как я только что узнал об этом сегодня. Но можно ли сделать то, о чем вы просите, с Rx?   -  person Reza M.    schedule 16.03.2011
comment
Можете ли вы проверить и увидеть, доставляются ли MouseWheel события в источник перетаскивания вместо элемента управления, над которым находится мышь?   -  person Ben Voigt    schedule 16.03.2011
comment
Итак, вы хотите поместить объект в форму со свитками или более крупный объект, который не помещается в форму. Я прав ?   -  person Carlos Valenzuela    schedule 16.03.2011
comment
@Chuck: Мой конкретный вариант использования — перетаскивание строк из одного представления сетки данных в другое. Но я могу придумать множество других вариантов использования, где это было бы полезно.   -  person BlueRaja - Danny Pflughoeft    schedule 16.03.2011
comment
@Ben Voigt: Нет, ни элементы управления источником, ни пунктом назначения, ни форма размещения не получают событие MouseWheel. Просто теряется в бездне..   -  person BlueRaja - Danny Pflughoeft    schedule 16.03.2011


Ответы (4)


Вы можете получить глобальный MouseWheel с помощью клавиатуры.

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Microsoft.Win32.SafeHandles;

using BOOL = System.Boolean;
using DWORD = System.UInt32;
using HHOOK = SafeHookHandle;
using HINSTANCE = System.IntPtr;
using HOOKPROC = HookProc;
using LPARAM = System.IntPtr;
using LRESULT = System.IntPtr;
using POINT = System.Drawing.Point;
using ULONG_PTR = System.IntPtr;
using WPARAM = System.IntPtr;

public delegate LRESULT HookProc(int nCode, WPARAM wParam, LPARAM lParam);

internal static class NativeMethods
{
    [DllImport("User32.dll", SetLastError = true)]
    internal static extern HHOOK SetWindowsHookEx(
        HookType idHook,
        HOOKPROC lpfn,
        HINSTANCE hMod,
        DWORD dwThreadId);

    [DllImport("User32.dll")]
    internal static extern LRESULT CallNextHookEx(
        HHOOK hhk,
        int nCode,
        WPARAM wParam,
        LPARAM lParam);

    [DllImport("User32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    internal static extern BOOL UnhookWindowsHookEx(
        IntPtr hhk);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr GetModuleHandle(string lpModuleName);
}

internal static class NativeTypes
{
    internal enum MSLLHOOKSTRUCTFlags : uint
    {
        LLMHF_INJECTED = 0x00000001U,
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct MSLLHOOKSTRUCT
    {
        internal POINT pt;
        internal DWORD mouseData;
        internal MSLLHOOKSTRUCTFlags flags;
        internal DWORD time;
        internal ULONG_PTR dwExtraInfo;
    }
}

internal static class NativeConstants
{
    internal const int WH_MOUSE_LL = 14;

    internal const int HC_ACTION = 0;

    internal const int WM_MOUSEWHEEL = 0x020A;
    internal const int WM_MOUSEHWHEEL = 0x020E;

    internal const int WHEEL_DELTA = 120;
}

public enum HookType
{
    LowLevelMouseHook = NativeConstants.WH_MOUSE_LL
}

public enum HookScope
{
    LowLevelGlobal,
}

public class SafeHookHandle : SafeHandleZeroOrMinusOneIsInvalid
{
    private SafeHookHandle() : base(true) { }

    public static SafeHookHandle SetWindowsHook(HookType idHook, HookProc lpfn, IntPtr hMod, uint dwThreadId)
    {
        var hhk = NativeMethods.SetWindowsHookEx(idHook, lpfn, hMod, dwThreadId);

        if(hhk.IsInvalid)
        {
            throw new Win32Exception();
        }
        else
        {
            return hhk;
        }
    }

    public IntPtr CallNextHook(int nCode, IntPtr wParam, IntPtr lParam)
    {
        return NativeMethods.CallNextHookEx(this, nCode, wParam, lParam);
    }

    protected override bool ReleaseHandle()
    {
        return NativeMethods.UnhookWindowsHookEx(this.handle);
    }
}

public abstract class WindowsHook : IDisposable
{
    private SafeHookHandle hhk;
    private HookProc lpfn;

    protected WindowsHook(HookType idHook, HookScope scope)
    {
        this.lpfn = this.OnWindowsHook;

        switch(scope)
        {
            case HookScope.LowLevelGlobal:
                IntPtr moduleHandle = NativeMethods.GetModuleHandle(null);
                this.hhk = SafeHookHandle.SetWindowsHook(idHook, this.lpfn, moduleHandle, 0U);
                return;
            default:
                throw new InvalidEnumArgumentException("scope", (int)scope, typeof(HookScope));
        }
    }

    protected virtual IntPtr OnWindowsHook(int nCode, IntPtr wParam, IntPtr lParam)
    {
        return this.hhk.CallNextHook(nCode, wParam, lParam);
    }

    public void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if(disposing)
        {
            if(this.hhk != null) { this.hhk.Dispose(); }
        }
    }
}

public class LowLevelMouseHook : WindowsHook
{
    public event MouseEventHandler MouseWheel;

    public LowLevelMouseHook() : base(HookType.LowLevelMouseHook, HookScope.LowLevelGlobal) { }

    protected sealed override IntPtr OnWindowsHook(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if(nCode == NativeConstants.HC_ACTION)
        {
            var msLLHookStruct = (NativeTypes.MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(NativeTypes.MSLLHOOKSTRUCT));

            switch(wParam.ToInt32())
            {
                case NativeConstants.WM_MOUSEWHEEL:
                case NativeConstants.WM_MOUSEHWHEEL:
                    this.OnMouseWheel(new MouseEventArgs(Control.MouseButtons, 0, msLLHookStruct.pt.X, msLLHookStruct.pt.Y, (int)msLLHookStruct.mouseData >> 16));
                    break;
            }
        }

        return base.OnWindowsHook(nCode, wParam, lParam);
    }

    protected virtual void OnMouseWheel(MouseEventArgs e)
    {
        if(this.MouseWheel != null)
        {
            this.MouseWheel(this, e);
        }
    }
} 

Пример использования:

using (LowLevelMouseHook hook = new LowLevelMouseHook())
{
    hook.MouseWheel += (sender, e) =>
    {
        Console.WriteLine(e.Delta);
    };
    Application.Run();
}

Код предоставляет класс LowLevelMouseHook с событием MouseWheel, которое ведет себя как событие из встроенных классов управления формами Windows.

(Кроме того, код разделен на абстрактный класс WindowsHooks для использования с другими ловушками, класс SafeHookHandle для обеспечения освобождения дескриптора и вспомогательные классы для нативных методов)

Вы должны посмотреть SetWindowsHookEx и CALLBACK LowLevelMouseProc, чтобы понять технику, стоящую за этим.


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

person riel    schedule 16.03.2011
comment
Хм... к сожалению, похоже, это не работает - я получаю сообщение Невозможно установить нелокальный хук без дескриптора модуля при попытке запустить программу. Оказывается, SafeHookHandle.SetWindowsHook.hMod не может быть IntPtr.Zero... - person BlueRaja - Danny Pflughoeft; 16.03.2011
comment
Использование результата GetModuleHandle(null), а не IntPtr.Zero, кажется, работает - я отредактирую это. Это именно то, что я искал.. Спасибо! - person BlueRaja - Danny Pflughoeft; 16.03.2011
comment
Пожалуйста. У меня самого были проблемы с шапкой IntPtr.Zero, но они внезапно исчезли (Win7 x64) и я ее оставил. - person riel; 16.03.2011

Нет, во время D+D нет идентифицируемого фокуса, а события D+D не сообщают о обратном движении колесика мыши. Типичным трюком является использование DragOver и проверка того, находится ли курсор перетаскивания близко к любому концу прокручиваемой области. И прокрутите с помощью таймера. Пример здесь.

person Hans Passant    schedule 27.01.2011
comment
Да, я делаю это, но я хочу, чтобы колесико мыши тоже работало. Блин, я надеялся на простое решение. Я не думаю, что вы знаете способ сделать это с глобальными событиями мыши (через P/Invoke)? - person BlueRaja - Danny Pflughoeft; 28.01.2011
comment
IMessageFilter может это сделать. Это не простое решение. - person Hans Passant; 28.01.2011
comment
Нет, отмените это, фокус размыт. Очень может быть. - person Hans Passant; 28.01.2011
comment
Я не верю, что это невозможно сделать; если MS Paint может прокручивать колесо, пока вы перетаскиваете выделение полосы или какую-либо другую фигуру, вы должны иметь возможность делать то же самое в своей собственной программе, независимо от того, требует ли это низкоуровневого наблюдения за мышью или что-то более простое. - person KeithS; 10.03.2011
comment
MSPaint не использует для этого D+D. Нет никакой причины. - person Hans Passant; 10.03.2011
comment
Что, если мы предположим, что у моего приложения есть фокус (и я буду перетаскивать только один элемент управления в приложении к другому)? - person BlueRaja - Danny Pflughoeft; 10.03.2011
comment
@Hans: Фокус не важен, важен захват мыши. Обычно мышь захватывается источником перетаскивания, и вы хотите прокрутить цель перетаскивания. - person Ben Voigt; 16.03.2011

Вместо того, чтобы использовать встроенную функциональность D + D и пытаться переопределить ее поведение с помощью PInvoke и других событий, вы можете вместо этого создать свою собственную систему перетаскивания на основе событий опускания и подъема мыши вдали, которая сохранит колесико мыши формы. возможности прокрутки.

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

using System;
using System.Windows.Forms;

public partial class Form1 : Form {
    public Form1() {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e) {
        for (int i=0; i<250; i++) listBox1.Items.Add("item " + i);
    }

    private void Label1MouseDown(object sender, MouseEventArgs e) {
        Cursor.Current = Cursors.SizeAll;
    }
}

Конечно, вы должны подключить свою собственную логику для удаления элементов (например, обработчик мыши, чтобы определить процесс удаления), и вы, вероятно, не хотите использовать курсор SizeAll, а что-то, что более показательно для перетаскивания. Этот пример просто показывает, что управлять собственным D+D может быть проще, чем пытаться переопределить черный ящик API.

person Paul Sasik    schedule 14.03.2011
comment
@Ben: Мое предложение на самом деле не захватывает мышь, оно просто меняет курсор при нажатии кнопки мыши. Фрагмент, который вы видите, — это буквально все, что потребовалось для проверки, за исключением, конечно, всего автоматически сгенерированного и дизайнерского файлового мусора. Все, что нужно сделать OP, чтобы завершить это, — это добавить флаг, обработчик мыши на соответствующем элементе управления и логику того, что должно делать падение. - person Paul Sasik; 16.03.2011
comment
Точно. Разница между вашим кодом и кодом фреймворка заключается в том, что фреймворк ДЕЙСТВИТЕЛЬНО захватывает мышь. - person Ben Voigt; 16.03.2011
comment
@Ben: извините, я неправильно истолковал ваш первоначальный комментарий как критику моего ответа. Я думал, что вы подумали, что я предложил захват... - person Paul Sasik; 16.03.2011

Как насчет этого:

В целевом DataGrid (тот, куда вы предполагаете попасть), когда указатель мыши достигает конца или начала, вы начинаете прокручивать вниз или вверх (конечно, это будет управлять событиями mousein/mouseout).

Попробуйте перетащить объект в Excel, если вы дойдете до конца/начала того, что видите, он начнет прокручиваться вниз/вверх.

Я не знаю, объясню ли я сам, дайте мне знать, и я постараюсь сделать это более явным

person Carlos Valenzuela    schedule 16.03.2011
comment
Да, я уже делаю это - но это обходной путь, а не исправление этого ограничения. - person BlueRaja - Danny Pflughoeft; 16.03.2011