ทำให้หน้าต่าง WPF ลากได้ไม่ว่าจะคลิกองค์ประกอบใดก็ตาม


111

คำถามของฉันคือ 2 เท่าและฉันหวังว่าจะมีวิธีแก้ปัญหาที่ง่ายกว่าสำหรับทั้งสองอย่างที่จัดทำโดยWPFแทนที่จะเป็นโซลูชันมาตรฐานจาก WinForms (ซึ่ง Christophe Geers ให้ไว้ก่อนที่ฉันจะชี้แจงนี้)

ก่อนอื่นมีวิธีทำให้ Window ลากได้โดยไม่ต้องจับและประมวลผลเหตุการณ์คลิกเมาส์ + ลากหรือไม่? ฉันหมายถึงหน้าต่างสามารถลากได้โดยแถบชื่อเรื่อง แต่ถ้าฉันตั้งค่าหน้าต่างไม่ให้มีและยังคงต้องการให้ลากได้มีวิธีที่จะกำหนดทิศทางเหตุการณ์ใหม่ไปยังสิ่งที่จัดการกับการลากแถบหัวเรื่องหรือไม่ เหรอ?

ประการที่สองมีวิธีใช้ตัวจัดการเหตุการณ์กับองค์ประกอบทั้งหมดในหน้าต่างหรือไม่? เช่นเดียวกับทำให้หน้าต่างลากได้ไม่ว่าผู้ใช้จะคลิก + ลากองค์ประกอบใด เห็นได้ชัดว่าไม่ต้องเพิ่มตัวจัดการด้วยตนเองในทุกองค์ประกอบ แค่ทำครั้งเดียว?

คำตอบ:


284

ได้เลยใช้MouseDownเหตุการณ์ต่อไปนี้ของไฟล์Window

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

สิ่งนี้จะช่วยให้ผู้ใช้ลากหน้าต่างเมื่อคลิก / ลากบนตัวควบคุมใด ๆ ยกเว้นสำหรับตัวควบคุมที่กินเหตุการณ์ MouseDown ( e.Handled = true)

คุณสามารถใช้PreviewMouseDownแทนได้MouseDownแต่เหตุการณ์ลากกินClickเหตุการณ์ดังนั้นหน้าต่างของคุณจะหยุดตอบสนองต่อเหตุการณ์การคลิกเมาส์ซ้าย หากคุณต้องการคลิกและลากแบบฟอร์มจากการควบคุมใด ๆ จริงๆคุณอาจใช้PreviewMouseDownเริ่มตัวจับเวลาเพื่อเริ่มการลากและยกเลิกการดำเนินการหากMouseUpเหตุการณ์เริ่มขึ้นภายใน X มิลลิวินาที


+1. ดีกว่ามากที่จะให้ตัวจัดการหน้าต่างจัดการการย้ายแทนที่จะแกล้งทำเป็นจำตำแหน่งและย้ายหน้าต่าง (วิธีหลังก็มีแนวโน้มที่จะผิดพลาดในบางกรณีเช่นกัน)
โจอี้

ทำไมไม่เพียงแค่ตั้งค่าMouseLeftButtonDownเหตุการณ์แทนที่จะตรวจสอบใน. c

1
@Drowin คุณอาจใช้เหตุการณ์นั้นแทนได้ แต่อย่าลืมทดสอบก่อนเนื่องจากMouseLeftButtonDownมีกลยุทธ์การกำหนดเส้นทางโดยตรงในขณะที่MouseDownมีกลยุทธ์การกำหนดเส้นทางที่เดือดปุด ๆ ดูส่วนข้อสังเกตของหน้า MSDN สำหรับ MouseLeftButtonDownสำหรับข้อมูลเพิ่มเติมและสิ่งพิเศษบางอย่างที่ควรระวังหากคุณกำลังจะใช้MouseLeftButtonDownมากกว่าMouseDownนี้
Rachel

@ Rachel ใช่ฉันใช้มันและได้ผล แต่ขอบคุณสำหรับคำอธิบาย!

