Jadikan jendela WPF dapat diseret, tidak peduli elemen apa yang diklik


111

Pertanyaan saya adalah 2 kali lipat, dan saya berharap ada solusi yang lebih mudah untuk keduanya disediakan oleh WPF daripada solusi standar dari WinForms (yang disediakan Christophe Geers, sebelum saya membuat klarifikasi ini).

Pertama, adakah cara untuk membuat Window dapat diseret tanpa menangkap dan memproses peristiwa klik-mouse + seret? Maksud saya, jendela dapat diseret oleh bilah judul, tetapi jika saya mengatur jendela agar tidak memilikinya dan masih ingin dapat menyeretnya, apakah ada cara untuk mengarahkan ulang peristiwa entah bagaimana ke apa pun yang menangani bilah judul menyeret ?

Kedua, apakah ada cara untuk menerapkan event handler ke semua elemen di jendela? Seperti, buat jendela bisa diseret apa pun elemen yang diklik + diseret pengguna. Jelas tanpa menambahkan handler secara manual, ke setiap elemen. Lakukan saja sekali di suatu tempat?

Jawaban:


284

Oke, terapkan MouseDownacara berikut dari AndaWindow

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

Ini akan memungkinkan pengguna untuk menyeret Jendela ketika mereka mengklik / menyeret pada kontrol apa pun, KECUALI untuk kontrol yang memakan acara MouseDown ( e.Handled = true)

Anda dapat menggunakan PreviewMouseDownalih-alih MouseDown, tetapi peristiwa seret memakan Clickacara tersebut, sehingga jendela Anda berhenti merespons peristiwa klik kiri mouse. Jika Anda BENAR-BENAR ingin dapat mengeklik dan menyeret formulir dari kontrol mana pun, Anda mungkin dapat menggunakan PreviewMouseDown, memulai pengatur waktu untuk memulai operasi seret, dan membatalkan operasi jika MouseUpperistiwa terjadi dalam X milidetik.


+1. Jauh lebih baik membiarkan pengelola jendela menangani pemindahan daripada memalsukannya dengan mengingat posisi dan memindahkan jendela. (Metode terakhir juga memiliki kecenderungan untuk salah dalam kasus tepi tertentu, bagaimanapun)
Joey

Mengapa tidak menyetel MouseLeftButtonDownacara saja, daripada memeriksa di .cs?

1
@Drowin Anda mungkin dapat menggunakan acara itu sebagai gantinya, tetapi pastikan Anda mengujinya terlebih dahulu karena MouseLeftButtonDownmemiliki strategi perutean langsung sementara MouseDownmemiliki strategi perutean yang menggelegak. Lihat bagian komentar dari halaman MSDN untuk MouseLeftButtonDown untuk info lebih lanjut, dan untuk beberapa hal-hal ekstra yang harus diperhatikan jika Anda akan menggunakan MouseLeftButtonDownlebih MouseDown.
Rachel

@Rachel Ya saya menggunakannya dan berhasil, tapi terima kasih atas penjelasannya!

2
@Rahul Menyeret UserControl jauh lebih sulit ... Anda harus menempatkannya di panel induk seperti Canvas dan secara manual mengatur properti X / Y (atau Canvas.Top dan Canvas.Left) saat pengguna menggerakkan mouse. Saya menggunakan acara mouse terakhir kali saya melakukan itu, jadi OnMouseDown menangkap posisi & mendaftarkan acara bergerak, OnMouseMove mengubah X / Y, dan OnMouseUp menghapus acara bergerak. Itulah ide dasarnya :)
Rachel

9

jika formulir wpf harus dapat diseret di mana pun ia diklik, penyelesaian yang mudah adalah menggunakan delegasi untuk memicu metode DragMove () baik pada peristiwa onload windows atau peristiwa pemuatan grid

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

2
Saya menambahkan ini ke konstruktor. Berhasil.
Joe Johnston

1
Ini akan memunculkan pengecualian jika Anda mengklik kanan di mana saja pada formulir, karena DragMovehanya dapat dipanggil saat tombol utama mouse tidak aktif .
Stjepan Bakrac

4

Terkadang, kita tidak memiliki akses ke Window, misalnya jika kita menggunakan DevExpress, yang tersedia hanyalah aUIElement .

Langkah 1: Tambahkan properti terlampir

