Archiv der Kategorie: Programmierung

Adobe Flex 3: Composite Pattern für Charakter-Animation mit inverser Kinematik

Aufgabenstellung

Es wird eine Flex-Anwendung entwickelt, die das Grundgerüst für ein 360 Grad Flex-Spiel mit einer Schlange erstellt.

Ansatz

Unter der Verwendung des Composite-Patterns werden die Teile der Schlange zusammengesetzt. Die Teile interagieren über „inverse Kinematik“ miteinander, d.h. jedes Teil ist Autonom, wird aber von seinem Verbindungsteil gezogen.

Die Schlange verfügt über die Eigenschaften:
– gleichmäßig beschleunigte Bewegung (Up-Taste am Keyboard)
– gleichmäßig verzögerte Bewegung (Up-Taste loslassen)
– Rückimpuls (Kollision mit Button)
– Lenkung in der Beschleunigung (Left- und Right-Button)
– Jedes vordere Verbindungsstütz zur Schlange bestimmt die Position des hinteren (inverse Kinematik)

Der Code ist minimal, die Main-Datei besteht lediglich aus der Schlange selbst und der Angabe, welches UI-Objekt eine Barriere (hier „Der böse Button“) sein soll. Diese Barriere verfügt über die Eigenschaften des Back-Impulses bei einer Kollision in der Beschleunigung.

Main-Code:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application backgroundColor="#ffffff" xmlns:mx="http://www.adobe.com/2006/mxml" 
                         layout="absolute" xmlns:snake="org.actionscript.snake.*">
  <snake:Snake barrier="{boeserButton}" height="100" width="100" snakelength="100" />
  <mx:Button x="10" y="256" id="boeserButton" width="400" label="Der böse Button" />
</mx:Application>

Lösung

In neuem Browser öffnen (bitte 1x anklicken und dann Pfeiltasten benutzen)

Code

Der Code wurde im Flex-Builder 3 entwickelt und ist als Flex 3-Projekt gezippt.
Adobe Flex 3 Projekt downloaden

SAP und JAVA: Zugriff auf Funktionsbausteine / BAPIs mit dem Java Connector

Aufgabenstellung

Es soll auf Daten aus dem SAP System zugegriffen werden, ohne die Datenbank direkt anzusprechen.

Ansatz

Durch die Verwendung des Java-Connectors (JCo) werden Funktionsbausteine / BAPIs als SAP Schnittstellen verwendet. Die Funktionsbausteine können über die SAP GUI mit dem BAPI-Explorer gesucht und mit der TA 37 getestet werden. Der nachfolgende Artikel beschäftigt sich mit dem Aufruf von einem Java-Server. Der JCo kann im Market Place von SAP kostenlos heruntergeladen werden.

Lösung

Remote Function Call (techn. RPC)

Ein Remote Function Call (RFC) ist die SAP-Implementierung von Remote Procedure Calls (RPC). Über einen RFC lassen sich Funktionsbausteine, bzw. BAPI’s aufrufen.

Ein RFC verwendet die Kommunikationsregeln des CPI-C-Protokolls, welches auf TCP/IP oder LU 6.2 basiert. LU 6.2 war dabei das Kommunikationsprotokoll von R/2-Mainframe-Systemen und wurde bei R/3 durch den einheitlichen TCP/IP-Standard ersetzt.

RFC’s nehmen ausschließlich Call-By-Value-Parameter entgegen, da die Übergabe von Zeigern und Speicheradressen hier keinen Sinn macht. In einem fremden System würde die Speicheradresse auf einen Speicher-Bereich zeigen, in dem keine oder Daten von anderen Anwendungen vorhanden sind.

Funktionsweise des Java Connectors (JCo)

Der Java Connector unterstützt den Aufruf von RFC’s, bzw. BAPIs aus einer externen Java-Umgebung (SAP-inbound-call), sowie den Aufruf externer JAVA-Funktionalitäten aus ABAP-Programmen heraus (SAP-outbound-call). Für die letztgenannte Variante existiert im Example-Verzeichnis des JCo ein Beispiel, in dem ein JCo-Server implementiert wird. Der JCo-Server muss dem SAP-System über die Transaktion SM59 bekanntgemacht werden.

Bei einem SAP-Inbound-Call ist die Vorgehensweise die folgende:

1.) Anmeldung der Java-Applikation am R/3-System

JCO.Client Conection = JCO.createClient(Mandant,
                                        Username,
                                        Password,
                                        Language,
                                        Hostname,
                                        Systemnummer);

Connection.connect();

2.) Herunterladen des Repository für den Funktionsbaustein

IRepository rep = JCO.createRepository("MyRepository", Connection);
IFunctionTemplate template = rep.getFunctionTemplate("BAPI_NAME");
JCO.Function funktion = new JCO.Function(template);

3.) Holen einer Referenz auf die Übergabeparameter

JCO.ParameterList import = funktion.getImportParameterList();
JCO.ParameterList table = funktion.getTableParameterList();

4.) Befüllen des Bausteins

5.) Funktionsbaustein ausführen

Connection.execute(funktion);

6.) Hole Referenz auf Rückgabeparameter

JCO.ParameterList export = funktion.getExportParameterList();
JCO.ParameterList table = funktion.getTableParameterList();

7.) Auslesen des Bausteins

Es werden synchrone (sRFC), asynchrone (aRFC) und transaktionale Aufrufe (tRFC, qRFC), sowie Connection-Pooling unterstützt (auf JAVA- wie auf ABAP-Seite).

SAP lobt zwar die Plattformunabhängigkeit des Connectors, jedoch stößt man bei einfachem Kopieren des Bytecodes auf Probleme, da hier eine plattformabhängige Bibliotheksdatei gekapselt wird, die bei höheren Releases nicht mehr funktioniert. Unter Windows wird die Datei librfc32.dll und unter Linux die Datei librfccm.so gekapselt.

Durch die Kapselung der Datei ist es ohne weiteres möglich, Konnektoren für nicht unterstützte Programmiersprachen zu implementieren.

