Parent de contrôle utilisateur WPF


183

J'ai un contrôle utilisateur que je charge dans un MainWindowà l'exécution. Je ne peux pas obtenir une poignée sur la fenêtre contenant à partir du UserControl.

J'ai essayé this.Parent, mais c'est toujours nul. Quelqu'un sait-il comment obtenir un handle vers la fenêtre contenant à partir d'un contrôle utilisateur dans WPF?

Voici comment le contrôle est chargé:

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

Réponses:


346

Essayez d'utiliser ce qui suit:

Window parentWindow = Window.GetWindow(userControlReference);

La GetWindowméthode parcourra le VisualTree pour vous et localisera la fenêtre qui héberge votre contrôle.

Vous devez exécuter ce code une fois le contrôle chargé (et non dans le constructeur Window) pour empêcher le GetWindowretour de la méthode null. Par exemple, câbler un événement:

this.Loaded += new RoutedEventHandler(UserControl_Loaded); 

6
Renvoie toujours null. C'est comme si le contrôle n'avait simplement pas de parent.
donniefitz2

2
J'ai utilisé le code ci-dessus et obtenir le parentWindow renvoie également null pour moi.
Peter Walke

106
J'ai découvert la raison pour laquelle il renvoie null. Je mettais ce code dans le constructeur de mon contrôle utilisateur. Vous devez exécuter ce code une fois le contrôle chargé. EG câblez un événement: this.Loaded + = new RoutedEventHandler (UserControl_Loaded);
Peter Walke

2
Après avoir examiné la réponse de Paul, il peut être judicieux d'utiliser la méthode OnInitialized au lieu de Loaded.
Peter Walke

@PeterWalke vous résout mon problème de très longue date ... Merci
Waqas Shabbir

34

J'ajouterai mon expérience. Bien que l'utilisation de l'événement Loaded puisse faire le travail, je pense qu'il peut être plus approprié de remplacer la méthode OnInitialized. Loaded se produit après que la fenêtre est affichée pour la première fois. OnInitialized vous donne la possibilité d'apporter des modifications, par exemple, ajouter des contrôles à la fenêtre avant qu'elle ne soit rendue.


8
+1 pour corriger. Comprendre la technique à utiliser peut parfois être subtile, en particulier lorsque vous avez des événements et des remplacements lancés dans le mélange (événement Loaded, remplacement OnLoaded, événement initialisé, remplacement OnInitialized, etc.). Dans ce cas, OnInitialized a du sens car vous souhaitez rechercher le parent et le contrôle doit être initialisé pour que le parent «existe». Loaded signifie quelque chose de différent.
Greg D

3
Window.GetWindowretourne encore nullen OnInitialized. Semble fonctionner dans l' Loadedévénement uniquement.
Physikbuddha

L'événement initialisé doit être défini avant InitializeComponent (); Quoi qu'il en soit, mes éléments liés (XAML) n'ont pas pu résoudre la source (fenêtre). J'ai donc fini d'utiliser l'événement chargé.
Lenor

15

Essayez d'utiliser VisualTreeHelper.GetParent ou utilisez la fonction récursive ci-dessous pour rechercher la fenêtre parente.

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

J'ai essayé de passer en utilisant ce code depuis mon contrôle utilisateur. J'ai passé ceci dans cette méthode mais il a retourné null, indiquant que c'est la fin de l'arbre (selon votre commentaire). Savez-vous pourquoi c'est? Le contrôle utilisateur a un parent qui est le formulaire contenant. Comment accéder à ce formulaire?
Peter Walke

2
J'ai découvert la raison pour laquelle il renvoie null. Je mettais ce code dans le constructeur de mon contrôle utilisateur. Vous devez exécuter ce code une fois le contrôle chargé. EG câblez un événement: this.Loaded + = new RoutedEventHandler (UserControl_Loaded)
Peter Walke

Un autre problème est dans le débogueur. VS exécutera le code de l'événement Load, mais il ne trouvera pas le parent Window.
bohdan_trotsenko

1
Si vous envisagez d'implémenter votre propre méthode, vous devez utiliser une combinaison de VisualTreeHelper et LogicalTreeHelper. En effet, certains contrôles non-fenêtre (comme Popup) n'ont pas de parents visuels et il semble que les contrôles générés à partir d'un modèle de données n'ont pas de parents logiques.
Brian Reichle

14

J'avais besoin d'utiliser la méthode Window.GetWindow (this) dans le gestionnaire d'événements Loaded. En d'autres termes, j'ai utilisé la réponse d'Ian Oakes en combinaison avec la réponse d'Alex pour obtenir le parent d'un contrôle utilisateur.

public MainView()
{
    InitializeComponent();

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

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

    ...
}

7

Cette approche a fonctionné pour moi mais elle n'est pas aussi précise que votre question:

App.Current.MainWindow

7

Si vous trouvez cette question et que le VisualTreeHelper ne fonctionne pas pour vous ou ne fonctionne pas de manière sporadique, vous devrez peut-être inclure LogicalTreeHelper dans votre algorithme.

Voici ce que j'utilise:

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

Vous manquez un nom de méthode LogicalTreeHelper.GetParentdans le code.
xmedeko

C'était la meilleure solution pour moi.
Jack B Nimble

6

Que dis-tu de ça:

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

J'ai trouvé que le parent d'un UserControl est toujours nul dans le constructeur, mais dans tous les gestionnaires d'événements, le parent est défini correctement. Je suppose que cela doit avoir quelque chose à voir avec la façon dont l'arbre de contrôle est chargé. Donc, pour contourner cela, vous pouvez simplement obtenir le parent dans l'événement de contrôle Loaded.

