WPF Kullanıcı Kontrolü Üst Öğesi


183

Bir MainWindowçalışma zamanında yüklediğim bir kullanıcı denetimi var . İçeren pencereden bir tanıtıcı alamıyorum UserControl.

Denedim this.Parent, ama her zaman boş. Herkes WPF bir kullanıcı denetiminden içeren pencereye bir tanıtıcı almak nasıl biliyor mu?

Denetimin nasıl yükleneceği aşağıda açıklanmıştır:

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

Yanıtlar:


346

Aşağıdakileri kullanmayı deneyin:

Window parentWindow = Window.GetWindow(userControlReference);

GetWindowYöntem sizin için VisualTree yürümek ve denetimi barındıran pencereyi bulacaktır.

GetWindowYöntemin dönmesini önlemek için denetim yüklendikten sonra (Pencere yapıcısında değil) bu kodu çalıştırmalısınız null. Örn: bir etkinlik bağlayın:

this.Loaded += new RoutedEventHandler(UserControl_Loaded); 

6
Yine de null değerini döndürür. Kontrolün ebeveyni yokmuş gibi.
donniefitz2

2
Yukarıdaki kodu kullandım ve parentWindow da benim için null döndürür olsun.
Peter Walke

106
Boş döndürme nedenini öğrendim. Bu kodu kullanıcı kontrolümün yapıcısına koyuyordum. Denetim yüklendikten sonra bu kodu çalıştırmalısınız. EG bir olay bağlar: this.Loaded + = new RoutedEventHandler (UserControl_Loaded);
Peter Walke

2
Paul'ün yanıtını inceledikten sonra, Loaded yerine OnInitialized yöntemini kullanmak mantıklı olabilir.
Peter Walke

@PeterWalke benim çok uzun zaman sorunumu çözdü ... Teşekkürler
Waqas Shabbir

34

Deneyimlerimi ekleyeceğim. Loaded olayını kullanmak işi yapabilse de, OnInitialized yöntemini geçersiz kılmanın daha uygun olabileceğini düşünüyorum. Yüklenen pencere ilk görüntülendikten sonra gerçekleşir. OnInitialized, herhangi bir değişiklik yapma şansı verir, örneğin, oluşturulmadan önce pencereye kontroller ekleme.


8
+1 için doğru. Hangi tekniğin kullanılacağını zaman zaman anlamak, özellikle de karışıma atılan olaylar ve geçersiz kılmalar olduğunda (Loaded olayı, OnLoaded geçersiz kılma, Initialized olayı, OnInitialized geçersiz kılma vb. Vb.). Bu durumda, OnInitialized üst öğeyi bulmak istediğiniz için mantıklıdır ve üst öğenin "varolması" için denetimin başlatılması gerekir. Yüklü farklı bir şey demektir.
Greg D

3
Window.GetWindowHala döner nulliçinde OnInitialized. LoadedSadece etkinlikte çalışıyor gibi görünüyor .
Physikbuddha

Initialized Olayı InitializeComponent (); Her neyse, Bağlı (XAML) Öğelerim kaynağı (Pencere) çözemedi. Bu yüzden Loaded Event'i kullanmaya son verdim.
Lenor

15

VisualTreeHelper.GetParent kullanmayı deneyin veya üst pencereyi bulmak için feryat özyinelemeli işlevi kullanın.

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

Kullanıcı kontrolümden bu kodu kullanarak geçmeyi denedim. Bunu bu yönteme geçtim, ancak ağacın sonu olduğunu belirten null döndürdü (yorumunuza göre). Bunun neden olduğunu biliyor musun? Kullanıcı denetiminin, içeren formu içeren bir üst öğesi vardır. Bu forma nasıl ulaşabilirim?
Peter Walke