Ein Tutorial der Zeitschrift „Der Entwickler“, welches Online zur Verfügung steht, zeigt durch Verwendung dieser Bibliothek, wie man Delphi an SAP-Systeme andocken kann. Der Artikel trägt den Namen „Delphi goes SAP“. Da es für Delphi keine Konnektoren wie den Java Connector gibt, ist die Kapselung dieser Datei notwendig.

Parameter von Funktionsbausteinen

Da die meisten SAP-Anwendungen im ABAP-Stack implementiert worden, greift man mit dem JAVA Connector hauptsächlich auf remotefähige Funktionsbausteine zu. Diesen werden entweder ausgefüllte Tabellen, oder Import-Parameter zur Verarbeitung übergeben. Als Ausgabe liefert ein Funktionsbaustein Fehler-Parameter, Export-Parameter, neue Tabellen oder auch die übergebenen Tabellen mit neuen Werten zurück. Hierfür ist es notwendig den syntaktischen Aufbau von JCO-Syntax zu kennen.

Import-Parameter

Import-Parameter kann man sich wie einfache Übergabeparametern aus bekannten Programmiersprachen vorstellen. Ein Import-Parameter entspricht einem Feld, was einen Datentyp beinhaltet (Char, Integer, String, Double…). Es kann keine Array’s, Listen oder Tabellen entgegennehmen.

SAP kennzeichnet hauseigene Funktionsbausteine mit einem vorangehenden I für „Import“. Mit der Transaktion SE16 ist es möglich sich die notwendigen Import-Parameter anzusehen.

Nicht alle Import-Parameter sind erforderlich, es gibt auch optionale Parameter. Nicht jeder Funktionsbaustein hat Import-Parameter.

Der nachstehende Code zeigt, wie mit Hilfe des JAVA Connectors der Funktionsbaustein „CFX_API_USER_GETDETAIL“, der die Detaildaten eines cFolders-Benutzers zurückgibt befüllt wird:

public String gibListeVonUsern()
{
  IRepository repository = JCO.createRepository("MYRepository", client);
  IFunctionTemplate ftemplate = repository
 .getFunctionTemplate("CFX_API_USER_GETDETAIL");
  Function func = ftemplate.getFunction();

  JCO.ParameterList input = func.getImportParameterList();
  input.setValue("KARPBJ01", "I_USER_ID");

  // Funktion aufrufen
  client.execute(func);
  (...)
}

Export-Parameter

Nachdem die Funktion ausgeführt wurde, kann über die Export-Parameter iteriert werden um die Rückgabewerte auszulesen. Dazu hat die Klasse ParameterList die Methode getFieldCount(), welche die Anzahl der Rückgabeparameter zurückliefert. Die Methode getString(index) ermöglich über den inkrementellen Index die Iteration mit einer Schleife. Dadurch lassen sich alle Parameter dynamisch auslesen.

Nachfolgend wird aus der aufgerufenden Funktion über die Export-Parameterliste iteriert und das Ergebnis an den String „informationen“ angehangen.


public String gibListeVonUsern()
{
  (...) //Hier war der Funktionsaufruf

  JCO.ParameterList output = func.getExportParameterList();

  for (int i = 0; i < output.getFieldCount(); i++)
  {
    informationen = informationen + output.getString(i) + "\n";
  }
  return informationen;
}

Changing-Parameter

Im Gegensatz zu Import- und Export-Parametern funktionieren Changing-Parameter in beide Richtungen (bidirektional). Der Aufruf ist dabei Call-By-Reference-ähnlich, da sich nach dem Ausführen des Funktionsbausteins mit dem Befehl client.execute(func); der Wert im Feld ändern kann. Das Befüllen und auslesen funktioniert dabei wie bei den Import- und Export-Parametern.

Table-Parameter

Table-Parameter sind bidirektional. Die Referenz auf eine Tabelle kann geholt werden, bevor die Funktion ausgeführt wurde. Hat man die Referenz auf die Tabelle, so lässt sich diese folgendermaßen befüllen:


  // Iteration über einen Vector
  for (int i = 0; i < vector.size(); i++)
  {
     // Neue Zeile an Tabelle anhängen und auf
     // neue Zeile wechseln
     table.appendRow();

     // schreibe in erste Spalte
     table.setValue((String)vector.get(i),0);

     // schreibe in zweite Spalte
     table.setValue((String)vector.get(i),1);
  }

Nachdem eine Funktion ausgeführt wurde, also nachdem Connection.execute(funktion); ausgeführt wurde, kann eine neue Tabelle zurückgeliefert werden, oder eine bereits befüllte Tabelle neue Werte beinhalten.

Um über eine Rückgabetabelle zu iterieren, gibt es die Funktion setRow(index). Das folgende Beispiel zeigt die Suche nach einem bestimmten String in der 3. Spalte und liefert das Feld „ID“ zurück:


  String rueckgabe = null;
  for (int i=0;i<outputtabelle.getNumRows();i++)
  {
    outputtabelle.setRow(i);

    if (outputtabelle.getString(2).equals(suche) )
    {
      rueckgabe = outputtabelle.getString("ID");
    }
  }

Wie ersichtlich wird, lässt sich die Spalte nicht nur anhand des inkrementellen Indexes ansprechen, sondern auch über den Spaltennamen.

In manchen Fällen ist es nötig eine eigene Tabelle zu erzeugen und diese an einen Funktionsbaustein zu übergeben. Zum Beispiel funktioniert die Zuweisung einer befüllten Ergebnistabelle an eine Übergabetabelle nicht, in diesem Fall muss man die Daten in einer Schleife einfach rauskopieren.

Eine eigene Tabellendefinition erzeugt man in diesem Fall so:


  JCO.MetaData smeta  = new JCO.MetaData("DIS");

  smeta.addInfo("DOKAR",  JCO.TYPE_STRING,  3,  0, 0);
  smeta.addInfo("DOKVR",  JCO.TYPE_STRING ,   2, 0, 0);
  smeta.addInfo("DOKTL",  JCO.TYPE_STRING ,   3, 0, 0);
  smeta.addInfo("DOKNR",  JCO.TYPE_STRING ,   3, 0, 0);

  JCO.Structure structure = new JCO.Structure(smeta); 
  JCO.Table table = new JCO.Table(structure);

