none
UITypeEditorを派生したクラスで値を変更した場合に、PropertyGridのPropertyValueChangedイベントを呼び出す方法は? RRS feed

  • 質問

  • こんにちは。

    C#2008上でForm上にPropertyGridを作成し、

    とあるプロパティに自作のUITypeEditorクラスをセットしました。

    このUITypeEditorクラス内で値のセットを行った場合に、

    どうにもPropertyGridのPropertyValueChangedイベントが呼び出されないようなのです。

    実際には、このUITypeEditorクラスは複数のプロパティにセットしてあり、

    「どの値が変更されたのか」をPropertyValueChangedイベントで判別しているため、

    PropertyValueChangedイベントを呼び出すか、「どの値が変更されたのか」の通知を行いたいのですが、

    どのように実装すべきでしょうか…?

    環境はVS2008, Windows7 64bitです。

    以下がサンプルコードとなります。(このコードではPropertyValueChangedイベントは呼び出されません)

    class TestData{

            [EditorAttribute(typeof(CustomEditor), typeof(System.Drawing.Design.UITypeEditor))]

            public int ChangeParam
            {
                get { return iChangeParam; }
    set { iChangeParam = value; }
            }

    }

    class EditorCustom : System.Drawing.Design.UITypeEditor
        {
            public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
            {
                return UITypeEditorEditStyle.Modal;
            }
            public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
            {
                //フォームを出してOKが押されたら
                if (1)
                {
                    (int)value += 1;
                }
                return value;
            }
        }

    どなたかうまく呼び出す手順をご存知ありませんでしょうか?

    以上

    宜しくお願い致します。

    2012年7月10日 8:00

回答

  • C#で試しましたが呼ばれましたよ。(バグ部分は修正しました)
    イベントの登録が外れていませんか?

    あるいは、

                //フォームを出してOKが押されたら

                if (1)
                {
                    (int)value += 1;
                }
                return value;

    なんだか、この部分がC++/CLIの書き方ですね。もしかしてC++/CLIでやってますか?
    このやり方だとPropertyValueChangedは呼ばれません。
    EditValueの戻り値が変わっていないと看做されるようですね。

    value=(int)value + 1;
    

    でやれば、ちゃんとPropertyValueChangedイベントが呼ばれます。

    public:
    	virtual System::Object^ EditValue(ITypeDescriptorContext^ context, IServiceProvider^ provider, System::Object^ value) override
    	{
    		System::Object^ value2=value;
    		if (MessageBox::Show("TEST",System::String::Empty, MessageBoxButtons::OKCancel)== DialogResult::OK )
    		{
    			(int)value +=1;
    			if(value != value2)
    			{
    				MessageBox::Show("変化してるよ");
    			}
    			else
    			{
    				MessageBox::Show("変化してないよ");
    			}
    		}
    		return value;
    	} 
    上記コードで試すと変化していないことになっているのが確認できます。
    #値型の中身を直接書き換えてしまっていると、object型のReferenceEqualsで同じと看做されるのかな

    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 回答としてマーク howling64 2012年7月11日 2:18
    2012年7月10日 12:03

