Страницы

Поиск по вопросам

среда, 24 октября 2018 г.

Как отследить перемещение одного окна над другим?

Здравствуйте. Меня очень интересует, как, например, в браузере google chrome (и не только) вкладка помещается в другое окно при перетаскивании? Это ведь не DragDrop. Что тогда? Все события мыши передаются только в то окно, которое я тащу, тогда как мне определить, что курсор мыши находится на панели вкладок другого окна, которое под перетаскиваемым?
Просьба не давать ссылки на готовые контролы табов, в которых это реализовано. Ну или с конкретным файлом и строкой. Потому что я пытался найти в них, как это реализовано, но не смог (на гитхабе).


Ответ

Ну вот вам пример.
Для начала, создадим окно, которое умеет определять, какому из экземпляров принадлежит точка на экране. Положим в окно контейнер для перемещаемых элементов.

(Код, работающий с Z-порядком, взят из этого ответа.)
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Windows; using System.Windows.Interop;
namespace CrossWindowDrop { public partial class DropContainerWindow : Window { public DropContainerWindow() { InitializeComponent(); }
// добавить перемещаемый элемент public void AcceptActor(Actor actor) { Arena.Children.Add(actor); }
// убрать перемещаемый элемент public void ReleaseActor(Actor actor) { Arena.Children.Remove(actor); }
// найти самое верхнее окно для данной точки public static DropContainerWindow BestTarget(Point screenPoint) { return AllByZOrder().FirstOrDefault(w => { var arena = w.Arena; var arenaPoint = arena.PointFromScreen(screenPoint); return arenaPoint.X >= 0 && arenaPoint.Y >= 0 && arenaPoint.X < arena.ActualWidth && arenaPoint.Y < arena.ActualHeight; }); }
// получить список всех окон, отсортированный по Z-порядку // для обхода окон приходится использовать WinAPI, т. к. // WPF не знает о Z-порядке static IEnumerable AllByZOrder() { var byHandle = App.Current.Windows .OfType() .ToDictionary(w => ((HwndSource)PresentationSource.FromVisual(w)) .Handle);
for (IntPtr hWnd = GetTopWindow(IntPtr.Zero); hWnd != IntPtr.Zero; hWnd = GetWindow(hWnd, GW_HWNDNEXT)) { DropContainerWindow w; if (byHandle.TryGetValue(hWnd, out w)) yield return w; } }
// вспомогательные WinAPI-функции и константы const uint GW_HWNDNEXT = 2; [DllImport("user32")] static extern IntPtr GetTopWindow(IntPtr hWnd); [DllImport("user32")] static extern IntPtr GetWindow(IntPtr hWnd, uint wCmd); } }
Теперь, сам перемещаемый элемент. Реализуем его как UserControl


using System; using System.Windows; using System.Windows.Controls; using System.Windows.Input;
namespace CrossWindowDrop { public partial class Actor : UserControl { public Actor() { InitializeComponent(); }
// эти три свойства валидны лишь на время движения
// запоминает позицию мыши внутри контрола Point relativeMousePos; // контейнер FrameworkElement parent; // окно, в котором мы находимся в данный момент DropContainerWindow window;
// на нажатии мыши начинаем движение void OnMouseDown(object sender, MouseButtonEventArgs e) { parent = (FrameworkElement)this.Parent; window = (DropContainerWindow)Window.GetWindow(this); relativeMousePos = e.GetPosition(this); // подписываемся на перемещение мыши MouseMove += OnDragMove; // и захватываем mouse capture Mouse.Capture(this); }
// при отпускании мыши прекращаем движение void OnMouseUp(object sender, MouseButtonEventArgs e) { // отписываемся от перемещения мыши MouseMove -= OnDragMove; // обновляем наше местоположение UpdatePosition(e); // отпускаем mouse capture Mouse.Capture(null); window = null; parent = null; }
void OnDragMove(object sender, MouseEventArgs e) { UpdatePosition(e); }
void UpdatePosition(MouseEventArgs e) { // абсолютное положение точки на экране var screenPoint = PointToScreen(relativeMousePos); // находим наиболее подходящее окно var bestWindow = DropContainerWindow.BestTarget(screenPoint); // если мы надо окном, и это не текущее окно... if (bestWindow != null && bestWindow != window) MigrateTo(bestWindow); // ... переезжаем! // позиция мыши относительно нашего контейнера var point = e.GetPosition(parent); // перемещаемся в нужную позицию Canvas.SetLeft(this, point.X - relativeMousePos.X); Canvas.SetTop(this, point.Y - relativeMousePos.Y); }
// самое интересное: переезд в новое окно void MigrateTo(DropContainerWindow newWindow) { // отпускаем мышь - наш контрол пересоздастся в другом окне Mouse.Capture(null); // просим наше окно выписать отпустить нас window.ReleaseActor(this); // а новое - принять newWindow.AcceptActor(this); // подправляем наши свойства parent = (FrameworkElement)this.Parent; window = newWindow; // и снова захватываем мышь Mouse.Capture(this); // вот и всё } } }
Ну и добавьте главную программу по вкусу.


using System.Windows; using System.Windows.Controls; using System.Windows.Media;
namespace CrossWindowDrop { public partial class App : Application { protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); var w1 = new DropContainerWindow() { Title = "Window 1" }; var actor1 = new Actor() { Background = Brushes.Violet }; var actor2 = new Actor() { Background = Brushes.Green }; Canvas.SetLeft(actor2, 50); w1.AcceptActor(actor1); w1.AcceptActor(actor2); w1.Show(); new DropContainerWindow() { Title = "Window 2" }.Show(); new DropContainerWindow() { Title = "Window 3" }.Show(); } } }
Всё! Теперь вы можете таскать цветные квадраты из одного окна в другое.

Комментариев нет:

Отправить комментарий