tagCANDY CGI VBレスキュー(花ちゃん) の Visual Basic 2010 用 掲示板(VB.NET 掲示板) [ツリー表示へ]   [Home]
一括表示(VB.NET VB2005)
タイトル文字列によるプロパティの参照
記事No10649
投稿日: 2012/01/19(Thu) 14:23
投稿者ふつうのこ
ご無沙汰しています。
ユーザーコントロールから別のフォーム上のコントロールのプロパティを
読みたいのですが可能ですか?
別フォームではあるものの同一アプリのフォームです。

同じフォーム上のコントロールなら、コントロールコレクションから文字列で取って来れる
のをネット上で探し当てましたが、ユーザーコントロールゆえにフォームが決まっていないので
別フォーム名を書いても仕方ありません。(同一フォームならParent句で何とかなるかも)

そのユーザーコントロールは各フォーム上にあり、読みたいプロパティもそのユーザーコント
ロールのプロパティです。
何か方法はないでしょうか。

環境は、Win2000 & VB2005です。

[ツリー表示へ]
タイトルRe: 文字列によるプロパティの参照
記事No10650
投稿日: 2012/01/20(Fri) 10:44
投稿者魔界の仮面弁士
> ユーザーコントロールから別のフォーム上のコントロールのプロパティを
> 読みたいのですが可能ですか?
可能ですが、そもそも何故、そのような動作が必要になっているのでしょうか?


> 同一フォームならParent句で何とかなるかも
Parent 句というのは、ParentForm プロパティ(UserControl / Form 等)や
Parent プロパティ(Control / UserControl / Form 等)のことでしょうか?


> コントロールコレクションから文字列で取って来れる
Modifiers = Private や GenerateMember = False なコントロールさえにも
アクセスできてしまうため、Controls 経由での直接操作はあまりおすすめしません。

ただ、フォームにしても Application.OpenForms でフォームを列挙できますので、
そうした良し悪しを抜きにすれば、そこからアクセスすることは一応可能です。

Private Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs) Handles Button1.Click
    DataGridView1.AllowUserToAddRows = False
    DataGridView1.AllowUserToDeleteRows = False
    Dim ff As New List(Of Form)()
    For Each f As Form In Application.OpenForms
        If f IsNot Me Then
            ff.Add(f)
        End If
    Next
    DataGridView1.DataSource = ff
End Sub

Private Sub DataGridView1_RowEnter(ByVal sender As Object, ByVal e As DataGridViewCellEventArgs) Handles DataGridView1.RowEnter
    Dim f As Form = TryCast(DataGridView1.Rows(e.RowIndex).DataBoundItem, Form)
    If f IsNot Nothing Then
        DataGridView2.DataSource = f.Controls
    End If
End Sub

もっとも、どれが処理対象のフォームなのかを特定する必要はありますし、
コントロールによって、プロパティの読み取り方もまちまちです。なので、
せめて Controls から操作するのではなく、値を返すための Interface を
独自に用意しておき、それを Implements したフォームのみを
操作するといった、タイプセーフな仕様を決めておいた方が良いでしょう。


> そのユーザーコントロールは各フォーム上にあり、読みたいプロパティも
> そのユーザーコントロールのプロパティです。

やろうとしている事の背景が見えてこないので提案しにくいのですが、
「各フォームが値を送りつける方法」、「ユーザーコントロール側から取りに行く方法」、
「仲介となるリスナークラスを通じて通信する方法」、「リフレクションで操作」など、
作り方自体はいろいろあるとは思います。


> 何か方法はないでしょうか。
たとえば:

(案1)ユーザーコントロール側に「値要求イベント」を用意して、
  各フォームがそれに応答させる。

→各フォームは WithEvents ないしは AddHandler を通じて、相手先の
 UserControl の値要求イベントに応答するようにする。そのイベントでは
 GiveFeedback イベントや QueryContinueDrag イベントなどのように、
 EventArgs 継承クラスを通じてプロパティ値を受け渡す。
→イベントではなく、デリゲートを使うパターンもあり。


(案2)必要なデータを一か所に集約して読み書きする。

→中継先となる集約所に、各フォームが値を書き込むようにしておき、
 ユーザーコントロールはそれを読み取る。
→単純なところでは、プロジェクトのプロパティの[設定]タブに
 項目を用意して、My.Settings 経由で読み書きするなど。
→共有変数、DB、ファイル、レジストリなどを中継先とする手もあり。
 読み書きのタイミングが競合しないよう、同時実行制御を考慮しておくこと。

[ツリー表示へ]
タイトルRe^2: 文字列によるプロパティの参照
記事No10652
投稿日: 2012/01/20(Fri) 13:47
投稿者ふつうのこ
回答ありがとうございます。

