DOBON.NET DOBON.NETプログラミング掲示板過去ログ

プロパティグリッドにプロパティタブを追加し、PointFを展開するには?

環境/言語:[VS2005]
分類:[.NET]

OS:XP
開発環境:VS2005

いつも拝見させて頂き非常に参考にさせていただいております。

http://msdn.microsoft.com/ja-jp/library/aa302334.aspx#vsnetpropbrow_topic05
現在上記を元にプロパティグリッドを作成してみようと試みています。
例では座標「Point」属性のコレクションを展開しプロパティグリッド上で値を変更できる様なのですが
これを「PointF」属性に変更して試してみたところ座標の参照は出来るのですが、値の変更が行えなくなりました。

「Point」から「PointF」に変更しただけなのですが、これは何が原因で変更できない状態に
なっているのでしょうか?

あれこれ調べてみましたが分からず、ここに質問させて頂きました。
どうかどなたかお教え願えないでしょうか。
型またはプロパティに付けられている TypeConverter 属性によって展開可能か等が判断されます。Point 型には TypeConverter 属性に PointConverter が指定されていますが、PointF 型には TypeConverter 属性が付いていません。

・ExpandableObjectConverter から派生させた PointFConverter 属性を作成し、
 ・CanConvertFrom(ITypeDescriptorContext, Type)
 ・CanConvertTo(ITypeDescriptorContext, Type)
 ・ConvertFrom(ITypeDescriptorContext, CultureInfo, Object)
 ・ConvertTo(ITypeDescriptorContext, CultureInfo, Object, Type)
 の4つのメソッドをオーバーライドして String と PointF との変換を実装する

・PointF 型のプロパティに TypeConverter 属性を付け上記の PointFConverter を指定する

という作業が必要です。
> ・ExpandableObjectConverter から派生させた PointFConverter 属性を作成し、
>  ・CanConvertFrom(ITypeDescriptorContext, Type)
>  ・CanConvertTo(ITypeDescriptorContext, Type)
>  ・ConvertFrom(ITypeDescriptorContext, CultureInfo, Object)
>  ・ConvertTo(ITypeDescriptorContext, CultureInfo, Object, Type)
>  の4つのメソッドをオーバーライドして String と PointF との変換を実装する
>
> ・PointF 型のプロパティに TypeConverter 属性を付け上記の PointFConverter

ご回答誠に有難うございます。
お教えいただいた通り「PointFConverter」を作成し、単一のPointF属性プロパティでは
編集が行えるようになりました。

  <TypeConverter(GetType(PointFConverter))> _
  Public Property PointF() As PointF
    Get
      Return _pointF
    End Get
    Set(ByVal Value As PointF)
      _pointF = Value
    End Set
  End Property

しかし、上記URLの例では、プロパティグリッドのカスタムタブを展開する際に
PointFコレクションを追加しており、<TypeConverter(GetType(PointFConverter))>を
指定する箇所が皆目見当がつきません…。
単一のPointFのプロパティ属性ではなく、下記の様なイメージでコレクションに対しての「TypeConverter」を
指定する方法はまた別にあるのでしょうか?。それともPointFに代わる新たなクラスを作成し、PointFConverterを
指定した物を作成する必要があるのでしょうか。

  <TypeConverter(GetType(PointFConverter))> _
  Public Property Points() As List(Of PointF)
    Get
      Return _points
    End Get
    Set(ByVal Value As List(Of PointF))
      _points = Value
    End Set
  End Property

ご教授頂ければ幸いです。
は、失礼しました。コレクションってとこをすっかり見落としていました。
ということはご参考ページで「代替プロパティビューの提供」となっているところでよろしいですね?
GetProperties メソッドで各 Point のプロパティ記述子に VertexPropertyDescriptor を渡しているのはおわかりだと思います。
で、VertexPropretyDescriptor(の基底クラスの PropertyDescriptor)には virtual なプロパティ Converter が用意されています。これをオーバーライドすることで、そのプロパティが利用する型コンバータを指定できます。
> は、失礼しました。コレクションってとこをすっかり見落としていました。
> ということはご参考ページで「代替プロパティビューの提供」となっているところでよろしいですね?
> GetProperties メソッドで各 Point のプロパティ記述子に VertexPropertyDescriptor を渡しているのはおわかりだと思います。
> で、VertexPropretyDescriptor(の基底クラスの PropertyDescriptor)には virtual なプロパティ Converter が用意されています。
> これをオーバーライドすることで、そのプロパティが利用する型コンバータを指定できます。