Struktur-Parameter

Strukturen gehören zu den Tabellen-Typ-Parametern und sind Sonderfälle. Eine Struktur kann innerhalb einer Tabelle, in Import- oder Export-Parametern auftreten. Hierbei handelt es sich lediglich um eine weitere Aufteilung innerhalb eines einzigen Feldes.

Der folgende Code zeigt, wie eine Referenz auf eine Inputstruktur und auf einen normalen Import-Parameter geholt wird .


JCO.Structure struktur =
func.getImportParameterList().getStructure("SELECT_DOCUMENTDATA");
JCO.ParameterList inputdata = func.getImportParameterList();
struktur.setValue(art, "DOCUMENTTYPE");
inputdata.setValue("X", "GETCLASSIFICATION");
Output.execute(funktion);

Fortsetzung folgt …

Adobe Flex 3: Event-Reihenfolge mit Methode testen

Problem

Adobe Flex 3 definiert eine Reihe von Standard-Events, die über die Kontext-Hilfe bei der Quellcodeingabe mit einem gelbem Blitz gekennzeichnet werden. Häufig nutzt man die Kontext-Hilfe im Flex-Builder (STRG+SPACE ruft sie beim Deklarieren/in der Deklaration eines MXML-Tags auf) um die definierten Events anzuzeigen.

Bei Event-Namen wie creationComplete, applicationComplete, InitializationComplete usw… stellt sich häuft die Frage nach der Reihenfolge

Ansatz

Es werden alle Events, die mit einem gelben Blitz versehen sind, an die deklarierte Methode „eventListener“ übergeben. Diese Methode listet im Debug-Modus über die TRACE-Funktion die Reihenfolge und das Auftreten der Events auf.

Lösung

<?xml version="1.0" encoding="utf-8"?>
<mx:Application added="eventListener(event)"
  addedToStage="eventListener(event)"
  applicationComplete="eventListener(event)"
  creationComplete="eventListener(event)"
  initialize="eventListener(event)"
  preinitialize="eventListener(event)"
  layout="absolute"
  xmlns:mx="http://www.adobe.com/2006/mxml">
 <mx:Script>
    <!&#91;CDATA&#91;
      import flash.utils.getQualifiedClassName;
      import mx.events.FlexEvent;
      
      private function eventListener(event:Event):void 
      {
         if (
              (event.target == this) || 
              (event.target == this.myDataGrid) ||
              (event.target == this.myPanel)
          ) 
         {
            trace(getQualifiedClassName(this) + ".eventListener: " +
	       getQualifiedClassName(event) + "." + event.type);
            trace("\ttarget: " + getQualifiedClassName(event.target));
          }
    }
 &#93;&#93;>
  </mx:Script>
  <mx:Panel added="eventListener(event)"
      addedToStage="eventListener(event)"
      creationComplete="eventListener(event)"
      id="myPanel"
      initialize="eventListener(event)"
      preinitialize="eventListener(event)">
  <mx:DataGrid  added="eventListener(event)"
           addedToStage="eventListener(event)"
           creationComplete="eventListener(event)"
           id="myDataGrid"
           initialize="eventListener(event)"
           preinitialize="eventListener(event)"/>
  </mx:Panel>
</mx:Application>

Adobe Flex 3: Drag and Drop API

Aufgabenstellung

UI-Komponenten sollen per Drag and Drop verschoben werden können.

Ansatz

Dieser Artikel beschreibt die notwendigen Flex-APIs (DragManager) für die Implementierung einer Drag and Drop-Funktionalität am Beispiel der (siehe Nachfolgeartikel) eLearning-Anwendung für das V-Modell.

Lösung

1.) Definition der verschiebbaren Komponenten und der Behälter-Komponenten.

<!-- WIRD VERSCHOBEN -->
<mx:Button dragComplete="dragCompleteHandler(event);" 
label="System-Entwurf" id="btnSystementwurf"  />
<mx:Button dragComplete="dragCompleteHandler(event);" 
label="Software-Entwurf" id="btnSoftwareentwurf"  />
..

<!-- WIRD HINEINGESCHOBEN -->
<mx:HBox verticalAlign="middle" verticalScrollPolicy="off" 
horizontalScrollPolicy="off" id="dropField1" />
<mx:HBox verticalAlign="middle" verticalScrollPolicy="off" 
horizontalScrollPolicy="off" id="dropField2" />

2.) Registrierung der Eventlistener


<mx:Application creationComplete="{onCreationComplete()} ...

public function onCreationComplete():void
{
   // Registrierung des Buttons "Anforderungsanalyse" für das Verschieben in ein Feld
   this.btnAnforderungsnanalyse.addEventListener( MouseEvent.MOUSE_DOWN, beginDrag );

   // Registrierung des Buttons "AbnahmeEntwurf" für das Verschieben in ein Feld
   this.btnAbnahmeEntwurf.addEventListener( MouseEvent.MOUSE_DOWN, beginDrag );

...

   // Die DropListener werden an die UI-Komponten, bzw. die Komponeten, in die 
   // die Objekte geschoben werden können, angehängt
   this.dropField1.addEventListener( DragEvent.DRAG_ENTER, acceptDrop );
   this.dropField2.addEventListener( DragEvent.DRAG_ENTER, acceptDrop );
   ..
   this.dropField1.addEventListener( DragEvent.DRAG_DROP, handleDrop );
   this.dropField2.addEventListener( DragEvent.DRAG_DROP, handleDrop );
}
&#91;/actionscript3&#93;

3.) Implementierung der Logik - Hier: Überprüfung ob alles richtig verschoben ist
&#91;actionscript3&#93;
private function dragCompleteHandler(event:DragEvent):void {
     var draggedButton:Button = event.dragInitiator as Button;
     var dragInitCanvas:HBox = event.dragInitiator.parent as HBox;

     var stimmt1:Boolean = getButtons(dropField1).indexOf('btnAnforderungsnanalyse')>-1;
     var stimmt2:Boolean = getButtons(dropField2).indexOf('btnSystemarchitektur')>-1;
               	
     if(stimmt1&&stimmt2) 
     {
          Alert.show('Congratulations you did it!'); 
     }                	 
}  

