Control de usuario de WPF principal

183

Tengo un control de usuario que cargo en MainWindowtiempo de ejecución. No puedo obtener un identificador de la ventana que contiene desde el UserControl.

Lo he intentado this.Parent, pero siempre es nulo. ¿Alguien sabe cómo obtener un identificador para la ventana que contiene desde un control de usuario en WPF?

Así es como se carga el control:

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
fuente

Respuestas:

346

Intenta usar lo siguiente:

Window parentWindow = Window.GetWindow(userControlReference);

El GetWindowmétodo recorrerá el VisualTree por usted y localizará la ventana que aloja su control.

Debe ejecutar este código después de que el control se haya cargado (y no en el constructor de Windows) para evitar que el GetWindowmétodo regrese null. Por ejemplo, cablear un evento:

this.Loaded += new RoutedEventHandler(UserControl_Loaded); 
Ian Oakes
fuente
66
Todavía devuelve nulo. Es como si el control simplemente no tiene padre.
donniefitz2
2
Usé el código anterior y obtengo parentWindow también devuelve nulo para mí.
Peter Walke
106
Descubrí la razón por la que está volviendo nulo. Estaba poniendo este código en el constructor de mi control de usuario. Debe ejecutar este código después de que se haya cargado el control. EG conecta un evento: this.Loaded + = new RoutedEventHandler (UserControl_Loaded);
Peter Walke
2
Después de revisar la respuesta de Paul, podría tener sentido usar el método OnInitialized en lugar de Loaded.
Peter Walke
@ PeterWalke resuelve mi problema de mucho tiempo ... Gracias
Waqas Shabbir
34

Agregaré mi experiencia. Aunque el uso del evento Loaded puede hacer el trabajo, creo que puede ser más adecuado anular el método OnInitialized. La carga se produce después de que la ventana se muestra por primera vez. OnInitialized le brinda la oportunidad de realizar cambios, por ejemplo, agregar controles a la ventana antes de que se procese.

Pablo
fuente
8
+1 para correcto. Comprender qué técnica usar puede ser sutil a veces, especialmente cuando tiene eventos y anulaciones en la mezcla (evento cargado, anulación OnLoaded, evento inicializado, anulación OnInitialized, etcetcetc). En este caso, OnInitialized tiene sentido porque desea encontrar el padre, y el control debe inicializarse para que el padre "exista". Cargado significa algo diferente.
Greg D
3
Window.GetWindowTodavía devuelve nullen OnInitialized. Parece funcionar solo en el Loadedevento.
Physikbuddha
El evento inicializado debe definirse antes de InitializeComponent (); De todos modos, mis Elementos Enlazados (XAML) no pudieron resolver la fuente (Ventana). Así que terminé de usar el evento cargado.
Lenor
15

Intente usar VisualTreeHelper.GetParent o use la siguiente función recursiva para encontrar la ventana principal.

 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
fuente
Intenté pasar usando este código desde mi control de usuario. Pasé esto a este método pero devolvió nulo, lo que indica que es el final del árbol (según su comentario). ¿Sabes por qué es esto? El control de usuario tiene un padre que es el formulario que lo contiene. ¿Cómo puedo manejar este formulario?
Peter Walke
2
Descubrí la razón por la que está volviendo nulo. Estaba poniendo este código en el constructor de mi control de usuario. Debe ejecutar este código después de que se haya cargado el control. EG conecta un evento: this.Loaded + = new RoutedEventHandler (UserControl_Loaded)
Peter Walke
Otro problema está en el depurador. VS ejecutará el código del evento Load, pero no encontrará la ventana principal.
bohdan_trotsenko
1
Si va a implementar su propio método, debe usar una combinación de VisualTreeHelper y LogicalTreeHelper. Esto se debe a que algunos controles que no son de ventana (como Popup) no tienen padres visuales y parece que los controles generados a partir de una plantilla de datos no tienen padres lógicos.
Brian Reichle
14

Necesitaba usar el método Window.GetWindow (this) dentro del controlador de eventos Loaded. En otras palabras, utilicé la respuesta de Ian Oakes en combinación con la respuesta de Alex para obtener un padre de control de usuario.

public MainView()
{
    InitializeComponent();

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

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

    ...
}
Alan Le
fuente
7

Este enfoque funcionó para mí, pero no es tan específico como su pregunta:

App.Current.MainWindow
Anthony Main
fuente
7

Si está encontrando esta pregunta y VisualTreeHelper no funciona para usted o no funciona esporádicamente, es posible que deba incluir LogicalTreeHelper en su algoritmo.

Esto es lo que estoy 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);
}
GordoFabuloso
fuente
Se pierde un nombre de método LogicalTreeHelper.GetParenten el código.
xmedeko
Esta fue la mejor solución para mí.
Jack B Nimble
6

Qué tal esto:

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
fuente
5

Descubrí que el padre de un UserControl siempre es nulo en el constructor, pero en cualquier caso los controladores están configurados correctamente. Supongo que debe tener algo que ver con la forma en que se carga el árbol de control. Entonces, para evitar esto, solo puede obtener el padre en el evento Cargado de controles.