Hongliang様のご回答で実現する事が出来ました。本当にありがとうございます。

    Public Overrides ReadOnly Property Converter() As TypeConverter
      Get
        Return New PointFConverter
      End Get
    End Property

もう1点お教え頂ければ幸いなのですが、「X,Y」という形で文字列で表示されるまでは
できました。しかし、今度は「+」をおしてもXとYで区切られて表示がされないのです。
これはまだ Overrides する必要のある Property が存在するのでしょうか。


しかし、このような事がお分かりになるのが本当にすごいです。
1人だと一生進みませんでした…。
ここまで書いた分だけで十分なはずですが、
> 今度は「+」をおしてもXとYで区切られて表示がされない
というのがどういう状況なのか把握できません。
Vertex1 ノードの行を展開したとして、表示されないのは Vertex1 の値でしょうか、その子ノードの X, Y の値でしょうか? あるいは子ノード X, Y の行自体が表示されないのでしょうか、はたまた子ノードが一行だけしか表示されないのでしょうか(どんな行になっているのでしょう)?
>ここまで書いた分だけで十分なはずですが、
> 今度は「+」をおしてもXとYで区切られて表示がされない
>というのがどういう状況なのか把握できません。
>Vertex1 ノードの行を展開したとして、表示されないのは Vertex1 の値でしょうか、
>その子ノードの X, Y の値でしょうか? あるいは子ノード X, Y の行自体が表示されないのでしょうか、
>はたまた子ノードが一行だけしか表示されないのでしょうか(どんな行になっているのでしょう)?

状況と致しましては、「+Verterx0 10,10」等とコレクション分表示されるのですが、左側の+をクリック致しましても
+が消え下の様に展開されないのです。

Verterx0
  X 10
  Y 10

「PointFConverter」では「ExpandableObjectConverter」を継承しているのですが、
これがまずいのでしょうか…?
PointFConverter が GetPropertiesSupported と GetProperties をオーバーライドしてたりしていませんか? ExpandableObjectConverter から派生する限り、これらをオーバーライドする必要はありませんが。
そうでないとなると、PointFConverter の実装を見せていただくのが解決の近道かなと思います。
何度も申し訳御座いません…。
以下、PointFConverterのソースです。

Public Class PointFConverter
  Inherits ExpandableObjectConverter

  'コンバータがオブジェクトを指定した型に変換できるか
  '(変換できる時はTrueを返す)
  'ここでは、CustomClass型のオブジェクトには変換可能とする
  Public Overloads Overrides Function CanConvertTo( _
    ByVal context As ITypeDescriptorContext, _
    ByVal destinationType As Type) As Boolean
    If destinationType Is GetType(PointF) Then
      Return True
    End If
    Return MyBase.CanConvertTo(context, destinationType)
  End Function

  '指定した値オブジェクトを、指定した型に変換する
  'CustomClass型のオブジェクトをString型に変換する方法を提供する
  Public Overloads Overrides Function ConvertTo( _
    ByVal context As ITypeDescriptorContext, _
    ByVal culture As System.Globalization.CultureInfo, _
    ByVal value As Object, _
    ByVal destinationType As Type) As Object
    If destinationType Is GetType(String) And TypeOf value Is PointF Then
      Dim cc As PointF = DirectCast(value, PointF)
      Return cc.X.ToString() + "," + cc.Y.ToString
    End If
    Return MyBase.ConvertTo(context, culture, value, destinationType)
  End Function

  'コンバータが特定の型のオブジェクトをコンバータの型に変換できるか
  '(変換できる時はTrueを返す)
  'ここでは、String型のオブジェクトなら変換可能とする
  Public Overloads Overrides Function CanConvertFrom( _
    ByVal context As ITypeDescriptorContext, _
    ByVal sourceType As Type) As Boolean
    If sourceType Is GetType(String) Then
      Return True
    End If
    Return MyBase.CanConvertFrom(context, sourceType)
  End Function

  '指定した値をコンバータの型に変換する
  'String型のオブジェクトをCustomClass型に変換する方法を提供する
  Public Overloads Overrides Function ConvertFrom( _
    ByVal context As ITypeDescriptorContext, _
    ByVal culture As System.Globalization.CultureInfo, _
    ByVal value As Object) As Object
    If TypeOf value Is String Then
      Dim ss As String() = value.ToString().Split(New Char() {","c}, 2)
      Dim cc As New PointF
      cc.X = Single.Parse(ss(0))
      cc.Y = Single.Parse(ss(1))
      Return cc
    End If
    Return MyBase.ConvertFrom(context, culture, value)
  End Function

