Pai de controle de usuário WPF

183

Eu tenho um controle de usuário que carrego em um MainWindowtempo de execução. Não consigo identificar a janela que contém o arquivo UserControl.

Eu tentei this.Parent, mas é sempre nulo. Alguém sabe como obter um identificador para a janela que contém de um controle de usuário no WPF?

Aqui está como o controle é carregado:

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
fonte

Respostas:

346

Tente usar o seguinte:

Window parentWindow = Window.GetWindow(userControlReference);

o GetWindow método guiará o VisualTree para você e localizará a janela que hospeda seu controle.

Você deve executar esse código após o carregamento do controle (e não no construtor Window) para impedir que o GetWindowmétodo retorne null. Por exemplo, conecte um evento:

this.Loaded += new RoutedEventHandler(UserControl_Loaded); 
Ian Oakes
fonte
6
Ainda retorna nulo. É como se o controle simplesmente não tivesse pai.
donniefitz2
2
Usei o código acima e o parentWindow também retorna nulo para mim.
23438 Peter Walke
106
Eu descobri o motivo pelo qual ele está retornando nulo. Eu estava colocando esse código no construtor do meu controle de usuário. Você deve executar esse código após o carregamento do controle. O EG conecta um evento: this.Loaded + = new RoutedEventHandler (UserControl_Loaded);
22826 Peter Walke
2
Depois de revisar a resposta de Paul, pode fazer sentido usar o método OnInitialized em vez de Loaded.
quer
@PeterWalke você resolve o meu problema de tempo muito longo ... Graças
Waqas Shabbir
34

Vou adicionar minha experiência. Embora o uso do evento Loaded possa fazer o trabalho, acho que pode ser mais adequado substituir o método OnInitialized. Carregado ocorre depois que a janela é exibida pela primeira vez. OnInitialized oferece a chance de fazer alterações, por exemplo, adicionar controles à janela antes que ela seja renderizada.

Paulo
fonte
8
+1 para corrigir. A compreensão de qual técnica usar pode ser sutil às vezes, especialmente quando você tem eventos e substituições lançados no mix (evento carregado, substituição OnLoaded, evento inicializado, evento OnInitialized, etcetcetc). Nesse caso, OnInitialized faz sentido porque você deseja encontrar o pai, e o controle deve ser inicializado para que o pai "exista". Carregado significa algo diferente.
Greg D
3
Window.GetWindowainda retorna nullem OnInitialized. Parece funcionar apenas no Loadedevento.
Physikbuddha 27/05
O evento inicializado deve ser definido antes de InitializeComponent (); De qualquer forma, meus Elementos Vinculados (XAML) não conseguiram resolver a fonte (Janela). Então, acabei usando o evento carregado.
Lenor
15

Tente usar VisualTreeHelper.GetParent ou use a função recursiva abaixo para encontrar a janela pai.

 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
fonte
Eu tentei passar usando esse código de dentro do meu controle de usuário. Eu passei isso para esse método, mas ele retornou nulo, indicando que é o fim da árvore (de acordo com o seu comentário). Você sabe por que isso é? O controle de usuário tem um pai que é o formulário que contém. Como obtenho um identificador para este formulário?
22826 Peter Walke
2
Eu descobri o motivo pelo qual ele está retornando nulo. Eu estava colocando esse código no construtor do meu controle de usuário. Você deve executar esse código após o carregamento do controle. Fio EG-se um evento: this.Loaded + = new RoutedEventHandler (UserControl_Loaded)
Peter Walke
Outro problema está no depurador. O VS executará o código do evento Load, mas não encontrará o pai da janela.
bohdan_trotsenko
1
Se você pretende implementar seu próprio método, use uma combinação de VisualTreeHelper e LogicalTreeHelper. Isso ocorre porque alguns controles que não são de janela (como Pop-up) não têm pais visuais e parece que os controles gerados a partir de um modelo de dados não têm pais lógicos.
22812 Brian Reichle
14

Eu precisava usar o método Window.GetWindow (this) no manipulador de eventos Loaded. Em outras palavras, usei a resposta de Ian Oakes em combinação com a resposta de Alex para obter o pai de um controle de usuário.

public MainView()
{
    InitializeComponent();

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

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

    ...
}
Alan Le
fonte
7

Essa abordagem funcionou para mim, mas não é tão específica quanto sua pergunta:

App.Current.MainWindow
Anthony Main
fonte
7

Se você está encontrando essa pergunta e o VisualTreeHelper não está funcionando para você ou esporadicamente, pode ser necessário incluir o LogicalTreeHelper no seu algoritmo.

Aqui está o que eu estou 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);
}
GordoFabulous
fonte
Você perde um nome de método LogicalTreeHelper.GetParentno código.
Xmedeko 29/05
Esta foi a melhor solução para mim.
Jack B Nimble
6

Que tal agora:

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

Descobri que o pai de um UserControl é sempre nulo no construtor, mas em qualquer manipulador de eventos o pai está definido corretamente. Eu acho que deve ter algo a ver com a maneira como a árvore de controle é carregada. Portanto, para contornar isso, basta obter o pai no evento Controles Carregados.