4.) Welche Buttons sind in den Container? (Hier HBOX)

private function getButtons(box:HBox):String
{
var btnString:String=“;
var numChildren:Number = box.numChildren;

for (var i:int = 0; i < numChildren; i++) { if(box.getChildAt(i)["id"]) { btnString=box.getChildAt(i)["id"]; } } return btnString; } [/actionscript3] 5.) Management der Drag and Drop Funktionalität [actionscript3] public function beginDrag( mouseEvent:MouseEvent ):void { // the drag initiator is the object being dragged (target of the mouse event) var dragInitiator:IUIComponent = mouseEvent.currentTarget as IUIComponent; // the drag source contains data about what's being dragged var dragSource:DragSource = new DragSource(); // add some information to the drag source dragSource.addData( "name", dragInitiator.name ); dragSource.addData( "parent", dragInitiator.parent.name ); // ask the DragManger to begin the drag DragManager.doDrag( dragInitiator, dragSource, mouseEvent, null ); } public function handleDrop( dragEvent:DragEvent ):void { var dragInitiator:Button = Button( dragEvent.dragInitiator ); var dropTarget:HBox = dragEvent.currentTarget as HBox; HBox(dropTarget).addChild(dragInitiator); } public function acceptDrop( dragEvent:DragEvent ):void { var dropTarget:IUIComponent = dragEvent.currentTarget as IUIComponent; var dragSource:DragSource = dragEvent.dragSource; // accept the drop DragManager.acceptDragDrop( dropTarget ); // show feedback DragManager.showFeedback( DragManager.MOVE ); } [/actionscript3]

Flex 3: Bemalbares mx:Canvas mit FluorineFX-Messaging (Montagsmaler)

Aufgabenstellung

Unter Verwendung der FluorineFX Messaging-API soll eine Montagsmaler-Anwendung implementiert werden. Dies bedeutet dass ein User auf eine Leinwand zeichnet und die anderen Seitenbesucher in Echtzeit eine Aktualisierung auf dem Bildschirm erhalten. Dies kann auch als Grundlage für weitere Kollaborationsanwendungen genutzt werden, in denen man gemeinsam ein Dokument konstruieren möchte.

Ansatz

Nachdem die Zeichnenfunktionalität implementiert ist, werden die Bildpunkte beim loslassen der Maustaste (MOUSE_RELEASE_EVENT) an alle Clients übertragen. Die Clients reagieren bei einer einkommenden Menge an Bildpunkten und zeichnen sofort auf die Leinwand. Es wird immer die komplette Leinwand mit allen Bildpunkten beim Mouse-Release gesendet.

Durch das Publisher/Subscriber-Prinzip abonniert die Flex-Anwendung beim Start sofort die Datenquelle „painter„. Die Datenquelle erhält Bildpunkte und verteilt sie an alle Abonennten, wenn diese nach dem zeichnen die Maus gesehn lassen.

Vorraussetzungen

Die FluorineFX-Konfigurationsdatei wird angepasst, so das es eine neue Messaging Quelle „painter“ existiert.

Konfiguration des Backends

services-config.xml:

<?xml version="1.0" encoding="utf-8" ?> 
<services-config>
    <services>
        <service-include file-path="remoting-config.xml" />
        <service-include file-path="messaging-config.xml" />
    </services>
    
    <!-- Custom authentication -->
    <security>
    </security>
    
    <channels>
        <channel-definition id="my-amf" class="mx.messaging.channels.AMFChannel">
           <endpoint uri="http://{server.name}:{server.port}/{context.root}/Gateway.aspx" 
             class="flex.messaging.endpoints.AMFEndpoint"/> 
            <properties>
            </properties>
        </channel-definition>

        <channel-definition id="my-rtmp" class="mx.messaging.channels.RTMPChannel">
            <endpoint uri="rtmp://{server.name}:2037" 
            class="flex.messaging.endpoints.RTMPEndpoint"/>
            <properties>
                <idle-timeout-minutes>20</idle-timeout-minutes>
            </properties>
        </channel-definition>

    </channels>
</services-config>

messaging-config.xml:

<?xml version="1.0" encoding="UTF-8"?>
<service id="message-service" class="flex.messaging.services.MessageService" 
    messageTypes="flex.messaging.messages.AsyncMessage">

     <!-- DO NOT CHANGE <adapters> SECTION-->
    <adapters>
	<adapter-definition id="messagingAdapter" 
             class="FluorineFx.Messaging.Services.Messaging.MessagingAdapter" 
             default="true"/>
    </adapters>
    <destination id="chat">
	<adapter ref="messagingAdapter"/>
        <properties>
            <network>
                <session-timeout>0</session-timeout>
            </network>
        </properties>
        <channels>
            <channel ref="my-rtmp"/>
        </channels>
    </destination>
    <destination id="textchat">
	<adapter ref="messagingAdapter"/>
        <properties>
            <network>
                <session-timeout>0</session-timeout>
            </network>
        </properties>
        <channels>
            <channel ref="my-rtmp"/>
        </channels>
    </destination>
    <destination id="painter">
	<adapter ref="messagingAdapter"/>
        <properties>
            <network>
                <session-timeout>0</session-timeout>
            </network>
        </properties>
        <channels>
            <channel ref="my-rtmp"/>
        </channels>
    </destination>
    <destination id="opminboxrefresher">
	<adapter ref="messagingAdapter"/>
    	<properties>
	    <network>
            	<session-timeout>0</session-timeout>
            </network>
    	</properties>
    	<channels>
         	<channel ref="my-rtmp"/>
    	</channels>
    </destination> 
</service>

Konfiguration des Frontends

Bei den Frontend Konfigurationsdateien kann einfach die Datei services-config.xml und die Datei messaging-config.xml kopiert werden. Es können aber absolute Pfade anstelle der Platzhalter wie {server.name},{server.port},{context.root} genutzt werden. Hierdurch ist eine lokale Entwicklung der Flex-Anwendungen mit einem entfernten IIS Server möglich.

Lösung

PaintMessaging.mxml (Projektdatei)

<?xml version="1.0" encoding="utf-8"?>
<mx:Application creationComplete="{init()}" xmlns:mx="http://www.adobe.com/2006/mxml" 
	layout="absolute" xmlns:components="components.*">
	<mx:Script>
		<!&#91;CDATA&#91;
			import models.ModelLocator;
			import mx.controls.Alert;
			import mx.messaging.events.MessageFaultEvent;
			import mx.messaging.events.MessageEvent;
			import mx.messaging.messages.AsyncMessage;
			import mx.managers.CursorManager;
			&#91;Bindable&#93;
            &#91;Embed(source="../assets/stiftklein.png")&#93;
            private var stift:Class;
            
            public var cursorID:int;
            
            public function init():void 
            {
            	consumer.subscribe();
            }
            
            public function mouseOverHandler(event:MouseEvent):void
            {
            	
            }
            
            public function downEvent(event:MouseEvent):void 
            {
            	
            } 
            
            public function upEvent(event:MouseEvent):void
            {
            	
            }
            
	private function messageHandler(event:MessageEvent):void
    	{
    	    ModelLocator.getInstance().lines=event.message.body as Array;
    	    dc_main.invalidateDisplayList();
    	}
   
   	private function messagefaultHandler(event:MessageFaultEvent):void
   	{ 
   	   Alert.show(event.faultCode+' '+event.faultDetail+' '+event.faultString); 
   	}     		
            
	&#93;&#93;>
	</mx:Script>
	<mx:Consumer id="consumer" destination="painter" 
		message="messageHandler(event)" fault="messagefaultHandler(event)"/>
    <mx:Producer id="producer" destination="painter" 
    	fault="messagefaultHandler(event)"/>		

    <mx:Panel mouseOver="{ cursorID = CursorManager.setCursor(stift); }" 
    	mouseOut="{CursorManager.removeCursor(cursorID);}" 
    	layout="absolute" 
    	title="Montagsmaler by Björn Karpenstein" 
    	backgroundColor="#ffffff" 
    	borderThicknessBottom="10" left="20" 
    	right="20" top="20" bottom="20">
       <components:DrawableCanvas id="dc_main" backgroundColor="#ffff80" 
       	backgroundAlpha="0" width="100%" height="100%" y="0" x="0">
       </components:DrawableCanvas>
    </mx:Panel>
		
</mx:Application>

DrawableCanvas.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" width="400" height="300" 
       creationComplete="init()">
    
    <mx:Script>
        <!&#91;CDATA&#91;
            
            public var lineWidth:Number = 5;
            
            public var lineColour:uint = 1;
            
            public var lineAlpha:Number = 1;
            
            
            private var enabler:DrawingEnabler;
            
            private function init():void
            {
                enabler = new DrawingEnabler(this);
            }            
            
            protected override function updateDisplayList(unscaledWidth:Number,
                            unscaledHeight:Number):void
            {
                super.updateDisplayList(unscaledWidth, unscaledHeight);
            
                if( enabler != null ) enabler.drawLines(graphics, lineWidth, 
                lineColour, lineAlpha);
            }
        &#93;&#93;>
    </mx:Script>
</mx:Canvas>

DrawingEnabler.as

package components
{
import flash.display.Graphics;
import flash.events.MouseEvent;

import models.ModelLocator;

import mx.core.Application;
import mx.core.UIComponent;
import mx.messaging.messages.AsyncMessage;

public class DrawingEnabler
{
private var _target:UIComponent;

private var _curentLine:Array;

public function DrawingEnabler( target:UIComponent ):void
{
_target = target;
_target.addEventListener(MouseEvent.MOUSE_DOWN, downEvent);
_target.addEventListener(MouseEvent.MOUSE_UP, upEvent);
_target.addEventListener(MouseEvent.MOUSE_OUT, upEvent);
}

public function sendMessage(stream:Array):void
{
var message:AsyncMessage = new AsyncMessage();
message.body = stream;
Application.application.producer.send(message);
}

public function newLine():void
{
_curentLine = new Array();
ModelLocator.getInstance().lines.push( _curentLine );

if( ModelLocator.getInstance().lines.length > 10 )
ModelLocator.getInstance().lines.shift();
}

public function addPoint(xIN:int, yIN:int):void
{
_curentLine.push( { x:xIN, y:yIN } );
_target.invalidateDisplayList();
}

public function drawLines(graphics:Graphics,lw:Number,lc:uint,la:Number):void
{
graphics.clear();
graphics.lineStyle(lw,lc,la);

for( var j:int = 0; j

Flex 3 und Google Maps API

Aufgabenstellung

In eine Flex-Anwendung soll Google Maps integriert werden.

Ansatz

1.) Aufruf der Seite http://code.google.com/intl/de/apis/maps/documentation/flash/
2.) Runterladen der Flash-Bibliothek (SWC-Datei) und einfügen in das LIBS-Verzeichnis der Flex-Anwendung
3.) Anfordern des Schlüssels unter „Wie beginne ich…“ und eintragen in den Code unten.

Lösung

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:Script>
	<!&#91;CDATA&#91;
		import com.google.maps.overlays.Marker;
		import com.google.maps.controls.ZoomControl;
		import com.google.maps.geom.Attitude;
		import mx.controls.Alert;
		import com.google.maps.*;
		import com.google.maps.geom.*;
		import com.google.maps.services.GeocodingEvent;
		import com.google.maps.services.ClientGeocoder;
		import com.google.maps.controls.NavigationControl;
		import com.google.maps.controls.MapTypeControl;
		import com.google.maps.MapEvent;
		
		public function onMapReady(event:MapEvent):void
		{
			// Die Bedienelemente der Karte hinzufügen
			map.addControl(new MapTypeControl());
			map.addControl(new NavigationControl());
			this.geocodeLocation('Fulda, Am Rinnweg 15');
		}
		
		public function onMapPreinitialize(event:MapEvent):void
		{
			var myMapOptions:MapOptions = new MapOptions();
			myMapOptions.viewMode = View.VIEWMODE_2D;
			myMapOptions.mapType=MapType.NORMAL_MAP_TYPE;
			//myMapOptions.zoom=0;
			map.setInitOptions(myMapOptions);
		}
		
		private function geocodeLocation(location:String=''):void
		{
			var geocoder:ClientGeocoder = new ClientGeocoder();
			geocoder.addEventListener(GeocodingEvent.GEOCODING_SUCCESS,
			geoCodeSuccess);
			geocoder.addEventListener(GeocodingEvent.GEOCODING_FAILURE, 
			geoCodeFailure);

			if(location!='')
			{
				geocoder.geocode(location);
			}
			else
			{
				geocoder.geocode(search.text);
			}
		}
		
		private function geoCodeSuccess(event:GeocodingEvent):void
		{
			var placemarks:Array = event.response.placemarks;
			map.flyTo(placemarks&#91;0&#93;.point, 18, new Attitude(35,60,35), 
			3);
			var marker:Marker = new Marker(placemarks&#91;0&#93;.point); 
			map.addOverlay(marker); 
		}
		
		private function geoCodeFailure(event:GeocodingEvent):void
		{
			Alert.show('Also wissense, das find ich einfach ned....',
                                       'Ähm...');			
		}		
	&#93;&#93;>
</mx:Script>
	<mx:TitleWindow layout="vertical" width="100%" height="100%" 
              title="Finde versteckte Schätze... 
             (c) die 1st-level-caches Schludi&amp;Mephi ">
		<maps:Map3D xmlns:maps="com.google.maps.*" 
			  id="map" 
			  mapevent_mapready="onMapReady(event)" 
			  mapevent_mappreinitialize="onMapPreinitialize(event)"
			  width="100%" 
			  height="100%"
    		  key="HIER KOMMT DER BEANTRAGTE GOOGLE KEY REIN"/>
    	<mx:HBox horizontalAlign="center" width="100%" height="30">
			<mx:Label x="70" y="524" text="Da will ich hin:" 
                                        width="120" fontWeight="bold"/>
			<mx:Button x="421" y="520" label="Suchen ..." 
                                        click="{geocodeLocation()}"/>
			<mx:TextInput id="search" x="167" y="520" 
                                       width="246"/>
    	</mx:HBox>
	</mx:TitleWindow>
</mx:Application>

Beispiel

http://baugebiete.fulda.de

FluorineFX: Standard Authentifizierung über die Session

Aufgabenstellung

Im Internet sollen sich Benutzer an einer Anmeldemaske einer Flex-Anwendung anmelden. Die Berechtigungen sollen anhand des Benutzernamens festgelegt werden.

Ansatz

1.) FLEX: Erstellen der Anmeldemaske mit User/Passwort
2.) C#: Überprüfung ob die User/Passwort-Kombination in der Datenbank vorhanden ist
3.) C#: Falls JA: Benutzer in die sessionvariable USERNAME schreiben / Falls NEIN: Tue nichts
4.) C#: Überprüfung der Backend-Services: Ist USERNAME leer?
NEIN: Darf der User die Daten aufrufen?? (Dem Benutzer kann eine ROLLE zugewiesen sein, siehe Artikel „FluorineFX: Windows Authentifierung… Single Sign On“, und anhand der Rolle kann ermittelt werden ob sie berechtigt ist die Daten abzurufen).
JA: Keine Daten ausliefern!!!
5.) FLEX: Falls user authentifziert: Maskenaufbau in der Flexanwendung entsprechend Rolle