背景はこうです。
ユーザーコントロールはピクチャボックスだけと言ってもいいのですが、ラベル10個と
ピクチャボックスだけのコントロールです。
そしてこのユーザーコントロールにDB交信やデータプロパティなど、アプリとしての重要機能を持たせています。
こうすればコーディング量も減りますし、サイズも小さくなりますので。

ピクチャボックスには DrawLine や DrawString で描画しているんですが、複数のフォームを
立ち上げている時、他のフォームと比較して描画を変えて欲しいと要望があり、この質問に
いたった訳です。

ユーザーコントロール内の描画コードですからフォーム名を指定して比較することが出来ず、
テキストで他フォームを取得するしかないかな、と思っていたのです。
Parentの事はヘルプに、コンテナを返す としてあったので、自分のフォームは取得出来る
かな、と思ったわけです。
やっていないので分かりませんけど。(^^;)

いろいろ提案していただいたのですが、デリゲートにしてもイベントにしても理解して
いないので提案自体を理解できません、ごめんなさい。(^^;)
とりあえず、Application.OpenForms でやってみます。
ユーザーコントロールは別のソルーションで作っているので認識されるか心配ですが…。

Interfaceを用意するとは、つまり間に仲介を入れるって事ですよね?
なぜ直接では危険になるのでしょう?
パフォーマンスも上がりますし、プロパティを読むだけなの直接ででいいと思うんです。

[ツリー表示へ]
タイトルRe^3: 文字列によるプロパティの参照
記事No10653
投稿日: 2012/01/20(Fri) 22:19
投稿者魔界の仮面弁士
> 文字列によるプロパティの参照
プロパティ名を文字列で指定してアクセスしたい場合は、CallByName を利用できます。


> ユーザーコントロールは別のソルーションで作っているので
公式には、ソルーションではなくソリューションですね。

プロジェクトだけでなく、ソリューションも分かれているのでしょうか?
最初の質問を見て、UserControl も同じEXE内で作られているかと誤読していました。



> デリゲートにしてもイベントにしても理解して
> いないので提案自体を理解できません
たとえば、ユーザーコントロール内で
 Me.Label1.Text = 別フォーム.TextBox1.Text
という代入操作を行いたいとします。

しかし、ユーザーコントロールは別のアセンブリになっているみたいですし、
上記のように、フォームのインスタンスを決め打ちにすることは難しいですよね。


これをイベントを使って実装する場合、たとえばユーザーコントロール側に、
 Public Event RequestText As EventHandler(Of RequestTextEventArgs)
のようなイベント(と RequestTextEventArgs クラス)を用意し、
これを通じてデータを貰うようにします。

データを渡す側となる別フォーム側では、そのイベントに応えるために
 Private WithEvents foo As MyUserControl
 Private Sub foo_RequestText(sender As Object, e As RequestTextEventArgs) Handles foo.RequestText
  e.LabelText = Me.TextBox1.Text
 End Sub
のように記述します。変数 foo には、通信先となるユーザーコントロールの
インスタンスを予めセットしておきます。

そうするとユーザーコントロール側は、いままで
 Me.Label1.Text = 別フォーム.TextBox1.Text
と書いていた(書こうとしていた)部分を
 Dim arg As New RequestTextEventArgs("初期値")
 RaiseEvent RequestText(Me, arg)
 Me.Label1.Text = arg.LabelText
のように差し替えることで、データの受け渡しができます。

用意したイベントが利用されなかった場合には、単に初期値が
表示されるだけなので、別フォームの有無に左右されませんし、
また、データを渡すべきその別フォーム側の処理が複雑化して、
 If CheckBox1.Checked Then
  e.LabelText = Me.TextBox1.Text
 Else
  e.LabelText = Me.TextBox2.Text
 End If
のように修正されることになっても、ユーザーコントロール側を手直しせずに済みます。



> Interfaceを用意するとは、つまり間に仲介を入れるって事ですよね?
こちらの手法も、もう少し具体的に書いてみると:

たとえば、ユーザーコントロール上の PictureBox に渡す画像が、
別フォームの PictureBox から自動転記されるようにするのだとします。

その場合に、
 Public Interface IRequestImage
  Function GetImage() As Image
 End Interface
というインターフェイスを用意し、それを「別フォーム」に Implements して
GetImage メソッドを実装しておきます。

そうすると、OpenForms 経由で取り出す際には、
 For Each f As Form In Application.OpenForms
  Dim obj As IRequestImage = TryCast(f, IRequestImage)
  If obj IsNot Nothing Then
   Me.PictureBox1.Image = obj.GetImage()
  End If
 Next
