0

私はMXMLから離れて、ActionScript内にカスタム コンポーネント コントロールを作成しました。

コントロールが正しく表示されます。問題は、表示リストから削除し、.addElement(control) メソッドを使用して再度追加した後に発生します。

これを再度追加するコードを次に示します。

private function displayParameters(parameters:ArrayCollection):void{

   for(var index:int = 0; index<parameters.length; index++){

      if(parameters[index] is ReportControl){

          var control:ReportControl = parameters[index] as ReportControl;
          control.percentWidth = 100;
          vgParameters.addElement(control);
      }
   }
}

ReportControl以下に示す基本クラスですcomboBoxMultiSelect。にはグラフィカルに特別なことは何もありませんReportControl。具体的な実装 (ポリモーフィック) のプログラム インターフェイスとしてのみ機能します。

public class comboBoxMultiSelect extends ReportControl{

    [Embed("../Assets/Icons/plus-16.png")]
    private var plusIcon:Class;
    [Embed("../Assets/Icons/minus-16.png")]
    private var minusIcon:Class;

    private var expanded:Boolean = false;
    private var buttonIconChanged:Boolean = false;

    private var _drp:ComboBox;
    private var _btnMultiple:Button;
    private var _horizontalGroup:HGroup;
    private var _multiSelector:ReportGridSelector;

    private var _multiSelection:Boolean = true;
    private var bMultiSelectionChanged:Boolean = false;        

    public function ToggleExpanded():void{
        expanded = !_expanded;
        buttonIconChanged = true;

        invalidateSize();
        invalidateProperties();
        invalidateDisplayList();
    }

    public function comboBoxMultiSelect(){
        super();
    }

    override protected function createChildren():void{

        super.createChildren();            

        if(!_horizontalGroup){
            _horizontalGroup = new HGroup();
            _horizontalGroup.gap = 0;
            _horizontalGroup.percentWidth = 100;
            _horizontalGroup.height = ReportControl.SIZE_DEFAULT_HEIGHT;
             addChild(_horizontalGroup);
        }

        if(!_drp){
            _drp = new ComboBox();
            _drp.text = GuiText;
            _drp.percentWidth = 100;
            _drp.height = ReportControl.SIZE_DEFAULT_HEIGHT; 
            _horizontalGroup.addElement(_drp);
        }

        if(!_btnMultiple && _multiSelection){
            _btnMultiple = new Button;
            _btnMultiple.setStyle("icon", plusIcon);
            _btnMultiple.width = 20;
            _btnMultiple.height = ReportControl.SIZE_DEFAULT_HEIGHT;
            _btnMultiple.visible = true;
            _btnMultiple.addEventListener(MouseEvent.CLICK,
                         function(event:MouseEvent):void{
                                 ToggleExpanded();   
                         });
            _horizontalGroup.addElement(_btnMultiple);
        }
    }

    override protected function commitProperties():void{
        super.commitProperties();

        if(buttonIconChanged){

            if(_expanded==true){
                _btnMultiple.setStyle("icon", minusIcon);
            }
            else{
                _btnMultiple.setStyle("icon", plusIcon);
            }
            buttonIconChanged = false;
        }

    }

    override protected function updateDisplayList(unscaledWidth:Number,
                                         unscaledHeight:Number):void{

        super.updateDisplayList(unscaledWidth, unscaledHeight);

        _horizontalGroup.width = unscaledWidth;
        _horizontalGroup.height = unscaledHeight;
    }

    override protected function measure():void{

        super.measure();
        measuredMinWidth = measuredWidth = ReportControl.SIZE_DEFAULT_WIDTH;

        //minimum size      //default size
        if(_expanded==true)
            measuredMinHeight= measuredHeight = 200;            
        else
            measuredMinHeight= measuredHeight = 
                               ReportControl.SIZE_DEFAULT_HEIGHT;
    }
}

を使用してコントロールを追加し直すとvgParameters.addElement(control)comboBoxMultiSelectが正しくレンダリングされません。plusIconボタンの内側_btnMultipleは最初は正しく配置されていませんが、約 0.5 ~ 1 秒後にすぐに修正されます。

問題はcomboBoxMultiSelect内にあると確信していますが、アイコンを強制的に同じ場所に留まらせる方法がわかりません。

これは私の努力の結果、非常に迷惑です。私が間違っていることについて誰か考えがありますか?

ありがとう :)

更新 -----> ReportControl コードは次のとおりです。

