Induk Kontrol Pengguna WPF

183

Saya memiliki kontrol pengguna yang saya muat ke dalam MainWindowsaat runtime. Saya tidak bisa mendapatkan pegangan di jendela yang berisi dari jendela UserControl.

Saya sudah mencoba this.Parent, tetapi selalu nol. Adakah yang tahu cara mendapatkan pegangan ke jendela yang berisi dari kontrol pengguna di WPF?

Berikut adalah cara kontrol dimuat:

private void XMLLogViewer_MenuItem_Click(object sender, RoutedEventArgs e)
{
    MenuItem application = sender as MenuItem;
    string parameter = application.CommandParameter as string;
    string controlName = parameter;
    if (uxPanel.Children.Count == 0)
    {
        System.Runtime.Remoting.ObjectHandle instance = Activator.CreateInstance(Assembly.GetExecutingAssembly().FullName, controlName);
        UserControl control = instance.Unwrap() as UserControl;
        this.LoadControl(control);
    }
}

private void LoadControl(UserControl control)
{
    if (uxPanel.Children.Count > 0)
    {
        foreach (UIElement ctrl in uxPanel.Children)
        {
            if (ctrl.GetType() != control.GetType())
            {
                this.SetControl(control);
            }
        }
    }
    else
    {
        this.SetControl(control);
    }
}

private void SetControl(UserControl control)
{
    control.Width = uxPanel.Width;
    control.Height = uxPanel.Height;
    uxPanel.Children.Add(control);
}
donniefitz2
sumber

Jawaban:

346

Coba gunakan yang berikut ini:

Window parentWindow = Window.GetWindow(userControlReference);

The GetWindowMetode akan berjalan VisualTree untuk Anda dan menemukan jendela yang hosting kendali Anda.

Anda harus menjalankan kode ini setelah kontrol dimuat (dan bukan di konstruktor Window) untuk mencegah GetWindowmetode kembali null. Misalnya memasang sebuah acara:

this.Loaded += new RoutedEventHandler(UserControl_Loaded); 
Ian Oakes
sumber
6
Masih mengembalikan nol. Seolah-olah kontrol tidak memiliki orangtua.
donniefitz2
2
Saya menggunakan kode di atas dan mendapatkan parentWindow juga mengembalikan null untuk saya.
Peter Walke
106
Saya menemukan alasan pengembalian nol. Saya meletakkan kode ini ke konstruktor kontrol pengguna saya. Anda harus menjalankan kode ini setelah kontrol dimuat. EG memasang sebuah acara: this.Loaded + = new RoutedEventHandler (UserControl_Loaded);
Peter Walke
2
Setelah meninjau respons dari Paul, mungkin masuk akal untuk menggunakan metode OnInitialized daripada Loaded.
Peter Walke
@PeterWalke Anda memecahkan masalah saya yang sangat lama ... Terima kasih
Waqas Shabbir
34

Saya akan menambahkan pengalaman saya. Meskipun menggunakan acara Loaded dapat melakukan pekerjaan, saya pikir mungkin lebih cocok untuk mengganti metode OnInitialized. Loaded terjadi setelah jendela pertama kali ditampilkan. OnInitialized memberi Anda kesempatan untuk membuat perubahan apa pun, misalnya, menambahkan kontrol ke jendela sebelum diberikan.

paul
sumber
8
+1 untuk yang benar. Memahami teknik mana yang harus digunakan kadang-kadang halus, terutama ketika Anda punya acara dan menimpanya dilemparkan ke dalam campuran (Acara dimuat, override OnLoaded, Acara diinisialisasi, override OnInitialized, dlletcetc). Dalam hal ini, OnInitialized masuk akal karena Anda ingin menemukan induk, dan kontrol harus diinisialisasi agar induk "ada". Dimuat berarti sesuatu yang berbeda.
Greg D
3
Window.GetWindowmasih kembali nulldi OnInitialized. Tampaknya hanya bekerja di Loadedacara tersebut.
Physikbuddha
Acara diinisialisasi harus didefinisikan sebelum InisialisasiKomponen (); Bagaimanapun, Elemen Binded (XAML) saya tidak dapat menyelesaikan sumber (Window). Jadi saya akhirnya menggunakan Acara yang Dimuat.
Lenor
15

