WPF User Control Parent


183

Ich habe ein Benutzersteuerelement, das ich MainWindowzur Laufzeit in ein lade . Ich kann das enthaltende Fenster aus dem nicht in den Griff bekommen UserControl.

Ich habe es versucht this.Parent, aber es ist immer null. Weiß jemand, wie man von einem Benutzersteuerelement in WPF ein Handle auf das enthaltende Fenster bekommt?

So wird das Steuerelement geladen:

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

Antworten:


346

Versuchen Sie Folgendes:

Window parentWindow = Window.GetWindow(userControlReference);

Die GetWindowMethode durchsucht den VisualTree für Sie und sucht das Fenster, in dem sich Ihr Steuerelement befindet.

Sie sollten diesen Code ausführen, nachdem das Steuerelement geladen wurde (und nicht im Window-Konstruktor), um zu verhindern, dass die GetWindowMethode zurückgegeben wird null. ZB ein Ereignis verkabeln:

this.Loaded += new RoutedEventHandler(UserControl_Loaded); 

6
Gibt immer noch null zurück. Es ist, als hätte das Steuerelement nur kein Elternteil.
donniefitz2

2
Ich habe den obigen Code verwendet und bekomme das parentWindow, das für mich ebenfalls null zurückgibt.
Peter Walke

106
Ich habe herausgefunden, warum es null zurückgibt. Ich habe diesen Code in den Konstruktor meines Benutzersteuerelements eingefügt. Sie sollten diesen Code ausführen, nachdem das Steuerelement geladen wurde. EG verdrahtet ein Ereignis: this.Loaded + = neuer RoutedEventHandler (UserControl_Loaded);
Peter Walke

2
Nach Überprüfung der Antwort von Paul ist es möglicherweise sinnvoll, die OnInitialized-Methode anstelle von Loaded zu verwenden.
Peter Walke

@ PeterWalke Sie lösen mein sehr langes Problem ... Danke
Waqas Shabbir

34

Ich werde meine Erfahrung hinzufügen. Obwohl die Verwendung des Loaded-Ereignisses den Job erledigen kann, ist es meiner Meinung nach besser geeignet, die OnInitialized-Methode zu überschreiben. Das Laden erfolgt nach der ersten Anzeige des Fensters. Mit OnInitialized können Sie Änderungen vornehmen, z. B. Steuerelemente zum Fenster hinzufügen, bevor es gerendert wird.


8
+1 für richtig. Das Verstehen, welche Technik verwendet werden soll, kann manchmal subtil sein, insbesondere wenn Ereignisse und Überschreibungen in den Mix geworfen werden (geladenes Ereignis, überschriebene OnLoaded-Überschreibung, initialisiertes Ereignis, überschriebene OnInitialized-Überschreibung usw. usw.). In diesem Fall ist OnInitialized sinnvoll, da Sie das übergeordnete Element suchen möchten und das Steuerelement initialisiert werden muss, damit das übergeordnete Element "vorhanden" ist. Geladen bedeutet etwas anderes.
Greg D

3
Window.GetWindownoch zurück nullin OnInitialized. Scheint Loadednur in der Veranstaltung zu funktionieren .
Physikbuddha

Das initialisierte Ereignis muss vor InitializeComponent () definiert werden. Wie auch immer, meine gebundenen (XAML) Elemente konnten die Quelle (Fenster) nicht auflösen. Also habe ich das Loaded Event beendet.
Lenor

15

Versuchen Sie es mit VisualTreeHelper.GetParent oder verwenden Sie die folgende rekursive Funktion, um das übergeordnete Fenster zu finden.

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

Ich habe versucht, diesen Code innerhalb meiner Benutzersteuerung zu verwenden. Ich habe dies an diese Methode übergeben, aber es wurde null zurückgegeben, was darauf hinweist, dass es das Ende des Baums ist (gemäß Ihrem Kommentar). Weißt du warum das so ist? Das Benutzersteuerelement hat ein übergeordnetes Element, das das enthaltende Formular ist. Wie komme ich zu diesem Formular?
Peter Walke

