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 2008年

2
上記のコードを使用して、parentWindowもnullを返します。
Peter Walke

106
nullを返す理由を見つけました。このコードをユーザーコントロールのコンストラクターに入れました。コントロールが読み込まれた後、このコードを実行する必要があります。EGはイベントを結び付けます:this.Loaded + = new RoutedEventHandler(UserControl_Loaded);
Peter Walke

2
Paulからの応答を確認した後、LoadedではなくOnInitializedメソッドを使用することは意味があるかもしれません。
Peter Walke、2011

@PeterWalkeあなたは私の非常に長い時間の問題を解決します...ありがとう
Waqas Shabbir

34

私の経験を追加します。Loadedイベントを使用すれば十分ですが、OnInitializedメソッドをオーバーライドする方が適していると思います。Loadedは、ウィンドウが最初に表示された後に発生します。OnInitializedを使用すると、変更を加えることができます。たとえば、ウィンドウをレンダリングする前にウィンドウにコントロールを追加できます。


8
正解は+1。使用する手法を理解することは、特にイベントとオーバーライドがミックスにスローされる場合(Loadedイベント、OnLoadedオーバーライド、Initializedイベント、OnInitializedオーバーライドなど)、微妙な場合があります。この場合、親を見つけたいのでOnInitializedは意味があり、親が「存在する」ようにコントロールを初期化する必要があります。読み込まれたということは、別のことを意味します。
Greg D

3
Window.GetWindowまだ返しnullOnInitialized。で動作しているようですLoaded唯一のイベント。
Physikbuddha、2015年

InitializedEvent()は、InitializeComponent()の前に定義する必要があります。とにかく、バインドされた(XAML)要素がソース(ウィンドウ)を解決できませんでした。そこで、Loadedイベントの使用を終了しました。
Lenor、

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

2
nullを返す理由を見つけました。このコードをユーザーコントロールのコンストラクターに入れました。コントロールが読み込まれた後、このコードを実行する必要があります。EGはイベントを接続します。this.Loaded+ = new RoutedEventHandler(UserControl_Loaded)
Peter Walke

別の問題はデバッガにあります。VSはLoadイベントのコードを実行しますが、ウィンドウの親を見つけられません。
bohdan_trotsenko 2009

1
独自のメソッドを実装する場合は、VisualTreeHelperとLogicalTreeHelperを組み合わせて使用​​する必要があります。これは、一部のウィンドウ以外のコントロール(Popupなど)に視覚的な親がなく、データテンプレートから生成されたコントロールに論理的な親がないように見えるためです。
Brian Reichle、2012

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

これは私にとって最良の解決策でした。
ジャックBニンブル

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ユーザーコントロールのDataContextはNullです


1
最初に「ツリー」に入るのを待つ必要があります。時々かなり不快です。
user7116 2009年

3

別の方法:

var main = App.Current.MainWindow as MainWindow;

私のために働いた、それをコンストラクターではなく "Loaded"イベントに配置する必要があります(プロパティウィンドウを表示してダブルクリックすると、ハンドラーが追加されます)。
Contango、2015年

(私の投票は、Ianが承認した回答に対するものであり、これは単なる記録のためです)ユーザーコントロールがShowDialogを使用して別のウィンドウにあり、コンテンツをユーザーコントロールに設定している場合、これは機能しませんでした。同様のアプローチは、App.Current.Windowsをウォークスルーし、次の条件のウィンドウを使用することです。idxは(Current.Windows.Count-1)から0(App.Current.Windows [idx] == userControlRef)までtrueです。。これを逆の順序で行うと、最後のウィンドウになる可能性が高く、1回の反復で正しいウィンドウが得られます。userControlRefは通常、UserControlクラス内のこれです。
msanjay 2016

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チェックを使用する必要があります。最初にnullをチェックするように 'while'を変更し、nullでない場合はウィンドウでないかどうかをチェックします。それ以外の場合は、単に中断/終了します。
Mark A. Donohoe

@MarquelV聞こえます。一般的に、私は理論的に何かがうまくいかない場合にスタックする可能性があるすべてのループに「avoidInfiniteLoop」チェックを追加します。防御的プログラミングの一部。プログラムがハングを回避するので、多くの場合、それは良い配当を支払います。デバッグ中に非常に役立ち、オーバーランがログに記録されている場合は本番環境で非常に役立ちます。私は(他の多くの中で)この手法を使用して、動作するだけの堅牢なコードを記述できるようにします。
Contango、

私は防御的なプログラミングを得て、それについて原則的に同意しますが、コードレビュー担当者として、これは実際のロジックフローの一部ではない任意のデータを導入するためのフラグが立てられると思います。ツリーを無限に再帰することは不可能であるため、nullをチェックすることにより、無限再帰を停止するために必要なすべての情報がすでにあります。もちろん、親を更新するのを忘れて無限ループになることもありますが、その任意の変数を更新するのを忘れるのも同じくらい簡単です。つまり、無関係な新しいデータを導入せずにnullをチェックするのは、すでに防御的なプログラミングです。
Mark A. Donohoe

1
@MarquelIV同意する必要があります。ヌルチェックを追加すると、防御プログラミングが向上します。
Contango、


1
DependencyObject GetTopParent(DependencyObject current)
{
    while (VisualTreeHelper.GetParent(current) != null)
    {
        current = VisualTreeHelper.GetParent(current);
    }
    return current;
}

DependencyObject parent = GetTopParent(thisUserControl);

0

上記の金メッキ版(私WindowMarkupExtension:-のコンテキスト内でを推測できる汎用関数が必要です

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イベントではウィンドウを取得できない(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プロパティがまだ初期化されていないため)この呼び出しをコンストラクタに配置しないでください。それをloadingイベントハンドラー、またはアプリケーションの他の部分に追加します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.