Coba gunakan VisualTreeHelper.GetParent atau gunakan fungsi rekursif di bawah untuk menemukan jendela induk.

 public static Window FindParentWindow(DependencyObject child)
    {
        DependencyObject parent= VisualTreeHelper.GetParent(child);

        //CHeck if this is the end of the tree
        if (parent == null) return null;

        Window parentWindow = parent as Window;
        if (parentWindow != null)
        {
            return parentWindow;
        }
        else
        {
            //use recursion until it reaches a Window
            return FindParentWindow(parent);
        }
    }
Jobi Joy
sumber
Saya mencoba meneruskan menggunakan kode ini dari dalam kendali pengguna saya. Saya melewati ini ke metode ini tetapi mengembalikan nol, menunjukkan bahwa itu adalah akhir dari pohon (sesuai dengan komentar Anda). Apakah Anda tahu mengapa ini terjadi? Kontrol pengguna memiliki induk yang merupakan formulir yang berisi. Bagaimana saya bisa menangani formulir ini?
Peter Walke
2
Saya menemukan alasan pengembalian nol. Saya meletakkan kode ini ke konstruktor kontrol pengguna saya. Anda harus menjalankan kode ini setelah kontrol dimuat. EG memasang sebuah acara: this.Loaded + = RoutedEventHandler baru (UserControl_Loaded)
Peter Walke
Masalah lain ada di debugger. VS akan mengeksekusi kode acara Load, tetapi tidak akan menemukan induk Window.
bohdan_trotsenko
1
Jika Anda akan mengimplementasikan metode Anda sendiri, Anda harus menggunakan kombinasi VisualTreeHelper dan LogicalTreeHelper. Ini karena beberapa kontrol non-jendela (seperti Popup) tidak memiliki orangtua visual dan tampaknya kontrol yang dihasilkan dari templat data tidak memiliki orangtua yang logis.
Brian Reichle
14

Saya perlu menggunakan metode Window.GetWindow (ini) dalam Loaded event handler. Dengan kata lain, saya menggunakan jawaban kedua Ian Oakes dalam kombinasi dengan jawaban Alex untuk mendapatkan orang tua kontrol pengguna.

public MainView()
{
    InitializeComponent();

    this.Loaded += new RoutedEventHandler(MainView_Loaded);
}

void MainView_Loaded(object sender, RoutedEventArgs e)
{
    Window parentWindow = Window.GetWindow(this);

    ...
}
Alan Le
sumber
7

Pendekatan ini berhasil bagi saya tetapi tidak spesifik seperti pertanyaan Anda:

App.Current.MainWindow
Anthony Main
sumber
7

Jika Anda menemukan pertanyaan ini dan VisualTreeHelper tidak berfungsi untuk Anda atau bekerja secara sporadis, Anda mungkin perlu memasukkan LogicalTreeHelper dalam algoritme Anda.

Inilah yang saya gunakan:

public static T TryFindParent<T>(DependencyObject current) where T : class
{
    DependencyObject parent = VisualTreeHelper.GetParent(current);
    if( parent == null )
        parent = LogicalTreeHelper.GetParent(current);
    if( parent == null )
        return null;

    if( parent is T )
        return parent as T;
    else
        return TryFindParent<T>(parent);
}
GordoFabulous
sumber
Anda melewatkan nama metode LogicalTreeHelper.GetParentdalam kode.
xmedeko
Ini solusi terbaik bagi saya.
Jack B Nimble
6

Bagaimana dengan ini:

DependencyObject parent = ExVisualTreeHelper.FindVisualParent<UserControl>(this);

public static class ExVisualTreeHelper
{
    /// <summary>
    /// Finds the visual parent.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="sender">The sender.</param>
    /// <returns></returns>
    public static T FindVisualParent<T>(DependencyObject sender) where T : DependencyObject
    {
        if (sender == null)
        {
            return (null);
        }
        else if (VisualTreeHelper.GetParent(sender) is T)
        {
            return (VisualTreeHelper.GetParent(sender) as T);
        }
        else
        {
            DependencyObject parent = VisualTreeHelper.GetParent(sender);
            return (FindVisualParent<T>(parent));
        }
    } 
}
Eric Coulson
sumber
5

