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





Используйте 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. Я помещал этот код в конструктор своего пользовательского элемента управления. Вы должны запустить этот код после загрузки элемента управления. НАПРИМЕР. подключите событие: this.Loaded + = new RoutedEventHandler (UserControl_Loaded)
Другая проблема в отладчике. VS выполнит код события Load, но не найдет родителя Window.
Если вы собираетесь реализовать свой собственный метод, вам следует использовать комбинацию VisualTreeHelper и LogicalTreeHelper. Это связано с тем, что некоторые элементы управления, не являющиеся оконными (например, Popup), не имеют визуальных родителей, и кажется, что элементы управления, созданные из шаблона данных, не имеют логических родителей.
Попробуйте использовать следующее:
Window parentWindow = Window.GetWindow(userControlReference);
Метод GetWindow проведет вас по VisualTree и найдет окно, в котором размещен ваш элемент управления.
Вы должны запустить этот код после загрузки элемента управления (а не в конструкторе Window), чтобы предотвратить возврат GetWindow методом null. Например. подключить событие:
this.Loaded += new RoutedEventHandler(UserControl_Loaded);
По-прежнему возвращает null. Как будто у элемента управления просто нет родителя.
Я использовал приведенный выше код и получил, что parentWindow также вернул мне значение null.
Я выяснил причину, по которой он возвращает значение null. Я помещал этот код в конструктор своего пользовательского элемента управления. Вы должны запустить этот код после загрузки элемента управления. НАПРИМЕР. подключите событие: this.Loaded + = new RoutedEventHandler (UserControl_Loaded);
После просмотра ответа Пола, возможно, имеет смысл использовать метод OnInitialized вместо Loaded.
после того, как я получу свое родительское окно, оно выдает исключение в папках ресурсов. Собственник собственности .....
Я обнаружил, что родительский элемент UserControl в конструкторе всегда имеет значение NULL, но в любых обработчиках событий родительский элемент установлен правильно. Я предполагаю, что это должно иметь какое-то отношение к способу загрузки дерева управления. Чтобы обойти это, вы можете просто получить родительский элемент в событии Control Loaded.
Для примера ознакомьтесь с этим вопросом Контекст данных пользовательского элемента управления WPF имеет значение NULL
Вы должны сначала подождать, пока он не окажется в «дереве». Иногда довольно неприятно.
Мне нужно было использовать метод 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 имеет смысл, потому что вы хотите найти родителя, а элемент управления должен быть инициализирован, чтобы родительский элемент «существовал». Загружен означает другое.
Window.GetWindow по-прежнему возвращает null в OnInitialized. Кажется, работает только в событии Loaded.
Инициализированное событие должно быть определено перед InitializeComponent (); В любом случае, мои привязанные (XAML) элементы не смогли разрешить источник (окно). Итак, я закончил использовать загруженное событие.
Как насчет этого:
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);
Пожалуйста, удалите это и интегрируйте любую точку, которая еще не охвачена в ваш другой ответ (который я поддержал как хороший ответ)
Этот подход сработал для меня, но он не так конкретен, как ваш вопрос:
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», а не в конструктор (откройте окно свойств, дважды щелкните, и он добавит обработчик для вас).
(Я голосую за принятый ответ Яна, это только для записи) Это не сработало, когда пользовательский элемент управления находится в другом окне с ShowDialog, устанавливая содержимое для пользовательского элемента управления. Аналогичный подход - пройти через App.Current.Windows и использовать окно, в котором следующее условие для idx от (Current.Windows.Count - 1) до 0 (App.Current.Windows [idx] == userControlRef) - правда . Если мы сделаем это в обратном порядке, скорее всего, это будет последнее окно, и мы получим правильное окно всего за одну итерацию. userControlRef обычно это в классе UserControl.
У меня работает:
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.
Для меня это было лучшим решением.
У меня это не сработало, так как оно зашло слишком далеко вверх по дереву и получило абсолютное корневое окно для всего приложения:
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, то проверьте, не является ли это окном. В противном случае просто сломаться / выйти.
@MarquelV, я тебя слышу. Как правило, я добавляю проверку «escapeInfiniteLoop» в цикл каждый, который теоретически может застрять, если что-то пойдет не так. Это часть защитного программирования. Время от времени это приносит хорошие дивиденды, поскольку программа избегает зависаний. Очень полезно во время отладки и очень полезно в производстве, если переполнение регистрируется. Я использую эту технику (среди многих других), чтобы писать надежный код, который просто работает.
Я занимаюсь защитным программированием и в принципе согласен с этим, но как рецензент кода я думаю, что это будет помечено за введение произвольных данных, которые не являются частью фактического логического потока. У вас уже есть вся информация, необходимая, чтобы остановить бесконечную рекурсию, проверив значение null, так как бесконечно рекурсировать вверх по дереву невозможно. Конечно, вы можете забыть обновить родительский объект и получить бесконечный цикл, но вы можете так же легко забыть обновить эту произвольную переменную. Другими словами, это защитное программирование уже, которое проверяет на null без введения новых, несвязанных данных.
@MarquelIV Я должен согласиться. Добавление дополнительной нулевой проверки - лучшее защитное программирование.
Позолоченное издание вышеупомянутого (мне нужна общая функция, которая может вывести 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 еще не инициализировано). Добавьте его в обработчик события загрузки или в другие части вашего приложения.
Я попытался передать этот код из своего пользовательского элемента управления. Я передал это в этот метод, но он вернул значение null, указывая, что это конец дерева (согласно вашему комментарию). Вы знаете, почему это так? У пользовательского элемента управления есть родительский элемент, который представляет собой содержащую форму. Как мне получить доступ к этой форме?