Para um exemplo de checkout, esta pergunta DataContext do WPF User Control é Nulo

Alex
fonte
1
Você meio que tem que esperar que ele esteja na "árvore" primeiro. Muito desagradável às vezes.
user7116
3

Outra maneira:

var main = App.Current.MainWindow as MainWindow;
Pnct
fonte
Trabalhou para mim, tenho que colocá-lo no evento "Carregado" em vez de no construtor (abra a janela de propriedades, clique duas vezes e ele adicionará o manipulador para você).
Contango
(Meu voto é para a resposta aceita por Ian, isso é apenas para registro) Isso não funcionou quando o controle do usuário está em outra janela com ShowDialog, configurando o conteúdo para o controle do usuário. Uma abordagem semelhante é percorrer o App.Current.Windows e usar a janela onde a seguinte condição, para o idx de (Current.Windows.Count - 1) a 0 (App.Current.Windows [idx] == userControlRef) é verdadeira . Se fizermos isso na ordem inversa, é provável que seja a última janela e obteremos a janela correta com apenas uma iteração. userControlRef normalmente é esse dentro da classe UserControl.
msanjay
3

Está funcionando para mim:

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

Isso não funcionou para mim, pois subiu demais na árvore e obteve a janela raiz absoluta para todo o aplicativo:

Window parentWindow = Window.GetWindow(userControlReference);

No entanto, isso funcionou para obter a janela imediata:

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();
Contango
fonte
Você deve usar uma verificação nula em vez de uma variável arbitrária 'AvoidInfiniteLoop'. Altere seu 'while' para verificar se há nulo primeiro e, se não for nulo, verifique se não é uma janela. Caso contrário, apenas quebre / saia.
Mark A. Donohoe
@MarquelV eu ouço você. Geralmente, adiciono uma verificação "AvoidInfiniteLoop" a cada loop que, em teoria, poderia ficar preso se algo der errado. É parte da programação defensiva. De vez em quando, ele paga bons dividendos, pois o programa evita um travamento. Muito útil durante a depuração e muito útil na produção se a saturação estiver registrada. Eu uso essa técnica (entre muitas outras) para permitir a criação de código robusto que simplesmente funcione.
Contango
Recebo programação defensiva e concordo em princípio sobre isso, mas como revisor de código, acho que isso seria sinalizado pela introdução de dados arbitrários que não fazem parte do fluxo lógico real. Você já possui todas as informações necessárias para interromper a recursão infinita, verificando se é nulo, pois é impossível recuperar uma árvore infinitamente. Claro que você pode esquecer de atualizar o pai e ter um loop infinito, mas também pode esquecer de atualizar essa variável arbitrária. Em outras palavras, é uma programação defensiva procurar nulos sem a introdução de dados novos e não relacionados.
Mark-Donohoe
1
@MarquelIV Eu tenho que concordar. Adicionar uma verificação nula adicional é uma melhor programação defensiva.
Contango
1
DependencyObject parent = ExVisualTreeHelper.FindVisualParent<UserControl>(this);
Eric Coulson
fonte
Por favor, apague este e integrar qualquer ponto Isto faz que não está já coberto em sua outra resposta (que eu upvoted como resposta uma boa)
Ruben Bartelink
1
DependencyObject GetTopParent(DependencyObject current)
{
    while (VisualTreeHelper.GetParent(current) != null)
    {
        current = VisualTreeHelper.GetParent(current);
    }
    return current;
}

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

Edição banhada a ouro das opções acima (eu preciso de uma função genérica que possa inferir a Windowdentro do contexto de uma 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á corretamente uma janela na seguinte base:

  • a raiz Windowpercorrendo a árvore visual (se usada no contexto de umUserControl )
  • a janela na qual é usada (se for usada no contexto da Windowmarcação de uma)
Ruben Bartelink
fonte
0

Diferentes abordagens e estratégias diferentes. No meu caso, não consegui encontrar a janela da minha caixa de diálogo usando o VisualTreeHelper ou os métodos de extensão da Telerik para encontrar o pai de um determinado tipo. Em vez disso, encontrei minha exibição na minha caixa de diálogo, que aceita injeção personalizada de conteúdo usando Application.Current.Windows.

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

O Window.GetWindow(userControl)retornará a janela real somente após a janela foi inicializado (InitializeComponent() método acabado).

Isso significa que, se o controle do usuário for inicializado junto com a janela (por exemplo, você coloca o controle do usuário no arquivo xaml da janela), no OnInitializedevento do controle do usuário você não receberá a janela (será nula), pois causa Nesse caso, o controle do usuárioOnInitialized evento acionado antes da janela ser inicializada.

Isso também significa que, se o controle do usuário for inicializado após a janela, você poderá obter a janela já no construtor do controle do usuário.

ChocapicSz
fonte
0

Se você deseja apenas obter um pai específico, não apenas a janela, um pai específico na estrutura da árvore e também não usar contadores de recursão ou loop de interrupção, você pode usar o seguinte:

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

Apenas não faça essa chamada em um construtor (já que a Parentpropriedade ainda não foi inicializada). Adicione-o no manipulador de eventos de carregamento ou em outras partes do seu aplicativo.

Lucaci Andrei
fonte