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

183

У меня есть пользовательский элемент управления, который я загружаю во время 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);
}
donniefitz2
источник

Ответы:

346

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

Window parentWindow = Window.GetWindow(userControlReference);

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

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

this.Loaded += new RoutedEventHandler(UserControl_Loaded); 
Йен Оукс
источник
6
Все еще возвращает ноль. Это как если бы элемент управления просто не имеет родителя.
donniefitz2
2
Я использовал код выше и получить parentWindow также возвращает мне значение null.
Питер Уолк
106
Я выяснил причину, по которой он возвращается null. Я помещал этот код в конструктор моего пользовательского элемента управления. Вы должны запустить этот код после загрузки элемента управления. EG подключить событие: this.Loaded + = new RoutedEventHandler (UserControl_Loaded);
Питер Уолк
2
Изучив ответ Пола, возможно, имеет смысл использовать метод OnInitialized вместо Loaded.
Питер Уолк
@PeterWalke вы решаете мою очень долгую проблему времени ... Спасибо
Вакас Шаббир
34

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

Павел
источник
8
+1 за правильный. Понимание того, какую технику использовать, иногда может быть едва уловимым, особенно когда в микс добавляются события и переопределения (событие Loaded, переопределение OnLoaded, событие Initialized, переопределение OnInitialized, etcetcetc). В этом случае OnInitialized имеет смысл, потому что вы хотите найти родителя, и элемент управления должен быть инициализирован, чтобы родительский объект «существовал». Loaded означает что-то другое.
Грег Д
3
Window.GetWindowпо- прежнему возвращается nullв OnInitialized. Кажется, работает только в Loadedслучае.
Physikbuddha
Инициализированное событие должно быть определено до InitializeComponent (); В любом случае, мои Binded (XAML) элементы не смогли разрешить источник (окно). Итак, я прекратил использовать Loaded Event.
Ленор
15

Попробуйте использовать 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);
        }
    }
Джоби Джой
источник
Я попытался передать, используя этот код из моего пользовательского контроля. Я передал это в этот метод, но он возвратил ноль, указывая, что это конец дерева (согласно вашему комментарию). Ты знаешь почему это? Пользовательский элемент управления имеет родителя, который является формой, содержащей. Как мне получить ручку для этой формы?
Питер Уолк
2
Я выяснил причину, по которой он возвращается null. Я помещал этот код в конструктор моего пользовательского элемента управления. Вы должны запустить этот код после загрузки элемента управления. EG подключить событие: this.Loaded + = new RoutedEventHandler (UserControl_Loaded)
Питер Уолк
Еще одна проблема в отладчике. VS выполнит код события Load, но не найдет родителя Window.
Богдан_троценко
1
Если вы собираетесь реализовать свой собственный метод, вы должны использовать комбинацию VisualTreeHelper и LogicalTreeHelper. Это связано с тем, что некоторые неоконные элементы управления (например, Popup) не имеют визуальных родительских элементов, и кажется, что элементы управления, созданные из шаблона данных, не имеют логических родительских элементов.
Брайан Рейхл
14

Мне нужно было использовать метод 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);

    ...
}
Алан Ле
источник
7

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

App.Current.MainWindow
Энтони Мейн
источник
7

Если вы нашли этот вопрос, и 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);
}
GordoFabulous
источник
Вы пропустите имя метода LogicalTreeHelper.GetParentв коде.
xmedeko
Это было лучшее решение для меня.
Джек Б. Нимбл
6

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

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

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

В качестве примера проверки этого вопроса DataContext элемента управления пользователя WPF имеет значение Null

Alex
источник
1
Вы вроде как должны подождать, пока оно окажется в «дереве» первым. Довольно неприятный временами.
user7116
3

По-другому:

var main = App.Current.MainWindow as MainWindow;
Pnct
источник
Сработало для меня, нужно поместить его в событие «Loaded», а не в конструктор (откройте окно свойств, дважды щелкните, и он добавит обработчик для вас).
Контанго
(Мой голос за принятый ответ от Яна, это только для записи) Это не сработало, когда пользовательский элемент управления находится в другом окне с ShowDialog, устанавливая содержимое для пользовательского элемента управления. Аналогичный подход заключается в том, чтобы пройти через App.Current.Windows и использовать окно, в котором следующее условие для idx от (Current.Windows.Count - 1) до 0 (App.Current.Windows [idx] == userControlRef) равно истинным , Если мы сделаем это в обратном порядке, вероятно, это будет последнее окно, и мы получим правильное окно всего за одну итерацию. userControlRef обычно это внутри класса UserControl.
мсанджай
3

Это работает для меня:

DependencyObject GetTopLevelControl(DependencyObject control)
{
    DependencyObject tmp = control;
    DependencyObject parent = null;
    while((tmp = VisualTreeHelper.GetParent(tmp)) != null)
    {
        parent = tmp;
    }
    return parent;
}
Налан Мадхесваран
источник
2

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

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();
Контанго
источник
Вы должны использовать нулевую проверку вместо произвольной переменной 'avoInfiniteLoop'. Измените свое 'while', чтобы сначала проверить на null, а если не на null, то убедитесь, что это не окно. В противном случае просто сломать / выйти.
Марк А. Донохо
@ MarquelV Я тебя слышу. Как правило, я добавляю проверку «avoInfiniteLoop» в каждый цикл, который теоретически может застрять, если что-то пойдет не так. Его часть оборонительного программирования. Время от времени, это приносит хорошие дивиденды, поскольку программа избегает зависания. Очень полезно во время отладки и очень полезно в производстве, если переполнение зарегистрировано. Я использую эту технику (среди многих других), чтобы позволить писать надежный код, который просто работает.
Contango
Я получаю защитное программирование, и я в принципе согласен с этим, но, как рецензент кода, я думаю, что это будет помечено для введения произвольных данных, которые не являются частью реального логического потока. У вас уже есть вся информация, необходимая для остановки бесконечной рекурсии, путем проверки на нулевое значение, так как невозможно бесконечно восстанавливать дерево. Конечно, вы можете забыть обновить родительский элемент и иметь бесконечный цикл, но вы также можете легко забыть обновить эту произвольную переменную. Другими словами, это уже оборонительное программирование, чтобы проверять на ноль без введения новых, не связанных данных.
Марк А. Донохо
1
@ MarquelIV Я должен согласиться. Добавление дополнительной проверки нуля лучше защитного программирования.
контанго
1
DependencyObject parent = ExVisualTreeHelper.FindVisualParent<UserControl>(this);
Эрик Колсон
источник
Пожалуйста, удалите это и включите любое замечание, которое это делает, которое еще не отражено в вашем другом ответе (который я одобрил как хороший ответ)
Рубен Бартелинк
1
DependencyObject GetTopParent(DependencyObject current)
{
    while (VisualTreeHelper.GetParent(current) != null)
    {
        current = VisualTreeHelper.GetParent(current);
    }
    return current;
}

DependencyObject parent = GetTopParent(thisUserControl);
Агус Сяхпутра
источник
0

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

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

public Window GetCurrentWindowOfType<TWindowType>(){
 return Application.Current.Windows.OfType<TWindowType>().FirstOrDefault() as Window;
}
Торе Аурстад
источник
0

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

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

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

ChocapicSz
источник
0

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

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свойство еще не инициализировано). Добавьте его в обработчик событий загрузки или в другие части вашего приложения.

Люкачи Андрей
источник