2
Ich habe herausgefunden, warum es null zurückgibt. Ich habe diesen Code in den Konstruktor meines Benutzersteuerelements eingefügt. Sie sollten diesen Code ausführen, nachdem das Steuerelement geladen wurde. EG verdrahtet ein Ereignis: this.Loaded + = neuer RoutedEventHandler (UserControl_Loaded)
Peter Walke

Ein weiteres Problem ist der Debugger. VS führt den Code des Ladeereignisses aus, findet jedoch das übergeordnete Fenster nicht.
bohdan_trotsenko

1
Wenn Sie Ihre eigene Methode implementieren möchten, sollten Sie eine Kombination aus VisualTreeHelper und LogicalTreeHelper verwenden. Dies liegt daran, dass einige Nicht-Fenster-Steuerelemente (wie Popup) keine visuellen übergeordneten Elemente haben und es scheint, dass aus einer Datenvorlage generierte Steuerelemente keine logischen übergeordneten Elemente haben.
Brian Reichle

14

Ich musste die Window.GetWindow (this) -Methode im Loaded-Ereignishandler verwenden. Mit anderen Worten, ich habe die Antwort von Ian Oakes in Kombination mit der Antwort von Alex verwendet, um die Eltern eines Benutzersteuerelements zu erhalten.

public MainView()
{
    InitializeComponent();

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

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

    ...
}

7

Dieser Ansatz hat bei mir funktioniert, ist aber nicht so spezifisch wie Ihre Frage:

App.Current.MainWindow

7

Wenn Sie diese Frage finden und der VisualTreeHelper nicht oder nur sporadisch für Sie funktioniert, müssen Sie möglicherweise LogicalTreeHelper in Ihren Algorithmus aufnehmen.

Folgendes verwende ich:

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

Sie vermissen einen Methodennamen LogicalTreeHelper.GetParentim Code.
Xmedeko

Dies war die beste Lösung für mich.
Jack B Nimble

6

Wie wäre es damit:

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

Ich habe festgestellt, dass das übergeordnete Element eines UserControl im Konstruktor immer null ist, aber in jedem Fallhandler ist das übergeordnete Element korrekt festgelegt. Ich denke, es muss etwas mit der Art und Weise zu tun haben, wie der Kontrollbaum geladen wird. Um dies zu umgehen, können Sie das übergeordnete Element im Steuerelement Loaded-Ereignis abrufen.

Als Beispiel für das Auschecken dieser Frage lautet der DataContext von WPF User Control Null


1
Man muss irgendwie warten, bis es im "Baum" ist. Manchmal ziemlich widerlich.
user7116

3

Ein anderer Weg:

var main = App.Current.MainWindow as MainWindow;

Hat für mich funktioniert, muss es in das "Loaded" -Ereignis und nicht in den Konstruktor eingefügt werden (rufen Sie das Eigenschaftenfenster auf, doppelklicken Sie und es wird der Handler für Sie hinzugefügt).
Contango

(Meine Stimme ist für die akzeptierte Antwort von Ian, dies ist nur für die Aufzeichnung.) Dies hat nicht funktioniert, wenn sich das Benutzersteuerelement in einem anderen Fenster mit ShowDialog befindet und den Inhalt auf das Benutzersteuerelement gesetzt hat. Ein ähnlicher Ansatz ist durch App.Current.Windows und verwenden das Fenster zu gehen , wo die folgende Bedingung, für idx aus (Current.Windows.Count - 1) bis 0 (App.Current.Windows [idx] == userControlRef) ist wahr . Wenn wir dies in umgekehrter Reihenfolge tun, ist es wahrscheinlich das letzte Fenster und wir erhalten das richtige Fenster mit nur einer Iteration. userControlRef ist normalerweise dies innerhalb der UserControl-Klasse.
Msanjay

3

Es funktioniert für mich:

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

3

Dies funktionierte bei mir nicht, da es zu weit oben im Baum lag und das absolute Stammfenster für die gesamte Anwendung erhielt:

Window parentWindow = Window.GetWindow(userControlReference);

Dies funktionierte jedoch, um das unmittelbare Fenster zu erhalten:

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