Lösung

Hier die wichtigsten Auszüge aus meiner Lösung:

FLEX:

...
<mx:Script>
public function getAuthenticationHandler(event:ResultEvent):void
{
   if( (event.result as Boolean) )
   {
     // Schalte ViewStack um (keine Loginmaske mehr)
     loginStack.selectedChild=tarzanScreen;

     // Speichere Username in Modellocator (evtl. auch Rolle holen)
     ModelLocator.getInstance().myUser=txtUsername.text;
   }
   else
   {
      Alert.show('Wrong username or password!');
    }						
}
</mx:Script>
...
<mx:TextInput id="txtUsername" />
<mx:TextInput id="txtPasswort" displayAsPassword="true"/>
...
<mx:RemoteObject id="authenticationService" destination="GenericDestination"  
                                          source="TarzanServices.AuthenticationService" 
                                          showBusyCursor="true" 
                                          fault="faultHandler(event)" >
    <mx:method name="getAuthentication" result="getAuthenticationHandler(event)"/>
</mx:RemoteObject>

C#: Benutzername ermitteln: Falls „none“ einfach nix in Maske/Service machen:

using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Data.SqlClient;
using System.Web;
using FluorineFx;
using System.Security.Principal;
...
public string getSessionUser()
{
    if (FluorineFx.Context.FluorineContext.Current.Session["username"]==null) 
    return "none";

    return FluorineFx.Context.FluorineContext.Current.Session["username"].ToString();
}

