Родительский элемент управления пользователя WPF

У меня есть пользовательский элемент управления, который я загружаю в MainWindow во время выполнения. Я не могу получить дескриптор содержащего окна от UserControl.

Я пробовал this.Parent, но он всегда нулевой. Кто-нибудь знает, как получить дескриптор содержащего окна из пользовательского элемента управления в WPF?

Вот как загружается элемент управления:

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);
}
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
186
0
163 709
17

Ответы 17

Используйте VisualTreeHelper.GetParent или рекурсивную функцию ниже, чтобы найти родительское окно.

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

Я попытался передать этот код из своего пользовательского элемента управления. Я передал это в этот метод, но он вернул значение null, указывая, что это конец дерева (согласно вашему комментарию). Вы знаете, почему это так? У пользовательского элемента управления есть родительский элемент, который представляет собой содержащую форму. Как мне получить доступ к этой форме?

Peter Walke 05.06.2009 19:44

Я выяснил причину, по которой он возвращает значение null. Я помещал этот код в конструктор своего пользовательского элемента управления. Вы должны запустить этот код после загрузки элемента управления. НАПРИМЕР. подключите событие: this.Loaded + = new RoutedEventHandler (UserControl_Loaded)

Peter Walke 05.06.2009 20:12

Другая проблема в отладчике. VS выполнит код события Load, но не найдет родителя Window.

bohdan_trotsenko 22.07.2009 12:30

Если вы собираетесь реализовать свой собственный метод, вам следует использовать комбинацию VisualTreeHelper и LogicalTreeHelper. Это связано с тем, что некоторые элементы управления, не являющиеся оконными (например, Popup), не имеют визуальных родителей, и кажется, что элементы управления, созданные из шаблона данных, не имеют логических родителей.

Brian Reichle 13.07.2012 14:31

Попробуйте использовать следующее:

Window parentWindow = Window.GetWindow(userControlReference);

Метод GetWindow проведет вас по VisualTree и найдет окно, в котором размещен ваш элемент управления.

Вы должны запустить этот код после загрузки элемента управления (а не в конструкторе Window), чтобы предотвратить возврат GetWindow методом null. Например. подключить событие:

this.Loaded += new RoutedEventHandler(UserControl_Loaded); 

По-прежнему возвращает null. Как будто у элемента управления просто нет родителя.

donniefitz2 20.11.2008 19:16

Я использовал приведенный выше код и получил, что parentWindow также вернул мне значение null.

Peter Walke 05.06.2009 19:38

Я выяснил причину, по которой он возвращает значение null. Я помещал этот код в конструктор своего пользовательского элемента управления. Вы должны запустить этот код после загрузки элемента управления. НАПРИМЕР. подключите событие: this.Loaded + = new RoutedEventHandler (UserControl_Loaded);

Peter Walke 05.06.2009 20:12

После просмотра ответа Пола, возможно, имеет смысл использовать метод OnInitialized вместо Loaded.

Peter Walke 11.08.2011 00:13

после того, как я получу свое родительское окно, оно выдает исключение в папках ресурсов. Собственник собственности .....

albatross 03.01.2017 15:43

Я обнаружил, что родительский элемент UserControl в конструкторе всегда имеет значение NULL, но в любых обработчиках событий родительский элемент установлен правильно. Я предполагаю, что это должно иметь какое-то отношение к способу загрузки дерева управления. Чтобы обойти это, вы можете просто получить родительский элемент в событии Control Loaded.

Для примера ознакомьтесь с этим вопросом Контекст данных пользовательского элемента управления WPF имеет значение NULL

Вы должны сначала подождать, пока он не окажется в «дереве». Иногда довольно неприятно.

user7116 24.01.2009 19:15

Мне нужно было использовать метод Window.GetWindow (this) в обработчике событий Loaded. Другими словами, я использовал оба ответа Яна Оукса в сочетании с ответом Алекса, чтобы получить родительский элемент пользовательского элемента управления.

public MainView()
{
    InitializeComponent();

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

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

    ...
}

Добавлю свой опыт. Хотя использование события Loaded может выполнить эту работу, я думаю, что может быть более подходящим переопределить метод OnInitialized. Загружается происходит после первого отображения окна. OnInitialized дает вам возможность вносить любые изменения, например, добавлять элементы управления в окно, прежде чем оно будет отображено.

+1 за правильность. Понимание того, какой метод использовать, иногда может быть тонким, особенно когда у вас есть события и переопределения, добавленные в микс (событие Loaded, переопределение OnLoaded, событие Initialized, переопределение OnInitialized, etcetcetc). В этом случае OnInitialized имеет смысл, потому что вы хотите найти родителя, а элемент управления должен быть инициализирован, чтобы родительский элемент «существовал». Загружен означает другое.

Greg D 20.03.2010 18:33
Window.GetWindow по-прежнему возвращает null в OnInitialized. Кажется, работает только в событии Loaded.
Physikbuddha 27.05.2015 11:25

Инициализированное событие должно быть определено перед InitializeComponent (); В любом случае, мои привязанные (XAML) элементы не смогли разрешить источник (окно). Итак, я закончил использовать загруженное событие.

Lenor 10.11.2018 18:16