2
Boş döndürme nedenini öğrendim. Bu kodu kullanıcı kontrolümün yapıcısına koyuyordum. Denetim yüklendikten sonra bu kodu çalıştırmalısınız. EG bir olay hazırladı: this.Loaded + = yeni RoutedEventHandler (UserControl_Loaded)
Peter Walke

Başka bir sorun hata ayıklayıcıda. VS, Load olayının kodunu yürütür, ancak Window üst öğesini bulamaz.
bohdan_trotsenko

1
Kendi yönteminizi uygulayacaksanız, VisualTreeHelper ve LogicalTreeHelper birleşimlerini kullanmalısınız. Bunun nedeni, pencere olmayan bazı kontrollerin (Popup gibi) görsel ebeveynleri olmaması ve bir veri şablonundan oluşturulan kontrollerin mantıksal ebeveynleri olmadığıdır.
Brian Reichle

14

Loaded olay işleyicisi içinde Window.GetWindow (this) yöntemini kullanmam gerekiyordu. Başka bir deyişle, her iki Ian Oakes cevabını Alex'in cevabı ile birlikte bir kullanıcı kontrolünün ebeveyni olarak kullandım.

public MainView()
{
    InitializeComponent();

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

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

    ...
}

7

Bu yaklaşım benim için işe yaradı, ancak sorunuz kadar spesifik değil:

App.Current.MainWindow

7

Bu soruyu buluyorsanız ve VisualTreeHelper sizin için çalışmıyor veya ara sıra çalışmıyorsa, algoritmanıza LogicalTreeHelper'ı eklemeniz gerekebilir.

İşte ne kullanıyorum:

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

Kodda bir yöntem adını LogicalTreeHelper.GetParentkaçırdınız.
xmedeko

Bu benim için en iyi çözümdü.
Jack B Nimble

6

Buna ne dersin:

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

5

Bir UserControl üst yapıcı her zaman null olduğunu buldum, ancak herhangi bir olay işleyicilerin üst doğru ayarlanmış. Sanırım kontrol ağacının yüklenmesi ile ilgili bir şey olmalı. Bu nedenle, bunun üstesinden gelmek için yalnızca denetimleri Loaded olayında üst öğeyi alabilirsiniz.

Örnek bir ödeme için bu soru WPF Kullanıcı Denetiminin DataContext'i Null


1
Önce "ağaçta" olmasını beklemek zorundasınız. Bazen oldukça iğrenç.
user7116

3

Diğer yol:

var main = App.Current.MainWindow as MainWindow;

Benim için çalıştı, yapıcı yerine "Loaded" olayı koymak zorunda (özellikler penceresini getirmek, çift tıklayın ve sizin için işleyici ekleyecektir).
Contango

(Oyum Ian tarafından kabul edilen cevap için, bu sadece kayıt için) Bu kullanıcı kontrolü ShowDialog ile başka bir pencerede, kullanıcı kontrolüne içerik ayarlandığında işe yaramadı. 0 (App.Current.Windows [IDX] == userControlRef) için - Benzer bir yaklaşım, (1 Current.Windows.Count) den IDX için aşağıdaki koşulu, burada pencere App.Current.Windows yürümek ve kullanmaktır doğru . Bunu ters sırayla yaparsak, son pencere olması muhtemeldir ve tek bir yineleme ile doğru pencereyi alırız. userControlRef genellikle bu UserControl sınıfı içinde.
msanjay

3

Benim için çalışıyor:

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

2

Bu benim için işe yaramadı, çünkü ağaç çok ileri gitti ve tüm uygulama için mutlak kök penceresi var:

Window parentWindow = Window.GetWindow(userControlReference);

Ancak, bu hemen pencereyi almak için çalıştı:

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

Rasgele bir 'avoInfiniteLoop' değişkeni yerine null bir denetim kullanmalısınız. Önce null olup olmadığını kontrol etmek için 'while' değerini değiştirin ve null değilse, bir pencere olup olmadığını kontrol edin. Aksi takdirde, sadece koparın / çıkın.
Mark A. Donohoe