End Class
こちらこそ毎度確認不足でどうも済みません。

PropertyTab を使う場合、そのタブのあらゆる要素の子要素の取得は全て PropertyTab.GetProperties で処理されるようです。
つまり、Vertex1 を展開した場合でも PropertyTab.GetProperties で子要素の有無が判断されるわけです。
で、PointsPropertyTab の GetProperties は、component が FunkyButton 以外の場合は直接 TypeDescriptor.GetConverter を呼び出しています。
しかし Vertex1 を展開した場合では component は PointF 型のため、そして PointF に既定の TypeConverter が存在しないために、生の TypeConverter が返されてしまうことになります(生の TypeConverter は GetPropertyDescriptorSupported が false を返すためプロパティを持たない扱いになります)。

PropertyTab.GetProperties の第一引数 ITypeDescriptorContext には 3 つのプロパティがあります。
このうち PropertyDescriptor は、現在の要素の親の GetProperties で取得された PropertyDescriptorCollection の現在の要素に該当する PropertyDescriptor が格納されています(親が存在しない場合は Nothing)。
つまり、Vertex1 を展開したときに呼び出される PointsPropertyTab.GetProperties の context.PropertyDescriptor は VertexPropertyDescriptor になっているということです。
そして PropertyDescriptor には GetChildProperties というおあつらえ向きのメソッドが用意されています。

整理すると、PointsPropertyTab.GetProperties で、
・component が FunkyButton でない場合は context.PropertyDescriptor をチェック。
・context.PropertyDescriptor が Nothing でない場合、この GetChildProperties を呼び出す。引数は GetProperties の component と attr をそのまま渡す。
ということになりますね。

以下余談。調べ物の参考になれば。
恥ずかしながら、当初から FunkyButton というか PropertyTab 周りは全くチェックせず、普通の PropertyGrid で確認していました。
それで巧く動かないというので初めてコードサンプルをダウンロードして動作チェックしたわけです。
確認の手順として、まず PointFConverter の各メソッドが正常に動いているのを MessageBox など使って確認しました(一応 GetProperties などもオーバーライドさせました)。
ここで GetProperties が呼び出されていないことに気づきました。もちろん普通の PropertyGrid だと問題なく呼び出されます。
そこで普通の PropertyGrid ではどのようなルートで呼び出されているのかを確認すべく、GetProperties 内に System.Diagnostics.StackTrace でメソッドの呼び出し履歴を表示させるようにしてみると、PropertyTab.GetProperties が呼び出していることが明らかになりました。
詳細なご説明文まで書いて頂き恐縮です…。
初めは意味がわかりませんでしたが、10回ほど繰り返し読ませて頂き
なんとなく理解できた様な気がします。(今でも怪しいですが)

PointsPropertyTab内のGetPropertiesメソッド内にて、PointFConverterを返す事により
実現出来ました。

      If component.GetType Is GetType(PointF) Then
        tc = New PointFConverter
        Return TypeDescriptor.GetProperties(component, attrs)
      End If

貴重なお時間を割いて頂き、ご回答頂きありがとうございました。
もう少し勉強させて頂き、他にも応用できる様に頑張りたいと思います。
本当にありがとうございました。
解決済み!

DOBON.NET | プログラミング道 | プログラミング掲示板