- PR -

ユーザーコントロールのプロパティ自動設定

投稿者投稿内容
マリン
常連さん
会議室デビュー日: 2006/05/28
投稿数: 41
投稿日時: 2006-05-29 16:25
VB2005でユーザーコントロールの設計として例えば以下のようになっているものとします。
コード:
Public Class UserControl1
    Private _AProp As Integer
    Private _BProp As Integer

    <DefaultValue(0)> _
    Public Property AProp() As Integer
        Get
            Return _AProp
        End Get
        Set(ByVal value As Integer)
            _AProp = value
        End Set
    End Property

    <DefaultValue(0), RefreshProperties(RefreshProperties.All)> _
    Public Property BProp() As Integer
        Get
            Return _BProp
        End Get
        Set(ByVal value As Integer)
            _BProp = value
            If value < 0 Then _AProp = 1
        End Set
    End Property
End Class


要約しますとAPropとBPropの2つのプロパティがあり、BPropに負の値がセットされた場合にはAPropに1をセットすることが多いためBPropのセット時にAPropへ1を自動セットする、といった感じです。
APropとBPropは厳密な関連性があるわけではなく、このコントロールを使用するユーザーの利便性を考慮して自動セットしたいだけなので、先にBPropに負の値をセットして自動的にAPropが1になってから、手動でAPropの値を変えてもよいという風にしたいと思っています。
DateTimePickerでCustomFormatが入力されたら自動でFormatプロパティをCustomにするようなイメージと言った方がわかりやすいでしょうか?
あるいはVB6時代のFormもBorderStyleを既定の「2 - 可変」から「1 - 固定(実線)」などに変更するとMaxButtonやMinButtonが自動でFalseになりますが、MinButtonだけTrueに戻すことも可能でしたよね?そんな感じです。

APropとBPropの名前が逆転(APropの値に応じてBPropを初期化する)していれば希望通りの動作をしてくれるのですが、デザイナが自動生成するInitializeComponent内のコードは下記のようにアルファベット順(?)でプロパティを設定するようなのでいったんAPropに手動設定した値がセットされるものの直後のBProp設定によって初期化される、という風になってしまいます。
コード:
    Me.UserControl11.AProp = 5
    Me.UserControl11.BProp = -1


デザイナが自動生成するプロパティ設定の順番を操作できるような属性がないか探したのですがそれらしいものが見つかりませんでした。
これを実現する何かよい方法がありましたら教えてください。
R・田中一郎
ぬし
会議室デビュー日: 2005/11/03
投稿数: 979
投稿日時: 2006-05-29 23:27
ちょっとややこしくて、僕も混乱しているのですが。

要するに、あるプロパティをデザイナでセットしたら、これに関連して他のプロパティの値が自動的に切り替わる。しかし、その後、直接プロパティの値を変更することもできると理解しました。

プロパティの記述される順番を変える方法は僕もわからないのですが。

良い方法というよりは、細工方法として似たような処理を作ったことがあります。
それは、初期値(0やnull)を指定しておいて、その場合は、元となるプロパティの値から一定の規則に従った値が指定されているものと同じ動作をするというものです。
値が指定されていれば、その値に従った処理をします。
マリン
常連さん
会議室デビュー日: 2006/05/28
投稿数: 41
投稿日時: 2006-05-30 13:08
ご回答ありがとうございます。

引用:

R・田中一郎さんの書き込み (2006-05-29 23:27) より:

ちょっとややこしくて、僕も混乱しているのですが。

要するに、あるプロパティをデザイナでセットしたら、これに関連して他のプロパティの値が自動的に切り替わる。しかし、その後、直接プロパティの値を変更することもできると理解しました。



ご理解いただいた通りです。なかなかうまく説明できなくてすみません。
具体的に作ろうとしていたのはこれもよくあると思うのですが数値のみ入力可能なTextBoxを継承したコントロールで拡張プロパティとしてTypeとDisplayFormat、Min/MaxValueといったプロパティなどを追加してあります。
(前回のファンクションキーコントロールに続いてこれもInputManではNumberコントロールとして存在するものの簡易版です(笑))
Typeは入力制限に関するプロパティで整数型(数値と+-,のみ入力可)と小数型(さらに.を入力可)を選択でき、DisplayFormatはLeaveイベントで自動フォーマットされる書式を設定できるという設計にしました。
整数型の場合は"#,##0"、小数型の場合は"#,##0.0"であることがほとんどなのでTypeプロパティを変更した際に自動でこれをDisplayFormatに設定し、やっぱりカンマはいらないとか小数部2桁にしたい、といった場合にはDisplayFormatを書き換えればよいというものです。
このためInitializeComponent内ではアルファベット順的にDisplayFormatがまず設定されてからTypeが設定され、DisplayFormatが初期化されてしまうという現象になっておりました。