Как насчет этого:

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));
        }
    } 
}
DependencyObject parent = ExVisualTreeHelper.FindVisualParent<UserControl>(this);

Пожалуйста, удалите это и интегрируйте любую точку, которая еще не охвачена в ваш другой ответ (который я поддержал как хороший ответ)

Ruben Bartelink 02.06.2016 19:18

Этот подход сработал для меня, но он не так конкретен, как ваш вопрос:

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

DependencyObject parent = GetTopParent(thisUserControl);

Другой путь:

var main = App.Current.MainWindow as MainWindow;

Сработало для меня, нужно поместить его в событие «Loaded», а не в конструктор (откройте окно свойств, дважды щелкните, и он добавит обработчик для вас).

Contango 06.01.2015 23:20

(Я голосую за принятый ответ Яна, это только для записи) Это не сработало, когда пользовательский элемент управления находится в другом окне с ShowDialog, устанавливая содержимое для пользовательского элемента управления. Аналогичный подход - пройти через App.Current.Windows и использовать окно, в котором следующее условие для idx от (Current.Windows.Count - 1) до 0 (App.Current.Windows [idx] == userControlRef) - правда . Если мы сделаем это в обратном порядке, скорее всего, это будет последнее окно, и мы получим правильное окно всего за одну итерацию. userControlRef обычно это в классе UserControl.

msanjay 17.02.2016 14:28

У меня работает:

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

Если вы столкнулись с этим вопросом и VisualTreeHelper не работает для вас или работает спорадически, вам может потребоваться включить LogicalTreeHelper в свой алгоритм.

Вот что я использую:

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

В коде отсутствует название метода LogicalTreeHelper.GetParent.

xmedeko 29.05.2015 11:45

Для меня это было лучшим решением.

Jack B Nimble 18.07.2017 22:09

У меня это не сработало, так как оно зашло слишком далеко вверх по дереву и получило абсолютное корневое окно для всего приложения:

Window parentWindow = Window.GetWindow(userControlReference);

Однако это помогло получить немедленное окно:

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

Вы должны использовать нулевую проверку вместо произвольной переменной «escapeInfiniteLoop». Измените «while», чтобы сначала проверить наличие null, а если не null, то проверьте, не является ли это окном. В противном случае просто сломаться / выйти.

Mark A. Donohoe 03.12.2019 09:36

@MarquelV, я тебя слышу. Как правило, я добавляю проверку «escapeInfiniteLoop» в цикл каждый, который теоретически может застрять, если что-то пойдет не так. Это часть защитного программирования. Время от времени это приносит хорошие дивиденды, поскольку программа избегает зависаний. Очень полезно во время отладки и очень полезно в производстве, если переполнение регистрируется. Я использую эту технику (среди многих других), чтобы писать надежный код, который просто работает.

Contango 03.12.2019 14:45

Я занимаюсь защитным программированием и в принципе согласен с этим, но как рецензент кода я думаю, что это будет помечено за введение произвольных данных, которые не являются частью фактического логического потока. У вас уже есть вся информация, необходимая, чтобы остановить бесконечную рекурсию, проверив значение null, так как бесконечно рекурсировать вверх по дереву невозможно. Конечно, вы можете забыть обновить родительский объект и получить бесконечный цикл, но вы можете так же легко забыть обновить эту произвольную переменную. Другими словами, это защитное программирование уже, которое проверяет на null без введения новых, несвязанных данных.

Mark A. Donohoe 03.12.2019 18:46

@MarquelIV Я должен согласиться. Добавление дополнительной нулевой проверки - лучшее защитное программирование.

Contango 04.12.2019 12:22

Позолоченное издание вышеупомянутого (мне нужна общая функция, которая может вывести Window в контексте 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() правильно выведет окно на следующем основании:

  • корень Window путем обхода визуального дерева (если используется в контексте UserControl)
  • окно, в котором он используется (если он используется в контексте разметки Window)

Разные подходы и разные стратегии. В моем случае я не смог найти окно своего диалога ни с помощью VisualTreeHelper, ни с помощью методов расширения от Telerik, чтобы найти родителя данного типа. Вместо этого я нашел свое диалоговое окно, которое принимает пользовательскую инъекцию содержимого с помощью Application.Current.Windows.

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

Window.GetWindow(userControl) вернет фактическое окно только после того, как окно было инициализировано (метод InitializeComponent() завершен).

Это означает, что если ваш пользовательский элемент управления инициализируется вместе с его окном (например, вы помещаете свой пользовательский элемент управления в файл xaml окна), то в событии OnInitialized пользовательского элемента управления вы не получите окно (оно будет нулевым), потому что в этом случае событие OnInitialized пользовательского элемента управления срабатывает до инициализации окна.

Это также означает, что если ваш пользовательский элемент управления инициализируется после своего окна, вы можете получить это окно уже в конструкторе пользовательского элемента управления.

Если вы просто хотите получить конкретный родительский элемент, а не только окно, конкретный родительский элемент в древовидной структуре, а также не использовать рекурсию или счетчики циклов жесткого прерывания, вы можете использовать следующее:

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

Только не помещайте этот вызов в конструктор (поскольку свойство Parent еще не инициализировано). Добавьте его в обработчик события загрузки или в другие части вашего приложения.

Другие вопросы по теме