Làm cho cửa sổ WPF có thể kéo được, bất kể phần tử nào được nhấp vào


111

Câu hỏi của tôi gấp 2 lần và tôi hy vọng có các giải pháp dễ dàng hơn cho cả hai được cung cấp bởi WPF hơn là các giải pháp tiêu chuẩn từ WinForms (mà Christophe Geers đã cung cấp, trước khi tôi làm rõ điều này).

Đầu tiên, có cách nào để làm cho Window có thể kéo được mà không cần chụp và xử lý các sự kiện nhấp chuột + kéo không? Ý tôi là cửa sổ có thể kéo được bằng thanh tiêu đề, nhưng nếu tôi đặt cửa sổ không có và vẫn muốn kéo nó, có cách nào để chỉ dẫn lại các sự kiện bằng cách nào đó đến bất cứ thứ gì xử lý việc kéo thanh tiêu đề không ?

Thứ hai, có cách nào để áp dụng trình xử lý sự kiện cho tất cả các phần tử trong cửa sổ không? Như trong, làm cho cửa sổ có thể kéo được bất kể người dùng nhấp + kéo phần tử nào. Rõ ràng là không cần thêm trình xử lý theo cách thủ công, vào từng phần tử. Chỉ làm điều đó một lần ở đâu đó?

Câu trả lời:


284

Chắc chắn, hãy áp dụng MouseDownsự kiện sau đây của bạnWindow

private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
    if (e.ChangedButton == MouseButton.Left)
        this.DragMove();
}

Điều này sẽ cho phép người dùng kéo Cửa sổ khi họ nhấp / kéo trên bất kỳ điều khiển nào, NGOẠI TRỪ cho các điều khiển ăn sự kiện MouseDown ( e.Handled = true)

Bạn có thể sử dụng PreviewMouseDownthay thế MouseDown, nhưng sự kiện kéo ăn Clicksự kiện, do đó cửa sổ của bạn ngừng phản hồi các sự kiện nhấp chuột trái. Nếu bạn THỰC SỰ muốn có thể nhấp và kéo biểu mẫu từ bất kỳ điều khiển nào, bạn có thể sử dụng PreviewMouseDown, khởi động bộ hẹn giờ để bắt đầu thao tác kéo và hủy thao tác nếu MouseUpsự kiện kích hoạt trong vòng X mili giây.


+1. Tốt hơn nhiều nên để trình quản lý cửa sổ xử lý việc di chuyển thay vì giả mạo nó bằng cách ghi nhớ vị trí và di chuyển cửa sổ. (Phương pháp thứ hai cũng có xu hướng đi sai trong trường hợp cạnh nhất định, dù sao)
Joey

Tại sao không chỉ thiết lập MouseLeftButtonDownsự kiện, thay vì đăng ký .cs?

1
@Drowin Bạn có thể sử dụng sự kiện đó thay thế, nhưng hãy đảm bảo bạn kiểm tra sự kiện đó trước vì MouseLeftButtonDowncó chiến lược định tuyến trực tiếp trong khi MouseDowncó chiến lược định tuyến sôi nổi. Xem phần nhận xét của trang MSDN dành cho MouseLeftButtonDown để biết thêm thông tin và để biết thêm một số điều cần biết nếu bạn sắp sử dụng MouseLeftButtonDownhết MouseDown.
Rachel

@Rachel Vâng tôi đang sử dụng nó và nó hoạt động, nhưng cảm ơn đã giải thích!

2
@Rahul Kéo một UserControl khó hơn nhiều ... bạn sẽ cần đặt nó vào bảng điều khiển mẹ như Canvas và đặt các thuộc tính X / Y (hoặc Canvas.Top và Canvas.Left) theo cách thủ công khi người dùng di chuyển chuột. Lần trước tôi đã sử dụng các sự kiện chuột, vì vậy OnMouseDown nắm bắt vị trí & đăng ký sự kiện di chuyển, OnMouseMove thay đổi X / Y và OnMouseUp loại bỏ sự kiện di chuyển. Đó là ý tưởng cơ bản của nó :)
Rachel

9