2
@Rahul การลาก UserControl นั้นยากกว่ามาก ... คุณจะต้องวางไว้ในพาเนลหลักเช่น Canvas และตั้งค่าคุณสมบัติ X / Y (หรือ Canvas.Top และ Canvas.Left) ด้วยตนเองเมื่อผู้ใช้เลื่อนเมาส์ ฉันใช้เหตุการณ์เมาส์ครั้งสุดท้ายที่ฉันทำเช่นนั้นดังนั้น OnMouseDown จับตำแหน่งและลงทะเบียนเหตุการณ์การย้าย OnMouseMove เปลี่ยน X / Y และ OnMouseUp ลบเหตุการณ์การย้าย นั่นเป็นแนวคิดพื้นฐานของมัน :)
Rachel

9

หากรูปแบบ wpf จำเป็นต้องลากได้ไม่ว่าจะคลิกที่ใดวิธีแก้ไขปัญหาง่ายๆคือการใช้ผู้รับมอบสิทธิ์เพื่อเรียกใช้เมธอด DragMove () ในเหตุการณ์ onload ของ windows หรือเหตุการณ์การโหลดกริด

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

2
ฉันเพิ่มสิ่งนี้ไปยังตัวสร้าง ใช้งานได้อย่างมีเสน่ห์
Joe Johnston

1
การดำเนินการนี้จะทำให้เกิดข้อยกเว้นหากคุณคลิกขวาที่ใดก็ได้ในแบบฟอร์มเนื่องจากDragMoveสามารถเรียกใช้ได้เมื่อปุ่มเมาส์หลักลงเท่านั้น
Stjepan Bakrac

4

บางครั้งเราไม่สามารถเข้าถึงได้Windowเช่นหากเรากำลังใช้DevExpressสิ่งที่มีอยู่คือไฟล์UIElement .

ขั้นตอนที่ 1: เพิ่มคุณสมบัติที่แนบมา

วิธีแก้ปัญหาคือ:

  1. ขอเข้า MouseMoveกิจกรรม
  2. ค้นหาแผนผังภาพจนกว่าเราจะพบผู้ปกครองคนแรก Window ;
  3. สอบถามเกี่ยวกับการค้นพบใหม่ของเรา.DragMove()Window

รหัส:

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);
        }
    }
}

ขั้นตอนที่ 2: เพิ่มคุณสมบัติที่แนบไปยังองค์ประกอบใด ๆ เพื่อให้มันลากหน้าต่าง

ผู้ใช้สามารถลากทั้งหน้าต่างโดยคลิกที่องค์ประกอบเฉพาะหากเราเพิ่มคุณสมบัติที่แนบมานี้:

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

ภาคผนวก A: ตัวอย่างขั้นสูงเพิ่มเติม

ในตัวอย่างนี้จากDevExpressเราแทนที่แถบชื่อเรื่องของหน้าต่างเชื่อมต่อด้วยสี่เหลี่ยมผืนผ้าสีเทาของเราเองจากนั้นตรวจสอบให้แน่ใจว่าหากผู้ใช้คลิกและลากกล่าวว่าสี่เหลี่ยมผืนผ้าสีเทาหน้าต่างจะลากตามปกติ:

<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>

Disclaimer: ฉันกำลังไม่ได้มีส่วนเกี่ยวข้องกับDevExpress เทคนิคนี้จะใช้ได้กับองค์ประกอบของผู้ใช้ใด ๆ รวมถึงWPF มาตรฐานหรือTelerik (ผู้ให้บริการไลบรารี WPF ที่ดีรายอื่น)


1
นี่คือสิ่งที่ฉันต้องการ IMHO โค้ด WPF ทั้งหมดที่อยู่ข้างหลังควรเขียนเป็นลักษณะการทำงานที่แนบมา
fjch1997

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

มีการทิ้งข้อยกเว้นในบางกรณี (เช่นในหน้าต่างคุณมีภาพที่คลิกได้ซึ่งเมื่อคลิกแล้วจะเปิดกล่องข้อความเมื่อคุณออกจากกล่องข้อความคุณจะได้รับข้อผิดพลาด) การใช้งานจะปลอดภัยกว่า

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

คุณแน่ใจว่าได้กดปุ่มซ้ายในขณะนั้น


