WPF User Control Parent


183

Mam kontrolę użytkownika, którą ładuję do MainWindowśrodowiska wykonawczego. Nie mogę uzyskać uchwytu na okno zawierające z UserControl.

Próbowałem this.Parent, ale zawsze jest nieważne. Czy ktoś wie, jak uzyskać uchwyt do zawierającego okno z kontrolki użytkownika w WPF?

Oto jak ładuje się kontrolkę:

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

Odpowiedzi:


346

Spróbuj użyć następujących opcji:

Window parentWindow = Window.GetWindow(userControlReference);

GetWindowMetoda będzie chodzić VisualTree dla Ciebie i zlokalizuj okno, które jest gospodarzem swoją kontrolę.

Należy uruchomić ten kod po załadowaniu formantu (a nie w konstruktorze Windows), aby zapobiec GetWindowzwróceniu metody null. Np. Zorganizuj zdarzenie:

this.Loaded += new RoutedEventHandler(UserControl_Loaded); 

6
Nadal zwraca wartość null. To tak, jakby kontrola po prostu nie miała rodzica.
donniefitz2,

2
Użyłem powyższego kodu i get dla parentWindow również zwraca dla mnie wartość null.
Peter Walke

106
Dowiedziałem się, dlaczego wartość ta jest zerowa. Wstawiałem ten kod do konstruktora mojej kontroli użytkownika. Powinieneś uruchomić ten kod po załadowaniu formantu. EG podłącza zdarzenie: this.Loaded + = new RoutedEventHandler (UserControl_Loaded);
Peter Walke

2
Po przejrzeniu odpowiedzi od Paula, sensowne może być użycie metody OnInitialized zamiast Loaded.
Peter Walke,

@PeterWalke rozwiązujesz mój bardzo długi problem ... Dzięki
Waqas Shabbir

34

Dodam moje doświadczenie. Chociaż użycie zdarzenia Loaded może wykonać zadanie, myślę, że bardziej odpowiednie może być zastąpienie metody OnInitialized. Załadowane następuje po pierwszym wyświetleniu okna. OnInitialized daje szansę na dokonanie jakichkolwiek zmian, na przykład dodanie kontrolek do okna przed jego renderowaniem.


8
+1 za poprawne. Zrozumienie, która technika może być czasami subtelna, szczególnie gdy masz zdarzenia i przesłonięcia wrzucone do miksu (wczytane zdarzenie, zastąpienie OnLoaded, zdarzenie inicjowane, zastąpienie OnInitialized itp.) W takim przypadku OnInitialized ma sens, ponieważ chcesz znaleźć element nadrzędny, a formant musi zostać zainicjowany, aby element nadrzędny „istniał”. Załadowane oznacza coś innego.
Greg D

3
Window.GetWindownadal zwraca nullsię OnInitialized. Wydaje się, że działa tylko w tym Loadedwydarzeniu.
Physikbuddha

Zainicjowane zdarzenie musi zostać zdefiniowane przed InitializeComponent (); W każdym razie moje powiązane elementy (XAML) nie mogły rozwiązać źródła (Window). Więc skończyłem używać Loaded Event.
Lenor,

15

Spróbuj użyć VisualTreeHelper.GetParent lub użyj poniższej funkcji rekurencyjnej, aby znaleźć okno nadrzędne.

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

Próbowałem przekazać ten kod z poziomu mojej kontroli użytkownika. Przekazałem to do tej metody, ale zwróciła wartość null, wskazując, że jest to koniec drzewa (zgodnie z twoim komentarzem). Czy wiesz dlaczego tak jest? Kontrola użytkownika ma element nadrzędny, który jest formą zawierającą. Jak uzyskać uchwyt do tego formularza?
Peter Walke

2
Dowiedziałem się, dlaczego wartość ta jest zerowa. Wstawiałem ten kod do konstruktora mojej kontroli użytkownika. Powinieneś uruchomić ten kod po załadowaniu formantu. EG załączy zdarzenie: this.Loaded + = new RoutedEventHandler (UserControl_Loaded)
Peter Walke

Kolejna kwestia dotyczy debuggera. VS wykona kod zdarzenia Load, ale nie znajdzie nadrzędnego okna.
bohdan_trotsenko

1
Jeśli zamierzasz wdrożyć własną metodę, powinieneś użyć kombinacji VisualTreeHelper i LogicalTreeHelper. Wynika to z faktu, że niektóre kontrolki inne niż okna (takie jak Popup) nie mają wizualnych elementów nadrzędnych i wydaje się, że formanty wygenerowane z szablonu danych nie mają elementów nadrzędnych logicznych.
Brian Reichle,

14

Musiałem użyć metody Window.GetWindow (this) w module Loaded event handler. Innymi słowy, użyłem zarówno odpowiedzi Iana Oakesa w połączeniu z odpowiedzią Alexa, aby uzyskać rodzica kontroli użytkownika.

public MainView()
{
    InitializeComponent();

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

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

    ...
}

7

To podejście działało dla mnie, ale nie jest tak szczegółowe, jak twoje pytanie:

App.Current.MainWindow

7

Jeśli znajdziesz to pytanie, a VisualTreeHelper nie działa dla ciebie lub działa sporadycznie, może być konieczne włączenie LogicalTreeHelper do algorytmu.

Oto czego używam:

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

Brakuje nazwy metody LogicalTreeHelper.GetParentw kodzie.
xmedeko

To było dla mnie najlepsze rozwiązanie.
Jack B Nimble

6

Co powiesz na to:

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

Przekonałem się, że element nadrzędny UserControl jest zawsze pusty w konstruktorze, ale we wszystkich modułach obsługi zdarzeń rodzic jest ustawiony poprawnie. Myślę, że to musi mieć coś wspólnego ze sposobem ładowania drzewa kontrolnego. Aby obejść ten problem, po prostu umieść element nadrzędny w zdarzeniu formantu Loaded.