C#: Überprüfe ob User/Kennwort-Kombination in der Datenbank vorhanden ist, falls JA, schreibe USERNAME in Sesseion und liefere TRUE zurück, damit Flex weiß das es von der Login-Maske weg darf.. (REAKTION IN FLEX).

public bool getAuthentication(string username, string password)
{
   SqlConnection conn = new SqlConnection(MyConfigurationManager.msSqlServerString);

   try
   {
      conn.Open();
      SqlCommand comm = new SqlCommand();
      comm.Connection = conn;
      comm.CommandText = "SELECT * FROM [users] WHERE [username]=@username "+
                                     "AND [password]=@passwort";
      comm.Parameters.AddWithValue("username", username);
      comm.Parameters.AddWithValue("passwort", password);

      int i = 0;
      SqlDataReader reader = comm.ExecuteReader();

       while (reader.Read())
       {
              i++;
       }

       if (i > 0)
       {
           // Schreibe USER in die Datenbank
           FluorineFx.Context.FluorineContext.
           Current.Session["username"] = username;
       }

       return i > 0;
   }
   catch (Exception e)
   {
        return false;
   }

   return false;
}

FluorineFX: Single-Sign-On per Windows Authentifizierung

Aufgabenstellung

In einem Windows Netzwerk mit Windows Servern sollen beim Aufruf einer Intranet-Web-Anwendung der angemeldete Benutzer automatisch ermittelt werden, so dass keine Benutzername/Kennwort-Maske zusätzlich erforderlich ist. Anhand des Benutzernamens können Berechtigungen festgemacht werden, die z.B. über ein Benutzerrollen-Konzept zugewiesen werden können.