すべての返信

  • C#で試しましたが呼ばれましたよ。(バグ部分は修正しました)
    イベントの登録が外れていませんか?

    あるいは、

                //フォームを出してOKが押されたら

                if (1)
                {
                    (int)value += 1;
                }
                return value;

    なんだか、この部分がC++/CLIの書き方ですね。もしかしてC++/CLIでやってますか?
    このやり方だとPropertyValueChangedは呼ばれません。
    EditValueの戻り値が変わっていないと看做されるようですね。

    value=(int)value + 1;
    

    でやれば、ちゃんとPropertyValueChangedイベントが呼ばれます。

    public:
    	virtual System::Object^ EditValue(ITypeDescriptorContext^ context, IServiceProvider^ provider, System::Object^ value) override
    	{
    		System::Object^ value2=value;
    		if (MessageBox::Show("TEST",System::String::Empty, MessageBoxButtons::OKCancel)== DialogResult::OK )
    		{
    			(int)value +=1;
    			if(value != value2)
    			{
    				MessageBox::Show("変化してるよ");
    			}
    			else
    			{
    				MessageBox::Show("変化してないよ");
    			}
    		}
    		return value;
    	} 
    上記コードで試すと変化していないことになっているのが確認できます。
    #値型の中身を直接書き換えてしまっていると、object型のReferenceEqualsで同じと看做されるのかな

    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 回答としてマーク howling64 2012年7月11日 2:18
    2012年7月10日 12:03
  • > 値型の中身を直接書き換えてしまっていると、object型のReferenceEqualsで同じと看做されるのかな

    unboxing を行われないからです。

    (int) value += 1;

    は、

    int temp = (int)value;
    temp = temp + 1;

    という意味になって、value は変化しません。

    2012年7月11日 1:18
  • >gekka様

    ご回答頂きありがとうございます。バグ修正もすみません。そもそもif文の中身が1というのは動かなかったですね。

    さて、今回の問題についてですが、

    中身が書き変わっていないといけないということでしょうか。

    実際には送られてくるvalueの値はクラスになっており、

    その中のメンバ変数(bool型)を変更することだけ行っております。

    同じobjectに対して変更を行っているので、

    ReferenceEqualsなら確かに変化していないとみなされるかもしれませんね…。

    イベントは外れていないようです。

    >K.Takaoka様

    すみません、確かに記載したサンプルコードが問題だったと思います。

    一旦、クラスのインスタンスを別に作成して、それを割り当てるようにしてみようかと思います。

    結果を少々お待ち頂けますでしょうか。

    2012年7月11日 2:05
  • 試してみました。ご指摘の通り、内部的にはReferenceEqualsのような動作結果でした。

    メンバの値を変更した場合にもPropertyValueChangedイベントが呼ばれるものだとばかり思っていたのが間違いでした。

    ご回答頂きましたご両名ともに、誠に感謝しております。ありがとうございました。

    サンプルコードは下記の通りです。

        class PropertyClass
        {
            public PropertyClass() { }

            [EditorAttribute(typeof(EditorClass), typeof(System.Drawing.Design.UITypeEditor))]
            public bool IsUse { get; set; }
        }

        class EditorClass : System.Drawing.Design.UITypeEditor
        {
            IWindowsFormsEditorService clsEditorService = null;

            public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
            {
                if (provider != null)
                {
                    clsEditorService = ((IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService)));

                    if (clsEditorService != null)
                    {
                        //ModalDialogEditClassはFormを継承したOKボタンを持つクラスです。OKボタンを押すとDialogResult.OKが戻ります。
                        using (ModalDialogEditClass frm = new ModalDialogEditClass())
                        {
                            DialogResult dr = clsEditorService.ShowDialog(frm);
                            if (dr == DialogResult.OK)
                            {
                                //うまくいかない例
                                //(value as PropertyClass).IsUse = true;
                                //return value;

                                //うまくいった例
                                object objReturn = new PropertyClass();
                                (objReturn as PropertyClass).IsUse = true;
                                return objReturn;
                            }
                            return value;
                        }
                    }
                }
            }
        }

    2012年7月11日 2:31
  • 通常は、プロパティに保持されている参照型のプロパティ等が変わるだけの場合には、PropertyChanged イベントや、そのオブジェクトに用意された各種変更イベントは発生してはならないため、広く利用されるライブラリ等のプロパティやエディタで上記のようなことを行う場合には、プロパティ値に変更がないのにイベントが発生するという不具合とみなされると思います。

    ※ DataSource プロパティに設定されたデータソースを操作するたびに、DataSourceChanged イベントが起こったら困りますよね。DataSourceChanged イベントはDataSoruceプロパティそのものを入れ替えた時だけに発生するものです。

    PropertyChanged イベントが発生する/発生しないとあわせて、RefreshPropertiesAttribute についても考えないとダメそうですね。また、参照型の特定のプロパティを変更するような場合には、ExpandableObjectConverter とかのTypeConvertorも参考になるかと思います。

    > 「どの値が変更されたのか」の通知を行いたいのですが、

    この部分も意味がわからなかったのですが、UITypeEditor から関連付けられているプロパティの名称等を取得したいということでしょうか? それならば context 引数等から得らたんじゃないかと思いますが・・・はいっていませんか?

    2012年7月11日 8:14
  • >K.Takaoka様

    ご返信頂きありがとうございます。以下、引用にて失礼いたします。

    >不具合とみなされる

    確かにそうですね。とはいえ、変更するメンバ自体もPropertyGrid上で表示しているため、

    そのメンバが書き変わったことを通知しないのかな?と思っていました。

    >RefreshPropertiesAttributeについて

    こちらはいくつかの場所で使っていますが、いまいちどのような時に再描画/再読み込みするのかわかっていません。

    これは勉強しておきます。

    >「どの値が変更されたのか」の通知

    すみません、私のプログラム上の話でして、

    PropertyGridをコントロール下に置いているFormクラスが別に存在しているのですが、

    このFormクラスまたはPropertyGridクラスへ、どの場所が変更されたのかを通知したかったのです。

    (サンプルコードの場合、「IsUseが変化した」ということを通知したいということです)

    今回の問題の解決で、それもうまくいきました。ありがとうございました。

    2012年7月12日 1:43