Pour un exemple, consultez cette question DataContext de WPF User Control est Null


1
Vous devez en quelque sorte attendre qu'il soit d'abord dans "l'arbre". Assez désagréable parfois.
user7116

3

Autrement:

var main = App.Current.MainWindow as MainWindow;

Cela a fonctionné pour moi, je dois le mettre dans l'événement "Loaded" plutôt que dans le constructeur (ouvrez la fenêtre des propriétés, double-cliquez et il ajoutera le gestionnaire pour vous).
Contango

(Mon vote est pour la réponse acceptée par Ian, ceci est juste pour l'enregistrement) Cela ne fonctionnait pas lorsque le contrôle utilisateur est dans une autre fenêtre avec ShowDialog, définissant le contenu sur le contrôle utilisateur. Une approche similaire consiste à parcourir App.Current.Windows et à utiliser la fenêtre où la condition suivante, pour idx de (Current.Windows.Count - 1) à 0 (App.Current.Windows [idx] == userControlRef) est vraie . Si nous faisons cela dans l'ordre inverse, ce sera probablement la dernière fenêtre et nous obtenons la fenêtre correcte avec une seule itération. userControlRef est généralement ce dans la classe UserControl.
msanjay

3

Cela fonctionne pour moi:

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

2

Cela n'a pas fonctionné pour moi, car il est allé trop loin dans l'arborescence et a obtenu la fenêtre racine absolue pour l'ensemble de l'application:

Window parentWindow = Window.GetWindow(userControlReference);

Cependant, cela a fonctionné pour obtenir la fenêtre immédiate:

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

Vous devez utiliser une vérification nulle au lieu d'une variable arbitraire «avoidInfiniteLoop». Modifiez votre «while» pour vérifier d'abord null, et s'il n'est pas nul, vérifiez s'il ne s'agit pas d'une fenêtre. Sinon, faites une pause / sortie.
Mark A. Donohoe

@MarquelV je vous entends. En général, j'ajoute une vérification "avoidInfiniteLoop" à chaque boucle qui pourrait en théorie se bloquer en cas de problème. Cela fait partie de la programmation défensive. De temps en temps, cela rapporte de bons dividendes car le programme évite un blocage. Très utile lors du débogage, et très utile en production si le dépassement est enregistré. J'utilise cette technique (parmi beaucoup d'autres) pour permettre l'écriture de code robuste qui fonctionne juste.
Contango

Je reçois une programmation défensive, et je suis d'accord en principe à ce sujet, mais en tant que réviseur de code, je pense que cela serait signalé pour l'introduction de données arbitraires qui ne font pas partie du flux logique réel. Vous avez déjà toutes les informations nécessaires pour arrêter la récursivité infinie en vérifiant la valeur null car il est impossible de récurer un arbre à l'infini. Bien sûr, vous pouvez oublier de mettre à jour le parent et avoir une boucle infinie, mais vous pouvez tout aussi facilement oublier de mettre à jour cette variable arbitraire. En d'autres termes, il est déjà de la programmation défensive de vérifier la valeur nulle sans l'introduction de nouvelles données non liées.
Mark A. Donohoe

1
@MarquelIV Je dois être d'accord. L'ajout d'un contrôle nul supplémentaire est une meilleure programmation défensive.
Contango

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

Veuillez supprimer ceci et intégrer tout point que cela fait qui n'est pas déjà couvert dans votre autre réponse (que j'ai voté comme une bonne réponse)
Ruben Bartelink

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

DependencyObject parent = GetTopParent(thisUserControl);

0

Edition plaquée or de ce qui précède (j'ai besoin d'une fonction générique qui peut en déduire un Windowdans le contexte d'un 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() inférera correctement une fenêtre sur la base suivante:

  • la racine Windowen parcourant l'arborescence visuelle (si utilisé dans le contexte de a UserControl)
  • la fenêtre dans laquelle il est utilisé (s'il est utilisé dans le contexte du Windowbalisage d'un)

0

Différentes approches et différentes stratégies. Dans mon cas, je n'ai pas pu trouver la fenêtre de ma boîte de dialogue en utilisant VisualTreeHelper ou des méthodes d'extension de Telerik pour trouver le parent d'un type donné. Au lieu de cela, j'ai trouvé ma vue de dialogue qui accepte l'injection personnalisée de contenu à l'aide de Application.Current.Windows.

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

0

Le Window.GetWindow(userControl)renverra la fenêtre réelle uniquement après l'initialisation de la fenêtre ( InitializeComponent()méthode terminée).

Cela signifie que si votre contrôle utilisateur est initialisé avec sa fenêtre (par exemple, vous placez votre contrôle utilisateur dans le fichier xaml de la fenêtre), alors sur l' OnInitializedévénement du contrôle utilisateur, vous n'obtiendrez pas la fenêtre (elle sera nulle), car dans ce cas, le contrôle de l'utilisateurOnInitialized événement déclenche avant l'initialisation de la fenêtre.

Cela signifie également que si votre contrôle utilisateur est initialisé après sa fenêtre, vous pouvez obtenir la fenêtre déjà dans le constructeur du contrôle utilisateur.


0

Si vous souhaitez simplement obtenir un parent spécifique, non seulement la fenêtre, un parent spécifique dans la structure arborescente, et également ne pas utiliser de récursivité ou de compteurs de boucle d'arrêt dur, vous pouvez utiliser ce qui suit:

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

Ne placez pas cet appel dans un constructeur (car la Parentpropriété n'est pas encore initialisée). Ajoutez-le dans le gestionnaire d'événements de chargement ou dans d'autres parties de votre application.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.