@MarquelV Seni duyuyorum. Genellikle, bir şey yanlış giderse teoride sıkışmış olabilir her döngü için bir "avoInfiniteLoop" denetimi ekleyin . Savunma programlamasının bir parçası. Her zaman, program askıda kalmayı önlediğinden iyi temettüler öder. Hata ayıklama sırasında çok faydalıdır ve taşma günlüğü tutulursa üretimde çok yararlıdır. Ben sadece çalışan sağlam kod yazma sağlamak için (diğerleri arasında) bu tekniği kullanın.
Contango

Defansif programlama alıyorum ve bu konuda prensipte hemfikirim, ancak bir kod inceleyici olarak, bunun gerçek mantık akışının bir parçası olmayan keyfi verileri tanıtmak için işaretleneceğini düşünüyorum. Null olup olmadığını kontrol ederek sonsuz özyinelemeyi durdurmak için gereken tüm bilgilere zaten sahipsiniz, çünkü bir ağacı sonsuz bir şekilde geri almak imkansızdır. Elbette üst öğeyi güncellemeyi ve sonsuz bir döngüye sahip olmayı unutabilirsiniz, ancak bu rastgele değişkeni güncellemeyi de kolayca unutabilirsiniz. Başka bir deyişle, yeni, ilgisiz veriler kullanılmadan null olup olmadığını kontrol etmek zaten savunmacı bir programlamadır.
Mark A. Donohoe

1
@MarquelIV Katılıyorum. Ek bir boş kontrol eklemek daha iyi savunma programlamasıdır.
Contango


1
DependencyObject GetTopParent(DependencyObject current)
{
    while (VisualTreeHelper.GetParent(current) != null)
    {
        current = VisualTreeHelper.GetParent(current);
    }
    return current;
}

DependencyObject parent = GetTopParent(thisUserControl);

0

Yukarıdaki Altın kaplama baskısı (Ben Windowbağlamında bir çıkarım yapabilir genel bir fonksiyon gerekir 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() aşağıdaki pencereyi doğru şekilde çıkarır:

  • Windowgörsel ağacı yürüterek kök (a bağlamında kullanılıyorsa UserControl)
  • içinde kullanıldığı pencere (bir Window'biçimlendirme bağlamında kullanılıyorsa )

0

Farklı yaklaşımlar ve farklı stratejiler. Benim durumumda VisualTreeHelper veya verilen türün üst bulmak için Telerik uzatma yöntemleri kullanarak iletişim penceresini bulamadım. Bunun yerine, Application.Current.Windows kullanarak içeriğin özel enjeksiyonunu kabul eden iletişim görünümümü buldum.

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

0

Window.GetWindow(userControl)Pencere başlatıldı sonra gerçek pencere geçer ( InitializeComponent()yöntem bitmiş).

Bu, kullanıcı denetiminiz penceresiyle birlikte başlatılırsa (örneğin, kullanıcı denetiminizi pencerenin xaml dosyasına koyarsanız), kullanıcı denetiminin OnInitializedolayında pencereyi alamazsınız (boş olacaktır), çünkü bu durumda kullanıcı kontrolüOnInitialized olayı, pencere başlatılmadan önce tetiklenir.

Bu ayrıca, kullanıcı denetiminiz penceresinden sonra başlatılırsa, pencereyi zaten kullanıcı denetiminin yapıcısında bulabileceğiniz anlamına gelir.


0

Yalnızca pencere, ağaç yapısındaki belirli bir üst öğe değil, aynı zamanda özyineleme veya sabit devre döngü sayaçları kullanmayan belirli bir üst öğe almak istiyorsanız, aşağıdakileri kullanabilirsiniz:

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

Bu çağrıyı bir yapıcıya koymayın ( Parentözellik henüz başlatılmadığından). Yükleme olayı işleyicisine veya uygulamanızın diğer bölümlerine ekleyin.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.