Родительский контроль пользователя 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);
}
14 ответов
Попробуйте использовать следующее
Window parentWindow = Window.GetWindow(userControlRefernce);
Метод GetWindow проведет за вами VisualTree и найдет окно, в котором находится ваш элемент управления.
Вы должны запустить этот код после загрузки элемента управления, чтобы метод GetWindow не возвращал ноль. EG подключить событие:
this.Loaded += new RoutedEventHandler(UserControl_Loaded);
Я добавлю свой опыт. Хотя использование события Loaded может сделать эту работу, я думаю, что может быть более подходящим переопределить метод OnInitialized. Loaded происходит после первого отображения окна. OnInitialized дает вам возможность вносить любые изменения, например, добавлять элементы управления в окно, прежде чем оно будет отображено.
Попробуйте использовать 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);
}
}
Мне нужно было использовать метод 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);
...
}
Если вы нашли этот вопрос, и 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);
}
Этот подход работал для меня, но он не так конкретен, как ваш вопрос:
App.Current.MainWindow
Как насчет этого:
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));
}
}
}
Я обнаружил, что родительский элемент UserControl всегда равен нулю в конструкторе, но в любых обработчиках событий родительский элемент установлен правильно. Я думаю, это как-то связано с тем, как загружается дерево управления. Таким образом, чтобы обойти это, вы можете просто получить родителя в элементе управления Loaded.
В качестве примера проверки этого вопроса DataContext элемента управления пользователя WPF имеет значение Null
Это работает для меня:
DependencyObject GetTopLevelControl(DependencyObject control)
{
DependencyObject tmp = control;
DependencyObject parent = null;
while((tmp = VisualTreeHelper.GetParent(tmp)) != null)
{
parent = tmp;
}
return parent;
}
Если вы просто хотите получить определенный родительский элемент, а не только окно, конкретный родительский элемент в древовидной структуре, а также не использовать рекурсию или счетчики циклов жесткого прерывания, вы можете использовать следующее:
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
свойство еще не инициализировано). Добавьте его в обработчик события загрузки или в другие части вашего приложения.
Это не сработало для меня, так как зашло слишком далеко вверх по дереву и получило абсолютное корневое окно для всего приложения:
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();
DependencyObject parent = ExVisualTreeHelper.FindVisualParent<UserControl>(this);
DependencyObject GetTopParent(DependencyObject current)
{
while (VisualTreeHelper.GetParent(current) != null)
{
current = VisualTreeHelper.GetParent(current);
}
return current;
}
DependencyObject parent = GetTopParent(thisUserControl);
В Window.GetWindow(userControl)
вернет фактическое окно только после инициализации окна (InitializeComponent()
метод закончен).
Это означает, что если ваш пользовательский элемент управления инициализируется вместе со своим окном (например, вы помещаете свой пользовательский элемент управления в файл xaml окна), то в пользовательском элементе управления OnInitialized
вы не получите окно (оно будет пустым), потому что в этом случае пользовательский элемент управления OnInitialized
событие запускается до инициализации окна.
Это также означает, что если ваш пользовательский элемент управления инициализируется после своего окна, то вы можете получить это окно уже в конструкторе пользовательского элемента управления.
Разные подходы и разные стратегии. В моем случае я не смог найти окно своего диалога ни с помощью VisualTreeHelper, ни с помощью методов расширения от Telerik, чтобы найти родителя данного типа. Вместо этого я нашел свое диалоговое окно, которое принимает пользовательскую инъекцию содержимого с помощью Application.Current.Windows.
public Window GetCurrentWindowOfType<TWindowType>(){
return Application.Current.Windows.OfType<TWindowType>().FirstOrDefault() as Window;
}
Позолоченное издание выше (мне нужна общая функция, которая может выводить 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
разметка)