Genitore controllo utente WPF


183

Ho un controllo utente che carico in un MainWindowruntime. Non riesco a ottenere un handle sulla finestra contenente dal UserControl.

Ho provato this.Parent, ma è sempre nullo. Qualcuno sa come ottenere un handle per la finestra contenente da un controllo utente in WPF?

Ecco come viene caricato il controllo:

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

Risposte:


346

Prova a utilizzare quanto segue:

Window parentWindow = Window.GetWindow(userControlReference);

Il GetWindow metodo eseguirà VisualTree per te e individuerà la finestra che ospita il tuo controllo.

È necessario eseguire questo codice dopo che il controllo è stato caricato (e non nel costruttore Window) per impedire il GetWindowritorno del metodo null. Ad esempio, collegare un evento:

this.Loaded += new RoutedEventHandler(UserControl_Loaded); 

6
Restituisce comunque null. È come se il controllo non avesse un genitore.
donniefitz2,

2
Ho usato il codice sopra e ottenere parentWindow restituisce anche null per me.
Peter Walke,

106
Ho scoperto il motivo per cui sta tornando null. Stavo inserendo questo codice nel costruttore del mio controllo utente. È necessario eseguire questo codice dopo il caricamento del controllo. EG collega un evento: this.Loaded + = new RoutedEventHandler (UserControl_Loaded);
Peter Walke,

2
Dopo aver esaminato la risposta di Paul, potrebbe avere senso utilizzare il metodo OnInitialized anziché Loaded.
Peter Walke,

@PeterWalke hai risolto il mio problema molto lungo ... Grazie
Waqas Shabbir,

34

Aggiungerò la mia esperienza. Sebbene l'utilizzo dell'evento Loaded possa fare il lavoro, penso che potrebbe essere più adatto a sovrascrivere il metodo OnInitialized. Caricato si verifica dopo la prima visualizzazione della finestra. OnInitialized ti dà la possibilità di apportare modifiche, ad esempio, aggiungere controlli alla finestra prima che venga renderizzata.


8
+1 per corretto. Comprendere quale tecnica usare può essere sottile a volte, specialmente quando hai eventi e sostituzioni lanciati nel mix (evento Loaded, overLoaded override, Initialized event, OnInitialized override, etcetcetc). In questo caso, OnInitialized ha senso perché si desidera trovare il genitore e il controllo deve essere inizializzato affinché il genitore "esista". Caricato significa qualcosa di diverso.
Greg D

3
Window.GetWindowritorna ancora nulldentro OnInitialized. Sembra funzionare Loadedsolo nell'evento.
Physikbuddha,

L'evento inizializzato deve essere definito prima di InitializeComponent (); Ad ogni modo, i miei elementi Binded (XAML) non sono stati in grado di risolvere l'origine (Window). Quindi ho finito per usare l'evento Loaded.
Lenor,

15

Prova a utilizzare VisualTreeHelper.GetParent o usa la seguente funzione ricorsiva per trovare la finestra principale.

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

Ho provato a passare usando questo codice dal mio controllo utente. Ho passato questo in questo metodo ma ha restituito null, indicando che è la fine dell'albero (secondo il tuo commento). Sai perché questo è? Il controllo utente ha un genitore che è il modulo contenente. Come faccio a gestire questo modulo?
Peter Walke,

2
Ho scoperto il motivo per cui sta tornando null. Stavo inserendo questo codice nel costruttore del mio controllo utente. È necessario eseguire questo codice dopo il caricamento del controllo. EG collega un evento: this.Loaded + = new RoutedEventHandler (UserControl_Loaded)
Peter Walke,

Un altro problema è nel debugger. VS eseguirà il codice dell'evento Load, ma non troverà il genitore Window.
bohdan_trotsenko,

1
Se hai intenzione di implementare il tuo metodo, dovresti usare una combinazione di VisualTreeHelper e LogicalTreeHelper. Questo perché alcuni controlli non-finestra (come Popup) non hanno genitori visivi e sembra che i controlli generati da un modello di dati non abbiano genitori logici.
Brian Reichle,

14

Avevo bisogno di usare il metodo Window.GetWindow (questo) nel gestore di eventi Loaded. In altre parole, ho usato la risposta di Ian Oakes in combinazione con la risposta di Alex per ottenere il genitore di un controllo utente.

public MainView()
{
    InitializeComponent();

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

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

    ...
}

7

Questo approccio ha funzionato per me ma non è specifico come la tua domanda:

App.Current.MainWindow

7

Se trovi questa domanda e VisualTreeHelper non funziona per te o non funziona sporadicamente, potresti dover includere LogicalTreeHelper nel tuo algoritmo.

Ecco cosa sto usando:

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

Ti manca un nome di metodo LogicalTreeHelper.GetParentnel codice.
xmedeko,

Questa è stata la soluzione migliore per me.
Jack B Nimble,

6

Cosa ne pensi di questo:

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

Ho scoperto che il genitore di un UserControl è sempre nullo nel costruttore, ma in ogni caso i gestori il genitore è impostato correttamente. Immagino che debba avere qualcosa a che fare con il modo in cui l'albero di controllo viene caricato. Quindi, per ovviare a questo, puoi semplicemente ottenere il genitore nell'evento Loaded controlli.