のように、そのインターフェイスを実装しているかどうかを判断基準に
できますし、値の取り出しもそのインターフェイスを通じて行えます。



> なぜ直接では危険になるのでしょう?
(あれ、危険って書きましたっけ)
まぁ、低レベル操作なので注意は必要でしょうね。

たとえば TextBox1 に対して、デザイナで「Modifiers = Private」に変更した場合、
自フォームからは Me.TextBox1 でアクセスできますが、自フォーム以外からは
「対象フォーム.TextBox1」の構文が使えなくなります。
また、デザイナで「GenerateMember = False」にしていた場合には、
自フォームでさえ Me.TextBox1 でアクセスできなくなります。

しかし Controls 経由だと、それらにもアクセスされてしまいます。

極端な話、外部からコントロールを削除したり追加することさえできるわけで、
フォーム側が想定していなかった動作になってしまうこともあります。もちろん、
そのような使い方はしないと思いますが、カプセル化という点からみると、
あまり積極的には使わない方が良いとは思いますよ。


> プロパティを読むだけなの直接ででいいと思うんです。
たとえば、上記の Interface を使ったコードで、ユーザーコントロール側の
 obj.GetImage()
となっている部分を、直接対象コントロールのプロパティを読み取るように
 DirectCast(fm.Controls("PictureBox1"), PictureBox).Image
に変更したとしても、値を取り出すことはできます。

しかしタイプセーフではないため、指定するコントロール名をスペルミスしても
検出できませんし、コントロールの制御方法がユーザーコントロール側に
委ねられるため、各フォーム側のコントロール構成を変更しずらくなります。

しかし Interface/Event/Delegate 等を通じて処理する方法だと、
コントロールの制御は、情報を提供するフォーム自身に書かれるため、
こうした問題は発生しませんし、別フォーム側の改修があっても、
その修正範囲がユーザーコントロール側にまで及ばずに済みます。


ただ、常にそうした実装が優れているかというと、それはやはりケースバイケースとなります。
データバインド(ListBox の DisplayMember や ValueMember など)などのように、
文字列でのプロパティアクセスが必要になるケースもありますので。



> 他のフォームと比較して描画を変えて欲しいと要望があり
それは、どのような比較処理なのでしょうか。

たとえば、各フォームのコントロール構成や処理内容がほぼ一定であるような場合には、
先の Interface 案や イベント案だと、わざわざ同じ処理を各フォームに
繰り返し実装していくことになるため、かえって面倒になることもあるかと思います。

そうした場合には、ユーザーコントロール側に処理を書くのではなく、
中継役となる『拡張プロバイダー』を用意する手法もあります。
http://msdn.microsoft.com/ja-jp/library/ms171835.aspx

これなら、利用者側はフォームに貼ってデザイン時設定するだけで済みますので、
利用者側の作業を軽減できます。もちろん、最初に中継処理を実装する手間は必要ですが。

[ツリー表示へ]
タイトルRe^4: 文字列によるプロパティの参照
記事No10654
投稿日: 2012/01/21(Sat) 17:44
投稿者ふつうのこ
丁寧な回答と解説、ありがとうございます。
感謝感激です。m(_ _)m

最初は Winアプリを作って、追加→ユーザーコントロール として作ったんですが、なぜか
ツールボックスに追加されず、手動で追加しなきゃいけないのかな?と思って、表示されて
いるフォルダを見てもファイルが無く、諦めて別のソリューションでユーザーコントロールを
作ってから Winアプリを作ったんです。

多分、ファイルは既定のC:\Documents and Settings\...じゃなく D:\VS2005というフォルダ
に保存しているので、それでだろうと思うんですけど。
検索しても uctlrAns.dll(←ユーザーコントロールの名前)というファイルは無かったので。

描画の変更というのは、単に重複している項目は赤の太文字にするだけなんです。
イベントの利用は便利そうなので、そうしようかと思ったのです。
でも、項目が20ほどありまして、相手の項目を一つずつ調べて描画するのでアクセス回数
は都合400回。
いくらなんでも400回もイベントが起きたらパフォーマンス落ちるだろうな、と思ったんです
が、多分オブジェクト丸ごとも受け渡し出来ますよね?

なら、別フォームはイベントを受けたら自分のプロパティをIListにして、そのIListを1回で
渡して、描画はIListを見て行えばいいんじゃないかって思いました。
ただ、uctrlAnsは、全てのフォームで同じ名前を使っているのでIListが複数来る事に
ならないかと心配ではありますが…。

Application.OpenFormsでやってみるって言ったんですが、まだやってません、
すみません。(^^;)

インプリメントするって、フォームを作れば、
Public Class frmMain
    …
End Class
ってなりますが、その後に
Public Interface honyarara
   …