Saya telah menemukan bahwa induk dari UserControl selalu nol di konstruktor, tetapi dalam setiap penangan acara induk diatur dengan benar. Saya kira itu pasti ada hubungannya dengan cara pohon kontrol dimuat. Jadi untuk menyiasati ini, Anda bisa mendapatkan induk di acara kontrol Loaded.

Sebagai contoh checkout pertanyaan ini DataContext Kontrol Pengguna WPF adalah Null

Alex
sumber
1
Anda harus menunggu dulu di "pohon" terlebih dahulu. Kadang-kadang sangat menjengkelkan.
user7116
3

Cara lain:

var main = App.Current.MainWindow as MainWindow;
Pnct
sumber
Bekerja untuk saya, harus meletakkannya di acara "Loaded" daripada konstruktor (membuka jendela properti, klik dua kali dan itu akan menambahkan penangan untuk Anda).
Contango
(Pilihan saya untuk jawaban yang diterima oleh Ian, ini hanya untuk catatan) Ini tidak berfungsi ketika kontrol pengguna di jendela lain dengan ShowDialog, mengatur konten ke kontrol pengguna. Pendekatan serupa adalah berjalan melalui App.Current.Windows dan menggunakan jendela di mana kondisi berikut, untuk idx dari (Current.Windows.Count - 1) hingga 0 (App.Current.Windows [idx] == userControlRef) benar . Jika kita melakukan ini dalam urutan terbalik, kemungkinan itu adalah jendela terakhir dan kita mendapatkan jendela yang benar hanya dengan satu iterasi. userControlRef biasanya ini dalam kelas UserControl.
msanjay
3

Ini bekerja untuk saya:

DependencyObject GetTopLevelControl(DependencyObject control)
{
    DependencyObject tmp = control;
    DependencyObject parent = null;
    while((tmp = VisualTreeHelper.GetParent(tmp)) != null)
    {
        parent = tmp;
    }
    return parent;
}
Nalan Madheswaran
sumber
2

Ini tidak berfungsi untuk saya, karena terlalu jauh ke atas pohon, dan mendapatkan jendela root absolut untuk seluruh aplikasi:

Window parentWindow = Window.GetWindow(userControlReference);

Namun, ini berhasil untuk mendapatkan jendela langsung:

DependencyObject parent = uiElement;
int avoidInfiniteLoop = 0;
while ((parent is Window)==false)
{
    parent = VisualTreeHelper.GetParent(parent);
    avoidInfiniteLoop++;
    if (avoidInfiniteLoop == 1000)
    {
        // Something is wrong - we could not find the parent window.
        break;
    }
}
Window window = parent as Window;
window.DragMove();
Contango
sumber
Anda harus menggunakan cek nol alih-alih variabel 'avoidInfiniteLoop' yang berubah-ubah. Ubah 'sementara' Anda untuk memeriksa nol terlebih dahulu, dan jika bukan nol, maka periksa apakah itu bukan jendela. Kalau tidak, cukup break / keluar.
Mark A. Donohoe
@MarquelV aku mendengarmu. Secara umum, saya menambahkan cek "avoidInfiniteLoop" ke setiap loop yang secara teori bisa macet jika ada masalah. Itu bagian dari pemrograman defensif. Sering kali, ia membayar dividen yang baik karena program menghindari hang. Sangat berguna selama debugging, dan sangat berguna dalam produksi jika overrun dicatat. Saya menggunakan teknik ini (di antara banyak lainnya) untuk mengaktifkan penulisan kode yang kuat yang hanya berfungsi.
Contango
Saya mendapatkan pemrograman defensif, dan saya setuju secara prinsip tentang hal itu, tetapi sebagai peninjau kode, saya pikir ini akan ditandai untuk memperkenalkan data arbitrer yang bukan bagian dari aliran logika aktual. Anda sudah memiliki semua informasi yang diperlukan untuk menghentikan rekursi tak terbatas dengan memeriksa nol karena tidak mungkin untuk mengulang pohon tanpa batas. Tentu Anda bisa lupa untuk memperbarui orang tua dan memiliki loop tak terbatas, tetapi Anda bisa dengan mudah lupa untuk memperbarui variabel yang sewenang-wenang itu. Dengan kata lain, itu sudah pemrograman defensif untuk memeriksa nol tanpa pengenalan data baru yang tidak terkait.
Mark A. Donohoe
1
@MarquelIV saya harus setuju. Menambahkan cek nol tambahan adalah pemrograman defensif yang lebih baik.
Contango
1
DependencyObject parent = ExVisualTreeHelper.FindVisualParent<UserControl>(this);
Eric Coulson
sumber
Harap hapus ini dan integrasikan setiap titik yang membuat ini belum tercakup dalam jawaban Anda yang lain (yang saya angkat sebagai jawaban yang bagus)
Ruben Bartelink
1
DependencyObject GetTopParent(DependencyObject current)
{
    while (VisualTreeHelper.GetParent(current) != null)
    {
        current = VisualTreeHelper.GetParent(current);
    }
    return current;
}