ฉันใช้e.LeftButtonแทนMouse.LeftButtonการใช้ปุ่มที่เกี่ยวข้องกับอาร์กิวเมนต์โดยเฉพาะแม้ว่ามันจะไม่สำคัญก็ตาม
Fls'Zen

2

สามารถลากและวางแบบฟอร์มได้โดยคลิกที่ใดก็ได้บนแบบฟอร์มไม่ใช่แค่แถบชื่อเรื่อง สิ่งนี้มีประโยชน์หากคุณมีรูปแบบไร้ขอบ

บทความเกี่ยวกับ CodeProject นี้แสดงให้เห็นถึงวิธีการแก้ปัญหาที่เป็นไปได้วิธีหนึ่งในการนำไปใช้:

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

โดยทั่วไปลูกหลานของประเภทฟอร์มจะถูกสร้างขึ้นซึ่งจะมีการจัดการเหตุการณ์ที่ใช้เมาส์ลงขึ้นและย้าย

  • วางเมาส์ลง: จำตำแหน่ง
  • การเคลื่อนย้ายเมาส์: จัดเก็บตำแหน่งใหม่
  • เลื่อนเมาส์ขึ้น: แบบฟอร์มตำแหน่งไปยังตำแหน่งใหม่

และนี่คือวิธีแก้ปัญหาที่คล้ายกันซึ่งอธิบายไว้ในวิดีโอสอน:

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

ฉันไม่อนุญาตให้ลากแบบฟอร์มเมื่อผู้ใช้คลิกที่ตัวควบคุมในแบบฟอร์มดังกล่าว ผู้ใช้จะได้ผลลัพธ์ที่แตกต่างกันเมื่อคลิกที่ตัวควบคุมต่างๆ เมื่อแบบฟอร์มของฉันเริ่มเคลื่อนไหวอย่างกะทันหันเพราะฉันคลิกที่กล่องรายการปุ่มป้ายกำกับ ... ฯลฯ ที่จะสับสน


แน่นอนว่ามันจะไม่เคลื่อนที่ด้วยการคลิกตัวควบคุมใด ๆ แต่ถ้าคุณคลิกและลากคุณจะไม่คาดหวังว่าฟอร์มจะย้าย ฉันหมายความว่าคุณจะไม่คาดหวังว่าปุ่มหรือกล่องรายการจะย้ายตัวอย่างเช่นหากคุณคลิก + ลากมันการเคลื่อนไหวของแบบฟอร์มเป็นความคาดหวังโดยธรรมชาติหากคุณพยายามคลิกและลากปุ่มในแบบฟอร์มฉันคิดว่า
Alex K

เดาว่านั่นเป็นเพียงรสนิยมส่วนตัว อย่างไรก็ตาม .... การควบคุมจะต้องจัดการกับเหตุการณ์เมาส์เดียวกัน คุณจะต้องแจ้งรูปแบบหลักของเหตุการณ์เหล่านี้เนื่องจากเหตุการณ์เหล่านี้ไม่เกิดขึ้น
Christophe Geers

นอกจากนี้ในขณะที่ฉันทราบถึงโซลูชัน WinForms สำหรับสิ่งนี้ฉันก็หวังว่าจะมีวิธีที่ง่ายขึ้นใน WPF ฉันคิดว่าฉันควรทำให้สิ่งนี้ชัดเจนขึ้นในคำถาม (ตอนนี้เป็นเพียงแท็ก)
Alex K

ขออภัยฉันไม่ดี ไม่สังเกตเห็นแท็ก WPF ไม่ได้กล่าวถึงในคำถามเดิม ฉันเพิ่งถือว่า WinForms เป็นค่าเริ่มต้นมองข้ามแท็ก
Christophe Geers

2

ดังที่ได้กล่าวไว้แล้วโดย@ fjch1997สะดวกในการใช้พฤติกรรม นี่คือตรรกะหลักเหมือนกับในคำตอบของ @ 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();
        }
    }
}

การใช้งาน:

<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

ทั้งหมดนี้จำเป็น!

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

วิธีที่มีประโยชน์ที่สุดทั้งสำหรับฟอร์ม WPF และ windows ตัวอย่าง 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);
    }

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.