Para ver un ejemplo, consulte esta pregunta el DataContext de WPF User Control es nulo

Alex
fuente
1
Tienes que esperar a que esté en el "árbol" primero. Bastante desagradable a veces.
user7116
3

De otra manera:

var main = App.Current.MainWindow as MainWindow;
Pnct
fuente
Funcionó para mí, tengo que ponerlo en el evento "Cargado" en lugar del constructor (abra la ventana de propiedades, haga doble clic y agregará el controlador por usted).
Contango
(Mi voto es por la respuesta aceptada por Ian, esto es solo para el registro) Esto no funcionó cuando el control de usuario está en otra ventana con ShowDialog, configurando el contenido para el control de usuario. Un enfoque similar es recorrer App.Current.Windows y usar la ventana donde la siguiente condición, para idx de (Current.Windows.Count - 1) a 0 (App.Current.Windows [idx] == userControlRef) es verdadera . Si hacemos esto en orden inverso, es probable que sea la última ventana y obtengamos la ventana correcta con solo una iteración. userControlRef es típicamente esto dentro de la clase UserControl.
msanjay
3

Me esta funcionando:

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

Esto no funcionó para mí, ya que subió demasiado el árbol y obtuvo la ventana raíz absoluta para toda la aplicación:

Window parentWindow = Window.GetWindow(userControlReference);

Sin embargo, esto funcionó para obtener la ventana inmediata:

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();
Aplazamiento de pago
fuente
Debería usar una comprobación nula en lugar de una variable arbitraria 'evitarInfiniteLoop'. Cambie su 'while' para verificar primero nulo, y si no es nulo, luego verifique si no es una ventana. De lo contrario, simplemente rompa / salga.
Mark A. Donohoe
@MarquelV Te escucho. En general, agrego una comprobación de "evitarInfiniteLoop" a cada bucle que, en teoría, podría atascarse si algo sale mal. Es parte de la programación defensiva. De vez en cuando, paga buenos dividendos ya que el programa evita un bloqueo. Muy útil durante la depuración, y muy útil en producción si se registra el desbordamiento. Utilizo esta técnica (entre muchas otras) para permitir escribir código robusto que simplemente funcione.
Contango
Recibo programación defensiva, y estoy de acuerdo en principio al respecto, pero como revisor de código, creo que esto se marcaría por introducir datos arbitrarios que no son parte del flujo lógico real. Ya tiene toda la información necesaria para detener la recursión infinita al verificar nula, ya que es imposible repetir un árbol infinitamente. Claro que podría olvidarse de actualizar el padre y tener un bucle infinito, pero podría olvidarse fácilmente de actualizar esa variable arbitraria. En otras palabras, es ya programación defensiva para comprobar nula y sin la introducción de nuevos, los datos no relacionado.
Mark A. Donohoe
1
@MarquelIV Tengo que estar de acuerdo. Agregar una verificación nula adicional es una mejor programación defensiva.
Contango
1
DependencyObject parent = ExVisualTreeHelper.FindVisualParent<UserControl>(this);
Eric Coulson
fuente
Borre esto e integre cualquier punto que haga que no esté cubierto en su otra respuesta (que he votado como una buena respuesta)
Ruben Bartelink
1
DependencyObject GetTopParent(DependencyObject current)
{
    while (VisualTreeHelper.GetParent(current) != null)
    {
        current = VisualTreeHelper.GetParent(current);
    }
    return current;
}

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

Edición chapada en oro de lo anterior (necesito una función genérica que pueda inferir a Windowen el contexto de 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á correctamente una ventana de la siguiente manera:

  • la raíz Windowcaminando por el árbol visual (si se usa en el contexto de unUserControl )
  • la ventana dentro de la cual se usa (si se usa en el contexto de un Windowmarcado)
Ruben Bartelink
fuente
0

Diferentes enfoques y diferentes estrategias. En mi caso, no pude encontrar la ventana de mi cuadro de diálogo mediante el uso de VisualTreeHelper o los métodos de extensión de Telerik para encontrar el padre del tipo dado. En cambio, encontré mi vista de diálogo que acepta la inyección personalizada de contenido usando Application.Current.Windows.

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

El Window.GetWindow(userControl)devolverá la ventana real solo después de que la ventana se haya inicializado ( InitializeComponent()método terminado).

Esto significa que si su control de usuario se inicializa junto con su ventana (por ejemplo, coloca su control de usuario en el archivo xaml de la ventana), entonces, en el OnInitializedevento del control de usuario , no obtendrá la ventana (será nula), porque en ese caso el control del usuarioOnInitialized evento dispara antes de que se inicialice la ventana.

Esto también significa que si su control de usuario se inicializa después de su ventana, puede obtener la ventana ya en el constructor del control de usuario.

ChocapicSz
fuente
0

Si solo desea obtener un elemento primario específico, no solo la ventana, un elemento primario específico en la estructura de árbol y también no utiliza contadores de recursión o bucle de ruptura dura, puede usar lo siguiente:

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

Simplemente no ponga esta llamada en un constructor (ya que la Parentpropiedad aún no se ha inicializado). Agréguelo en el controlador de eventos de carga o en otras partes de su aplicación.

Lucaci Andrei
fuente