Per un esempio di checkout questa domanda DataContext di WPF User Control è Null


1
Devi prima aspettare che si trovi prima nell'albero. Abbastanza odioso a volte.
user7116,

3

Un altro modo:

var main = App.Current.MainWindow as MainWindow;

Ha funzionato per me, devo inserirlo nell'evento "Loaded" anziché nel costruttore (apri la finestra delle proprietà, fai doppio clic e aggiungerà il gestore per te).
Contango,

(Il mio voto è per la risposta accettata da Ian, questo è solo per la cronaca) Questo non ha funzionato quando il controllo utente è in un'altra finestra con ShowDialog, impostando il contenuto sul controllo utente. Un approccio simile è quello di attraversare App.Current.Windows e utilizzare la finestra in cui la seguente condizione, per idx da (Current.Windows.Count - 1) a 0 (App.Current.Windows [idx] == userControlRef) è vera . Se lo facciamo in ordine inverso, è probabile che sia l'ultima finestra e otteniamo la finestra corretta con una sola iterazione. userControlRef è in genere questo all'interno della classe UserControl.
msanjay,

3

Funziona per me:

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

2

Questo non ha funzionato per me, poiché è andato troppo in alto nell'albero e ha ottenuto la finestra principale assoluta per l'intera applicazione:

Window parentWindow = Window.GetWindow(userControlReference);

Tuttavia, questo ha funzionato per ottenere la finestra immediata:

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

Dovresti usare un controllo null invece di una variabile arbitraria 'avoidInfiniteLoop'. Cambia 'while' per verificare prima la presenza di null e, se non è null, controlla se non è una finestra. Altrimenti, basta interrompere / uscire.
Mark A. Donohoe,

@MarquelV Ti sento. In genere, aggiungo un controllo "avoidInfiniteLoop" a ogni ciclo che potrebbe teoricamente rimanere bloccato se qualcosa va storto. Fa parte della programmazione difensiva. Ogni tanto paga buoni dividendi poiché il programma evita il blocco. Molto utile durante il debug e molto utile in produzione se viene registrato il sovraccarico. Uso questa tecnica (tra le altre cose) per consentire la scrittura di un codice robusto che funzioni.
Contango

Ricevo una programmazione difensiva, e in linea di principio sono d'accordo, ma come revisore del codice, penso che questo sarebbe contrassegnato per l'introduzione di dati arbitrari che non fanno parte del flusso logico effettivo. Hai già tutte le informazioni necessarie per fermare la ricorsione infinita controllando null poiché è impossibile ricorrere all'infinito su un albero. Sicuramente potresti dimenticare di aggiornare il genitore e avere un ciclo infinito, ma potresti anche dimenticare di aggiornare quella variabile arbitraria. In altre parole, è già una programmazione difensiva verificare la presenza di null senza l'introduzione di dati nuovi e non correlati.
Mark A. Donohoe,

1
@MarquelIV Devo essere d'accordo. L'aggiunta di un controllo null aggiuntivo è una migliore programmazione difensiva.
Contango

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

Si prega di eliminare questo e integrare qualsiasi punto che questo non sia già coperto nella tua altra risposta (che ho valutato come una buona risposta)
Ruben Bartelink

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

DependencyObject parent = GetTopParent(thisUserControl);

0

Edizione placcata in oro di quanto sopra (ho bisogno di una funzione generica che possa inferire a Window nel contesto di 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() inferirà correttamente una finestra sulla base seguente:

  • la radice Windowcamminando sull'albero visivo (se usato nel contesto di aUserControl )
  • la finestra all'interno della quale viene utilizzato (se utilizzato nel contesto del Windowmarkup di un)

0

Approcci diversi e strategie diverse. Nel mio caso non sono riuscito a trovare la finestra della mia finestra di dialogo utilizzando VisualTreeHelper o i metodi di estensione di Telerik per trovare il genitore di un determinato tipo. Invece, ho trovato la mia visualizzazione della mia finestra di dialogo che accetta l'iniezione personalizzata dei contenuti utilizzando Application.Current.Windows.

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

0

Il Window.GetWindow(userControl)tornerà alla finestra effettiva solo dopo che la finestra è stata inizializzata (InitializeComponent() metodo finito).

Ciò significa che se il controllo utente viene inizializzato insieme alla relativa finestra (ad esempio, si inserisce il controllo utente nel file xaml della finestra), quindi OnInitializedsull'evento del controllo utente non verrà visualizzata la finestra (sarà nulla), perché quel caso è il controllo dell'utenteOnInitialized evento generato prima che la finestra venga inizializzata.

Ciò significa anche che se il controllo utente viene inizializzato dopo la sua finestra, è possibile ottenere la finestra già nel costruttore del controllo utente.


0

Se vuoi solo ottenere un genitore specifico, non solo la finestra, un genitore specifico nella struttura ad albero e anche non utilizzare contatori di ricorsione o loop di interruzione, puoi utilizzare quanto segue:

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

Basta non inserire questa chiamata in un costruttore (poiché la Parentproprietà non è ancora inizializzata). Aggiungilo nel gestore dell'evento di caricamento o in altre parti dell'applicazione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.