トップ回答者
PropertyGridの内容を動的に変更

質問
-
PropertyGridの内容を動的に変更する為このフォーラムに載っていた方法
を参考にして下記のようなコードを書いたのですが、うまくいきません。
「PropertyウィンドウまたはPropertyGridの表示内容の動的変更 」
http://forums.microsoft.com/msdn-ja/ShowPost.aspx?PostID=1015197&SiteID=7
Code Snippet[TypeConverter(typeof(XxxxTypeConverter))
public class Xxxx
{
bool bFlag = false;
int iNumber = 10;
[RefreshProperties ( RefreshProperties.All ) ]
public bool Flag
{
get { return bFlag; }
set { bFlag = value; }
}
[BrowsableAttribute(false)]
public int Num
{
get { return iNumber; }
set { iNumber = value; }
}
}
internal class XxxxTypeConverter : TypeConverter
{
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context,Object value,Attribute[] attributes)
{
PropertyDescriptorCollection coll = TypeDescriptor.GetProperties(value, attributes);
if ((value as Xxxx).Flag)
{
PropertyDescriptorCollection nonBrowsableProperties = TypeDescriptor.GetProperties(value, new Attribute[] { BrowsableAttribute.No });
PropertyDescriptor pd = nonBrowsableProperties.Find("Num", false);
coll.Add(pd);
}
return coll;
}
public override bool GetPropertiesSupported(ITypeDescriptorContext context)
{
return true;
}
}coll.Add(pd);の部分でNotSupportedExceptionがスローされてしまいます。
collがreadOnlyになっている為、失敗するようなのですがどうすれば良いでしょうか。
PropertyDescriptorCollectionを作り直しての表示/非表示の動的に変更できたのですが
可能であればDefaultValueAttribute等の属性も扱いたいと思います。
またTypeConverterは使わず
インスタンスの属性(BrowsableAttributeやDefaultValue等)を
リフレクションを使って動的に変更できないかと思いましたが、
こちらも手詰まりです。もしこちらの方法でもお分かりの方
がいればお願い致します。
(それが可能なのかどうなのかもわかりません。)
宜しくお願い致します。
回答
-
danpa さんからの引用 coll.Add(pd);の部分でNotSupportedExceptionがスローされてしまいます。
collがreadOnlyになっている為、失敗するようなのですがどうすれば良いでしょうか。どうも私が動作確認していませんって書いたコードっぽいな(苦笑danpa さんからの引用 PropertyDescriptorCollectionを作り直しての表示/非表示の動的に変更できたのですが
これが正解だと思います。ArrayList や List`1 を利用すれば簡単ですし。danpa さんからの引用 可能であればDefaultValueAttribute等の属性も扱いたいと思います。 BrowsableAttribute と DefaultValueAttribute で大きな違いはないと思います。記述されているコードの BrowsableAttribute の部分を DefaultValueAttribute に変更すればよいだけだと思いますが、何か問題が発生したのでしょうか?# Add の呼び出しを修正した後で、という意味です -
ご回答ありがとうございます。
K.Takaoka さんからの引用 BrowsableAttribute と DefaultValueAttribute で大きな違いはないと思います。記述されているコードの BrowsableAttribute の部分を DefaultValueAttribute に変更すればよいだけだと思いますが、何か問題が発生したのでしょうか?# Add の呼び出しを修正した後で、という意味です
下記コードのようにTypeDescriptor.AddAttributeを使うのかと思ったのですが、意図したようには動きませんでした。
宜しくお願い致します。Code Snippetusing System; using System.ComponentModel; using System.Data; namespace PropertyGridTest { [TypeConverter(typeof(XxxxTypeConverter))] public class Xxxx { bool bFlag = false; int num = 30; [RefreshProperties(RefreshProperties.All)] public bool Flag { get { return bFlag; } set { bFlag = value; } } [DefaultValueAttribute(30)] public int Num { get { return num; } set { num = value; } } } internal class XxxxTypeConverter : TypeConverter { public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, Object value, Attribute[] attributes) { PropertyDescriptorCollection coll = TypeDescriptor.GetProperties(value); PropertyDescriptorCollection ret; Xxxx obj = value as Xxxx; if (obj != null) { int cnt = 0; PropertyDescriptor[] pdArray = new PropertyDescriptor[0]; if (!obj.Flag) { foreach (PropertyDescriptor pd in coll) { if (pd.Name.Equals("Flag")) { System.Array.Resize(ref pdArray, cnt + 1); pdArray[cnt++] = pd; } } ret = new PropertyDescriptorCollection(pdArray, false); } else { foreach (PropertyDescriptor pd in coll) { if (pd.Name.Equals("Flag") || pd.Name.Equals("Num")) { System.Array.Resize(ref pdArray, cnt + 1); pdArray[cnt] = pd; if (pdArray[cnt].Name.Equals("Num")) { // TypeDescriptor.AddAttributesとかも思ったのですがこのコードでは駄目でした //TypeDescriptor.AddAttributes( pd , new Attribute[] { new DefaultValueAttribute(5) } ); } cnt++; } } ret = new PropertyDescriptorCollection(pdArray, false); } // "Num"のDefaultValueAttributeを「5」に書き換えたいのですが方法がわかりません。 return ret; } return base.GetProperties(context, value, attributes); } public override bool GetPropertiesSupported(ITypeDescriptorContext context) { return true; } } }
-
danpa さんからの引用
K.Takaoka さんからの引用 どうも私が動作確認していませんって書いたコードっぽいな(苦笑そういう言い方は無いと思います。いや、元の動かないコードの投稿者は私ですよね、ってことです。
danpa さんからの引用
下記コードのようにTypeDescriptor.AddAttributeを使うのかと思ったのですが、意図したようには動きませんでした。
宜しくお願い致します。TypeDescriptorProvider (AddAttribute) は .NET 2.0 の新機能で、前提条件や用途にあわせて使い方がかわってくるので、ちょっとおいておいたほうがいいかもしれません。TypeDescriptorProvider そのものは、目的を達成する選択肢としては誤ってはいなくて、最初の投稿に書かれているdanpa さんからの引用
またTypeConverterは使わず
インスタンスの属性(BrowsableAttributeやDefaultValue等)を
リフレクションを使って動的に変更できないかと思いましたが、という方向性に近いアプローチになります。読み込み終わったメタデータは改変不可能なので、リフレクションなどを利用して動的に変更することはできませんが、.NET 2.0 から TypeDescriptorProvider を利用することで、違った見え方をするようにできるようになっていると認識しています。(このあたりは実際に触ってみていないので、私の認識が間違っているかもしれないですが)TypeDescriptorProvider はおいといて話を戻して、 TypeConverter を利用する場合なのですが、SimplePropertyDescriptor を生成する TypeDescriptor.CreateProperty() を利用するのが手軽でしょう。Class1 というクラスの Value というプロパティに、表示が有効な BrowsableAttribute と 10 という既定値を設定した DefaultValueAttribute を設定する場合、
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
List<PropertyDescriptor> newProperties = new List<PropertyDescriptor>();foreach (PropertyDescriptor pd in TypeDescriptor.GetProperties(value, attributes))
{
if (pd.Name == "Value")
{
List<Attribute> newAttributes = new List<Attribute>();// TypeDescriptor から得た Value プロパティの属性を複製する
foreach (Attribute attr in pd.Attributes)
{
if ((attr is BrowsableAttribute)
|| (attr is DefaultValueAttribute))
{
// BrowsableAttribute と DefaultValueAttribute は
// 強制的に上書きするため複製の対象外とする
}
else
{
newAttributes.Add(attr);
}
}// BrowsableAttribute と DefaultValueAttribute を追加
newAttributes.Add(BrowsableAttribute.Yes);
newAttributes.Add(new DefaultValueAttribute(10));// 新しい PropertyDescriptor を作成
newProperties.Add(
TypeDescriptor.CreateProperty(typeof(Class1), pd, newAttributes.ToArray())
);
}
else
{
// Value プロパティ以外は何も変更しない
newProperties.Add(pd);
}
}
return new PropertyDescriptorCollection(newProperties.ToArray());
}
のようなかんじになります。# この例はフィルタをそのままバイパスしているので Browsable の部分の動作が期待# したように動かないきもしますが、プロパティの属性を入れ替える例ってことで。 -
詳しいご回答ありがとうございます。
申し訳ありません。私のまったくの誤解でした。
K.Takaoka さんからの引用 という方向性に近いアプローチになります。読み込み終わったメタデータは改変不可能なので、勉強になります。方向が絞れそうです。
参考にしてXxxxTypeConverterのGetPropertiesを作り直してみました。Code Snippetpublic override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, Object value, Attribute[] attributes)
{
PropertyDescriptorCollection coll = TypeDescriptor.GetProperties(value,attributes);
Xxxx obj = value as Xxxx;
if (obj != null)
{
List<PropertyDescriptor> pdArray = new List<PropertyDescriptor>();
foreach (PropertyDescriptor pd in coll)
{
if (pd.Name.Equals("Num") && obj.Flag )
{
List<Attribute> newAttributes = new List<Attribute>();
foreach (Attribute attr in pd.Attributes)
{
if (!(attr is BrowsableAttribute)
&& !(attr is ReadOnlyAttribute)
&& !(attr is DefaultValueAttribute) )
{
newAttributes.Add(attr);
}
}
newAttributes.Add(new BrowsableAttribute(false)); // この変更ではPropertyGridに影響しないようです。
newAttributes.Add(new ReadOnlyAttribute(false));
newAttributes.Add(new DefaultValueAttribute(5));
pdArray.Add(TypeDescriptor.CreateProperty(typeof(Xxxx), pd, newAttributes.ToArray()));
}
else if (pd.Name.Equals("Flag"))
{
pdArray.Add(pd);
}
}
return new PropertyDescriptorCollection(pdArray.ToArray());
}
return base.GetProperties(context, value, attributes);
}K.Takaoka さんからの引用 # この例はフィルタをそのままバイパスしているので Browsable の部分の動作が期待# したように動かないきもしますが、プロパティの属性を入れ替える例ってことで。
Browsableがfalseになっていると
TypeDescriptor.GetProperties(value,attributes);
でそのプロパティを取得できないようですね。
それだけではなくtrueからfalseも無理でした。
何故BrowsableAttributeのみ上手くいかないかはよくわかりません。
自動で入ってくるComVisibleAttributeを無視する等も試したのですが結果は変わりませんでした。 -
danpa さんからの引用 K.Takaoka さんからの引用 # この例はフィルタをそのままバイパスしているので Browsable の部分の動作が期待# したように動かないきもしますが、プロパティの属性を入れ替える例ってことで。
Browsableがfalseになっていると
TypeDescriptor.GetProperties(value,attributes);
でそのプロパティを取得できないようですね。
それだけではなくtrueからfalseも無理でした。
何故BrowsableAttributeのみ上手くいかないかはよくわかりません。
自動で入ってくるComVisibleAttributeを無視する等も試したのですが結果は変わりませんでした。はい。本筋とは関係ないのではしょってしまいましたが、せっかくなので説明してみようかと思います。GetProperties() は、指定した属性をもつプロパティだけを取得するメソッドです。PropertyGrid から「一覧に表示するプロパティをよこせ」と言われるわけですね。この「一覧に表示する」という限定をしているのが、GetProperties の引数 Attribute[] attributes です。# PropertyGrid 以外も相手にする場合、色々な取得が発生する場合がありますTypeDescriptor.GetProperties() も同様の機能を備えているので、PropertyGrid から TypeConverter.GetProperties に引き渡された attributes を、TypeDescriptor.GetProperties() に与えると、「一覧に表示するプロパティ」だけが得られることになります。つまり、元のオブジェクトに、Browsable(false) とメタデータを記載しているプロパティが存在した場合、TypeDescriptor.GetProperties(value, attributes) とやると、そのプロパティを得ることはできません。前スレッドで Browsable.No を指定して TypeDescriptor.GetProperties() を呼び出しているのは「一覧に表示しないプロパティのリストをくれ」という指定なわけですね。以上のようなかんじの動作ですので、ProperyGrid は TypeConverter.GetProperties() から得られた PropertyDescriptorCollection に含まれるプロパティをすべて表示します。(当然ですね? PropertyGrid は、「表示するプロパティの一覧」を取得したと思ってるんですから)つまり、PropertyGrid の一覧から削除したいプロパティは、TypeConverter.GetProperties() の実装で PropertyDescriptorCollection に含めない( add しない ) ように実装すればよいわけです。
すべての返信
-
danpa さんからの引用 coll.Add(pd);の部分でNotSupportedExceptionがスローされてしまいます。
collがreadOnlyになっている為、失敗するようなのですがどうすれば良いでしょうか。どうも私が動作確認していませんって書いたコードっぽいな(苦笑danpa さんからの引用 PropertyDescriptorCollectionを作り直しての表示/非表示の動的に変更できたのですが
これが正解だと思います。ArrayList や List`1 を利用すれば簡単ですし。danpa さんからの引用 可能であればDefaultValueAttribute等の属性も扱いたいと思います。 BrowsableAttribute と DefaultValueAttribute で大きな違いはないと思います。記述されているコードの BrowsableAttribute の部分を DefaultValueAttribute に変更すればよいだけだと思いますが、何か問題が発生したのでしょうか?# Add の呼び出しを修正した後で、という意味です -
ご回答ありがとうございます。
K.Takaoka さんからの引用 BrowsableAttribute と DefaultValueAttribute で大きな違いはないと思います。記述されているコードの BrowsableAttribute の部分を DefaultValueAttribute に変更すればよいだけだと思いますが、何か問題が発生したのでしょうか?# Add の呼び出しを修正した後で、という意味です
下記コードのようにTypeDescriptor.AddAttributeを使うのかと思ったのですが、意図したようには動きませんでした。
宜しくお願い致します。Code Snippetusing System; using System.ComponentModel; using System.Data; namespace PropertyGridTest { [TypeConverter(typeof(XxxxTypeConverter))] public class Xxxx { bool bFlag = false; int num = 30; [RefreshProperties(RefreshProperties.All)] public bool Flag { get { return bFlag; } set { bFlag = value; } } [DefaultValueAttribute(30)] public int Num { get { return num; } set { num = value; } } } internal class XxxxTypeConverter : TypeConverter { public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, Object value, Attribute[] attributes) { PropertyDescriptorCollection coll = TypeDescriptor.GetProperties(value); PropertyDescriptorCollection ret; Xxxx obj = value as Xxxx; if (obj != null) { int cnt = 0; PropertyDescriptor[] pdArray = new PropertyDescriptor[0]; if (!obj.Flag) { foreach (PropertyDescriptor pd in coll) { if (pd.Name.Equals("Flag")) { System.Array.Resize(ref pdArray, cnt + 1); pdArray[cnt++] = pd; } } ret = new PropertyDescriptorCollection(pdArray, false); } else { foreach (PropertyDescriptor pd in coll) { if (pd.Name.Equals("Flag") || pd.Name.Equals("Num")) { System.Array.Resize(ref pdArray, cnt + 1); pdArray[cnt] = pd; if (pdArray[cnt].Name.Equals("Num")) { // TypeDescriptor.AddAttributesとかも思ったのですがこのコードでは駄目でした //TypeDescriptor.AddAttributes( pd , new Attribute[] { new DefaultValueAttribute(5) } ); } cnt++; } } ret = new PropertyDescriptorCollection(pdArray, false); } // "Num"のDefaultValueAttributeを「5」に書き換えたいのですが方法がわかりません。 return ret; } return base.GetProperties(context, value, attributes); } public override bool GetPropertiesSupported(ITypeDescriptorContext context) { return true; } } }
-
danpa さんからの引用
K.Takaoka さんからの引用 どうも私が動作確認していませんって書いたコードっぽいな(苦笑そういう言い方は無いと思います。いや、元の動かないコードの投稿者は私ですよね、ってことです。
danpa さんからの引用
下記コードのようにTypeDescriptor.AddAttributeを使うのかと思ったのですが、意図したようには動きませんでした。
宜しくお願い致します。TypeDescriptorProvider (AddAttribute) は .NET 2.0 の新機能で、前提条件や用途にあわせて使い方がかわってくるので、ちょっとおいておいたほうがいいかもしれません。TypeDescriptorProvider そのものは、目的を達成する選択肢としては誤ってはいなくて、最初の投稿に書かれているdanpa さんからの引用
またTypeConverterは使わず
インスタンスの属性(BrowsableAttributeやDefaultValue等)を
リフレクションを使って動的に変更できないかと思いましたが、という方向性に近いアプローチになります。読み込み終わったメタデータは改変不可能なので、リフレクションなどを利用して動的に変更することはできませんが、.NET 2.0 から TypeDescriptorProvider を利用することで、違った見え方をするようにできるようになっていると認識しています。(このあたりは実際に触ってみていないので、私の認識が間違っているかもしれないですが)TypeDescriptorProvider はおいといて話を戻して、 TypeConverter を利用する場合なのですが、SimplePropertyDescriptor を生成する TypeDescriptor.CreateProperty() を利用するのが手軽でしょう。Class1 というクラスの Value というプロパティに、表示が有効な BrowsableAttribute と 10 という既定値を設定した DefaultValueAttribute を設定する場合、
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
List<PropertyDescriptor> newProperties = new List<PropertyDescriptor>();foreach (PropertyDescriptor pd in TypeDescriptor.GetProperties(value, attributes))
{
if (pd.Name == "Value")
{
List<Attribute> newAttributes = new List<Attribute>();// TypeDescriptor から得た Value プロパティの属性を複製する
foreach (Attribute attr in pd.Attributes)
{
if ((attr is BrowsableAttribute)
|| (attr is DefaultValueAttribute))
{
// BrowsableAttribute と DefaultValueAttribute は
// 強制的に上書きするため複製の対象外とする
}
else
{
newAttributes.Add(attr);
}
}// BrowsableAttribute と DefaultValueAttribute を追加
newAttributes.Add(BrowsableAttribute.Yes);
newAttributes.Add(new DefaultValueAttribute(10));// 新しい PropertyDescriptor を作成
newProperties.Add(
TypeDescriptor.CreateProperty(typeof(Class1), pd, newAttributes.ToArray())
);
}
else
{
// Value プロパティ以外は何も変更しない
newProperties.Add(pd);
}
}
return new PropertyDescriptorCollection(newProperties.ToArray());
}
のようなかんじになります。# この例はフィルタをそのままバイパスしているので Browsable の部分の動作が期待# したように動かないきもしますが、プロパティの属性を入れ替える例ってことで。 -
詳しいご回答ありがとうございます。
申し訳ありません。私のまったくの誤解でした。
K.Takaoka さんからの引用 という方向性に近いアプローチになります。読み込み終わったメタデータは改変不可能なので、勉強になります。方向が絞れそうです。
参考にしてXxxxTypeConverterのGetPropertiesを作り直してみました。Code Snippetpublic override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, Object value, Attribute[] attributes)
{
PropertyDescriptorCollection coll = TypeDescriptor.GetProperties(value,attributes);
Xxxx obj = value as Xxxx;
if (obj != null)
{
List<PropertyDescriptor> pdArray = new List<PropertyDescriptor>();
foreach (PropertyDescriptor pd in coll)
{
if (pd.Name.Equals("Num") && obj.Flag )
{
List<Attribute> newAttributes = new List<Attribute>();
foreach (Attribute attr in pd.Attributes)
{
if (!(attr is BrowsableAttribute)
&& !(attr is ReadOnlyAttribute)
&& !(attr is DefaultValueAttribute) )
{
newAttributes.Add(attr);
}
}
newAttributes.Add(new BrowsableAttribute(false)); // この変更ではPropertyGridに影響しないようです。
newAttributes.Add(new ReadOnlyAttribute(false));
newAttributes.Add(new DefaultValueAttribute(5));
pdArray.Add(TypeDescriptor.CreateProperty(typeof(Xxxx), pd, newAttributes.ToArray()));
}
else if (pd.Name.Equals("Flag"))
{
pdArray.Add(pd);
}
}
return new PropertyDescriptorCollection(pdArray.ToArray());
}
return base.GetProperties(context, value, attributes);
}K.Takaoka さんからの引用 # この例はフィルタをそのままバイパスしているので Browsable の部分の動作が期待# したように動かないきもしますが、プロパティの属性を入れ替える例ってことで。
Browsableがfalseになっていると
TypeDescriptor.GetProperties(value,attributes);
でそのプロパティを取得できないようですね。
それだけではなくtrueからfalseも無理でした。
何故BrowsableAttributeのみ上手くいかないかはよくわかりません。
自動で入ってくるComVisibleAttributeを無視する等も試したのですが結果は変わりませんでした。 -
danpa さんからの引用 K.Takaoka さんからの引用 # この例はフィルタをそのままバイパスしているので Browsable の部分の動作が期待# したように動かないきもしますが、プロパティの属性を入れ替える例ってことで。
Browsableがfalseになっていると
TypeDescriptor.GetProperties(value,attributes);
でそのプロパティを取得できないようですね。
それだけではなくtrueからfalseも無理でした。
何故BrowsableAttributeのみ上手くいかないかはよくわかりません。
自動で入ってくるComVisibleAttributeを無視する等も試したのですが結果は変わりませんでした。はい。本筋とは関係ないのではしょってしまいましたが、せっかくなので説明してみようかと思います。GetProperties() は、指定した属性をもつプロパティだけを取得するメソッドです。PropertyGrid から「一覧に表示するプロパティをよこせ」と言われるわけですね。この「一覧に表示する」という限定をしているのが、GetProperties の引数 Attribute[] attributes です。# PropertyGrid 以外も相手にする場合、色々な取得が発生する場合がありますTypeDescriptor.GetProperties() も同様の機能を備えているので、PropertyGrid から TypeConverter.GetProperties に引き渡された attributes を、TypeDescriptor.GetProperties() に与えると、「一覧に表示するプロパティ」だけが得られることになります。つまり、元のオブジェクトに、Browsable(false) とメタデータを記載しているプロパティが存在した場合、TypeDescriptor.GetProperties(value, attributes) とやると、そのプロパティを得ることはできません。前スレッドで Browsable.No を指定して TypeDescriptor.GetProperties() を呼び出しているのは「一覧に表示しないプロパティのリストをくれ」という指定なわけですね。以上のようなかんじの動作ですので、ProperyGrid は TypeConverter.GetProperties() から得られた PropertyDescriptorCollection に含まれるプロパティをすべて表示します。(当然ですね? PropertyGrid は、「表示するプロパティの一覧」を取得したと思ってるんですから)つまり、PropertyGrid の一覧から削除したいプロパティは、TypeConverter.GetProperties() の実装で PropertyDescriptorCollection に含めない( add しない ) ように実装すればよいわけです。