Ansatz

Es wird die FluorineFX-API verwendet um den Windows User zu ermitteln. Dies kann im Preloader der Flex/Flash-Anwendung geschehen. Die Masken werden nachträglich anhand der Rolle aufgebaut bzw. angepasst.

Definition des Benutzerrollenkonzeptes

Eine Rolle ist eine Aufgabe, die eine Person in einem Unternehmen hat. In der Datenbank würde man auf relationaler Ebene 3 Tabellen benötigen, um ein Benutzerrollen-Konzept zu realisieren. Ein Benutzer hat keine, eine oder mehrere Rollen, wobei eine Rolle keinem, einem oder mehreren Benutzern zugewiesen werden kann. Man benötigt also eine Tabelle USER und eine Tabelle ROLE. Da es sich hierbei um eine M:N-Beziehung handelt, ensteht eine Zusatztabelle USERROLE, die die Foreign Keys der Tabelle USER und ROLE speichert.

Vorraussetzungen

In der Firma wird vorzugsweise der Internet-Explorer verwendet. Die Verwendung von Firefox ist möglich, benötigt aber eine Spezialkonfiguration für Windows-Authentifizierung.

Im IE:

Unter Extras / Internetoptionen / Erweitert / Integrierte Windows Authentifzierung aktivieren

Im Firefox:

  • in der Adressleiste „about:config“ eingeben
  • im Fenster rechte Maustaste, dann Neu -> String
  • Eigenschaftsnamen „network.automatic-ntlm-auth.trusted-uris“ eingeben
  • String-Wert: Name des Portalrechners. Wenn auf das Portal beispielsweise

über http://musterfirma/teamworks zugegriffen wird ist der korrekte Wert „musterfirma“ eingetragen

Im IIS Manager sollte mit der rechten Maustaste auf der Web-Anwendung / Eigenschaften der Haken „Windows Authentication“ gesetzt sein, sonst liefert der IIS nichts zurück.

Lösung

1.) Anlegen der 3 Tabellen USER, USERROLE und ROLE.
2.) Der Windows-User kann in einem ordinären Backendservice mit folgendem Code ermittelt werden:

System.Security.Principal.WindowsPrincipal p =
System.Threading.Thread.CurrentPrincipal as System.Security.Principal.WindowsPrincipal;
string strName = p.Identity.Name;
string userWithDeletedDomainPrefix = strName.Substring(strName.IndexOf("\\") + 1).Trim();

3.) Reaktion in der Flex-Anwendung auf die zugewiesenen Rollen (Maskenaufbau)
4.) Schützen der Backendservices anhand der zugewiesenen Rollen (Auch ohne Flex keine sicherheitskritischen Daten aufrufbar)

ASP.NET und C#: Excel Dateien im Web darstellen

Aufgabenstellung

Eine Excel Datei, die in einer Firma ständig aktualisiert wird, soll auf Smart Phones (Blackberrys, iPhones, Windows Mobiles, HTCs …) abrufbar sein.

Vorraussetzungen

Vorweg solllte der Artikel „WebDAV mit IIS einrichten“ durchgearbeitet werden, um Zugriff zu bekommen.

Ansatz

Die Datei wird ortstransparent auf einem WebDAV-Laufwerk (siehe Artikel WebDAV einrichten) eines Microsoft IIS Server gespeichert, der nach dem Verfahren von Artikel „WebDAV einrichten“ konfiguriert ist.. Der Microsoft JET Treiber verfügt über die Fähigkeiten, Excel Dateien zu laden (nur XLS, nicht XLSX oder XLSM). Im IIS Server wird eine ASP.NET Anwendung angelegt. Das nachfolgende Beispiel ist so eingerichtet, das es versuchen wird aus dem Web Root der Anwendung eine Datei test.xls zu laden.

Lösung

Anlegen eine ASP.NET Seite:

<%@ Page Language="C#" AutoEventWireup="true"  
CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Unbenannte Seite</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:GridView ID="GridView1" runat="server">
        </asp:GridView>
        &nbsp;</div>
        <asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
    </form>
</body>
</html>

… mit dem folgenden Code:

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Data.OleDb;

public partial class _Default : System.Web.UI.Page 
{
    protected void Page_Load(object sender, EventArgs e)
    {

        string appPath = HttpContext.Current.Request.ApplicationPath;
        string physicalPath = HttpContext.Current.Request.MapPath(appPath).
                                                         Replace("\\","\\\\");

        string ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0; 
        Data Source="+physicalPath+"\\test.xls; Extended Properties="+
                                                              \"Excel 8.0;HDR=Yes\";";

        string CommandText = "select * from [Tabelle1$]";

        OleDbConnection myConnection = new OleDbConnection(ConnectionString);
        OleDbCommand myCommand = new OleDbCommand(CommandText, myConnection);

        myConnection.Open();

        GridView1.DataSource = myCommand.ExecuteReader(CommandBehavior.CloseConnection);
        GridView1.DataBind();

        myConnection.Close();

        Label1.Text = physicalPath;
    }
}

Beispiel

Diese Datei kann per WebDAV bearbeitet werden:
http://178.77.69.22/test.xls

und wird on-the-fly in eine HTML-Seite umgewandelt:
http://178.77.69.22/exceltoweb/Default.aspx

Baum von mx:AdvancedDataGrid in die Zwischenablage kopieren

Aufgabe