[Event (name= "controlChanged", type="Reporting.ReportControls.ReportControlEvent")]
[Event (name= "controlIsNowValid", type="Reporting.ReportControls.ReportControlEvent")]
public class ReportControl extends UIComponent
{
    private var _guiText:String;
    private var _amfPHPArgumentName:String;
    private var _reportResult:ReportResult;
    private var _sequence:int;
    private var _reportId:int;
    private var _controlConfiguration:ReportParameterVO;
    private var _isValid:Boolean = false;
    internal var _selection:Object;

    /**
     * SIZE_DEFAULT_HEIGHT = 22
     */
    internal static const SIZE_DEFAULT_HEIGHT:int = 22;

    /**
     * SIZE_DEFAULT_WIDTH = 150
     */
    internal static const SIZE_DEFAULT_WIDTH:int = 150;

    public function get ControlConfiguration():ReportParameterVO{
        return _controlConfiguration;
    }

    public function set ControlConfiguration(value:ReportParameterVO):void{

        _controlConfiguration = value;            
        _guiText = (value ? value.GuiText:"");
        _amfPHPArgumentName = (value ? value.AMFPHP_ArgumentName: "");
        _sequence = (value ? value.Sequence : null);
        _reportId = (value ? value.ReportId : null);            
    }

    public function get IsValid():Boolean{
        return _isValid;
    }

    public function get ReportID():int{
        return _reportId;
    }

    public function get Sequence():int{
        return _sequence;
    }

    public function get ControlRepResult():ReportResult{
        return _reportResult;
    }
    public function set ControlRepResult(value:ReportResult):void{
        _reportResult = value;
    }

    internal function set Selection(value:Object):void{
        _selection = value;
    }

    internal function get Selection():Object{
        return _selection;
    }

    public function get ParameterSelection():Object{
        return _selection;
    }

    public function get GuiText():String{
        return _guiText;
    }

    public function get AmfPHPArgumentName():String{
        return _amfPHPArgumentName;
    }

    public function ReportControl(){
        //TODO: implement function
        super();
    }

    public function dispatchControlChanged():void{
        this.dispatchEvent(new ReportControlEvent(ReportControlEvent.CONTROL_CHANGED, this, true));
    }
    public function dispatchControlIsNowValid():void{
        this.dispatchEvent(new ReportControlEvent(ReportControlEvent.CONTROL_IS_NOW_VALID, this, true));
    }

    public function addSelfToValueObject(valueObject:Object):Object{
        valueObject[AmfPHPArgumentName] = _selection;
        return valueObject;
    }

}
4

3 に答える 3

1

上記のコメントで説明したSparkスキニングアーキテクチャの意味の例を示します。それはあなたの質問に対する直接の答えではありませんが、あなたはそれが面白いと思うかもしれないと思いました。簡潔にするために、コンポーネントよりもいくらか単純にする必要があります。また、質問のコードの一部を削除したように見えるため、それが何をするのか正確にはわかりません。

これは、ボタンをクリックするだけで通常の状態と展開された状態を切り替えることができるコンポーネントになります。まず、スキンクラスを作成します。通常は最初にホストコンポーネントを作成しますが、この方法で説明する方が簡単です。

<!-- my.skins.ComboBoxMultiSelectSkin -->
<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009" 
        xmlns:s="library://ns.adobe.com/flex/spark"
        height.normal="25" height.expanded="200">

    <fx:Metadata>
        [HostComponent("my.components.ComboBoxMultiSelect")]
    </fx:Metadata>

    <s:states>
        <s:State name="normal" />
        <s:State name="expanded" />
    </s:states>

    <s:layout>
        <s:HorizontalLayout gap="0" />
    </s:layout>

    <s:ComboBox id="comboBox" width="100%" />
    <s:Button id="toggleButton" width="20"
              icon.normal="@Embed('../Assets/Icons/plus-16.png')"
              icon.expanded="@Embed('../Assets/Icons/minus-16.png')"/>

</s:Skin>

したがって、コンポーネントの外観とレイアウトを完全に設定しました。頭痛が消えるのを感じますか?私はこれがとてもエレガントだと思っています。2つの状態があり、コンポーネントの高さは、ボタンのアイコンと同様に、現在選択されている状態に調整されます。状態を切り替える方法とタイミングはコンポーネントの動作であり、ホストコンポーネントで定義されます。

それでは、そのホストコンポーネントをプレーンなActionScriptで作成しましょう。このために、SkinnableComponentを拡張します(UIComponentの代わりにSkinnableComponentを拡張する場合は、ReportControlも拡張できることに注意してください)。

[SkinState("normal")]
[SkinState("expanded")]
public class ComboBoxMultiSelect extends SkinnableComponent {