End Interface
って書けばいいんですか?

すぐ手に届きそうな目標には、ガムシャラにのめり込んでいくんですけど、あまりに遠い目標
だと、なんかやるのが億劫になってしまうんです。
それに性格的に、テストしてうまく動作するのを確かめてからでないと、実際の作業に移れな
いんです。
よく言えば慎重、悪く言えば臆病。(^^;)

イベントの事はヘルプなりネットなりでしっかり理解してからやってみます。

[ツリー表示へ]
タイトルRe^5: 文字列によるプロパティの参照
記事No10655
投稿日: 2012/01/21(Sat) 18:49
投稿者魔界の仮面弁士
アプリ仕様が未だにイメージできずにいます…。


> 描画の変更というのは、単に重複している項目は赤の太文字にするだけなんです。
各項目は、どこで管理されているのでしょうか?
各フォーム? ユーザーコントロール? あるいはそれ以外の場所?

それらの値は、一か所で管理されているのでしょうか。
それとも複数の場所に分散しているのでしょうか。

分散しているのだとしたら、それらをかき集めて重複判定を行う処理は
どこが担当しているのでしょうか。


> でも、項目が20ほどありまして、相手の項目を一つずつ調べて描画するのでアクセス回数
> は都合400回。
20項目なら、400回ではなく20回な気がしますが…二乗した回数に
なる理由がイメージできないです。

それに項目が何個あったとしても、値要求イベントの発行回数は
1回にまとめてリクエストすれば良い気がしますが、項目ごとに
個別に分けて発行しないと、何か都合が悪いのでしょうか?


> いくらなんでも400回もイベントが起きたらパフォーマンス落ちるだろうな、と思ったんです
1回あたりの処理コストが示されていませんし、発生頻度にしても
1時間に400回なのか1秒間に400回なのか一回の再描画ごとに400回なのか、
説明されていませんので、パフォーマンスについては判断できかねます。

たとえば、
 Private moveCount As Long = 0
 Private Sub Form1_MouseMove(ByVal sender As Object, ByVal e As MouseEventArgs) Handles Me.MouseMove
  moveCount += 1
  Debug.WriteLine(String.Format("{0} at {1:HH\:mm\:ss\.ffff}", moveCount, Now))
 End Sub
なコードを書いて、フォーム上でマウスカーソルをぐりぐり動かせば、
0.5秒〜10.0秒程度で、400回分のイベント通知に到達しますね。
(マウスの精度にもよりますが)


> が、多分オブジェクト丸ごとも受け渡し出来ますよね?
できますよ。というか、配列や Integer や String なども「オブジェクト」ですよね。
もちろん、独自定義の構造体やクラス、TextBox や Form などもオブジェクトです。

ここでいうオブジェクトというのは、何を指していますか?


> 別フォームはイベントを受けたら自分のプロパティをIListにして、そのIListを1回で
> 渡して、描画はIListを見て行えばいいんじゃないかって思いました。
その IList をどう使うのか、大まかなコードを見せてもらえませんか?


> 最初は Winアプリを作って、追加→ユーザーコントロール として作ったんですが、
再利用する予定が無いなら、それで良いと思いますよ。
プロジェクトを分けてしまうと、かえって管理しにくいでしょうし。


> なぜかツールボックスに追加されず、
ツールボックスに追加されるかどうかは、[ツール]-[オプション]設定の
[Windows フォーム デザイナ]-[全般]-[AutoToolboxPopulate] で決まります。

もちろんこの設定に頼らず、自分で手動追加することもできます。


> 手動で追加しなきゃいけないのかな?と思って、
> 表示されているフォルダを見てもファイルが無く、
ユーザーコントロールを追加した後で、まだ一度もプロジェクトを
ビルドしていない場合には、そもそもツールボックスに載せられません。

ビルドした後だとすれば、ツールボックスを右クリックして
[アイテムの選択...]からファイルを選べます。

VB2005 の場合、ビルド結果の EXE ファイルが
 プロジェクトのフォルダ\bin\Debug\
 プロジェクトのフォルダ\bin\Release\
の下に出来るはずなので、そこから拾ってください。

なお、この出力先はプロジェクトのプロパティの
[コンパイル]タブの[ビルド出力パス]で変更可能です。


> 検索しても uctlrAns.dll(←ユーザーコントロールの名前)というファイルは無かったので。
無いと思いますよ。DLL プロジェクトを作成したわけではありませんから。

自プロジェクト内にユーザーコントロールを追加しているのですから、
WindowsApplication1.exe などの、自EXEそのものを選択してあげましょう。


> インプリメントするって、フォームを作れば、
Interface の定義は、ユーザーコントロールの DLL にて行ってください。