Solusinya adalah:

  1. Hubungkan MouseMove acara;
  2. Cari pohon visual sampai kita menemukan orang tua pertama Window ;
  3. Hubungi .DragMove()kami yang baru ditemukan Window.

Kode:

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

Langkah 2: Tambahkan Properti Terlampir ke elemen apa pun untuk membiarkannya menyeret jendela

Pengguna dapat menyeret seluruh jendela dengan mengklik elemen tertentu, jika kita menambahkan properti terlampir ini:

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

Lampiran A: Contoh Lanjutan Opsional

Dalam contoh dari DevExpress ini , kami mengganti bilah judul jendela dok dengan persegi panjang abu-abu kami sendiri, lalu memastikan bahwa jika pengguna mengeklik dan menyeret persegi abu-abu tersebut, jendela akan menyeret secara normal:

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

Penafian: Saya tidak berafiliasi dengan DevExpress . Teknik ini akan bekerja dengan elemen pengguna apa pun, termasuk WPF standar atau Telerik (penyedia perpustakaan WPF bagus lainnya).


1
Inilah yang saya inginkan. IMHO semua kode WPF di belakang harus ditulis sebagai perilaku terlampir.
fjch1997

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

Melempar pengecualian dalam beberapa kasus (yaitu jika di jendela Anda juga memiliki gambar yang dapat diklik yang ketika diklik membuka kotak pesan. Ketika Anda keluar dari kotak pesan, Anda akan mendapatkan kesalahan) Lebih aman untuk digunakan

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

Jadi Anda yakin tombol kiri ditekan pada saat itu.


Saya menggunakan e.LeftButtonalih-alih Mouse.LeftButtonsecara khusus menggunakan tombol yang terkait dengan acara args, meskipun itu mungkin tidak masalah.
Fls'Zen

2

Anda dapat menyeret & menjatuhkan formulir dengan mengklik di mana saja pada formulir, bukan hanya bilah judul. Ini berguna jika Anda memiliki formulir tanpa bingkai.

Artikel di CodeProject ini menunjukkan satu kemungkinan solusi untuk menerapkan ini:

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

Pada dasarnya turunan dari tipe Formulir dibuat di mana peristiwa mouse turun, naik dan pindah ditangani.

  • Mouse ke bawah: ingat posisi
  • Gerakan mouse: menyimpan lokasi baru
  • Mouse up: bentuk posisi ke lokasi baru

Dan inilah solusi serupa yang dijelaskan dalam tutorial video:

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

Saya tidak akan mengizinkan menyeret formulir saat pengguna mengklik kontrol dalam formulir tersebut. Pengguna memberikan hasil yang berbeda ketika mereka mengklik kontrol yang berbeda. Ketika formulir saya tiba-tiba mulai bergerak karena saya mengklik kotak daftar, tombol, label ... dll. itu akan membingungkan.


Tentu itu tidak akan bergerak dengan mengklik kontrol apa pun, tetapi jika Anda mengklik dan menyeret, tidakkah Anda mengharapkan formulir untuk bergerak. Maksud saya, Anda tidak akan mengharapkan tombol atau kotak daftar bergerak misalnya jika Anda mengklik + menyeretnya, gerakan formulir adalah ekspektasi alami jika Anda mencoba mengklik dan menyeret tombol di formulir, menurut saya.
Alex K

Tebak, itu hanya selera pribadi. Bagaimanapun .... kontrol perlu menangani kejadian mouse yang sama. Anda harus memberi tahu formulir induk dari peristiwa ini karena tidak muncul.
Christophe Geers

Juga, sementara saya mengetahui solusi WinForms untuk ini, saya berharap cara yang lebih mudah untuk ada di WPF, saya kira saya harus memperjelas pertanyaan ini (sekarang ini hanya tag).
Alex K

Maaf, salahku. Tidak memperhatikan tag WPF. Tidak disebutkan dalam pertanyaan awal. Saya hanya berasumsi WinForms secara default, melihat-lihat tag.
Christophe Geers

2

Seperti yang telah disebutkan oleh @ fjch1997, lebih mudah menerapkan perilaku. Ini dia, logika inti adalah sama seperti di @ loi.efy ini jawabannya :

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

Pemakaian:

<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

Ini semua dibutuhkan!

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

Metode yang paling berguna, baik untuk bentuk WPF dan windows, contoh 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>

sumber

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.