Батько для контролю користувачів 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);
}

Відповіді:


346

Спробуйте використовувати наступне:

Window parentWindow = Window.GetWindow(userControlReference);

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

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

this.Loaded += new RoutedEventHandler(UserControl_Loaded); 

6
Все ще повертає нуль. Це так, ніби в контролі просто немає батьків.
donniefitz2

2
Я використовував код вище, і батьківське вікно також повертає нульове значення для мене.
Пітер Уолк

106
Я з'ясував причину повернення нуля. Я вводив цей код у конструктор мого управління користувачем. Ви повинні запустити цей код після завантаження елемента керування. EG з'єднати подію: this.Loaded + = new RoutedEventHandler (UserControl_Loaded);
Пітер Уолк

2
Переглянувши відповідь від Пола, можливо, має сенс використовувати метод OnInitialized замість Loaded.
Пітер Уолк

@PeterWalke ти вирішуєш мою дуже давню проблему ... Дякую
Waqas Shabbir

34

Додам свій досвід. Хоча використання події Loaded може зробити цю роботу, я вважаю, що це може бути більш придатним для зміни способу OnInitialized. Завантаження відбувається після першого відображення вікна. OnInitialized дає шанс внести будь-які зміни, наприклад, додати елементи керування у вікно, перш ніж воно буде надано.


8
+1 для правильної. Розуміння того, яку техніку використовувати, може часом виявлятися тонкою, особливо коли у вас є події та переосмислення, кинуті в суміш (Loaded event, OnLoaded override, Initialized event, OnInitialized override, etcetcetc). У цьому випадку OnInitialized має сенс, тому що ви хочете знайти батьківського, а контроль повинен бути ініціалізований, щоб батьківський "існував". Навантажений означає щось інше.
Грег Д

3
Window.GetWindowвсе ще повертається nullв OnInitialized. Здається, працює лише у Loadedподії.
Фізикбудда

Ініціалізована подія повинна бути визначена перед InitializeComponent (); У будь-якому разі, мої Binded (XAML) елементи не могли вирішити джерело (Window). Тому я закінчив використовувати завантажену подію.
Ленор

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
Я з'ясував причину повернення нуля. Я вводив цей код у конструктор мого управління користувачем. Ви повинні запустити цей код після завантаження елемента керування. EG підключення події: this.Loaded + = new RoutedEventHandler (UserControl_Loaded)
Пітер Уолк

Інша проблема - у налагоджувачі. VS виконає код події Load, але він не знайде батьківського вікна.
bohdan_trotsenko

1
Якщо ви збираєтеся реалізувати власний метод, вам слід використовувати комбінацію VisualTreeHelper і LogicalTreeHelper. Це пов’язано з тим, що деякі невіконні елементи керування (наприклад, Popup) не мають візуальних батьків, і, здається, у елементів управління, створених із шаблону даних, немає логічних батьків.
Брайан Райхле

14

Мені потрібно було використати метод Window.GetWindow (цей) в обробці завантажених подій Loaded. Іншими словами, я використовував обидва відповіді Ian Oakes у поєднанні з відповіддю Алекса, щоб отримати батьківський контроль користувача.

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

Ви пропустите ім'я методу 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 завжди є нульовим у конструкторі, але в будь-яких обробниках подій батько встановлений правильно. Я здогадуюсь, це повинно мати щось спільне із завантаженням керуючого дерева. Тож, щоб обійти це, ви можете просто завести батьків у контрольній події.

Для прикладу перевірки цього питання DataContext WPF User Control є Null


1
Вам начебто доведеться чекати, коли воно спочатку опиниться на «дереві». Дуже неприємно часом.
користувач7116

3

Інший спосіб:

var main = App.Current.MainWindow as MainWindow;

Працював для мене, мусиш помістити його у події "Завантажений", а не конструктор (підніміть вікно властивостей, двічі клацніть, і це додасть обробник для вас).
Контанго

(Моє голосування - за прийняту відповідь Ієна, це просто для запису) Це не спрацювало, коли управління користувачем знаходиться в іншому вікні з ShowDialog, встановивши вміст на керування користувачем. Аналогічний підхід , щоб пройти через App.Current.Windows і використовувати вікно , в якому виконується така умова для IDX з (Current.Windows.Count - 1) до 0 (App.Current.Windows [IDX] == userControlRef) є істинним . Якщо ми робимо це в зворотному порядку, то, швидше за все, це буде останнє вікно, і ми отримаємо правильне вікно лише з однією ітерацією. userControlRef, як правило, це в класі UserControl.
msanjay

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 Я тебе чую. Як правило, я додаю перевірку "AvoInInfiniteLoop" до кожного циклу, який теоретично може застрягнути, якщо щось піде не так. Його частина оборонного програмування. Кожен так часто виплачує хороші дивіденди, коли програма уникає зависання. Дуже корисний під час налагодження та дуже корисний у виробництві, якщо вхід увійшов у систему. Я використовую цю техніку (серед багатьох інших), щоб увімкнути надійний код, який просто працює.
Контанго

Я отримую оборонне програмування, і в принципі я згоден з цим, але, переглядаючи код, я думаю, що це отримає позначення для введення довільних даних, які не є частиною фактичного логічного потоку. У вас вже є вся інформація, необхідна для припинення нескінченної рекурсії, перевіряючи наявність нуля, оскільки неможливо безкінечно повторювати дерево. Впевнені, що ви можете забути оновити батьківський і мати нескінченний цикл, але ви можете так само легко забути оновити цю довільну змінну. Іншими словами, це вже захисне програмування, щоб перевірити наявність нуля без введення нових, не пов'язаних між собою даних.
Марк А.

1
@MarquelIV Я повинен погодитися. Додавання додаткової нульової перевірки - краще захисне програмування.
Контанго


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, проходячи візуальним деревом (якщо він використовується в контексті a 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подія керування користувачем запускається до ініціалізації вікна.

Це також означає, що якщо ваше керування користувачем ініціалізується після його вікна, ви можете отримати це вікно вже в конструкторі управління користувача.


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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.