locked
動的に生成したコントロールの指定 RRS feed

  • 質問

  • 間違えてC#の方に質問しておりましたので、こちらで質問しなおしました。

     

    Silverlight3 C#.NETです。

    ボタンが押下されるたびにテキストボックスを追加していく処理を作成しています。

    txtID.Name = "txtID" + _Count.ToString();

    以上のようにカウントアップしながら追加していきます。

    それぞれのTextBoxから値を取得する場合、

    どのように指定すればよいでしょうか。

     

    (TextBox)("txtID" + Count.ToString()).Text

    (↑のようなことがしたい)

    2010年4月13日 3:59

回答

  • GridなりStackPanelなり何らかPanelコントロールに追加したのであればChildrenを辿って探すことができるでしょうし

    FindNameメソッドを使用するという手もあります。

     

     

    // foreachでChildrenを走査
    foreach (var textbox1 in this.StackPanel1.Children.OfType<Control>().Where(x => x.Name == "TextBox1"))
    {
        // ここに処理を記述
    }
    
    // findnameで探す
    var textbox1 = (TextBox)this.FindName("TextBox1");

     

    お好きな方法でどうぞ。

    ※FindName

    http://msdn.microsoft.com/ja-jp/library/system.windows.frameworkelement.findname(v=VS.95).aspx

     

    • 回答としてマーク 山本春海 2010年5月14日 0:49
    2010年4月13日 5:09
  • 解決されたようですが、VisualTreeHelperクラスを使う方法もあります。

    (参考)
    Get Child, Parent, or Children Objects in Silverlight
    http://www.mostlydevelopers.com/mostlydevelopers/blog/post/2009/06/17/Get-Child-Parent-or-Children-Objects-in-Silverlight.aspx

    #OfTypeはIEnumerable.OfTypeです。Linqです。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/
    • 回答としてマーク 山本春海 2010年5月14日 0:49
    2010年4月13日 6:53
    モデレータ
  • UserControl内のTextBoxのインスタンスを外部から取得したいのなら

    そのプロパティを用意しておけばいいと思います。

    外からのアクセスを許可することを明示しているわけですから精神衛生上(?)いいと思います。

    // UserControl1内でプロパティを設定
    public TextBox InnerTextBox
    {
        get{return this.TextBox1;}
    }

    もしくは先にあげられていたVisualTreeHelperを使用して取得することもできます。

    // VisualTreeHelperを最後まで取得するメソッドを作ってみる
    private List<DependencyObject> GetChildren(DependencyObject obj)
    {
        List<DependencyObject> list = new List<DependencyObject>();
        int childcount = VisualTreeHelper.GetChildrenCount(obj);
    
        for (int i = 0; i < childcount; i++)
        {
            var child = VisualTreeHelper.GetChild(obj, i);
            list.Add(child);
    
            foreach (var grandchild in this.GetChildren(child))
            {
                list.Add(grandchild);
            }
    
        }
    
        return list;
    
    }
    
    // UserControl内のTextBoxを取得する
    foreach (var textbox1 in this.GetChildren(this.UserControl1).OfType<TextBox>().Where(x => x.Name == "TextBox1"))
    {
        // お好きな処理をどうぞ
    }

    この場合は内部のTextBoxにつけられた名前を知っている必要があるのでどうかな、という気がしますが…

    UserControl内部のインスタンスを取得したいコントロールを一意に識別する方法を考えるよりは

    プロパティを用意するほうが手軽だと思います。

    • 回答としてマーク 山本春海 2010年5月14日 0:50
    2010年4月14日 5:32
  • <ItemsControl x:Name="ic">
     <ItemsControl.ItemTemplate>
     <DataTemplate>
      <TextBox Text="{Binding TaskName, Mode=TwoWay}" />
     </DataTemplate>
     </ItemsControl.ItemTemplate>
    </ItemsControl>

    のようにしておいて

     

    // データを表示するところ
    var tasks = ... 配列かリストあたりを取得 ...;
    // ItemsControlにデータを表示
    ic.ItemsSource = tasks;
    
    // データを取得するところ
    foreach (var task in tasks)
    {
     // TextBoxの中身がTaskNameに設定されてる
     MessageBox.Show(task.TaskName);
    }
    とすれば、多分VisualTreeをいじくったりしなくても良いような気がするのですがどうでしょうか?

     


    かずき Blog:http://blogs.wankuma.com/kazuki/
    • 回答としてマーク 山本春海 2010年5月14日 0:50
    2010年4月15日 15:23