    [SkinPart(required="true")]
    public var toggleButton:IEventDispatcher;

    [SkinPart(required="true")]
    public var comboBox:ComboBox;

    private var expanded:Boolean;

    override protected function partAdded(partName:String, instance:Object):void {
        super.partAdded(partName, instance);

        switch (instance) {
            case toggleButton:  
                toggleButton.addEventListener(MouseEvent.CLICK, handleToggleButtonClick); 
                break;
            case comboBox:
                comboBox.addEventListener(IndexChangeEvent.CHANGE, handleComboSelection);
                break;
        }
    }

    private function handleToggleButtonClick(event:MouseEvent):void {
        toggleExpanded();
    }

    private function handleComboSelection(event:IndexChangeEvent):void {
        //handle comboBox selection
    }

    protected function toggleExpanded():void {
        expanded = !expanded;
        invalidateSkinState();
    }

    override protected function getCurrentSkinState():String {
        return expanded ? "expanded" : "normal";
    }
}

さて、ここではもっとたくさんのことが起こっています。

  • 最初にSkinStateメタデータ宣言を確認します。スキンクラスがコンポーネントに割り当てられると、コンパイラはそのスキンに必要な状態が実装されているかどうかを確認します。
  • 次に、SkinPart宣言:ホストコンポーネントのプロパティの名前は、スキンクラスのタグのIDと正確に一致する必要があります。コンパイラにrequired設定されているように、これらのコンポーネントが実際にスキンに存在するかどうかをチェックします。trueオプションのスキンパーツが必要な場合は、に設定しfalseます。
  • toggleButtonタイプは次のとおりであることに注意してくださいIEventDispatcher。ホストコンポーネントの観点toggleButtonからは、CLICKイベントをディスパッチする必要があります。これは、スキンを作成できるように<s:Image id="toggleButton" source="..." />なり、すべてが同じように機能し続けることを意味します。これがどれほど強力かわかりますか?
  • スキンパーツのプロパティはすぐには割り当てられないため、partAdded()コンポーネントが使用可能になるたびに実行されるメソッドをオーバーライドします。ほとんどの場合、これはイベントリスナーを接続する場所です。
  • このtoggleExpanded()方法では、質問のコンポーネントと同じようにブール値を切り替えますが、スキンの状態を無効にするだけです。これにより、スキンはgetCurrentSkinState()メソッドを呼び出し、返された値にその状態を更新します。

Etvoilà!動作がactionscriptクラスにうまく分離された作業コンポーネントがあり、レイアウトの複雑さを心配する必要はありませんでした。また、同じ動作のコンポーネントを作成したいが、垂直方向ではなく水平方向に拡張する必要がある場合は、のwidth代わりに調整する新しいスキンを作成しheight、それを同じホストコンポーネントに割り当てます。

えっ、ちょっと待って!スキンをコンポーネントに割り当てる方法を説明するのをほとんど忘れていました。あなたはそれをインラインで行うことができます:

<c:ComboBoxMultiSelect skinClass="my.skins.ComboBoxMultiSelectSkin" />

またはスタイリングを通じて:

@namespace c "my.components.*";

c|ComboBoxMultiSelect {
    skinClass: ClassReference("my.skins.ComboBoxMultiSelectSkin")
}
于 2012-06-16T08:40:58.847 に答える
1

際立っていることの 1 つは、 の実装ですupdateDisplayList()。ご存知のように、これは、コンポーネントが子オブジェクトのサイズと配置を行う (および/またはプログラムによる描画を行う) 場所です。

ただし、子オブジェクトの幅/高さを直接設定するのではなく、Flex ライフサイクル メソッドのいずれかを使用する必要があります:setActualSize()またはsetLayoutBoundsSize(). setLayoutBoundsSize()火花コンポーネントで使用します。

Flex コンポーネントの幅/高さを設定すると、コンポーネント自体が無効になり、次の更新サイクルで再レンダリングできるようになります。ただし、コンポーネントをレンダリングしようとしているので、updateDisplayList()このメソッド内の子オブジェクトを無効にしないように注意する必要があります。

setActualSize()およびメソッドは Flex コンポーネントのsetLayoutBoundsSize()幅/高さを設定しますが、コンポーネントを無効にしません。

override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
    super.updateDisplayList(unscaledWidth, unscaledHeight);
    _horizontalGroup.setLayoutBoundsSize(unscaledWidth, unscaledHeight);
    // if you wanted to position objects, you would set their x/y coordinates
    // here with the move() or setLayoutBoundsPosition() methods
}

