【C#, WPF】WPFの要素を名前で検索する【コピペフリー】【翻訳記事】

ある要素を識別IDで検索して、それに対して何らかの変更を行う、という操作は言語を問わずしばしば行われます。

WPFの要素を名前で検索する方法については、以下のMicrosoft のアーティクルが公開されています。

方法: 要素を名前で検索する

https://learn.microsoft.com/ja-jp/dotnet/desktop/wpf/advanced/how-to-find-an-element-by-its-name?view=netframeworkdesktop-4.8

ですがコードの内容によってはこのメソッドではnullを返してしまい上手く行かないケースがあるため、対応方法をまとめました。

FindNameで要素が見つからない?

たとえばHTML/JSなんかだと要素を検索したければFindViewById()みたいなわかりやすいメソッドが用意されていますが、WPFで似たようなことをしようとするとちょっとややこしいことになります。

WPFで要素の名前から要素を検索するメソッドはFindName()です。

public object FindName (string name);

これはXAMLで作成、定義したオブジェクトのx:Name属性を参照します。ですので、例えば以下のようなXAMLを書いておくと、FindName()は正常に機能します。

<TextBox x:Name="textBox1">lorem ipsum</TextBox>
<Button x:Name="Button1">click!</Button>

ところが、コードで動的に生成したオブジェクトに対してFindName()を使っても、メソッドはnullを返してしまいます。

TextBox textBox = new TextBox();
textBox.Name = "textBox1";
parent.Children.Add(textBox);

TextBox t = (TextBox)FindName("textBox1");  //null.

Namex:Nameが別物であることを理解する必要があります。x:Nameは、読み込み時にXAML名義スコープにオブジェクトの名称を登録するために使用されます。これはコードを使用して設定することができません。

一方でName属性はコードで設定することができますが、XAMLが読み込まれた後からName属性を設定してもXAML名前スコープ内で代表的なフィールド参照が作成されるわけではありません。FindName()x:Name属性を参照してオブジェクトを検索するので、上記のようなコードで動的に生成したオブジェクトは発見することができないようです。

x:Name ディレクティブについての詳細な説明はMicrosoft公式ガイドをご覧ください:

https://learn.microsoft.com/ja-jp/dotnet/desktop/xaml-services/xname-directive

Name属性を参照してオブジェクトを探すメソッド

stackoverflowに寄せられた質問と回答に、この問題を解決するメソッドがありましたのでご紹介します。

/// <summary>
/// 指定したName属性を持つ子要素を探すメソッド
/// </summary>
/// <param name="parent">探したい要素の親要素</param>
/// <typeparam name="T">探したいオブジェクトの型</typeparam>
/// <param name="childName">探したい子要素のx:Name または Name</param>
/// <returns>条件にマッチする最初の子要素(無ければnull)</returns>
public static T FindChild<T>(DependencyObject parent, string childName)
   where T : DependencyObject
{    
  if (parent == null) return null;

  T foundChild = null;

  int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
  for (int i = 0; i < childrenCount; i++)
  {
    var child = VisualTreeHelper.GetChild(parent, i);
    T childType = child as T;
    if (childType == null)
    {
      foundChild = FindChild<T>(child, childName);

      if (foundChild != null) break;
    }
    else if (!string.IsNullOrEmpty(childName))
    {
      var frameworkElement = child as FrameworkElement;
      if (frameworkElement != null && frameworkElement.Name == childName)
      {
        foundChild = (T)child;
        break;
      }
    }
    else
    {
      foundChild = (T)child;
      break;
    }
  }

  return foundChild;
}
https://stackoverflow.com/questions/636383/how-can-i-find-wpf-controls-by-name-or-type

このメソッドをそのままどこにでもコピペして、以下のように書けば求めている挙動が得られると思います。第1引数は適宜正しい親要素を指定してください。

TextBox foundTextBox = FindChild<TextBox>(Application.Current.MainWindow, "myTextBoxName");