その後も色々探していてDesignModeというプロパティがあるのを知り
コード:
    Public Property BProp() As Integer
        Get
            Return _BProp
        End Get
        Set(ByVal value As Integer)
            _BProp = value
            If value < 0 AndAlso DesignMode Then _AProp = 1
        End Set
    End Property


というように条件に加えてみたところ、InitializeComponentの例の部分が処理されるタイミングではDesignModeがFalseなので_APropが自動初期化されないようになり、うまくいったかのように見えたのですが…。
このユーザーコントロールを配置したデバッグ用フォームを起動・終了させてデザイナ画面に戻ってくると、プロパティグリッドでは自動で切り替わった値が表示(AProp=1, BProp=-1)され、しかしInitializeComponent内はAProp=5、BProp=-1と手動で設定した内容が保たれており、同期のとれていないおかしな状態になってしまいました。

引用:

R・田中一郎さんの書き込み (2006-05-29 23:27) より:

良い方法というよりは、細工方法として似たような処理を作ったことがあります。
それは、初期値(0やnull)を指定しておいて、その場合は、元となるプロパティの値から一定の規則に従った値が指定されているものと同じ動作をするというものです。
値が指定されていれば、その値に従った処理をします。



上記の具体例ですとDisplayFormatが空だった場合にはTypeに応じた書式が自動で使われる仕組みにする、ということですね。
ただ、そうしますとDisplayFormatを空にしたい(自動フォーマットしなくてよい)場合に困ってしまいます。
(自動フォーマットするかどうか、というプロパティをさらに追加すればいいことなんでしょうけど…)
また、仮に自動設定されるプロパティとしてBoolean型のものを追加したとすると、プロパティグリッド上ではFalseとなっているのに実行時・内部的には勝手にTrueとして扱われるというようなことになり、空かそうでないかといった見た目に比べてかなり違和感がありそうな気がします。

最初の投稿で挙げたようにVB6のFormではBorderStyleを変えることでMax/MinButtonがFalseになったりしていましたが、VB2005ではFormBorderStyleをFixedSingleなどに変えてもMaximize/MinimizeBoxがFalseになったりしてくれないようなので、あまりやらないものなのでしょうか…?
設定を忘れてタイトルバーをダブルクリックしたらショボいウィンドウになったり…しません?(笑)
じゃんぬねっと
ぬし
会議室デビュー日: 2004/12/22
投稿数: 7811
お住まい・勤務地: 愛知県名古屋市
投稿日時: 2006-05-30 14:44
質問の意味を理解できていないかもしれませんが、
ISupportInitialize インターフェイスが必要ということでしょうか?

_________________
C# と VB.NET の入門サイト
じゃんぬねっと日誌
マリン
常連さん
会議室デビュー日: 2006/05/28
投稿数: 41
投稿日時: 2006-05-30 18:43
ご回答ありがとうございます。

引用:

じゃんぬねっとさんの書き込み (2006-05-30 14:44) より:

質問の意味を理解できていないかもしれませんが、
ISupportInitialize インターフェイスが必要ということでしょうか?



すみません、ISupportInitializeをキーワードに検索してみたのですがどのように使えばよいのかよくわかりませんでした。
以前にじゃんぬねっと様のホームページからExTextBoxをDLして試用させていただいたことがあったのですが、都合により使用が許可されなかったので自分で簡易版を作っているところです(泣)
あのExTextBoxでもAvailableTypeをIntやValueに設定した時点で自動的にFormatを設定してくれる設計になっていましたよね?
私もそれを真似て「AvailableType」「Format」というアルファベット順のプロパティ名にしておけばよかった(?)のに「DisplayFormat」「Type」としてしまったせいで苦労しております。
ExTextBoxではAvailableTypeよりもアルファベット順的に前になるプロパティがなかったと思いますが、もしあった場合に私が遭遇しているような現象を回避するような処理が何か組み込まれていましたらその方法を教えていただけるとうれしいです。
じゃんぬねっと
ぬし
会議室デビュー日: 2004/12/22
投稿数: 7811
お住まい・勤務地: 愛知県名古屋市
投稿日時: 2006-06-04 21:49
返事が遅くなりました。(*_ _)

引用:

マリンさんの書き込み (2006-05-30 18:43) より:

あのExTextBoxでもAvailableTypeをIntやValueに設定した時点で自動的にFormatを設定してくれる設計になっていましたよね?


今、これを見たら、何となくわかったような気がします。
思いつきですが、以下のようにされてはいかがでしょう。