Der Baum eine mx:AdvancedDataGrid mit mx:SummaryRows soll in die Zwischenablage kopiert werden.

Problem

Der DataProvider des mx:AdvancedDataGrid ist eigentlich flach. Der erzeugte Baum/die Hierarchie soll in die Zwischenablage kopiert werden, so dass er in Excel per Copy&Paste mit Einrückungen und Summen eingefügt werden kann.

Lösung

Eine rekursive Funktion wird erstellt, die über den erzeugten Baum iteriert:

// Für das Einrücken in die Zwischenablage
public var anzahlGroupingFelder:Number=0; // Gesamttiefe der Felder
public var aktuelleTiefe:Number=0;		  // Tiefe der aktuellen Rekursion
public var rekursionsString:String='';
public var farbenFuerLevel:Object = new Object();
private function getCompleteADGRekursion(treeData:ArrayCollection):void
{
	var aktuelleGruppeFelder:String='';
		
	for (var i:Number=0; i<treeData.length; i++)
	{
		if(i==0) aktuelleTiefe++;
		var tabs:String='';
		var behindtabs:String='';
		
		for (var l:Number=0; l<=anzahlGroupingFelder-aktuelleTiefe+3; l++) 
		{
			behindtabs+='<td></td>';						
		}
					
				
		var item:Object=treeData[i];
		if(item.children)
		{
			for (var j:Number=1; j<aktuelleTiefe; j++)
			{
				tabs+='<td></td>';						
			}
						
			var properties:String='<td><i><b>'+item.actuallastyear+
			'</b></i></td><td><i><b>'+item.actualyeartillaugust+
			'</b></i></td><td><i><b>'+item.actualyearlinearprojection+
			'</b></i></td><td><i><b>'+item.nextyear+
			'</b></i></td><td><i><b>'+item.nnextyear+
			'</i></td><td><i><b>'+item.nnnextyear+
			'</b></i></td><td><i><b>'+item.nnnnextyear+
			'</b></i></td>';
						
			rekursionsString+='<tr style="background-color:'+
			farbenFuerLevel&#91;aktuelleTiefe&#93;+';">'+tabs+
			'<td>'+item.GroupLabel+behindtabs+'</td>'+
			properties+'</tr>';
			getCompleteADGRekursion(item.children);
		}
		else
		{
			for (var k:Number=0; k<anzahlGroupingFelder; k++)
			{
				tabs+='<td style="background-color:#EFEFEF"></td>';						
			}
						
			var properties2:String='<td>'+item.productcharacteristic+
			'</td><td>'+item.productcharacteristic2+
			'</td><td>'+item.productcharacteristic3+
			'</td><td>'+item.material+
			'</td><td>'+item.actuallastyear+
			'</td><td>'+item.actualyeartillaugust+
			'</td><td>'+item.actualyearlinearprojection+
			'</td><td>'+item.nextyear+
			'</td><td>'+item.nnextyear+
			'</td><td>'+item.nnnextyear+
			'</td><td>'+item.nnnnextyear+'</td>';												
			
			rekursionsString+='<tr>'+tabs+properties2+'</tr>';
		}
					
		// Beim ersten gehts eins tiefer
					
		// Beim letzten eins zurück
		if(i==treeData.length-1) aktuelleTiefe--;
	}
}

Aufruf der rekursiven Funktion:

private function copyToClipboard(dg:AdvancedDataGrid):void
{
// Farben je Interationstiefe;

farbenFuerLevel[1]=’#00B684′;
farbenFuerLevel[2]=’#31C39C‘;
farbenFuerLevel[3]=’#63D3B5′;
farbenFuerLevel[4]=’#CEF3E7′;
farbenFuerLevel[5]=’#BBBBBB‘;
farbenFuerLevel[6]=’#AAAAAA‘;
farbenFuerLevel[7]=’#999999′;
farbenFuerLevel[8]=’#888888′;

var wasIstImDataProviderDebug:Object=dg.dataProvider;
try
{
anzahlGroupingFelder=Number(wasIstImDataProviderDebug.source.
grouping.fields.length);
}
catch(e:Error)
{
anzahlGroupingFelder=0;
}

if(anzahlGroupingFelder>0)
{
rekursionsString=‘‚;
rekursionsString=‘

‚;
rekursionsString=‘

‚;

for each (var order:ReportingOrderVO in reportingOrder)
{
if(order.position!=0)
{
rekursionsString+=‘

‚;
}
}

rekursionsString+=‘

‚;
rekursionsString+=‘

‚;
rekursionsString+=‘

‚;
rekursionsString+=‘

‚;
rekursionsString+=‘

‚;
rekursionsString+=‘

‚;
rekursionsString+=‘

‚,
rekursionsString+=‘

‚;
rekursionsString+=‘

‚;
rekursionsString+=‘

‚;
rekursionsString+=‘

‚;
getCompleteADGRekursion(
ArrayCollection(dg.hierarchicalCollectionView.source
.getRoot())
);
rekursionsString+=‘

‚+order.description+
Detail 1 Detail 2 Detail 3 Detail 4 Actual last year‚;
rekursionsString+=‘
Actual year‘;
rekursionsString+=‘ till august
Actual year linear projection Next year NNext year NNNext year NNNNext year

‚;
System.setClipboard(rekursionsString);
}
else
{
var totalExport:String = new String();
var colList:Array = new Array();
for(var i:int = 0; i < dg.columnCount; i++) { colList.push(dg.columns[i].dataField); totalExport += String(dg.columns[i].headerText) .replace('\r',' ') + "\t"; } totalExport += "\r"; for(var yp:int = 0; yp < reporting.length; yp++) { for(var xp:int = 0; xp < colList.length; xp++) { var insertString:String=reporting .getItemAt(yp)[colList[xp]]; if(!insertString) insertString=''; totalExport += insertString .replace("\t","") + "\t"; } totalExport += "\r"; } System.setClipboard(totalExport); } Alert.show('Data has been copied to clipboard. Now you can paste it to '+ 'Microsoft Excel or another application of your choice.'); } [/actionscript3]