それを参照している EXE 側では、通信相手となるフォームに
『Implements ステートメント』を記述します。

Public Class Form1
    Implements IHoge

あとは IHoge のメソッドの中身を書き加えて完成です。



> ただ、uctrlAnsは、全てのフォームで同じ名前を使っているので
名前が何故関係するのか分からないのですが、それはともかくとして。

Form1.uctrlAns1 が、Form2.uctrlAns1.Label1.Text を参照し、
Form2.uctrlAns1 も、Form1.uctrlAns1.Label1.Text を参照するのでしょうか?

それとも
Form1.uctrlAns1 が、Form2.Label1.Text を参照し、
Form2.uctrlAns1 が、Form1.Label1.Text を参照するのでしょうか?

データの参照構造が今一つイメージできないのですが、互いに通信しあうのならば、
やはり「中継クラス」を作ってデータを管理した方が良い気がします。

[ツリー表示へ]
タイトルRe^6: 文字列によるプロパティの参照
記事No10656
投稿日: 2012/01/23(Mon) 10:12
投稿者ふつうのこ
直ぐに回答してくださってたんですね、ありがとうございます。m(_ _)m

項目は、uctrlAns のプロパティP1…P20…として管理しています。
ですので、各フォームにこれらのプロパティがある訳です。
重複判定は uctrlAns に付けようと思っています。
比較対象は、フォーム生成元のフォームだけです。
また、ラベルは色の表示用に使っているだけなので全く関係ありません。
以後、生成元フォーム→form1、生成フォーム→form2 とします。

form2 の P1 を項目名を描画する際、form1.uctrlAns.P1…P20を調べなくてはならないので
20回のアクセス。
それが form2 の P20まであるのですから400回のアクセスが必要になると思っていた訳です。
今にして思えば、フォーム生成時にform1のプロパティを渡してやれば良いですね。
質問時には描画時にアクセスする事しか頭に無かったもので。(^^;)

それでもイベントの理解はやれる事が広がるような気がしますので、取り急ぎフォーム生成時
に渡す方法で今は対応するとしますが、ゆくゆくはイベントでの取得にしたいです。

で、その渡すオブジェクトの事なんですが。
IList はインターフェイスの事なんですね。(^^;)
私が思っていたのは、インデックスではなく、項目名を指定して値が取得できる配列です。
Perl にそのような配列があったのは覚えていますが、VBにもあったような気が…。
まあ、項目の順番を覚えていればいいだけなんですけど。
重複判定のメソッドはまだ作っていないので、コードはまだ無いです。(^^;)

ツールボックスには、EXE からもユーザーコントロールを見つけ出して追加してくれるんですね。
dll が別途生成されるものだと思っていました。

form1 にリストボックスがありまして、それで表示品名を変えれるようになっています。
そのリストボックスのチェンジイベントで uctrlAns をリフレッシュしています。
今のところ、form2.uctrlAns が form1.uctrlAns.P1P…P20 を参照するだけですが、ゆくゆく
は、イベントで双方向に出来ればいいなって思っています。
必要無いかもしれませんが。(^^;)

[ツリー表示へ]
タイトルRe^7: 文字列によるプロパティの参照
記事No10657
投稿日: 2012/01/23(Mon) 15:34
投稿者魔界の仮面弁士
> 直ぐに回答してくださってたんですね、ありがとうございます。m(_ _)m
質問内容の把握が追いついていないので、回りくどい回答に
なっている可能性もありますがご容赦を…。


> 比較対象は、フォーム生成元のフォームだけです。
(中略)
> 以後、生成元フォーム→form1、生成フォーム→form2 とします。
「フォーム生成元のフォーム」について確認させてください。最初の質問は、
『ユーザーコントロールから別のフォーム上のコントロールのプロパティを読みたい』
でしたよね。

フォームの表示処理は、
  Dim 子 As New フォーム型()
  子.Show(親)
または、
  Using 子 As New フォーム型()
    子.ShowDialog(親)
  End Using
などとして呼び出しているものと推測しますが、ここでいう form1 というのは、

 (1) 「New でフォームを生成する処理」が書かれているフォーム

 (2) 「Show/ShowDialog での呼び出し処理」が書かれているフォーム

 (3) 子フォームのオーナー(上記でいうところの [親]の部分)

のうち、いずれを指しているのでしょうか。(1=2=3 な場合もありますが)
(フォーム生成元というだけあって、1 の事なのかな…)



> プロパティP1…P20…として管理しています。
それぞれのプロパティに関連性があるのですよね?

同種のものであれば、
 (1) foo.P(1) などとアクセスできるよう「引数付きプロパティ」にまとめる。
 (2) 配列を返すプロパティを追加する
 (3) コレクションを返すプロパティを追加する