Na przykład pobranie tego pytania DataContext WPF User Control ma wartość Null


1
Musisz trochę poczekać, aż pojawi się w „drzewie”. Czasami dość nieznośny.
user7116

3

Inny sposób:

var main = App.Current.MainWindow as MainWindow;

Pracowałem dla mnie, musiałem umieścić go w zdarzeniu „Załadowano”, a nie konstruktora (przywołaj okno właściwości, kliknij dwukrotnie, a to doda program obsługi dla Ciebie).
Contango,

(Mój głos jest na zaakceptowaną odpowiedź Iana, to tylko na zapis). To nie zadziałało, gdy kontrolka użytkownika jest w innym oknie z ShowDialog, ustawiając zawartość na kontrolkę użytkownika. Podobne podejście przejść przez App.Current.Windows i użyć okna, gdy następujący warunek dla idx Current.Windows.Count z (1) - 0 (App.Current.Windows [idx] == userControlRef) jest prawdziwy . Jeśli zrobimy to w odwrotnej kolejności, prawdopodobnie będzie to ostatnie okno i otrzymamy prawidłowe okno z tylko jedną iteracją. userControlRef jest zwykle to w klasie UserControl.
msanjay

3

Działa dla mnie:

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

2

Nie działało to dla mnie, ponieważ poszło za daleko w górę drzewa i uzyskało absolutne okno główne dla całej aplikacji:

Window parentWindow = Window.GetWindow(userControlReference);

Jednak działało to, aby uzyskać bezpośrednie okno:

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

Powinieneś użyć kontroli zerowej zamiast arbitralnej zmiennej „removeInfiniteLoop”. Zmień swój „while”, aby najpierw sprawdzić, czy ma wartość null, a jeśli nie, to sprawdź, czy nie jest to okno. W przeciwnym razie wystarczy przerwać / wyjść.
Mark A. Donohoe,

@MarquelV Słyszę cię. Zasadniczo do każdej pętli, która teoretycznie może utknąć, jeśli coś pójdzie nie tak, dodam sprawdzanie „unikaj nieskończoności” . Jest to część programowania obronnego. Co jakiś czas przynosi dobre dywidendy, ponieważ program unika zawieszenia się. Bardzo przydatne podczas debugowania i bardzo przydatne w produkcji, jeśli rejestrowane jest przekroczenie. Używam tej techniki (między innymi), aby umożliwić pisanie solidnego kodu, który po prostu działa.
Contango,

Dostaję programowanie defensywne i co do zasady się z tym zgadzam, ale jako recenzent kodu, myślę, że zostanie to oznaczone jako wprowadzenie dowolnych danych, które nie są częścią rzeczywistego przepływu logiki. Masz już wszystkie informacje potrzebne do zatrzymania nieskończonej rekurencji poprzez sprawdzenie, czy nie ma wartości zerowej, ponieważ nie można w nieskończoność powracać na drzewo. Jasne, że możesz zapomnieć o aktualizacji elementu nadrzędnego i mieć nieskończoną pętlę, ale równie łatwo możesz zapomnieć o aktualizacji tej dowolnej zmiennej. Innymi słowy, programowanie defensywne polega już na sprawdzaniu wartości zerowej bez wprowadzania nowych, niepowiązanych danych.
Mark A. Donohoe,

1
@MarquelIV Muszę się zgodzić. Dodanie dodatkowej kontroli zerowej jest lepszym programowaniem defensywnym.
Contango,


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

DependencyObject parent = GetTopParent(thisUserControl);

0

Pozłacane wydanie powyższego (potrzebuję ogólnej funkcji, która może wywnioskować Windoww kontekście 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() poprawnie wnioskuje o oknie na podstawie:

  • root Windowprzez chodzenie po drzewie wizualnym (jeśli jest używany w kontekście a UserControl)
  • okno, w którym jest używane (jeśli jest używane w kontekście Windowznaczników)

0

Różne podejścia i różne strategie. W moim przypadku nie mogłem znaleźć okna mojego okna dialogowego za pomocą VisualTreeHelper lub metod rozszerzenia Telerik w celu znalezienia rodzica danego typu. Zamiast tego znalazłem mój widok okna dialogowego, który akceptuje niestandardowe wstrzykiwanie zawartości za pomocą Application.Current.Windows.

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

0

Window.GetWindow(userControl)Powróci rzeczywistego okna tylko po zamknięciu okna został zainicjowany ( InitializeComponent()metoda zakończona).

Oznacza to, że jeśli twoja kontrola użytkownika zostanie zainicjowana razem z jej oknem (na przykład umieścisz kontrolę użytkownika w pliku xaml okna), to w OnInitializedprzypadku zdarzenia kontroli użytkownika nie otrzymasz okna (będzie puste), ponieważ w takim przypadku OnInitializedzdarzenie kontrolki użytkownika zostanie uruchomione przed zainicjowaniem okna.

Oznacza to również, że jeśli twoja kontrola użytkownika jest zainicjowana po jej oknie, możesz uzyskać okno już w konstruktorze kontroli użytkownika.


0

Jeśli chcesz uzyskać konkretnego rodzica, nie tylko okno, konkretnego rodzica w strukturze drzewa, a także nie korzystasz z liczników pętli rekurencji lub twardego przerwania, możesz użyć następujących opcji:

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

Po prostu nie umieszczaj tego wywołania w konstruktorze (ponieważ Parentwłaściwość nie została jeszcze zainicjowana). Dodaj go w module obsługi zdarzenia ładowania lub w innych częściach aplikacji.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.