WPF 사용자 컨트롤 부모


183

MainWindow런타임에 로드하는 사용자 정의 컨트롤이 있습니다. 에서 포함 창에 대한 핸들을 얻을 수 없습니다 UserControl.

시도 this.Parent했지만 항상 null입니다. 누구나 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를 안내하고 컨트롤을 호스팅하는 창을 찾습니다.

GetWindow메서드가 반환 되지 않도록하려면 컨트롤이로드 된 후 (창 생성자가 아닌)이 코드를 실행해야합니다 null. 예를 들어 이벤트를 연결하십시오.

this.Loaded += new RoutedEventHandler(UserControl_Loaded); 

6
여전히 null을 반환합니다. 마치 컨트롤에 부모가없는 것처럼 보입니다.
donniefitz2

2
위의 코드를 사용하고 parentWindow도 null을 반환합니다.
Peter Walke 2016 년

106
null을 반환하는 이유를 알았습니다. 이 코드를 사용자 컨트롤의 생성자에 넣었습니다. 컨트롤이로드 된 후에이 코드를 실행해야합니다. EG 이벤트를 연결합니다 : this.Loaded + = new RoutedEventHandler (UserControl_Loaded);
Peter Walke 2016 년

2
Paul의 응답을 검토 한 후 Loaded 대신 OnInitialized 메소드를 사용하는 것이 좋습니다.
피터 워키

... 당신이 내 매우 오랜 시간이 문제를 해결 감사합니다 @PeterWalke
Waqas의 Shabbir에게

34

내 경험을 추가하겠습니다. Loaded 이벤트를 사용하면 작업을 수행 할 수 있지만 OnInitialized 메서드를 재정의하는 것이 더 적합 할 수 있습니다. 창을 처음 표시 한 후로드가 발생합니다. OnInitialized를 사용하면 렌더링 전에 창에 컨트롤을 추가하는 등의 변경 작업을 수행 할 수 있습니다.


8
올바른 +1. 특히 어떤 이벤트와 재정의가 믹스에 던져 질 때 (로드 된 이벤트, OnLoaded 재정의, 초기화 된 이벤트, OnInitialized 재정의 등) 어떤 기법을 사용하는 것이 미묘 할 수 있는지 이해합니다. 이 경우 OnInitialized는 부모를 찾으려고하므로 부모가 "존재"하도록 컨트롤을 초기화해야합니다. 로드는 다른 것을 의미합니다.
Greg D

3
Window.GetWindow아직 반환 nullOnInitialized. Loaded이벤트에서만 작동하는 것 같습니다 .
Physikbuddha

초기화 된 이벤트는 InitializeComponent () 전에 정의되어야합니다. 어쨌든 내 Binded (XAML) 요소가 소스 (Window)를 확인할 수 없습니다. 그래서 나는 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);
        }
    }

내 사용자 정의 컨트롤 에서이 코드를 사용하여 전달하려고했습니다. 나는 이것을이 메소드에 전달했지만 null을 반환하여 트리의 끝임을 나타냅니다 (댓글에 따라). 이것이 왜 그런지 아십니까? 사용자 정의 컨트롤에는 포함 양식 인 부모가 있습니다. 이 양식을 어떻게 처리합니까?
Peter Walke 2016 년

2
null을 반환하는 이유를 알았습니다. 이 코드를 사용자 컨트롤의 생성자에 넣었습니다. 컨트롤이로드 된 후에이 코드를 실행해야합니다. 이벤트 최대 EG 와이어 : this.Loaded + = 새 RoutedEventHandler (UserControl_Loaded)
피터 트키

또 다른 문제는 디버거에 있습니다. VS는 Load 이벤트 코드를 실행하지만 Window 부모를 찾지 않습니다.
bohdan_trotsenko

1
자체 메서드를 구현하려는 경우 VisualTreeHelper와 LogicalTreeHelper를 함께 사용해야합니다. 이는 팝업과 같은 일부 비 윈도우 컨트롤에는 시각적 부모가없고 데이터 템플릿에서 생성 된 컨트롤에는 논리적 인 부모가없는 것 같습니다.
브라이언 라이 클