Sie sollten eine Nullprüfung anstelle einer beliebigen Variablen "removeInfiniteLoop" verwenden. Ändern Sie Ihr 'while', um zuerst nach null zu suchen. Wenn dies nicht null ist, überprüfen Sie, ob es sich nicht um ein Fenster handelt. Ansonsten einfach brechen / beenden.
Mark A. Donohoe

@MarquelV Ich höre dich. Im Allgemeinen füge ich jeder Schleife eine "VermeidenInfiniteLoop" -Prüfung hinzu , die theoretisch hängen bleiben könnte, wenn etwas schief geht. Es ist Teil der defensiven Programmierung. Von Zeit zu Zeit zahlt es sich aus, da das Programm ein Hängenbleiben vermeidet. Sehr nützlich beim Debuggen und sehr nützlich in der Produktion, wenn der Überlauf protokolliert wird. Ich verwende diese Technik (unter anderem), um das Schreiben von robustem Code zu ermöglichen, der einfach funktioniert.
Contango

Ich bekomme defensive Programmierung, und ich stimme dem im Prinzip zu, aber als Code-Reviewer denke ich, dass dies für die Einführung beliebiger Daten markiert wird, die nicht Teil des tatsächlichen Logikflusses sind. Sie haben bereits alle Informationen, die erforderlich sind, um die unendliche Rekursion zu stoppen, indem Sie nach Null suchen, da es unmöglich ist, einen Baum unendlich zu rekursieren. Sicher, Sie könnten vergessen, das übergeordnete Element zu aktualisieren und eine Endlosschleife zu haben, aber Sie könnten genauso gut vergessen, diese beliebige Variable zu aktualisieren. Mit anderen Worten, es ist bereits eine defensive Programmierung, ohne Einführung neuer, nicht verwandter Daten auf Null zu prüfen.
Mark A. Donohoe

1
@MarquelIV Ich muss zustimmen. Das Hinzufügen einer zusätzlichen Nullprüfung ist eine bessere defensive Programmierung.
Contango

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

Bitte löschen Sie dies und integrieren Sie jeden Punkt, der nicht bereits behandelt wurde, in Ihre andere Antwort (die ich als gute Antwort bewertet habe)
Ruben Bartelink

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

DependencyObject parent = GetTopParent(thisUserControl);

0

Vergoldete Ausgabe des oben genannten (Ich brauche eine generische Funktion, die a Windowim Kontext von a ableiten kann 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() wird ein Fenster auf der folgenden Basis korrekt ableiten:

  • die Wurzel Windowdurch Gehen über den visuellen Baum (falls im Kontext von a verwendet UserControl)
  • das Fenster, in dem es verwendet wird (wenn es im Kontext des WindowMarkups von a verwendet wird)

0

Unterschiedliche Ansätze und unterschiedliche Strategien. In meinem Fall konnte ich das Fenster meines Dialogfelds weder mithilfe von VisualTreeHelper noch mithilfe von Erweiterungsmethoden von Telerik finden, um übergeordnete Elemente eines bestimmten Typs zu finden. Stattdessen habe ich meine Dialogansicht gefunden, die das benutzerdefinierte Einfügen von Inhalten mit Application.Current.Windows akzeptiert.

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

0

Das Window.GetWindow(userControl)eigentliche Fenster wird erst zurückgegeben, nachdem das Fenster initialisiert wurde ( InitializeComponent()Methode beendet).

Dies bedeutet, dass, wenn Ihr Benutzersteuerelement zusammen mit seinem Fenster initialisiert wird (z. B. wenn Sie Ihr Benutzersteuerelement in die xaml-Datei des Fensters einfügen), das OnInitializedEreignis beim Ereignis des Benutzersteuerelements nicht angezeigt wird (es ist null) In diesem Fall wird das OnInitializedEreignis des Benutzersteuerelements ausgelöst, bevor das Fenster initialisiert wird.

Dies bedeutet auch, dass Sie das Fenster bereits im Konstruktor des Benutzersteuerelements abrufen können, wenn Ihr Benutzersteuerelement nach seinem Fenster initialisiert wird.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.