一部の子オブジェクトも同様にサイズ変更されているように見えることに注意してくださいcreateChildren()...そして、この場合、基本Flexコンポーネントが何であるかは明確ではありません(どのクラスがReportControl拡張されますか?

このようにすると、レンダリングの不具合が解消される場合があります。幅/高さのプロパティを直接設定する場合よりも、実行されるコードが少なくなることは間違いありません。

[編集]

HGroupこのコンポーネントでは不要な との相互作用である可能性があります。この方法でコンポーネントを作成するのは楽しいと思いますが、もっと面倒な場合もあります...そのため、@RIAStar は別のアプローチを賢明に指摘しています。

この道を進みたい場合は、さらにいくつかのアイデアがあります。

1) で行っているサイジングを見てください。createChildren()たとえば、HGrouppercentWidth が指定されupdateDisplayList()ていますが、固定幅が指定されています (これはニシンかもしれませんが、percentWidth は設定しません)。

2) コンポーネントを削除した後、または再追加する前に、コンポーネントをだましてそれ自体を検証させることができる場合があります。時間の無駄かもしれないハッキーな予感。

3) コンポーネントから「HGroup」を削除します。それは一種の不必要です。レイアウト要件は、数行の Actionscript で実行できるほど単純です。レイアウト要件がより複雑になるにつれて、マイレージは異なります!

createChildren()、コンボ ボックスとボタンを に直接追加しますUIComponentupdateDisplayList()次に、次のようにサイズを変更して配置します。

override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
    super.updateDisplayList(unscaledWidth, unscaledHeight);
    var padding:Number = 10;
    var gap:Number = 0;

    // make the ComboBox consume all of the width execpt for 20px and gap + padding
    var availableWidth:Number = unscaledWidth - 20 - gap - (2*padding);
    _drp.setLayoutBoundsSize(availableWidth, unscaledHeight); // combo box 100% width
    _btnMultiple.setLayoutBoundsSize(20, unscaledHeight); // button is 20px wide

    // now position them ...
    // probably should not use 0, rather calculate a Y coordinate that centers them
    // in the unscaledHeight
    _drp.setLayoutBoundsPosition(padding, 0);
    _btnMultiple.setLayoutBoundsPosition(unscaledWidth - padding - 20, 0);
}
于 2012-06-16T00:00:30.557 に答える
0

お二方、ご回答ありがとうございました!コンセプトを説明する際の細部への配慮と注意は素晴らしいです!トレビエン!

@RIAstarしかし、すでに配置されているコードの量が原因で、私のアーキテクチャを変更する(視覚要素を動作から分離する)と、コードの大規模なリファクタリングが必要になり、明示的に要求されていない機能に多額の費用がかかります。(実行時に変更できるコントロールの視覚的表現) 確かに興味深いので、将来のバージョンに追加する予定です。

とはいえ、問題の解決策を見つけることができたと思います。コントロールを再度追加する前に検証するという @SunilD. の提案から構築することにしました。;-)

コントロールを見ると、画像のレンダリングに問題があるのはボタンだけであることに気付きました。テストするために、アイコン付きのボタンインスタンスだけを追加および削除ましたが、同じ動作が見られました! (comboBoxMultiSelect がどのように実装されたかに関係なく)ボタンが最初に作成されたときにこれを実行していないことにも気付きましたでは、ボタンが表示リストから削除されたときに、ボタンを再構築してみませんか?

イベントに配線comboBoxMultiSelectFlexEvent.REMOVE、ボタン参照を破棄し、新しいものを作成し、AddChild() で再度追加しました。以下、イベントの解説です。

「removeChild()、removeChildAt()、removeElement()、または removeElementAt() メソッドを使用して、コンポーネントがコンテンツの子としてコンテナから削除されたときに送出されます。コンポーネントが非コンテンツの子としてコンテナから削除された場合、 rawChildren.removeChild() または rawChildren.removeChildAt() メソッドでは、イベントはディスパッチされません。

このイベントは、ディスパッチ オブジェクトにアタッチされた関連するリスナーが 1 つ以上ある場合にのみディスパッチされます。」

案の定、これによりアイコンが正しく表示されなくなり、何が起こっているのかが説明されました。何らかの理由で、ボタンが追加されたときにスタイルを適用するために、ボタンが複数のレンダリング イベントを取得しています。他の誰かがこの動作を複製できますか?

今の本当の疑問は、「埋め込まれたアイコンが影響を受けないように、表示リストからボタンを削除して追加し直す最良の方法は何ですか?」ということだと思います。

于 2012-06-19T16:54:22.450 に答える