DependencyObject parent = GetTopParent(thisUserControl);
Agus Syahputra
sumber
0

Edisi berlapis emas di atas (saya perlu fungsi generik yang dapat menyimpulkan Windowdalam konteks MarkupExtension: -

public sealed class MyExtension : MarkupExtension
{
    public override object ProvideValue(IServiceProvider serviceProvider) =>
        new MyWrapper(ResolveRootObject(serviceProvider));
    object ResolveRootObject(IServiceProvider serviceProvider) => 
         GetService<IRootObjectProvider>(serviceProvider).RootObject;
}

class MyWrapper
{
    object _rootObject;

    Window OwnerWindow() => WindowFromRootObject(_rootObject);

    static Window WindowFromRootObject(object root) =>
        (root as Window) ?? VisualParent<Window>((DependencyObject)root);
    static T VisualParent<T>(DependencyObject node) where T : class
    {
        if (node == null)
            throw new InvalidOperationException("Could not locate a parent " + typeof(T).Name);
        var target = node as T;
        if (target != null)
            return target;
        return VisualParent<T>(VisualTreeHelper.GetParent(node));
    }
}

MyWrapper.Owner() akan menyimpulkan Window dengan benar sebagai berikut:

  • root Windowdengan berjalan pohon visual (jika digunakan dalam konteks a UserControl)
  • jendela yang digunakan (jika digunakan dalam konteks Windowmarkup)
Ruben Bartelink
sumber
0

Pendekatan dan strategi yang berbeda. Dalam kasus saya, saya tidak dapat menemukan jendela dialog saya baik melalui menggunakan VisualTreeHelper atau metode ekstensi dari Telerik untuk menemukan induk dari tipe yang diberikan. Sebagai gantinya, saya menemukan tampilan dialog saya yang menerima suntikan konten khusus menggunakan Application.Current.Windows.

public Window GetCurrentWindowOfType<TWindowType>(){
 return Application.Current.Windows.OfType<TWindowType>().FirstOrDefault() as Window;
}
Tore Aurstad
sumber
0

The Window.GetWindow(userControl)akan kembali jendela yang sebenarnya hanya setelah jendela itu diinisialisasi ( InitializeComponent()metode selesai).

Ini berarti, bahwa jika kontrol pengguna Anda diinisialisasi bersama dengan jendelanya (misalnya Anda memasukkan kontrol pengguna Anda ke file xaml jendela), maka pada acara kontrol pengguna OnInitializedAnda tidak akan mendapatkan jendela (itu akan menjadi nol), karena di hal ini yang menyebabkan kontrol pengguna OnInitializeddiaktifkan sebelum jendela diinisialisasi.

Ini juga berarti bahwa jika kontrol pengguna Anda diinisialisasi setelah jendelanya, maka Anda bisa mendapatkan jendela itu di konstruktor kontrol pengguna.

ChocapicSz
sumber
0

Jika Anda hanya ingin mendapatkan orang tua tertentu, tidak hanya jendela, orang tua tertentu dalam struktur pohon, dan juga tidak menggunakan rekursi, atau penghitung loop keras, Anda dapat menggunakan yang berikut ini:

public static T FindParent<T>(DependencyObject current)
    where T : class 
{
    var dependency = current;

    while((dependency = VisualTreeHelper.GetParent(dependency) ?? LogicalTreeHelper.GetParent(dependency)) != null
        && !(dependency is T)) { }

    return dependency as T;
}

Hanya saja, jangan letakkan panggilan ini di konstruktor (karena Parentproperti belum diinisialisasi). Tambahkan di pengatur acara pemuatan, atau di bagian lain dari aplikasi Anda.

Lucaci Andrei
sumber