なども用意しておくと便利かも。


> 重複判定は uctrlAns に付けようと思っています。
ごめんなさい、何と何の重複を判定するのか、まだ把握できていません。

(1) 別々のコントロールの同名プロパティの値重複。
たとえば、Form1.uctrlAns1.P1 と Form2.uctrlAns1.P1 と Form3.uctrlAns1.P1 を比較。

(2) 別々のコントロールの全プロパティの値重複。
Form1.uctrlAns1.P1〜20 と Form2.uctrlAns1.P1〜20 と Form3.uctrlAns1.P1〜20 を比較。

(3) 同一フォーム上の同一コントロール内での全プロパティの値重複。
Form1.uctrlAns1.P1 と Form1.uctrlAns1.P2 と Form1.uctrlAns1.P3 を比較。

(4) 同一フォーム上の別々のコントロール上の同名プロパティの値重複。
たとえば、Form1.uctrlAns1.P1 と Form1.uctrlAns2.P1 と Form1.uctrlAns3.P1 を比較。

など。理解が覚束なくて申し訳ない…。


> form2 の P1 を項目名を描画する際、form1.uctrlAns.P1…P20を調べなくてはならないので
> 20回のアクセス。
データをコレクション等で管理しておけば、プロパティを分けずとも、
一度に値を受け渡せるようになるかと思います。

プロパティ自体は分けておきたいのだとしても、たとえば
 Public GetPValues(ByRef p1 As foo, ByRef p2 As foo, …)
という 20 個の出力引数(ByRef)を持つメソッドを通じて値を受け取らせたり、
あるいは――
 「Public P(1 To 20) As 何某」な配列とか、
 「20行のレコードを持った DataTable」とか
 「20列のレコードを持った DataTable」とか
 「P1〜P20 というプロパティを持つクラスや構造体」
――を返すプロパティを用意するという手もあります。

DataTable にしておけば、データの並び替えや条件検索も
DataView を通じて容易に行えるというメリットがあります。

これが VB2008 なら、Linq を使ってさらに簡単にできるのですけれどね。


> 私が思っていたのは、インデックスではなく、項目名を指定して値が取得できる配列です。
VB だと、Dictionary(Of TKey, TValue) クラスが該当します。
あるいは DataTable とか。

'コレクションの宣言
Dim col As New Dictionary(Of String, Date)()

'データと名前を追加
col.Add("P1", Now)  'Add メソッドでの追加
col("P5") = Today   'インデクサでの新規追加
'col.Add("P5", Now) '同じ名前での重複登録はNG

'名前による取得
ListBox1.Items.Add(col("P1"))
ListBox1.Items.Add(col!P5)      'ディクショナリ アクセス演算子での呼び出しも可
'ListBox1.Items.Add(col("P0"))  '未登録の名前を参照するのは NG


'値の書き換えも可能
col("P1") = #1/23/2012 3:00:00 PM#

'名前の削除もできる
col.Remove("P0")    '既存の名前を指定して削除(戻り値 True)
col.Remove("P9")    '未登録の値でも削除はできる(戻り値 False)

'名前が存在しているか?
If Not col.ContainsKey("P0") Then
    MsgBox("キーP0は未登録です")
End If

'For Each での列挙が可能
For Each p As KeyValuePair(Of String, Date) In col
    ListBox2.Items.Add(p.Key)
    ListBox3.Items.Add(p.Value)
Next


> ゆくゆくは、イベントで双方向に出来ればいいなって思っています。
双方向なら、データバインドを利用すると便利かもしれません。

データソースと データコンシューマの間で双方向にやりとりできますし、
同じデータを複数個所で参照していれば、それらすべてに変更が通知されますので。


Private tbl As New DataTable()
Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load
    tbl.Columns.Add("P1", GetType(String))
    tbl.Columns.Add("P2", GetType(String))
    tbl.Rows.Add("123", "456")
    tbl.Rows.Add("789", "987")
    tbl.Rows.Add("654", "321")
    tbl.AcceptChanges()

    ListBox1.DataSource = tbl
    ListBox1.DisplayMember = "P1"

    DataGridView1.DataSource = tbl
End Sub

Private Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs) Handles Button1.Click
    tbl.Rows(2)("P1") = "TEST"
End Sub

[ツリー表示へ]
タイトルRe^8: 文字列によるプロパティの参照
記事No10658
投稿日: 2012/01/23(Mon) 20:02
投稿者ふつうのこ
相変わらず丁寧な回答と解説、ありがとうございます。m(_ _)m