UserControl1 クラスに bool 型の initializeFlag というプライベート メンバを持つ。
OnHandleCreated メソッドをオーバーライドして、このフラグを true にする。
(基底クラスの OnHandleCreated メソッドの呼び出しは、最初に行う)

BProp プロパティの Setter で、セットしようとする値と現在の値が、同じ値の場合は無視するようにする。

# 他のプロパティも、そうしておくことが望ましいです。
# 将来、[プロパティ名] + Changed イベントが実装されるかもしれませんから。

セットしようとする値と、現在の値が違う値の場合は、まず、プロパティ変数 (実体) に値をセットする。
その後、DesignMode でない場合かつ、ハンドルが生成されていない場合は、
条件による (value < 0 の時の) AProp への値の強制設定 (AProp = 1) は行わない。

引用:

<DefaultValue(0), RefreshProperties(RefreshProperties.All)> _
Public Property BProp() As Integer


関係のないところですが、RefreshProperties 属性の値は Repaint で良いです。

_________________
C# と VB.NET の入門サイト
じゃんぬねっと日誌
マリン
常連さん
会議室デビュー日: 2006/05/28
投稿数: 41
投稿日時: 2006-06-05 16:53
じゃんぬねっとさん、ご回答ありがとうございます。
前回ファンクションキーコントロールに関する質問で教えていただいたように、ここでもOnHandleCreatedが判断材料として使えたのですね。
早速、以下のように修正してみたところ、デザイン時以外では初期化されないようになりました。

コード:
Public Class UserControl1
    Private _initFlag As Boolean
    Private _AProp As Integer
    Private _BProp As Integer

    '----省略----

    <DefaultValue(0), RefreshProperties(RefreshProperties.Repaint)> _
    Public Property BProp() As Integer
        Get
            Return _BProp
        End Get
        Set(ByVal value As Integer)
            If _BProp = value Then Return
            _BProp = value
            If DesignMode AndAlso _initFlag AndAlso value < 0 Then
                _AProp = 1
            End If
			'ここにChangedイベント発生を実装可能
        End Set
    End Property

    Protected Overrides Sub OnHandleCreated(ByVal e As System.EventArgs)
        MyBase.OnHandleCreated(e)
        _initFlag = True
    End Sub
End Class


じゃんぬねっと
ぬし
会議室デビュー日: 2004/12/22
投稿数: 7811
お住まい・勤務地: 愛知県名古屋市
投稿日時: 2006-06-05 17:29
引用:

マリンさんの書き込み (2006-06-05 16:53) より:

早速、以下のように修正してみたところ、デザイン時以外では初期化されないようになりました。


解決できたようで良かったです。

細かいところですが、Changed イベントの実装位置は、_BProp = value 直後が望ましいです。

コード:

    Public Class UserControl1 : Inherits System.Windows.Forms.UserControl

      #Region " プライベート フィールド "

        Private initializeFlag As Boolean

      #End Region

      #Region " イベント定義 "

        Public Event APropChanged(ByVal sender As Object, ByVal e As System.EventArgs)
        Public Event BPropChanged(ByVal sender As Object, ByVal e As System.EventArgs)

      #End Region

      #Region " AProp プロパティ "

        Private _AProp As Integer

        <DefaultValue(0)> _
        Public Property AProp() As Integer
            Get
                Return Me._AProp
            End Get

            Set
                If Me._AProp = Value Then
                    Return
                End If

                Me._AProp = Value
                RaiseEvents APropChanged(Me, New System.EventArgs())
            End Set
        End Property

      #End Region

      #Region " BProp プロパティ "

        Private _BProp As Integer

        <DefaultValue(0), RefreshProperties(RefreshProperties.Repaint)> _
        Public Property BProp() As Integer
            Get
                Return Me._BProp
            End Get

            Set
                If Me._BProp = Value Then
                    Return
                End If

                Me._BProp = Value
                RaiseEvent BPropChanged(Me, New System.EventArgs())

                If Me.DesignMode AndAlso Me.initializeFlag Then
                    If Value < 0 Then
                        Me.AProp = 1
                    End If
                End If
            End Set
        End Property

      #Region " OnHandleCreated メソッド (Overrides)

        Protected Overrides Sub OnHandleCreated(ByVal e As System.EventArgs)
            MyBase.OnHandleCreated(e)
            Me.initializeFlag = True
        End Sub

      #End Region

    End Class


今の実装で APropChanged イベントが追加された場合、
BProp の変更によって AProp が変更された時に、
APropChanged イベントが BPropChanged イベントより先に発生するからです。

_________________
C# と VB.NET の入門サイト
じゃんぬねっと日誌

スキルアップ/キャリアアップ(JOB@IT)