nếu biểu mẫu wpf cần được kéo bất kể nó được nhấp vào đâu, công việc dễ dàng xung quanh là sử dụng một đại biểu để kích hoạt phương thức DragMove () trên sự kiện windows onload hoặc sự kiện tải lưới

private void Grid_Loaded(object sender, RoutedEventArgs 
{
      this.MouseDown += delegate{DragMove();};
}

2
Tôi đã thêm điều này vào hàm tạo. Hoạt động một sự quyến rũ.
Joe Johnston

1
Điều này sẽ tạo ra một ngoại lệ nếu bạn nhấp chuột phải vào bất kỳ đâu trên biểu mẫu, vì DragMovechỉ có thể được gọi khi nút chuột chính xuống.
Stjepan Bakrac

4

Đôi khi, chúng tôi không có quyền truy cập Window, ví dụ: nếu chúng tôi đang sử dụng DevExpress, tất cả những gì có sẵn là a UIElement.

Bước 1: Thêm thuộc tính đính kèm

Giải pháp là:

  1. Kết nối vào MouseMovecác sự kiện;
  2. Tìm kiếm cây trực quan cho đến khi chúng tôi tìm thấy cha mẹ đầu tiên Window;
  3. Gọi .DragMove()cho chúng tôi mới được phát hiện Window.

Mã:

using System.Windows;
using System.Windows.Input;
using System.Windows.Media;

namespace DXApplication1.AttachedProperty
{
    public class EnableDragHelper
    {
        public static readonly DependencyProperty EnableDragProperty = DependencyProperty.RegisterAttached(
            "EnableDrag",
            typeof (bool),
            typeof (EnableDragHelper),
            new PropertyMetadata(default(bool), OnLoaded));

        private static void OnLoaded(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            var uiElement = dependencyObject as UIElement;
            if (uiElement == null || (dependencyPropertyChangedEventArgs.NewValue is bool) == false)
            {
                return;
            }
            if ((bool)dependencyPropertyChangedEventArgs.NewValue  == true)
            {
                uiElement.MouseMove += UIElementOnMouseMove;
            }
            else
            {
                uiElement.MouseMove -= UIElementOnMouseMove;
            }

        }

        private static void UIElementOnMouseMove(object sender, MouseEventArgs mouseEventArgs)
        {
            var uiElement = sender as UIElement;
            if (uiElement != null)
            {
                if (mouseEventArgs.LeftButton == MouseButtonState.Pressed)
                {
                    DependencyObject parent = uiElement;
                    int avoidInfiniteLoop = 0;
                    // Search up the visual tree to find the first parent window.
                    while ((parent is Window) == false)
                    {
                        parent = VisualTreeHelper.GetParent(parent);
                        avoidInfiniteLoop++;
                        if (avoidInfiniteLoop == 1000)
                        {
                            // Something is wrong - we could not find the parent window.
                            return;
                        }
                    }
                    var window = parent as Window;
                    window.DragMove();
                }
            }
        }

        public static void SetEnableDrag(DependencyObject element, bool value)
        {
            element.SetValue(EnableDragProperty, value);
        }

        public static bool GetEnableDrag(DependencyObject element)
        {
            return (bool)element.GetValue(EnableDragProperty);
        }
    }
}

Bước 2: Thêm Thuộc tính đính kèm vào bất kỳ phần tử nào để phần tử đó kéo cửa sổ

Người dùng có thể kéo toàn bộ cửa sổ bằng cách nhấp vào một phần tử cụ thể, nếu chúng tôi thêm thuộc tính đính kèm này:

<Border local:EnableDragHelper.EnableDrag="True">
    <TextBlock Text="Click me to drag this entire window"/>
</Border>

Phụ lục A: Ví dụ Nâng cao Tùy chọn

Trong ví dụ này từ DevExpress , chúng tôi thay thế thanh tiêu đề của cửa sổ docking bằng hình chữ nhật màu xám của riêng mình, sau đó đảm bảo rằng nếu người dùng nhấp và kéo hình chữ nhật màu xám nói trên, cửa sổ sẽ kéo bình thường:

<dx:DXWindow x:Class="DXApplication1.MainWindow" Title="MainWindow" Height="464" Width="765" 
    xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:dxdo="http://schemas.devexpress.com/winfx/2008/xaml/docking" 
    xmlns:local="clr-namespace:DXApplication1.AttachedProperty"
    xmlns:dxdove="http://schemas.devexpress.com/winfx/2008/xaml/docking/visualelements"
    xmlns:themeKeys="http://schemas.devexpress.com/winfx/2008/xaml/docking/themekeys">

    <dxdo:DockLayoutManager FloatingMode="Desktop">
        <dxdo:DockLayoutManager.FloatGroups>
            <dxdo:FloatGroup FloatLocation="0, 0" FloatSize="179,204" MaxHeight="300" MaxWidth="400" 
                             local:TopmostFloatingGroupHelper.IsTopmostFloatingGroup="True"                             
                             >
                <dxdo:LayoutPanel ShowBorder="True" ShowMaximizeButton="False" ShowCaption="False" ShowCaptionImage="True" 
                                  ShowControlBox="True" ShowExpandButton="True" ShowInDocumentSelector="True" Caption="TradePad General" 
                                  AllowDock="False" AllowHide="False" AllowDrag="True" AllowClose="False"
                                  >
                    <Grid Margin="0">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="*"/>
                        </Grid.RowDefinitions>
                        <Border Grid.Row="0" MinHeight="15" Background="#FF515151" Margin="0 0 0 0"
                                                                  local:EnableDragHelper.EnableDrag="True">
                            <TextBlock Margin="4" Text="General" FontWeight="Bold"/>
                        </Border>
                        <TextBlock Margin="5" Grid.Row="1" Text="Hello, world!" />
                    </Grid>
                </dxdo:LayoutPanel>
            </dxdo:FloatGroup>
        </dxdo:DockLayoutManager.FloatGroups>
    </dxdo:DockLayoutManager>
</dx:DXWindow>

Tuyên bố từ chối trách nhiệm: Tôi không liên kết với DevExpress . Kỹ thuật này sẽ hoạt động với bất kỳ phần tử người dùng nào, bao gồm WPF tiêu chuẩn hoặc Telerik (một nhà cung cấp thư viện WPF tốt khác).


1
Đây chính xác là những gì tôi muốn. IMHO tất cả mã WPF phía sau phải được viết dưới dạng hành vi đính kèm.
fjch1997

3
private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
    this.DragMove();
}