現在はまだフォームは2つしかありません。(^^;)
2つともデザイナで作成した Form1、Form2 です。
Form1 が初期表示フォーム、Form2 はForm1.Button1 のクリックで
Form2.Show として呼び出してます。

構想では、Form2 から今度は、Dim frm3 as New Form1 として Form3、Form4
を作るつもりですが、今は Form1.uctrlAns を描画し直しています。
Form3 が作られれば、比較対象は Form3.uctrlAns にしなければなりません。

ですので重複判定は、2 になるかと。なお、比較するのは常に 1つのフォームだけです。

Form 生成に関しては、1 = 2 = 3 になるのが当然だと思っていましたが、
1 != 2 != 3 != 1 にも出来るのですね、初めて知りました。

Dictionary クラスって VB2005 で追加されたんですね。
VB6(VB6 の話をすると歳がばれてしまいますね^^;)の ListBox だか ComboBox だかで
Key と Value の2つを格納出来たので、.NET になってもあるだろうと思っていたんですが、
まさか 2005 まで無かったとは…。

それに Dictionary クラスの名前空間が System.Collections.Generic だなんて、
知らなかったらヘルプで探せる訳が無いですよ。(^^;)
まだ System 名前空間だったら見つけられる可能性もあるんですが、なぜ分ける必要が
あるんでしょうね?
困ってもヘルプじゃ何も解決出来ないって、以前言った意味が分かるでしょう?(^^;)

後、引数の Of ってなんなんですか?
Of って付く事で、使い方に注意しなければならない事とかはあるんでしょうか?

[ツリー表示へ]
タイトルRe^9: 文字列によるプロパティの参照
記事No10659
投稿日: 2012/01/23(Mon) 21:32
投稿者魔界の仮面弁士
> VB6(VB6 の話をすると歳がばれてしまいますね^^;)の
VB6 で言えば、Dictionary オブジェクトに相当しますね。

VB6 時代において、キーと値の組み合わせで管理するものとしては、他にも
Collection オブジェクトや PropertyBag オブジェクトがありました。
少し毛色が違いますが、Recordset なども列名と値(と行)での管理に使えます。


> ListBox だか ComboBox だかで Key と Value の2つを格納出来たので、
Key/Value 格納機能は無かったと思います。もしかして、ActiveX コントロールの
DBListBox / DBComboBox / DataListBox / DataComboBox あたりか、あるいは
「ItemData(Index As Integer) As Long」プロパティのことを仰っているのでしょうか。


> まさか 2005 まで無かったとは…。
List(Of T) は 2005 からの実装ですが、それ以前の .NET バージョンでも
System.Collections.Hashtable を使うことで、連想配列を扱えます。
(List の方が便利なので、現在では Hashtable はあまり使われないですけれどね)


> それに Dictionary クラスの名前空間が System.Collections.Generic だなんて、
> 知らなかったらヘルプで探せる訳が無いですよ。(^^;)
「ジェネリック(Generic)」は .NET 2.0 の目玉機能の一つなので、
登場当時はかなり話題になっていました。特に List クラスは、VB2008 以降でも
良く使われるものなので、これを機に、是非とも習得されることをお奨めします。


なお VB2005 のプロジェクトでは、既定の名前空間として
 Microsoft.VisualBasic
 System
 System.Collections
 System.Collections.Generic
 System.Data
 System.Drawing
 System.Diggonostics
 System.Windows.Forms
が組み込まれています。(プロジェクトのプロパティで確認できます)

覚えなくても良いので、それぞれの名前にどんなクラスが含まれているのかを
ヘルプで眺めておくと、名前空間ごとの役割が見えてくるかと思います。


> まだ System 名前空間だったら見つけられる可能性もあるんですが、なぜ分ける必要が
> あるんでしょうね?
……あれを System 名前空間に置くのはどうかと思いますが、そもそも
ひとつの名前空間に無数のオブジェクトが詰め込まれていったら、
そちらの方が探しにくいと思うのですが――そんなことは無いですか?

ただ、あちこちの名前空間にいろいろな機能があるため、分かりにくいという
ユーザーの声はあったようです。そのため VB2005 では、「My 名前空間」を
用意することになったようですね。(今回の件とは関係ないですけれども)

その My にしても、邪魔だ邪道だとして、元の名前空間管理だけで十分という
ユーザーもいますし……まぁ、いろいろな考え方があるということなのでしょうね。


> 困ってもヘルプじゃ何も解決出来ないって、以前言った意味が分かるでしょう?(^^;)
ごめんなさい。以前言われたかどうかをまったく覚えていないです。(^^;)

 そして、それを解決できるようにするための策が、先の System 名前空間に
 入れるという案だというのであれば、私はそれに拒否の票を投じます。(^^;)


