質問者
静的に標準ライブラリのコントロールをコントロール配列として提供するには

質問
-
いつもお世話になっております。
実現したい機能は独自パネルに貼り付けたコントロールを配列のようにアクセスできることです。
Controlが貼り付けられたときに配列化する機能をCodeDomSerializerで実現できないかと色々と試してみましたが情報も少なくできませんでした。
別の方法がないのかと検討中です。
コントロール配列UserPanel panel1 = new UserPanel();
Button btn1 = new Button();
Button btn2 = new Button();
panel1.Controls.AddRange(new Control[] { btn1, btn2 });
// 以下正常に通ることが望み
Debug.Assert(btn1[0] == btn1, "オブジェクトは別物です。");
Debug.Assert(btn1[1] == btn2, "オブジェクトは別物です。");
Debug.Assert(btn2[0] == btn1, "オブジェクトは別物です。");
Debug.Assert(btn2[1] == btn2, "オブジェクトは別物です。");
以下のURLに参考になりそうなサンプルがありましたが、コンポーネントを呼び出す形なのと、Controlとしてコレクションに追加しているため、キャストが必要なのが不満要素です。
GotDotNet User Sample ControlArray
拡張プロパティなる拡張インデクサを提供でき、かつそれらを提供先コントロールで扱えたらよいんですが、提供元でしかプロパティを呼び出せませんでした。
プロパティでもよいかとも思いましたが、キャストが必要だと好ましくありません
実現は可能なのでしょうか?
泥臭くいコードになってもよいですが、キャストが必要になるような書き方はあまりしたくありませんが、最終手段で考えています。
実現可能な方法をご存じでしたら、教えていただけると幸いです。
よろしくお願いします。
すべての返信
-
レスありがとうございます。
具体的にどのように使えば可能ですか?
一応自分の中で考えられる事は考えてはいるんですが、結局はキャストが必要になるんじゃないかと思いました。
理想的な動作はVSのデザイナ上で標準コントロールや自作コントロールを複数置いた場合に、配列化の機能を追加したいということです。
button1という名前で貼り付けたボタンが作られた場合、その名前で配列みたいに扱いたいのですが、自分の中ではデザイン時のシリアル化を制御する他ないと行き当たりました。
既存の物を拡張したコントロールを作るだけなら難しくはありません。。
ですが、標準コントロールをそのまま扱えるのが望ましいとの要望がありまして・・・・
どんなコードになってもいいので実現できれば良いと思っています。
ふと思ったのですがキャスト以前の問題ですよね・・・
既存のクラスは変更しないでインデクサを追加するって不可能じゃないかと
javascriptみたいなプロトタイプチェーンが可能でしたら好き放題できたんですが orz
可能な手段があるようでした何でもよいので教えていただけると助かります。
よろしくお願いします。 -
他の方も書かれていますが、文章からは要求されていることが曖昧でわかりにくいので、もうすこし文章量や具体例を増やされるとよいのではないかと思います。
最初のコード例にしても btn1 と btn2 という変数が配列であるのか非配列であるのかわからずコンパイルエラーになるようにしかみえません。
CodeDomSerliazer ですが、CodeDomSerializer はコンポーネントをコードに保存する際の動作をカスタマイズできるものですが、保存されるコンポーネントの内容となるコードを追加/変更ができますが、保存先であるストアとなる 変数 をカスタマイズする用途に使えたかは記憶にありません。Form や UserControl の上のコンポーネントを具体的な型を指定して、Code SnippetButton btn1 = this.buttons[1]; // this = Form
TextBox textbox1 = this.textboxes[2]; // this = Formのような形でアクセスするためには、buttons や textboxes のインデクサが自身を保持する型を知らなければならないので、Visual Studio 2005 では IDE が ParameterizedType を理解できないので、ちょっと難しかったと思います。Code SnippetButton btn1 = this.array.Get<Button>(1); // this = FormTextBox textbox1;
this.array.Get(2, out textbox1); // this = Formみたいなかんじで取得時に型を指定するような Generics の使い方であれば、コンポーネントとして構築するのは難しくはないと思います。 -
レスありがとうございます。
理解しづらい説明で済みませんでした。
説明下手でして、うまく表現できません orz
> もうすこし文章量や具体例を増やされるとよいのではないかと思います。
具体的な例を挙げますとVB6のコントロール配列と同等以上の物が欲しいです。
>CodeDomSerliazer ですが、CodeDomSerializer はコンポーネントをコードに保存する際の動作をカスタマイズできるものですが、保存されるコンポーネントの内容となるコードを追加/変更ができますが、保存先であるストアとなる 変数 をカスタマイズする用途に使えたかは記憶にありません。
その親元のシリアライズでもよいのでカスタマイズ出来れば実現できそうなんですが、取得の仕方がまだわかっておりません。
他に情報もないため SharpDevelop のソースコードを見ながら簡易的なフォームデザイナを作ってみようかとも思っています。
説明が下手で大変申し訳ないです。
どうやったら説明がうまくなるのか悩みの種だったり・・・
以上です。
分かりづらい文ですが、どうかよろしくお願いします。
-
レスありがとうございます。
済みませんでした、言葉足らずですね・・・
例えばフォームデザイナ上でボタンを貼り付けた時に以下のコードが生成されると思います。
サンプル1#region Windows フォーム デザイナで生成されたコード
///
/// デザイナ サポートに必要なメソッドです。このメソッドの内容を
/// コード エディタで変更しないでください。
///
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(152, 26);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(87, 65);
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = true;
//
// Form3
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(292, 273);
this.Controls.Add(this.button1);
this.Name = "Form3";
this.Text = "Form3";
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Button button1;理想としているコードは以下のようになることです。
サンプル2#region Windows フォーム デザイナで生成されたコード
/// <summary>
/// デザイナ サポートに必要なメソッドです。このメソッドの内容を
/// コード エディタで変更しないでください。
/// </summary>
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button[1];
this.button1[0] = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// button1[0]
//
this.button1[0].Location = new System.Drawing.Point(152, 26);
this.button1[0].Name = "button1";
this.button1[0].Size = new System.Drawing.Size(87, 65);
this.button1[0].TabIndex = 0;
this.button1[0].Text = "button1";
this.button1[0].UseVisualStyleBackColor = true;
//
// Form3
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(292, 273);
this.Controls.Add(this.button1[0]);
this.Name = "Form3";
this.Text = "Form3";
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Button[] button1;サンプル2のコードでは
button1[0].Text = "サンプル2";
のように記述出来ると思います。
シリアル化以外で上記のような呼び出し方が可能なのでしょうか?
色々調べてみても、最終的にシリアル化に行き着くのですが・・・・
-
レゲレゲ さんからの引用 > もうすこし文章量や具体例を増やされるとよいのではないかと思います。
具体的な例を挙げますとVB6のコントロール配列と同等以上の物が欲しいです。
例としては具体的ですが、説明としては抽象的すぎる書き方の悪例にならっていそうです(苦笑「VB6のコントロール配列」について知らない人からすれば、どのようなものであるかサッパリ理解できませんし、逆に VB6 のコントロール配列を知っている場合であっても、その機能性のうち何が重要であったり必須であったりするかといった認識が異なる場合がありますので、このような書き方は絶対にしないほうがいいです。レゲレゲ さんからの引用 その親元のシリアライズでもよいのでカスタマイズ出来れば実現できそうなんですが、取得の仕方がまだわかっておりません。
他に情報もないため SharpDevelop のソースコードを見ながら簡易的なフォームデザイナを作ってみようかとも思っています。
「親元のうんぬん」というのがよくわかりませんが、前述したように、CodeDomSerializer で修正できる部分は、Code Snippetthis.button1.Property = InitializeValuethis.button1.Method();という部分の、背景を黄色くした部分です。背景を紫色にした部分は CodeDomSerializer からは修正できません。Visual Studio 2003 から 2005 への変更で、紫色の部分をローカル変数にできるようになっていたりしますが、それらはシリアライザではなくデザイナ側から提供される機能です。また、上記のようなコード部分のうち、背景を黄色くした部分のコードは、button1 のクラス(たとえば System.Windows.Forms.Button クラス)のシリアライザが生成する部分ですので、Panel の派生クラスではなく、Panel に配置されうるすべてのコントロールの派生クラスが対象になってしまいますので、現実的な解決策ではないでしょう。前の投稿にも記載しましたが、ボタンを配列にするとしたら、ボタンはボタンで配置しておいて、追加で「配列のように見せるコンポーネント」を別途用意して、Code Snippetprivate 自作コンポーネントButtons buttons;private void InitializeComponent(){// GenerateMember プロパティを false にしてローカル変数として生成させるSystem.Windows.Forms.Button button1 = new System.Windows.Forms.Button();// button1button1.xxx = ...;// button2button1.xxx = ...;// buttonsthis.buttons.Initialize(new System.Windows.Forms.Button[]{ button1, button2, button3, button4 });}// たとえば button3 の Text プロパティの変更this.buttons[2].Text = "Button3";みたいなかんじにするのが、実装的に簡単で、コーディング上も配列であるかのように扱えて便利だと思います。
-
レスありがとうございます。
>例としては具体的ですが、説明としては抽象的すぎる書き方の悪例にならっていそうです(苦笑
>「VB6のコントロール配列」について知らない人からすれば、どのようなものであるかサッパリ理解できませんし、逆に VB6 のコントロール配列を知っている場合であっても、その機能性のうち何が重要であったり必須であったりするかといった認識が異なる場合がありますので、このような書き方は絶対にしないほうがいいです。済みません説明不足ですね orz
何が重要かを考えますと、同じコントロールが存在する場合に、配列へとコードが書き換わることが望ましいです。
通常コピーやコントロールを複数置いた場合、自動的に振られる名前の連番になると思います。
Code SnippetButton button1 = new Button();
Button button2 = new Button();
望んでいるコードは以下のようになることです。
Code SnippetButton[] button = new Button[] { new Button(), new Button() };
各標準コントロールを継承するなりすれば可能ですが、継承せずにそのようなコードをデザイン上から操作したときに生成できないものかと試行錯誤しているのですが、シリアル化処理を取得して、生成されるコードを書き換えるくらいしか思いつきませんでした。
> 「親元のうんぬん」というのがよくわかりませんが、前述したように、CodeDomSerializer で修正できる部分は、
親元だけでは説明になってませんね・・・・
親元は以下のメソッドのことを言いたかったのですが、どれが親元か分からなかったため、そのように書いてしまいました。
Code SnippetCodeDomProvider#GenerateCodeFromMember(CodeTypeMember member, TextWriter writer, CodeGeneratorOptions options);引数に指定されている「CodeTypeMember」を取得&編集できれば、コントロール配列のコードを出力できるんじゃないかと推測してます。
> 前の投稿にも記載しましたが、ボタンを配列にするとしたら、ボタンはボタンで配置しておいて、追加で「配列のように見せるコンポーネント」を別途用意して、
コードまで書いてくださってありがとうございます!
「継承しないでも配列として扱えるように」と言われていまして、その方法で進められません。。
CodeDomなどで動的にソースを生成するなら別なんですが、元あるコントロールを継承しない方法で模索しています。