Là một ngoại lệ trong một số trường hợp (ví dụ: nếu trên cửa sổ bạn cũng có một hình ảnh có thể nhấp vào mà khi nhấp vào sẽ mở một hộp thông báo. Khi thoát khỏi hộp thông báo, bạn sẽ gặp lỗi) Sẽ an toàn hơn khi sử dụng

private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
if (Mouse.LeftButton == MouseButtonState.Pressed)
            this.DragMove();
}

Vì vậy, bạn chắc chắn rằng nút bên trái được nhấn vào thời điểm đó.


Tôi đang sử dụng e.LeftButtonthay vì Mouse.LeftButtonsử dụng cụ thể nút được liên kết với các chuỗi sự kiện, mặc dù nó có thể sẽ không bao giờ quan trọng.
Fls'Zen

2

Có thể kéo và thả biểu mẫu bằng cách nhấp vào bất kỳ đâu trên biểu mẫu, không chỉ thanh tiêu đề. Điều này rất hữu ích nếu bạn có biểu mẫu không viền.

Bài viết này trên CodeProject trình bày một giải pháp khả thi để thực hiện điều này:

http://www.codeproject.com/KB/cs/DraggableForm.aspx

Về cơ bản, một hậu duệ của kiểu Biểu mẫu được tạo ra để xử lý các sự kiện chuột xuống, lên và di chuyển.

  • Di chuột xuống: nhớ vị trí
  • Di chuyển chuột: lưu trữ vị trí mới
  • Di chuột lên: vị trí biểu mẫu đến vị trí mới

Và đây là một giải pháp tương tự được giải thích trong một video hướng dẫn:

http://www.youtube.com/watch?v=tJlY9aX73Vs