> ヘルプじゃ何も解決出来ない
基礎よりも実践を積み重ねることで覚えていくタイプの人には
ヘルプを読み解くのは辛いみたいですが、逆に座学派にとっては
面白いようです。(私はやや座学派よりかも知れないです)

ヘルプは初見だと難解とは思いますが、それなりの情報量はあるので
「何も解決できない」ほどでは無いはずですし、ある程度読みとけるようになれば、
独学でもなんとかなっていくものですよ。とはいえ、基礎力などの影響も受けるので、
学校の勉強と一緒で、読み解けるようになるまでには個人差が大きく出ますが…。


> 後、引数の Of ってなんなんですか?
これは「ジェネリック」という概念です。Of の後にデータ型を指定します。

たとえば、System.Collections 名前空間の下にある
Hashtable や ArrayList といったコレクションは、各メンバーが
As Object 型であり、どんなデータでも格納できるコレクションです。

たとえば下記では、ArrayList に文字列を格納し、それを取り出しています。

 Dim x As New ArrayList()
 x.Add("あいうえお")
 x.Add("かきくけこ")
 x.Add("さしすせそ")
 x.Add(Now)   '引数が As Object なので、どんな型でも格納できてしまう。
 TextBox1.Text = CStr(x(1)) '「かきくけこ」

しかし、各メンバーが As Object として管理されるため、
取り出し時には元の型に再変換する手間が発生します(上記では CStr)。

文字列のコレクションとしたいなら、As Object ではなく、As String、
Class1のコレクションとしたいなら、As Class1 のみが管理されるよう
「特定の型」で扱えるようになっていた方が便利ですよね。

ですが、だからといって「〇〇型用のコレクション」を逐一作成するのも
効率が悪い話です。そこでジェネリックという仕組みでは、その「型」を、
クラスやメソッドにパラメータとして指定できるようになっています。
その型パラメータの指定に使われるのが、VB では Of というキーワードです。

 Dim x As New List(Of String)()   'String 型のリスト
 x.Add("あいうえお") '引数が As String なので、文字列のみが格納可能。
 x.Add("かきくけこ")
 x.Add("さしすせそ")
 TextBox1.Text = x(1) 'As String なので、CStr が不要。

上記は List(Of T) クラスを、String のコレクションとしています。

また、複数の型パラメータを必要とするクラスもあります。
先に使った Dictionary(Of TKey, TValue) クラスがそれであり、
キーの型と値の型の2つを渡すようになっています。


> Of って付く事で、使い方に注意しなければならない事とかはあるんでしょうか?
慣れてしまえば難しい物でもないので、細かい背景はともかくとして、
そういうものだと割り切って、とにかく使い慣れてみる事ですかね。

試しに ArrayList と List(Of T) を使い比べてみると、おそらくは
後者の方がいろいろと便利に感じると思います。


一応関連情報として、ジェネリックについての資料も紹介しておきますので、
興味があれば、後で目を通してみてください。
http://www.atmarkit.co.jp/fdotnet/special/generics01/generics01_02.html
http://msdn.microsoft.com/ja-jp/library/w256ka79%28vs.80%29.aspx

ついでにこのあたりも。VB2005 の新機能と呼ばれる部分です。
http://msdn.microsoft.com/ja-jp/library/ms172192%28vs.80%29.aspx
http://msdn.microsoft.com/ja-jp/library/y17w47af%28vs.80%29.aspx
http://msdn.microsoft.com/ja-jp/library/we86c8x2%28vs.80%29.aspx

[ツリー表示へ]
タイトルRe^10: 文字列によるプロパティの参照
記事No10660
投稿日: 2012/01/25(Wed) 14:59
投稿者ふつうのこ
毎回、丁寧な回答と解説、ありがとうございます。m(_ _)m

VB6 で確認しましたが、確かに Key は格納できませんね。(^^;)
それまで値しか格納出来ないと思っていたので、それを知った当時はかなり衝撃だったん
ですが…もう 10年近く前の事ですので違った思い込みをしていたようです。

ジェネリック型の事はよく理解できました、ありがとうございます。
VB2005 使い始めの頃、邪魔臭く感じた記憶があります。

Dictionary オブジェクトを使い、Form 表示時や 表示項目変更時に Form2.uctrlAns の
Dictionary のプロパティを変更する事で、値重複時の描画を変更できました。
元凶は、ユーザーコントロールを別ソリューションで作ってしまった事ですね。(^^;)

テストプロジェクトでテストした後、イベント利用してまた作り変えようかと思っています。
Form3, 4, 5 まで表示させるつもりですので。
その際また質問するかもしれないですが、その時はよろしくお願いします。m(_ _)m

[ツリー表示へ]