14

Loaded 이벤트 처리기에서 Window.GetWindow (this) 메서드를 사용해야했습니다. 즉, Ian Oakes의 대답과 Alex의 대답을 함께 사용하여 사용자 정의 컨트롤의 부모를 얻었습니다.

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

이것은 나에게 가장 좋은 해결책이었습니다.
Jack B Nimble

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의 부모가 항상 null 인 것을 발견했지만 모든 이벤트 처리기에서 부모가 올바르게 설정되었습니다. 컨트롤 트리가로드되는 방식과 관련이 있어야한다고 생각합니다. 따라서이 문제를 해결하려면 컨트롤 Loaded 이벤트에서 부모를 얻을 수 있습니다.

체크 아웃 예를 들어이 질문 WPF User Control의 DataContext는 Null입니다.


1
먼저 "트리"에 올 때까지 기다려야합니다. 때때로 꽤 무례합니다.
user7116

3

또 다른 방법:

var main = App.Current.MainWindow as MainWindow;

나를 위해 일한 경우 생성자가 아닌 "로드"이벤트에 넣어야합니다 (속성 창을 가져오고 두 번 클릭하면 처리기가 추가됩니다).
Contango

(내 투표는 Ian이 수락 한 답변에 대한 것이며, 이것은 기록에 대한 것입니다.) 사용자 컨트롤이 ShowDialog가있는 다른 창에 있고 내용을 사용자 컨트롤로 설정하면 작동하지 않았습니다. 비슷한 접근 방식은 App.Current.Windows를 살펴보고 (Current.Windows.Count-1)에서 0 (App.Current.Windows [idx] == userControlRef)까지 idx에 대해 다음 조건이 참인 창을 사용하는 것 입니다. . 우리가 이것을 역순으로하면, 그것은 마지막 창 일 가능성이 높으며 한 번의 반복으로 올바른 창을 얻습니다. 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();

임의의 'avoidInfiniteLoop'변수 대신 null 검사를 사용해야합니다. 'while'을 변경하여 null을 먼저 확인하고 null이 아닌 경우 창이 아닌지 확인하십시오. 그렇지 않으면 중단 / 나가기 만하면됩니다.
Mark A. Donohoe

@MarquelV 들려요. 일반적으로, 나는 무언가 잘못되면 이론적으로 멈출 수있는 모든 루프에 "avoidInfiniteLoop"체크를 추가합니다 . 방어 프로그래밍의 일부입니다. 프로그램이 중단을 피할 때마다 좋은 배당금을 지불합니다. 디버깅 중에 매우 유용하며 오버런이 기록되는 경우 프로덕션에 매우 유용합니다. 나는이 기술을 사용하여 강력한 코드를 작성할 수 있습니다.
Contango

나는 방어적인 프로그래밍을 얻었고 그것에 대해 원칙적으로 동의하지만 코드 검토자는 실제 논리 흐름의 일부가 아닌 임의의 데이터를 도입 한 것으로 표시 될 것이라고 생각합니다. 트리를 무한히 재귀하는 것은 불가능하므로 null을 확인하여 무한 재귀를 중지하는 데 필요한 모든 정보가 이미 있습니다. 물론 부모를 업데이트하는 것을 잊고 무한 루프를 가질 수는 있지만 임의의 변수를 업데이트하는 것을 쉽게 잊을 수 있습니다. 다시 말해 , 관련없는 새로운 데이터를 도입하지 않고 null을 검사 하는 것은 이미 방어적인 프로그래밍입니다.
Mark A. Donohoe

1
@MarquelIV 동의해야합니다. 추가 null 검사를 추가하면 방어 프로그래밍이 더 좋습니다.
Contango


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를 올바르게 추론합니다.

  • 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이벤트에서 창을 가져 오지 못합니다 (널 (null)). 이 경우 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속성이 아직 초기화되지 않았으므로 생성자에이 호출을 넣지 마십시오 . 로딩 이벤트 핸들러 또는 애플리케이션의 다른 부분에 추가하십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.