Tôi sẽ không cho phép kéo biểu mẫu khi người dùng nhấp vào một điều khiển trong biểu mẫu đã nói. Người dùng đạt được các kết quả khác nhau khi họ nhấp vào các điều khiển khác nhau. Khi biểu mẫu của tôi đột nhiên bắt đầu di chuyển vì tôi đã nhấp vào hộp danh sách, nút, nhãn ... v.v. điều đó sẽ gây nhầm lẫn.


Chắc chắn nó sẽ không di chuyển bằng cách nhấp vào bất kỳ điều khiển nào, nhưng nếu bạn nhấp và kéo, bạn sẽ không mong đợi biểu mẫu di chuyển. Ý tôi là bạn sẽ không mong đợi một nút hoặc một hộp danh sách di chuyển, ví dụ như bạn nhấp + kéo nó, chuyển động của biểu mẫu là một kỳ vọng tự nhiên nếu bạn cố gắng nhấp và kéo một nút trong biểu mẫu, tôi nghĩ vậy.
Alex K

Đoán xem, đó chỉ là sở thích cá nhân. Nhưng dù sao .... các điều khiển sẽ cần phải xử lý các sự kiện chuột giống nhau. Bạn sẽ phải thông báo cho biểu mẫu gốc về những sự kiện này khi chúng không nổi lên.
Christophe Geers

Ngoài ra, trong khi tôi đã biết về giải pháp WinForms cho vấn đề này, tôi đã hy vọng có một cách dễ dàng hơn để tồn tại trong WPF, tôi đoán tôi nên làm rõ điều này trong câu hỏi (hiện tại nó chỉ là một thẻ).
Alex K

Xin lỗi, lỗi của tôi. Không nhận thấy thẻ WPF. Không được đề cập trong câu hỏi ban đầu. Tôi chỉ giả sử WinForms theo mặc định, đã xem qua thẻ.
Christophe Geers

2

Như đã được đề cập bởi @ fjch1997 , rất thuận tiện để thực hiện một hành vi. Đây rồi, logic cốt lõi giống như trong câu trả lời của @ loi.efy :

public class DragMoveBehavior : Behavior<Window>
{
    protected override void OnAttached()
    {
        AssociatedObject.MouseMove += AssociatedObject_MouseMove;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.MouseMove -= AssociatedObject_MouseMove;
    }

    private void AssociatedObject_MouseMove(object sender, MouseEventArgs e)
    {
        if (e.LeftButton == MouseButtonState.Pressed && sender is Window window)
        {
            // In maximum window state case, window will return normal state and
            // continue moving follow cursor
            if (window.WindowState == WindowState.Maximized)
            {
                window.WindowState = WindowState.Normal;

                // 3 or any where you want to set window location after
                // return from maximum state
                Application.Current.MainWindow.Top = 3;
            }

            window.DragMove();
        }
    }
}

Sử dụng:

<Window ...
        xmlns:h="clr-namespace:A.Namespace.Of.DragMoveBehavior"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
    <i:Interaction.Behaviors>
        <h:DragMoveBehavior />
    </i:Interaction.Behaviors>
    ...
</Window>

1

Đây là tất cả những gì cần thiết!

private void UiElement_MouseMove(object sender, MouseEventArgs e)
    {
        if (e.LeftButton == MouseButtonState.Pressed)
        {
            if (this.WindowState == WindowState.Maximized) // In maximum window state case, window will return normal state and continue moving follow cursor
            {
                this.WindowState = WindowState.Normal;
                Application.Current.MainWindow.Top = 3;// 3 or any where you want to set window location affter return from maximum state
            }
            this.DragMove();
        }
    }

0

Phương pháp hữu ích nhất, cho cả WPF và biểu mẫu windows, ví dụ WPF:

    [DllImport("user32.dll")]
    public static extern IntPtr SendMessage(IntPtr hWnd, int wMsg, int wParam, int lParam);

    public static void StartDrag(Window window)
    {
        WindowInteropHelper helper = new WindowInteropHelper(window);
        SendMessage(helper.Handle, 161, 2, 0);
    }

0
<Window
...
WindowStyle="None" MouseLeftButtonDown="WindowMouseLeftButtonDown"/>
<x:Code>
    <![CDATA[            
        private void WindowMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            DragMove();
        }
    ]]>
</x:Code>

nguồn

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.