すべての返信

  • GridなりStackPanelなり何らかPanelコントロールに追加したのであればChildrenを辿って探すことができるでしょうし

    FindNameメソッドを使用するという手もあります。

     

     

    // foreachでChildrenを走査
    foreach (var textbox1 in this.StackPanel1.Children.OfType<Control>().Where(x => x.Name == "TextBox1"))
    {
        // ここに処理を記述
    }
    
    // findnameで探す
    var textbox1 = (TextBox)this.FindName("TextBox1");

     

    お好きな方法でどうぞ。

    ※FindName

    http://msdn.microsoft.com/ja-jp/library/system.windows.frameworkelement.findname(v=VS.95).aspx

     

    • 回答としてマーク 山本春海 2010年5月14日 0:49
    2010年4月13日 5:09
  • ありがとうございます。

    FindNameで値をとることはできました。

    しかし、ループ文の

    this.StackPanel1.Children.OfType<Control>().Where(x => x.Name == "TextBox1")
    の部分の仕様がわかりません。
    StackPanel1内の子要素に対してx.Name == "TextBox1"で検索し、該当するものを
    取得しているだろうということはわかるのですが
    Children.OfType< Control >
    の部分がChildren.OfTypeなどで検索しても必要な情報を得られませんでした。
    よろしければこの部分が何をしているのか教えていただけないでしょうか。

    2010年4月13日 5:49
  • 解決されたようですが、VisualTreeHelperクラスを使う方法もあります。

    (参考)
    Get Child, Parent, or Children Objects in Silverlight
    http://www.mostlydevelopers.com/mostlydevelopers/blog/post/2009/06/17/Get-Child-Parent-or-Children-Objects-in-Silverlight.aspx

    #OfTypeはIEnumerable.OfTypeです。Linqです。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/
    • 回答としてマーク 山本春海 2010年5月14日 0:49
    2010年4月13日 6:53
    モデレータ
  • ありがとうございます。

    >#OfTypeはIEnumerable.OfTypeです。Linqです。

    OfTypeはcastのようなもの、であっているのかな。

    指定した型のデータだけ抜き出してくれるもののようですね。

     

    まずループの方ですが、

    var tb = this.LayoutRoot2.Children.OfType<Control>()

    .Where(x => x.Name == "TextBox" + Index .ToString());

    とし、var型のデータとして抽出したは良いものの、

    TextBoxとして扱えないため値がとれません。

    TextBox tb = (TextBox)this.LayoutRoot2.Children.OfType<TextBox>()

    としてもエラーとなり駄目でした。

     

    VisualTreeHelperクラスもStackPanelの子要素まではとれました。

    しかしこれも同様に、名前を指定して特定のオブジェクトを取得する方法がわかりません。

    CType(parent, T).Name = name

    この辺りで条件を絞れているような気がするのですが・・・

    2010年4月13日 7:56
  • ChildrenやVisualTreeをたどる方法もあると思いますが、ご自身のコードでコントロールを作成しているのでしたら

    List<TextBox> textBoxList = new List(); // フィールドあたりに作成しておく
    // 追加処理
    TextBox txt = new TextBox();
    txt.Name = ....; // 何か名前を適当につける
    textBoxList.Add(txt); // 自分で動的に生成したオブジェクトを管理するためのListに入れる
    grid.Children.Add(txt); // そしてGUIに表示する

    といったようにListあたりを別に作って、そこで管理してしまうのが手っ取り早いと思います。
    (Nameをキーにアクセスするような使いかたをしたいならDictionary<T,K>を使うとか)

    コントロールを動的に生成する部分のコードに変更が入ってしまいますが、1つのやり方として検討してみては如何でしょうか。


    かずき Blog:http://blogs.wankuma.com/kazuki/
    2010年4月14日 1:56
  • var tb = this.LayoutRoot2.Children.OfType<Control>()

    .Where(x => x.Name == "TextBox" + Index .ToString());

    とし、var型のデータとして抽出したは良いものの、

    TextBoxとして扱えないため値がとれません。

    TextBox tb = (TextBox)this.LayoutRoot2.Children.OfType<TextBox>()

    としてもエラーとなり駄目でした。

    Enumerable.OfType<T>の結果はIEnumerable<T>なので、OfType<Control>でフィルターすればIEnumerable<Control>だし、OfType<TextBox>だったらIEnumerable<TextBox>になりますよね。あとは、foreachなんかで回して処理すればいいんじゃないですか?

    Visual Studioを使っているなら、ブレークポイントをおいてウォッチ式で値を確認してみたらどうでしょう。

     

    2010年4月14日 3:25
  • ありがとうございます。

    メンバのListで管理する方法は考えていました。

    ただDataGridとListの二つに同じインスタンス(?)を持つことになってしまいますので

    ソースレビューで弾かれないかな、と思っています。

     

    IEnumerable<TextBox>型ですか、ようやく理解できました。

    最初に提示されたforeach文を使用して

    StackPanel1の子要素TextBox1

    をとることができました。

    ただ、もう一点要件を追加したいのですが、

    StackPanel1の子要素ContentControl1に入ったUserControl1のTextBox1と

    StackPanel1の子要素UserControl1のTextBox1を取る方法はありますでしょうか。

    現状のforeachですと一件も取得できませんでした。

     

    2010年4月14日 4:09
  • UserControl内のTextBoxのインスタンスを外部から取得したいのなら

    そのプロパティを用意しておけばいいと思います。

    外からのアクセスを許可することを明示しているわけですから精神衛生上(?)いいと思います。

    // UserControl1内でプロパティを設定
    public TextBox InnerTextBox
    {
        get{return this.TextBox1;}
    }

    もしくは先にあげられていたVisualTreeHelperを使用して取得することもできます。

    // VisualTreeHelperを最後まで取得するメソッドを作ってみる
    private List<DependencyObject> GetChildren(DependencyObject obj)
    {
        List<DependencyObject> list = new List<DependencyObject>();
        int childcount = VisualTreeHelper.GetChildrenCount(obj);
    
        for (int i = 0; i < childcount; i++)
        {
            var child = VisualTreeHelper.GetChild(obj, i);
            list.Add(child);
    
            foreach (var grandchild in this.GetChildren(child))
            {
                list.Add(grandchild);
            }
    
        }
    
        return list;
    
    }
    
    // UserControl内のTextBoxを取得する
    foreach (var textbox1 in this.GetChildren(this.UserControl1).OfType<TextBox>().Where(x => x.Name == "TextBox1"))
    {
        // お好きな処理をどうぞ
    }

    この場合は内部のTextBoxにつけられた名前を知っている必要があるのでどうかな、という気がしますが…

    UserControl内部のインスタンスを取得したいコントロールを一意に識別する方法を考えるよりは

    プロパティを用意するほうが手軽だと思います。

    • 回答としてマーク 山本春海 2010年5月14日 0:50
    2010年4月14日 5:32
  • そもそも論てきなことを言ってしまって悪いのですが何かのインスタンスの配列があって、その件数分縦にTextBoxを並べて、あるプロパティの値を表示したいというときは、ItemsControlを使ってItemTemplateにTextBoxだけもったDataTemplateを作ってTextBoxのTextプロパティとTowWayでバインドします。

    こうしておくと、わざわざコントロールのプロパティからデータを拾わなくても、バインドしてるオブジェクトのプロパティに直接アクセスしてデータを拾ってくることが出来ます。


    かずき Blog:http://blogs.wankuma.com/kazuki/
    2010年4月14日 12:39
  • ありがとうございます。

    >//UserControl1内でプロパティを設定
    なぜ気づかなかったのか・・・かなり簡単に取れるようになりました。

    VisualTreeHelperはメソッドをそのまま使わせていただきました。
    DependencyObject 型というものが今ひとつ良くわかりませんでしたが。


    >var textbox1 in this.GetChildren(this.UserControl1).OfType<TextBox>()
    GetChildrenで子要素を特定してからフィルタを掛けるんですか。

    this.LayoutRoot1.Children.OfType<UserContorol1>()で抜き出してから
    自作したプロパティで引っ張ってくるのが今の所一番簡単かな、と思っています。


    ItemControlの使い方がよくわかりません。
        <ItemsControl x:Name="ic">
          <ItemsControl.ItemTemplate>
            <DataTemplate>
              <StackPanel x:Name="st">
                <TextBlock x:Name="a" Text="{Binding Path=TaskName}" />
                <TextBlock x:Name="b" Text=""/>
              </StackPanel>
            </DataTemplate>
          </ItemsControl.ItemTemplate>
        </ItemsControl>

    DataTemplateの内容をItemControlにAddしていくイメージなんでしょうか?


    2010年4月15日 3:02
  • <ItemsControl x:Name="ic">
     <ItemsControl.ItemTemplate>
     <DataTemplate>
      <TextBox Text="{Binding TaskName, Mode=TwoWay}" />
     </DataTemplate>
     </ItemsControl.ItemTemplate>
    </ItemsControl>

    のようにしておいて

     

    // データを表示するところ
    var tasks = ... 配列かリストあたりを取得 ...;
    // ItemsControlにデータを表示
    ic.ItemsSource = tasks;
    
    // データを取得するところ
    foreach (var task in tasks)
    {
     // TextBoxの中身がTaskNameに設定されてる
     MessageBox.Show(task.TaskName);
    }
    とすれば、多分VisualTreeをいじくったりしなくても良いような気がするのですがどうでしょうか?

     


    かずき Blog:http://blogs.wankuma.com/kazuki/
    • 回答としてマーク 山本春海 2010年5月14日 0:50
    2010年4月15日 15:23
  • ありがとうございます。

    テンプレートを用意し、Lsitの数分テンプレート内の要素を増やしていくイメージですね。

     

    > ... 配列かリストあたりを取得 ...;

    List<String> Array = new List<String>();

    Array.Add("あ");

    Array.Add("い");

    var tasks = Array;

    という形で試してみましたが、tasksにはTaskNameプロパティが存在せずエラーとなりました。

    適当なクラスを作成してみましたがこちらもうまくいきませんでした。

    private class hoge{

    public String TaskName = "あ";

    }

    hoge aaa = new hoge();

    var tasks = aaa;

    ic.ItemsSource = tasks;

    型がcollectionらしいのでキャストは出来ないそうです。

     

    配列かリストあたりとは何のことを示していますか?


    2010年4月16日 6:15
  • おしいです。

    ItemsSourceにはコレクションを入れます。

    クラスを作成したのならそれのリストを入れればうまくいくでしょう。

    上記例なら

    List<hoge> tasks = new List<hoge>();
    
    tasks.add(new hoge());
    
    ic.ItemsSource = tasks;

    とやるといいのでは無いでしょうか?

     

    以下私感ですけど

    長くなってきてますので当初の課題が解決したのなら

    一旦解決として締めて、他の疑問はまた新しく質問をしたほうがいいと思いますよ。

    2010年4月16日 7:07
  • ありがとうございます。

    >他の疑問はまた新しく質問をしたほうがいいと思いますよ。

    要件を追加した時点で新しく質問しなおすべきでしたね。

    今後はそうさせていただきます。

     

    ただ宜しければItemsControl だけ解決させてください。

    >tasks.add(new hoge());

    で無事TextBoxは動的に追加出来たのですが、中身が表示されません。

    List<hoge> tasks = new List<hoge>(); hoge ho = new hoge(); ho.TascName = "1です";

    tasks.Add(ho); ic.ItemsSource = tasks; //バインドに失敗してる?

    TextBoxに「あ」と表示されなかったので初期化後にも「1です」と入れてみたのですが、

    これで実行しても空欄のままでした。

    tasksの中にはちゃんとho.TascName = "1です"が入ってます。

    TextBoxの初期値はどのように追加すればよいでしょうか。

    2010年4月16日 8:52
  • 1.プロパティ名とバインディングマークアップに記述している文字列が一致するか確認して下さい。

    {Binding 〇〇}の〇〇の部分です。

    大文字・小文字を区別しますので注意して下さい。

    2.バインディングのソースに使用するクラスはpublicで宣言して下さい。

    今回の例で言えばhogeクラスをpublicにして下さい。

     

    おそらく上記2点確認していただければよさそうです。

    2010年4月21日 0:35