Zum Inhalt

Tipps, Tricks & Beispiele

Auf dieser Seite finden Sie praxisnahe Codebeispiele und Lösungen zu häufigen Aufgaben mit der COM-Aktiv-Schnittstelle. Die Beispiele stammen aus dem laufenden Support-Betrieb und werden fortlaufend ergänzt.

Ergänzende Referenz

Die grundlegenden Konzepte zu Verbindung, Ranges und Filtern werden ausführlich unter Verbindung und Datenzugriff beschrieben. Die hier gezeigten Beispiele dienen als konkrete Ergänzung.

Regeln bei COM-Zugriffen

Die microtech Software verfügt über ein Regelsystem, das bei bestimmten Aktionen automatisch greift. Nicht alle Regeln werden bei Zugriffen über die COM-Schnittstelle ausgelöst. Die folgende Übersicht zeigt, welche Auswertungspositionen bei COM-Zugriffen beachtet werden und welche nicht.

Positionen

Beachtet bei COM-Zugriffen:

  • Vor dem Speichern nach einer Neuanlage oder Änderung (über das Erfassungsformular)
  • Für das Bearbeiten von Positionen bzw. nach dem Wandeln / Einladen (über das Erfassungsformular) von Positionen (im Zielvorgang)
  • „Vor dem Übernehmen von Vorgangspositionen"

Nicht beachtet bei COM-Zugriffen:

  • Während der Erfassung
  • Positions-Prüfung vor dem Speichern Lauf 1 (über das Erfassungsformular)
  • Positions-Prüfung vor dem Speichern Lauf 2 (über das Erfassungsformular)

Vorgänge

Beachtet bei COM-Zugriffen:

  • Vor dem Buchen von Vorgängen
  • Nach dem Buchen / Stornieren von Vorgängen
  • Vor dem Speichern (nach einer Neuanlage, Änderung oder einem Import)
  • Während der Erfassung

Nicht beachtet bei COM-Zugriffen:

  • Vor dem Speichern nach einer Neuanlage oder Änderung (über das Erfassungsformular)

Wandeln

Beachtet bei COM-Zugriffen:

  • Nach dem Wandeln / Einladen (über das Erfassungsformular)
  • Für das Bearbeiten von Positionen bzw. nach dem Wandeln / Einladen (über das Erfassungsformular) von Positionen (im Zielvorgang)
  • Nach dem Übernehmen von Vorgangspositionen (im Zielvorgang)
  • Vor dem Übernehmen von Vorgangspositionen

Nicht beachtet bei COM-Zugriffen:

  • Nach dem Übernehmen von Vorgangspositionen (im Quellvorgang)

Adressen

Beachtet bei COM-Zugriffen:

  • Vor dem Speichern (nach einer Neuanlage, Änderung oder einem Import)

Nicht beachtet bei COM-Zugriffen:

  • Alle anderen Auswertungspositionen

Artikel

Nicht beachtet bei COM-Zugriffen:

  • Vor dem Speichern (nach einer Neuanlage, Änderung oder einem Import)
  • Vor dem Speichern nach einer Neuanlage oder Änderung (über das Erfassungsformular)
  • Beim Buchen / Stornieren von Positionen (aus Vorgängen)

Wahl der Vorgangspositionen beim Wandeln

Eine direkte Möglichkeit, beim Wandeln per COM einzelne Positionen an- oder abzuwählen, besteht nicht. Im SpecialObject AutoWandeln kann definiert werden, ob die Liefermenge beachtet werden soll und ob diese vor dem Wandeln neu berechnet wird.

Eine Position, deren Neuberechnung eine Liefermenge von 0 ergibt, oder eine Position, die bereits vor dem Wandeln die Liefermenge 0 hat, wird bei aktivierter Option „Liefermenge beachten" nicht mitgewandelt. Über die Liefermenge kann also gesteuert werden, welche Positionen gewandelt werden.

Sollen bestimmte Positionen unabhängig von der Liefermenge vom Wandeln ausgeschlossen werden, kann eine Positions-Regel „Vor dem Übernehmen von Vorgangspositionen" angelegt werden. Die Bedingung wird frei gestaltet; als Regelanweisung wird für das Feld WandelnKz der Feldwert gelöscht.

Grundlagen der COM-Verbindung

Init und SelectMand

Die Aufrufe Init und SelectMand sind die ersten Funktionen, die bei jeder COM-Anbindung Verwendung finden.

Verbindungsaufbau (Pseudocode)
BpApp.Init("Ihr Firmenname", "", "Ihr Anmeldename", "Ihr Password")
BpApp.SelectMand("Ihre Mandantennummer")

Der Parameter „Ihr Firmenname" entspricht dem Firmennamen, der in der microtech Software unter DATEI > HILFE > SUPPORT & HOTLINE angezeigt wird. Im dort geöffneten Fenster finden Sie unter „Ihre Kundendaten bei microtech" den Firmennamen und die Postleitzahl. Achten Sie auf exakte Groß-/Kleinschreibung und enthaltene Leerzeichen.

Beim Aufruf von SelectMand() wird die Nummer des gewünschten Mandanten als String übergeben. Die Vorgabe ist numerisch, es sind jedoch auch alphanumerische Werte möglich.

Voraussetzungen:

  • Der Mandant muss COM-Zugriffe erlauben: DATEI > INFORMATIONEN > AKTUELLE FIRMA / FILIALE / MANDANT > MANDANT BEARBEITEN, Karteiseite „weitere Angaben" → Option „COM Schnittstelle für registrierte Anwendungen verfügbar" aktivieren.
  • Der verwendete Benutzer benötigt das Recht „Fremdanwendungen dürfen über registrierte COM-Schnittstelle auf büro+ Daten zugreifen (und bearbeiten)" unter DATEI > INFORMATIONEN > EINSTELLUNGEN > Berechtigungsstrukturen > GLOBAL > DATEI.
  • Die Programminstallation muss im normalen Modus (auch nach jedem Update) mindestens einmal gestartet worden sein.

Bei Fehlermeldungen: Aktivieren Sie die erweiterte Fehlerprotokollierung. In vielen Fällen wird dann eine detailliertere Meldung über die COM-Schnittstelle ausgegeben.

COM-Aktiv Developer/Solution Partner: Bei einer Developer/Solution-Partner-Anbindung ändert sich der Init-Aufruf:

Init mit Connection Key
Init("Connection String", "Connection Key", "Anmeldename", "Password")

Connection String und Connection Key werden von microtech bereitgestellt. Vom Endbenutzer müssen nur noch Anmeldename und Passwort abgefragt werden.

Namen und Datentypen von Datenbankfeldern

Um eine Liste aller Datenbanken, Datenfelder und Indices zu erhalten, nutzen Sie das Feldliste-Werkzeug. Eine Anleitung dazu finden Sie unter Verbindung und Datenzugriff.

Die erzeugte Datei FELD.LST enthält alle Felder, mit denen Sie arbeiten können. Der * kennzeichnet Calculated Fields - diese sind nicht immer per Zuweisung änderbar. Anhand des Datentyps sehen Sie, wie die Felder weiter verarbeitet werden müssen (z. B. Array → BetragGruppe, Info → RichEdit).

Ein Feld mit dem Datentyp Table ist ein Verweis auf eine andere Tabelle. Über Feld.Info kann der Name der verknüpften Tabelle ermittelt werden. Meist wird zur Verknüpfung der Primärschlüssel der zugrundeliegenden Tabelle verwendet - das Feld selbst enthält keinen Wert.

Ausbaustufen

Nicht alle Felder sind in allen Ausbaustufen verfügbar. Es kann nur auf Felder und Tabellen zugegriffen werden, die in der zugrundeliegenden Version vorhanden sind.

Schreibschutz einzelner Felder kann sich ändern

Auch ob ein einzelnes Feld über COM beschreibbar ist, ist nicht dauerhaft festgelegt - ein Beispiel ist das Feld DBKuNa der Tabelle Regeln, das seit Programmgeneration 24 beschreibbar ist. Details dazu und ein generelles Vorgehen bei ähnlichen Fällen finden Sie unter Verbindung und Datenzugriff, Abschnitt "Felder lesen und schreiben".

Exception Handling und Transaktionen

Folgende Grundregeln gelten für alle Datenbankzugriffe:

  • Alle Datenbankzugriffe mit einer Exception-Behandlung versehen.
  • Jeder Fehler nach einem Edit oder Append und vor dem zugehörigen Post muss ein Cancel auslösen.
  • Jede Transaktion muss eine Fehlerbehandlung haben. Ein Rollback muss in jedem Fehlerfall gewährleistet sein.
  • LockTable durch intelligentes Retry-Management vermeiden.
Exception Handling für Tabellen (Delphi)
DataSet.Edit;
try
  //... DataSet bearbeiten ...
  DataSet.Post;
except
  DataSet.Cancel;
  raise;
end;
Exception Handling für Transaktionen (Delphi)
DataSet.StartTransaction;
try
  //... Aktionen in Tabelle A ausführen ...
  //... Aktionen in Tabelle B ausführen ...
  //... Aktionen in Tabelle n ausführen ...
  DataSet.Commit;
except
  DataSet.Rollback;
  raise;
end;
Erweitertes Beispiel für Transaktionen und NestedDataSets (Delphi)
var
  bTransactionStarted := boolean;

bTransactionStarted := DataSet.TryStartTransaction;
try
  DataSet.First;
  while not DataSet.EOF do
  begin
    DataSet.Edit;
    try
      DataSet.Fld['bspBoolean'].AsBoolean := False;
      DataSet.Post;
    except
      DataSet.Cancel;
      raise
    end;
    DataSet.Next;
  end;

  // Blob Table Elemente
  DataSet2.Edit;
  try
    NestedDataSet := DataSet2.NestedDataSets['Nested'];
    try
      NestedDataSet.Last;
      while not NestedDataSet.BOF do
      begin
        NestedDataSet.Edit;
        try
          NestedDataSet.Fld['BspBoolean'].AsBoolean := False;
          NestedDataSet.Post;
        except
          NestedDataSet.Cancel;
          raise
        end;
        NestedDataSet.Prior;
      end;
      NestedDataSet.PostNestedDataSet;
    finally
      NestedDataSet := nil;
    end;
    DataSet2.Post;
  except
    DataSet2.Cancel;
    raise;
  end;

  if bTransactionStarted then
    DataSet.Commit;
except
  if bTransactionStarted then
    DataSet.RollBack
  else
    DataSet.TransactionCorrupted;
  raise;
end;
Exception Handling (Visual Basic)
Public Sub Demo()
  Call DataSet.Append
  On Error GoTo PostError

  ' ...DataSet bearbeiten...

  DataSet.Post  'speichern
  GoTo Clear

PostError:
  DataSet.Cancel
  Resume Next

Clear:
  Set DataSet = Nothing
End Sub

Die Aufrufe einer Transaction stehen mit den Optionen RequestTimeOut und RequestRetryCount in Verbindung. Damit kann der Programmablauf kontrolliert werden:

Retry-Management (Pseudocode)
Application.RequestTimeOut = 2000   // 2 Sekunden für die Aktion
Application.RequestRetryCount = 0   // Beim ersten Fehlschlag Exception auslösen

while TransactionFailed oder Retry < 10 do
  TransactionFailed = DoTransaction
  Retry = Retry + 1
end while

Type Library einsehen

Um nachzusehen, welche Elemente in der Type Library existieren, verwenden Sie einen beliebigen Type Library Viewer, der mit eingebetteten TLBs umgehen kann. Die BpNexT.exe im Installationspfad (\Buero Plus NexT) enthält die TLB-Beschreibung mit der kompletten Definition aller Funktionsaufrufe und Aufzählungstypen.

Alternativ können Sie z. B. Microsoft Word verwenden: Öffnen Sie den Visual-Basic-Editor unter Extras > Makro > Visual Basic-Editor, setzen Sie einen Verweis auf die „BpNT Bibliothek" unter Extras > Verweise, und öffnen Sie den Objektkatalog unter Ansicht > Objektkatalog.

Weiterführende Informationen

Für grundlegende Fragen zur COM-Programmierung verwenden Sie die Dokumentation Ihrer Programmiersprache. Relevante Suchbegriffe: Verweise, Type Library, Automatisierung, COM, Interface, CreateOleObject, Early Binding, Late Binding, Gültigkeitsdauer, Reference Count, Component Object Model, QueryInterface.

BpRefLinks ermöglichen die Navigation innerhalb der microtech Software über ein URL-artiges Protokoll. Sie können z. B. in Schaubildern verwendet werden.

Aufbau

BpRef://<Bezeichner>@|<ParameterListe>

Der Bezeichner kann frei gewählt werden und dient der Identifikation des Links. Er kann auch leer sein. Die Parameterliste enthält mehrere Parameter im Schema <Name>=<Wert>, getrennt durch Komma.

Aktionen

hacOnline - Öffnet eine Webseite im Standardbrowser:

Webseite öffnen
BpRef://Microtech Webseite öffnen@|Action=hacOnline,Adresse=microtech.de

hacGoto - Führt programminterne Funktionen aus. Der Bereich wird über den Parameter DbsKz festgelegt:

In einen Bereich navigieren
BpRef://Alle_Vorgänge@|Action=hacGoto,DbsKz=dbsVorgang
BpRef://Stammdaten Vertreter@|Action=hacGoto,DbsKz=dbsVertreter
BpRef://Bestellvorschlag@|Action=hacGoto,DbsKz=dbsBestellvorschlag

Die Werte für DbsKz können im entsprechenden Bereich eingesehen werden: In der Übersicht steht unter Hilfe > Erzeuge Navigationslink ein Eintrag bereit, der einen fertigen Navigationslink erzeugt.

Suche und Index

Mit den Parametern Index und Suche wird gesteuert, was im Bereich angezeigt oder gesucht wird:

Suche mit Index
bpref://Vertreter aus Bad Kreuznach@|Action=hacGoto,DbsKz=dbsVertreter,Index=Ort,Suche=Bad Kreuznach
bpref://Interessenten@|Action=hacGoto,DbsKz=dbsAdressen,Index=Status,Suche=Interessent

Vorauswahl mit ExtSuche

In Bereichen mit Vorauswahl muss zusätzlich ExtSuche angegeben werden:

ExtSuche-Beispiele
bpref://Lastschriften@|Action=hacGoto,DbsKz=dbsZahlungsVerkehr,ExtSuche=7
bpref://Lieferscheine Anzeigen@|Action=hacGoto,DbsKz=dbsVorgang,ExtSuche=50
bpref://Eine Rechnung suchen@|Action=hacGoto,DbsKz=dbsVorgang,ExtSuche=70,Index=BelegNr,Suche=RE1234

Funktionskennzeichen (FktKz)

Mit FktKz wird gesteuert, welche Aktion ausgeführt wird. Für jeden Bereich stehen dieselben Kennzeichen zur Verfügung: fktNeu (Neuanlage), fktDrucken (Druckübersicht), fktAendern (Datensatz ändern), fktKopieren (Kopieren), fktLoeschen (Löschen).

Aktionen per FktKz
BpRef://Adressen Drucken@|Action=hacGoto,DbsKz=dbsAdressen,FktKz=fktDrucken
BpRef://Artikel_erfassen@|Action=hacGoto,DbsKz=dbsArtikel,FktKz=fktNeu
BpRef://Vertreter aus BK kopieren@|Action=hacGoto,DbsKz=dbsVertreter,Index=Ort,Suche=Bad Kreuznach,FktKz=fktKopieren

SendKeys und InfoText

Über SendKeys können Tastaturbefehle mitgegeben werden. Die Befehle müssen in doppelte Anführungszeichen gesetzt werden:

SendKeys-Beispiele
bpref://Fibu_Buchungen_einlesen@|Action=hacGoto,DbsKz=dbsBuchungenFiBu,SendKeys="[Alt+P] [L]"
bpref://Zahlungseingang_aktualisieren@|Action=hacGoto,DbsKz=dbsBankKontoAuszuege,SendKeys="[Alt+O]"
bpref://Geschaeftsvorfaelle neue Buchung@|Action=hacGoto,DbsKz=dbsBuchungenFiBu,SendKeys="[Alt+N]"

Mit InfoText kann ein Text mitgegeben werden, der dem Benutzer angezeigt wird:

InfoText-Beispiel
bpref://Mitarbeiter_infoText@|Action=hacGoto,DbsKz=dbsMitarbeiter,InfoText=Wählen Sie einen Mitarbeiter aus und prüfen Sie seine Abrechnung.

Die Berechtigungsprüfung ist voll integriert - ein Benutzer ohne Berechtigung für einen Datenbereich gelangt auch nicht über einen BpRefLink dorthin. Das BpRef-Protokoll ist systemweit registriert und kann von überall aus ausgeführt werden.

Mandanten auflisten

Mandanten abfragen (Delphi)
Program delphi;
var
  BpApp: Variant;
  BpTbl: Variant;
begin
  BpApp := CreateOleObject('BpNT.Application');
  BpApp.Init('Anmeldedaten','','Benutzer','Passwort');

  BpTbl := BpApp.MandList;
  ShowMessage(IntToStr(BpTbl.RecordCount));
  BpTbl.First;
  ShowMessage(BpTbl.Fields['MandNr'].AsString);

  VarClear(BpTbl);
  VarClear(BpApp);
end;

Kundendaten auslesen

Die Funktion GetKundendaten liefert eine eindeutige Identifizierung des Kunden bei microtech. Sie kann mit dem Login GetKundendaten aufgerufen werden, ohne dass ein Mandant ausgewählt sein muss.

Kundendaten abfragen (Delphi)
procedure Kundendaten;
var
  KdNr, FrmN, Plz: Variant;
begin
  try
    BpApp := CreateOleObject('BpNT.Application');
    BpApp.Init('Ihr Anbindungsname', 'Ihr Key', 'Ihr Login', 'Ihr Passwort');
    BpApp.GetKundendaten(KdNr, FrmN, Plz);
    Showmessage(KdNr + ' ' + FrmN + ' ' + Plz);
  finally
    BpApp := nil;
  end;
end;
Aufruf ohne reguläre Anmeldung
BpApp.Init('GetKundendaten','','','');

Versionsnummer

Das aktuelle Versionsnummernformat (Stand 2026) hängt davon ab, ob es sich um die Release- oder BETA-Version handelt und ob "GetVersion" oder "GetVersionEx" verwendet wird:

Format GetVersion GetVersionEx
Release 26.0.7200 Enterprise-26.2.7200
BETA 26.0.7204.63 Enterprise-26.2.7204.63

Die Bestandteile: Hauptversion (jährlich geändert), Nebenversion (Quartal, in dem die Version veröffentlicht wurde), Build-Version, Build-Ergänzung bei BETA-Version. Bei eigenen Prüfungen müssen alle 4 Blöcke berücksichtigt werden.

Versionsnummer über COM abfragen

GetVersion und GetVersionEx (Delphi)
procedure Version;
begin
  try
    BpApp := CreateOleObject('BpNT.Application');
    BpApp.Init('Ihr Anbindungsname', 'Ihr Key', 'Ihr Login', 'Ihr Passwort');
    Showmessage(BpApp.GetVersion);
    ShowMessage(BpApp.GetVersionEx);
  finally
    BpApp := nil;
  end;
end;

Die Funktion kann auch mit dem Login GetKundendaten aufgerufen werden.

Versionsnummer ohne COM-Instanz

Die BpNexT.exe trägt eine Versionsinformation, die über die Standard-Windows-Funktionen GetFileVersionInfoSize / GetFileVersionInfo abrufbar ist. Der Speicherort der Datei kann in der Registry unter dem Pfad HKEY_LOCAL_MACHINE\SOFTWARE\microtech\Buero Plus\NexT im Eintrag ProgrammPfad ermittelt werden.

Hinweis

Die auf diesem Weg gelesene Versionierung enthält keine Information über einen eingetragenen Schlüssel. Die schlüsselbasierten Informationen lesen Sie über die COM-Schnittstelle selbst aus.

Konfigurationsdaten speichern und lesen

Die COM-Schnittstelle bietet drei Ebenen zum Speichern von Konfigurationsdaten:

Ebene Property Gültigkeitsbereich
Mandant Konfiguration Mandantenbezogen
Benutzer BzrKonfiguration Benutzerbezogen
Global GloKonfiguration Gesamter Datenbestand
Konfiguration schreiben und lesen (Delphi)
// Schreiben
BpApp := CreateOleObject('BpNT.Application');
BpApp.Init('Firmenname','','Anmeldename','Password');
BpApp.SelectMand('1');

BpApp.Konfiguration['Wert1'] := 'vollkommen beliebiger String';
BpApp.BzrKonfiguration['Wert2'] := 'vollkommen beliebiger String1';
BpApp.GloKonfiguration['Wert3'] := 'vollkommen beliebiger String2';
VarClear(BpApp);

// Lesen
BpApp := CreateOleObject('BpNT.Application');
BpApp.Init('Firmenname','','Anmeldename','Password');
BpApp.SelectMand('1');

ShowMessage(BpApp.Konfiguration['Wert1']);
ShowMessage(BpApp.BzrKonfiguration['Wert2']);
ShowMessage(BpApp.GloKonfiguration['Wert3']);
VarClear(BpApp);

Defekte Instanz nachträglich löschen

Wenn eine COM-Instanz sich nicht korrekt beendet hat, kann sie über die Prozess-ID terminiert werden:

Instanz per Prozess-ID beenden (Delphi)
Program Delphi;
const
  soAppObject = 4;
  PROCESS_TERMINATE = $0001;
var
  BpApp: Variant;
  Handle: Cardinal;
  ProcH: THandle;
  ExitCode: Cardinal;
begin
  BpApp := CreateOleObject('BpNT.Application');
  BpApp.Init('Ihr Firmenname','','Benutzername','Passwort');
  BpApp.writeln('Kontrollfenster');

  Handle := (BpApp.GetSpecialObject(soAppObject) as AutoAppObject).GetAppProcessId;

  // Hier schwere Arbeit verrichten
  BpApp := nil;
  // nach der dann die Instanz nicht gelöscht werden kann.

  ProcH := OpenProcess(PROCESS_TERMINATE, false, Handle);
  TerminateProcess(ProcH, ExitCode);
  CloseHandle(ProcH);
end;

Adressen

Adresse und Anschrift anlegen

Eine Adresse und eine Anschrift gehören immer zusammen - eine Anschrift ohne gültige Adresse existiert nicht.

Adresse mit Rechnungs- und Lieferanschrift anlegen (VBA)
' BpApp wird außerhalb der Funktion gesetzt und freigegeben.
Private Sub AdressenAnschriften()
  Dim tblAdressen As BpNT.AutoDataSet
  Dim tblReAnschriften As BpNT.AutoDataSet
  Dim tblLiAnschriften As BpNT.AutoDataSet
  Dim lastAdrNr

  Set tblAdressen = BpApp.DataSetInfos("Adressen").CreateDataSet
  Set tblReAnschriften = BpApp.DataSetInfos("Anschriften").CreateDataSet
  Set tblLiAnschriften = BpApp.DataSetInfos("Anschriften").CreateDataSet

  On Error Resume Next

  ' Letzte Adressnummer suchen
  tblAdressen.Indices("NrNum").Select
  tblAdressen.Last

  If IsNumeric(tblAdressen.Fields("AdrNr").AsString) Then
    lastAdrNr = CInt(tblAdressen.Fields("AdrNr").AsString) + 1
  End If

  ' Adresse anlegen
  tblAdressen.Append
  tblAdressen.Fields("AdrNr").AsString = lastAdrNr
  tblAdressen.Fields("ReAnsNr").AsInteger = 0
  tblAdressen.Fields("LiAnsNr").AsInteger = 1
  tblAdressen.Post

  ' Rechnungsanschrift
  tblReAnschriften.Append
  tblReAnschriften.Fields("AdrNr").AsString = tblAdressen.Fields("AdrNr").AsString
  tblReAnschriften.Fields("AnsNr").AsInteger = 0
  tblReAnschriften.Fields("Ort").AsString = "Ort"
  tblReAnschriften.Fields("PLZ").AsString = "55545"
  tblReAnschriften.Fields("Str").AsString = "Straße"
  tblReAnschriften.Fields("Na1").AsString = "Title"
  tblReAnschriften.Fields("Na2").AsString = "Vorname"
  tblReAnschriften.Fields("Na3").AsString = "Nachname"
  tblReAnschriften.Post

  ' Lieferanschrift
  tblLiAnschriften.Append
  tblLiAnschriften.Fields("AdrNr").AsString = tblAdressen.Fields("AdrNr").AsString
  tblLiAnschriften.Fields("AnsNr").AsInteger = 1
  tblLiAnschriften.Fields("Ort").AsString = "Platz"
  tblLiAnschriften.Fields("PLZ").AsString = "55546"
  tblLiAnschriften.Fields("Str").AsString = "andere Str."
  tblLiAnschriften.Fields("Na1").AsString = "Anrede"
  tblLiAnschriften.Fields("Na2").AsString = "Li Firma"
  tblLiAnschriften.Fields("Na3").AsString = "Firma Zusatz"
  tblLiAnschriften.Post

  Set tblAdressen = Nothing
  Set tblReAnschriften = Nothing
  Set tblLiAnschriften = Nothing
End Sub

Tipp

Wenn die Ermittlung der höchsten Adressnummer nicht zeitnah oder nur einmalig durchgeführt wird, sollte eine zusätzliche Prüfung auf bereits existierende Nummern stattfinden. Beachten Sie auch die Option CreateDataSetEx - wenn Sie nur die Standardwerte einer Adresse benötigen, ist deren Verwendung einfacher.

Adresse mit Ansprechpartner anlegen

Ein Ansprechpartner benötigt zur Zuordnung folgende Informationen: AdrNr (Adressnummer), AnsNr (Anschriftennummer), AspNr (laufende Nummer des Ansprechpartners), StdKz (Hauptansprechpartner-Kennzeichen, nur einmal pro AdrNr/AnsNr-Kombination zulässig).

Ansprechpartner für Rechnungsadresse 10000
AdrNr := "10000"  ' Adresse des Ansprechpartner-Datensatzes
AnsNr := "0"      ' Anschriften-Nummer
AspNr := "0"      ' Eigene laufende Nummer
StdKz := TRUE     ' Nur einmal pro AdrNr/AnsNr-Kombination
Vollständiges Beispiel mit Ansprechpartner (VBA)
' BpApp wird außerhalb der Funktion gesetzt und freigegeben.
Private Sub AdressenAnschriften()
  Dim tblAdressen As BpNT.AutoDataSet
  Dim tblReAnschriften As BpNT.AutoDataSet
  Dim tblLiAnschriften As BpNT.AutoDataSet
  Dim tblAnsprechpartner As BpNT.AutoDataSet
  Dim lastAdrNr

  Set tblAdressen = BpApp.DataSetInfos("Adressen").CreateDataSet
  Set tblReAnschriften = BpApp.DataSetInfos("Anschriften").CreateDataSet
  Set tblLiAnschriften = BpApp.DataSetInfos("Anschriften").CreateDataSet
  Set tblAnsprechpartner = BpApp.DataSetInfos("Ansprechpartner").CreateDataSet

  On Error Resume Next

  tblAdressen.Indices("NrNum").Select
  tblAdressen.Last

  If IsNumeric(tblAdressen.Fields("AdrNr").AsString) Then
    lastAdrNr = CInt(tblAdressen.Fields("AdrNr").AsString) + 1
  End If

  tblAdressen.Append
  tblAdressen.Fields("AdrNr").AsString = lastAdrNr
  tblAdressen.Fields("ReAnsNr").AsInteger = 0
  tblAdressen.Fields("LiAnsNr").AsInteger = 1
  tblAdressen.Post

  ' Rechnungsanschrift
  tblReAnschriften.Append
  tblReAnschriften.Fields("AdrNr").AsString = tblAdressen.Fields("AdrNr").AsString
  tblReAnschriften.Fields("AnsNr").AsInteger = 0
  tblReAnschriften.Fields("Ort").AsString = "Ort"
  tblReAnschriften.Fields("PLZ").AsString = "55545"
  tblReAnschriften.Fields("Str").AsString = "Straße"
  tblReAnschriften.Fields("Na1").AsString = "Title"
  tblReAnschriften.Fields("Na2").AsString = "Vorname"
  tblReAnschriften.Fields("Na3").AsString = "Nachname"
  tblReAnschriften.Post

  ' Lieferanschrift
  tblLiAnschriften.Append
  tblLiAnschriften.Fields("AdrNr").AsString = tblAdressen.Fields("AdrNr").AsString
  tblLiAnschriften.Fields("AnsNr").AsInteger = 1
  tblLiAnschriften.Fields("Ort").AsString = "Platz"
  tblLiAnschriften.Fields("PLZ").AsString = "55546"
  tblLiAnschriften.Fields("Str").AsString = "andere Str."
  tblLiAnschriften.Fields("Na1").AsString = "Anrede"
  tblLiAnschriften.Fields("Na2").AsString = "Li Firma"
  tblLiAnschriften.Fields("Na3").AsString = "Firma Zusatz"
  tblLiAnschriften.Post

  ' Ansprechpartner für Rechnungsanschrift
  tblAnsprechpartner.Append
  tblAnsprechpartner.Fields("AdrNr").AsString = tblAdressen.Fields("AdrNr").AsString
  tblAnsprechpartner.Fields("AnsNr").AsInteger = tblReAnschriften.Fields("AnsNr").AsString
  tblAnsprechpartner.Fields("AspNr").AsInteger = 0
  tblAnsprechpartner.Fields("StdKz").AsBoolean = TRUE
  tblAnsprechpartner.Fields("Anr").AsString = "Frau"
  tblAnsprechpartner.Fields("VNa").AsString = "Das"
  tblAnsprechpartner.Fields("NNa").AsString = "Beispiel"
  tblAnsprechpartner.Post

  Set tblAdressen = Nothing
  Set tblReAnschriften = Nothing
  Set tblLiAnschriften = Nothing
  Set tblAnsprechpartner = Nothing
End Sub

Bankverbindungen in Adressen

Beim Hinterlegen von Bankverbindungen ist besonderes Augenmerk auf die Zuweisung via .Text zu legen. Diese Funktionalität erlaubt es, z. B. mit "Überweisung" den internen Wert 9 im Feld ZahlArt zu hinterlegen, ohne den numerischen Wert kennen zu müssen. Diese Funktionalität steht für viele Calculated Fields zur Verfügung.

Bankverbindung hinterlegen (Delphi)
Programm Delphi;
var
  BpApp: Application;
  BpTbl: AutoDataSet;
  BpNestedTbl: AutoDataSet;
begin
  BpApp := CoApplication.Create;
  BpApp.Init('ConnectionName', 'ConnectionKey', 'UserName', 'UserPass');
  BpApp.SelectMand('Mandanten Nummer');

  BpTbl := BpApp.DataSetInfos['Adressen'].CreateDataSet;
  BpNestedTbl := BpTbl.NestedDataSets['BnkVb'];

  // Adresse positionieren
  BpTbl.Indices['NR'].Select;
  Bptbl.SetKey;
  BpTbl.Fields['AdrNr'].AsString := '10000';
  BpTbl.GotoNearest;

  BpTbl.Edit;
  BpNestedTbl.Edit;
  BpNestedTbl.Fields['BLZ'].Text := '54500000';
  BpNestedTbl.Fields['BLZNr'].Text := '0';
  BpNestedTbl.Fields['KNr'].Text := '123456789';
  BpNestedTbl.Fields['Inhab'].Text := 'Bank Konto Inhaber';
  BpNestedTbl.Fields['IBAN'].Text := 'DE94 5450 0000 0123 4567 89';
  BpNestedTbl.Fields['ZahlArt'].Text := 'Überweisung';
  // Alternativ: BpNestedTbl.Fields['ZahlArt'].AsInteger := 9;
  BpNestedTbl.Post;
  BpNestedTbl.PostNestedDataSet;
  BpTbl.Post;
end;

Kreditor / Debitor einer Adresse

Ab Build 3922 werden die Buchungskonten zu Adressen anhand der Parameter des Adressstatus automatisch mit angelegt (nur bei Neuanlage).

Adresse mit automatischem Buchungskonto (Delphi)
Tbl := BpApp.DataSetInfos('Adressen').CreateDataSetEx;
Tbl.Append;
try
  Tbl.Fields('Status').AsString := 'Hersteller';
  Tbl.Fields('AdrNr').AsString := Tbl.SetupNr('');
  Tbl.Fields('Re.Na2').AsString := 'ReNa';
  Tbl.Post;
except
  Tbl.Cancel;
end;
Debitoren-/Kreditorenkonto manuell anlegen (Delphi)
// Kontenplaneintrag anlegen
BpTbl := BpApp.DataSetInfos['Kontenplan'].CreateDataSet;
BpTbl.Append;
BpTbl.Fields['KtoNr'].AsString := '10001';
BpTbl.Fields['Bez'].AsString := 'Eigenes COM Debitoren Konto';
BpTbl.Fields['Kategorie'].Text := 'Personenkonto/Debitoren';
BpTbl.Fields['EBBuchKz'].AsBoolean := True;
BpTbl.Post;
BpTbl := nil;

// Adresse anpassen
BpTbl := BpApp.DataSetInfos['Adressen'].CreateDataSet;
BpTbl.Indices['NR'].Select;
Bptbl.SetKey;
BpTbl.Fields['AdrNr'].AsString := '10001';
BpTbl.GotoNearest;
BpTbl.Edit;
BpTbl.Fields['BKtoNr'].AsString := '10001';
BpTbl.Post;

Tipp

Die Funktion .Text des Datenbankfeldes „Kategorie" erspart das Nachschlagen des numerischen Werts in den Parametern.

Vorgänge

Belegnummer eines neuen Vorgangs auslesen

Alle Felder des Vorgangs sind „live" - also vor dem Post les- und schreibbar. Die Belegnummer kann direkt nach dem Append ausgelesen werden:

Belegnummer auslesen (Delphi)
SpecialObj := BpApp.GetSpecialObject(1);
SpecialObj.Append(30, '10001');
SpecialObj.Positionen.Add(1, 'stk', '8');
Showmessage(SpecialObj.DataSet.Fields['BelegNr'].AsString);
SpecialObj.Post;

Vorgangspositionen

In den Tabellen Vorgang und VorgangArchiv befindet sich ein NestedDataSet VPo - dieses dient jedoch nur der internen Verwaltung und bietet keinen Zugriff auf die zugehörigen Positionen.

Die Vorgangspositionen befinden sich in den Tabellen VorgangPosition und VorgangPositionArchiv. Die zugehörigen Positionen eines Vorgangs können über die Belegnummer per SetRange abgefragt werden.

Infoblätter im Vorgang

Die Inhalte der Infoblätter werden in einem NestedDataSet VIBls gespeichert. Es sind nur für tatsächlich belegte Infoblätter Einträge vorhanden. Der einzig verfügbare Index nutzt das Feld KuBez, und jeder Infoblattwert darf nur einmal vorkommen. Infoblätter müssen in den Parametern definiert sein (BEARBEITEN > PARAMETER > VERKAUF/EINKAUF > VORGÄNGE UND ZWISCHENBELEGE > INFOBLATTBEZEICHNUNGEN).

Infoblätter lesen (Delphi)
Program Delphi;
var
  BpApp: Variant;
  BpTbl: Variant;
  BpTblNested: Variant;
  i: Integer;
begin
  BpApp := CreateOleObject('BpNT.Application');
  BpApp.Init('Anmeldedaten','','Benutzer','Passwort');
  BpApp.SelectMand('1');

  BpTbl := BpApp.DataSetInfos('Vorgang').CreateDataSet;
  BpTbl.First;

  BpTblNested := BpTbl.NestedDataSets['VIBls'];
  if BpTblNested.RecordCount = 0 then
    Showmessage('Keine Infoblätter belegt');

  BpTblNested.First;
  for i := 0 to (BpTblNested.RecordCount - 1) do
  begin
    Showmessage(BpTblNested.Fields['KuBez'].AsString + ' | ' +
                BpTblNested.Fields['Text'].AsString);
    BpTblNested.Next;
  end;

  BpTblNested := nil;
  BpTbl := nil;
  BpApp := nil;
end;
Infoblätter schreiben (Delphi)
Program Delphi;
var
  BpApp: Variant;
  BpTbl: Variant;
  BpTblNested: Variant;
begin
  BpApp := CreateOleObject('BpNT.Application');
  BpApp.Init('Anmeldedaten','','Benutzer','Passwort');
  BpApp.SelectMand('1');

  BpTbl := BpApp.DataSetInfos('Vorgang').CreateDataSet;
  BpTbl.First; // Hier den gewünschten Vorgang auswählen
  BpTbl.Edit;

  BpTblNested := BpTbl.NestedDataSets['VIBls'];
  BpTblNested.SetKey;
  BpTblNested.Fields['KuBez'].AsString := 'InfoBlt14';

  if not BpTblNested.GotoKey then
  begin
    // Infoblatt nicht vorhanden - neuen Eintrag anlegen
    BpTblNested.Append;
    BpTblNested.Fields['KuBez'].AsString := 'InfoBlt14';
    BpTblNested.Fields['Text'].AsString := 'Beschreibungstext für Info Blatt 14';
  end
  else
  begin
    BpTblNested.Edit;
    BpTblNested.Fields['Text'].AsString := 'Beschreibungstext für Info Blatt 14';
  end;

  BpTblNested.Post;
  BpTblNested.PostNestedDataSet;
  BpTbl.Post;

  BpTblNested := nil;
  BpTbl := nil;
  BpApp := nil;
end;

Artikel / Artikel-Varianten

Artikel-Varianten und -Ausprägungen werden über verschachtelte NestedDataSets verwaltet. Die Varianten befinden sich im NestedDataSet ArtAP der Tabelle Mandant, die zugehörigen Ausprägungen im NestedDataSet ArtAPs innerhalb der Varianten.

Varianten und Ausprägungen auslesen (Delphi)
procedure Auslesen;
var
  TblMandant: Variant;
  TblVarianten: Variant;
  TblAuspraegung: Variant;
begin
  TblMandant := BpApp.DataSetInfos['Mandant'].CreateDataSet;
  try
    if TblMandant.RecordCount < 1 then
      exit;

    TblVarianten := TblMandant.NestedDataSets['ArtAP'];
    try
      while not TblVarianten.Eof do
      begin
        writeln('Variante: ' + TblVarianten.Fields['Nr'].Text + ' - ' +
                TblVarianten.Fields['KuBez'].Text + ' - ' +
                TblVarianten.Fields['Bez'].Text);

        TblAuspraegung := TblVarianten.NestedDataSets['ArtAPs'];
        try
          if TblAuspraegung.RecordCount < 1 then
            exit;
          writeln('Ausprägung: ' + TblAuspraegung.Fields['Nr'].Text + ' - ' +
                  TblAuspraegung.Fields['Bez'].Text);
          TblAuspraegung.Next;
        finally
          TblAuspraegung := nil;
        end;
        TblVarianten.Next;
      end;
    finally
      TblVarianten := nil;
    end;
  finally
    TblMandant := nil;
  end;
end;
Variante mit Ausprägung anlegen (Delphi)
procedure Anlegen;
var
  TblMandant: Variant;
  TblVarianten: Variant;
  TblAuspraegung: Variant;
begin
  TblMandant := BpApp.DataSetInfos['Mandant'].CreateDataSet;
  try
    if not TblMandant.Eof then
    begin
      TblMandant.Edit;
      try
        TblVarianten := TblMandant.NestedDataSets['ArtAP'];
        try
          if not TblVarianten.Eof then
          begin
            TblVarianten.Append;
            try
              TblVarianten.Fields['Nr'].Text := '3';
              TblVarianten.Fields['KuBez'].Text := 'AAA';
              TblVarianten.Fields['Bez'].Text := 'AAA Test';

              TblAuspraegung := TblVarianten.NestedDataSets['ArtAPs'];
              try
                TblAuspraegung.Append;
                try
                  TblAuspraegung.Fields['Nr'].Text := 'Auspraegung';
                  TblAuspraegung.Fields['Bez'].Text := 'Bezeichnung';
                  TblAuspraegung.Post;
                  TblAuspraegung.PostNestedDataSet;
                except
                  TblAuspraegung.Cancel;
                end;
              finally
                TblAuspraegung := nil;
              end;

              TblVarianten.Post;
              TblVarianten.PostNestedDataSet;
            except
              TblVarianten.Cancel;
            end;
          end;
        finally
          TblVarianten := nil;
        end;

        TblMandant.Post;
      except
        TblMandant.Cancel;
      end;
    end;
  finally
    TblMandant := nil;
  end;
end;

Umsätze auslesen

Umsätze gibt es in mehreren Tabellen, z. B. Artikel und Vertreter. Jede dieser Tabellen hat ein NestedDataSet Ums, in dem die Umsätze zum jeweiligen Datensatz abgelegt sind.

Umsätze aus Artikeln lesen (VBA)
Dim BpApp As BpNT.Application
Dim DataSetArtikel As BpNT.IAutoDataSet
Dim NestedUmsatz As BpNT.IAutoDataSet

Set BpApp = CreateObject("BpNT.Application")
Call BpApp.Init("Firmenname", "", "Anmeldename", "Password")
Call BpApp.SelectMand("1")

Set DataSetArtikel = BpApp.DataSetInfos.Item("Artikel").CreateDataSet
Set NestedUmsatz = DataSetArtikel.NestedDataSets("Ums")

For Each Field In NestedUmsatz.Fields
  Debug.Print (Field.Name & " " & Field.Value)
Next

' Artikel neu positionieren
DataSetArtikel.Next

For Each Field In NestedUmsatz.Fields
  Debug.Print (Field.Name & " " & Field.Value)
Next

Verknüpfte Datentabellen

Um auf die richtigen Datensätze einer verknüpften Tabelle zuzugreifen, muss diese korrekt positioniert werden.

Tipp

Beachten Sie auch die Option CreateDataSetEx - eventuell führt diese schneller zum Ziel.

Adressen und Anschriften verknüpft abfragen (VBA)
Public Sub Adressen_Anschriften()
  Dim BpApp As BpNT.Application
  Dim DataSetAdressen As BpNT.IAutoDataSet
  Dim DataSetAnschriften As BpNT.IAutoDataSet

  Set BpApp = CreateObject("BpNT.Application")
  Call BpApp.Init("Firmenname", "", "Anmeldename", "Password")
  BpApp.SelectMand ("1")

  Set DataSetAdressen = BpApp.DataSetInfos.Item("Adressen").CreateDataSet
  DataSetAdressen.First

  Set DataSetAnschriften = BpApp.DataSetInfos.Item("Anschriften").CreateDataSet
  DataSetAnschriften.First

  ' Index "AdrNrAnsNr" auswählen (KeyField count = 2)
  DataSetAnschriften.Indices.Item("AdrNrAnsNr").Select

  Do While DataSetAdressen.EOF = False
    DataSetAnschriften.EditKey

    With DataSetAnschriften.Indices.Item("AdrNrAnsNr").IndexFields
      .Item("AdrNr").AsString = DataSetAdressen.Fields.Item("AdrNr").AsString
      .Item("AnsNr").AsInteger = DataSetAdressen.Fields.Item("ReAnsNr").AsInteger
    End With

    If Not DataSetAnschriften.GotoKey Then
      MsgBox ("Kein Eintrag gefunden")
    End If

    DataSetAdressen.Next
  Loop

  Set DataSetAdressen = Nothing
  Set DataSetAnschriften = Nothing
  Set BpApp = Nothing
End Sub

Wichtig

Wenn mehrere Felder zu setzen sind, darf die Variable, die auf IndexFields verweist, nur einmal abgeholt werden:

Dim IndexFields
WorkTabel.EditKey
IndexFields = WorkTabel.Indices.Item("ArtNrAdrNr").IndexFields
IndexFields.Item("ArtNr").AsString = "Artikelnummer"
IndexFields.Item("AdrNr").AsString = ""
WorkTabel.GotoKey

Ein leerer Wert bei einem Indexfeld wird so behandelt, als wäre das Feld nicht im Range enthalten.

Datensatzänderungen werden nicht angezeigt

Nach einem COM-Zugriff, der z. B. einen Datensatz als „Gesperrt" kennzeichnet, wird die Änderung in der laufenden microtech Software nicht sofort sichtbar. Eine direkte Funktion zur Aktualisierung der Ansicht gibt es nicht - der Datensatz wird spätestens beim nächsten Zugriff aktualisiert.

Erstellungs- und Änderungsdatum setzen

Die Funktion SetSatzDatum erlaubt das Setzen von Erstellungsdatum, Erstellungsbenutzer, Änderungsdatum und Änderungsbenutzer. Alternativ kann AutoSetSatzDatum verwendet werden - dieser Aufruf muss nach Edit, Append oder Insert erfolgen und setzt automatisch den Zeitpunkt des Postens sowie den Kurznamen des angemeldeten Benutzers.

Unterschied zwischen AendDat und LtzAend:

Feld Beschreibung
LtzAend (Letzte Datensatzänderung) Wird bei jeder Speicherung aktualisiert, unabhängig davon, was oder wodurch geändert wurde
AendDat (Änderungsdatum) Wird nur aktualisiert, wenn SetSatzDatum per COM genutzt wird oder ein Benutzer manuell über die Erfassungsmaske ändert

.NET in unmanaged Code

In .NET-Sprachen unterscheidet sich die Lebensdauer von COM-Objekten von vergleichbaren Sprachen. Eine Referenz auf ein COM-Objekt wird per Voreinstellung erst beim Beenden des Programms freigegeben. Wenn erneut eine gültige Instanz angefordert werden soll, muss die Freigabe explizit programmiert werden.

COM-Objekte in .NET freigeben
System.GC.Collect(0);
System.Runtime.InteropServices.Marshal.ReleaseComObject(Instanz1);
System.Runtime.InteropServices.Marshal.ReleaseComObject(Instanz2);
Alternative mit null-Zuweisung
System.Runtime.InteropServices.Marshal.ReleaseComObject(Instanz1);
Instanz1 = null;
GC.Collect();
GC.WaitForPendingFinalizers();
IDisposable-Pattern (C#)
public class BpNTWorker : IDisposable
{
    BpNT.Application _BpNTApp = null;
    BpNT.Anything _Anything = null;

    public void Work()
    {
        _BpNTApp = new ApplicationClass();
        // Work with _Application
    }

    public void Dispose()
    {
        if (_BpNTApp != null)
            System.Runtime.InteropServices.Marshal.ReleaseComObject(_BpNTApp);
        if (_Anything != null)
            System.Runtime.InteropServices.Marshal.ReleaseComObject(_Anything);
    }
}

Deployment

Die erstellte interop.dll, die COM und .NET verbindet, gehört zum Deployment-Vorgang Ihres Projektes. Bei Änderungen oder Erweiterungen der COM-Schnittstellen-Definition wird dies beim Kompilieren in der DLL abgelegt.

Bankverbindung wird nicht angezeigt

Eine gültige Bankverbindung besteht aus drei Feldern: Blz (Bankleitzahl), KNR (Kontonummer) und BlzNr (Bankleitzahlnummer). Ohne die BlzNr kann die Bankverbindung in den meisten Fällen nicht korrekt angezeigt werden. Der Standardwert für BlzNr ist 0 - dieser Wert wird verwendet, um z. B. Sparkassen mit einer Bankleitzahl und mehreren Filialen zu unterscheiden.

Wird eine Bankleitzahl verwendet, die in der Bankentabelle nicht enthalten ist, schlägt die Anzeige trotz gesetzter BlzNr fehl.

Hinweis

Die Feldnamen können variieren, z. B. durch andere Präfixe.

Gültige Zeichen

Um eine Eingabevalidierung für einzelne Felder zu erstellen, kann die Liste der gültigen Zeichen abgefragt werden:

[Autofeld].FieldInfo.ValidChars

Der Rückgabewert ist ein String mit allen zulässigen Eingabezeichen für dieses Feld.

Performance-Hinweis (ab Build 5000)

Diese Funktion prüft alle Unicode-Zeichen, die ein Feld enthalten könnte, und hat daher eine gewisse Laufzeit. Rufen Sie ValidChars wenn möglich nur einmal pro Feld auf und verwenden Sie das Ergebnis für alle weiteren Zugriffe.

Interface IUnknown - was muss getan werden?

Bei allen Funktionen, die „Stream" im Namen tragen, wird als Standardparameter IUnknown in der Deklaration verwendet. Ein SaveToStream erwartet beispielsweise als Parameter eine Variable, die das IStream-Interface unterstützt.

Layout per IStream importieren (Delphi)
procedure TForm1.Button1Click(Sender: TObject);
var
  BPNT: OleVariant;
  BPNTLayoutGroup: OleVariant;
  ExecLayout: OleVariant;
  Layout: OleVariant;
  Stream: IStream;
  FileStream: TFileStream;
begin
  FileStream := TFileStream.Create('C:\Artikel Export.BPL',
                fmOpenRead or fmShareDenyWrite);
  Stream := (TStreamAdapter.Create(FileStream, soOwned) as IStream);
  try
    BPNT := CreateOleObject('BpNT.Application');
    BpNT.Init('Firmenname','','Anmeldename','Passwort');
    BpNT.SelectMand('Mandantennummer');

    BPNTLayoutGroup := BpNT.OpenLayoutGroup(
      WideString('Au'), WideString('ArtExpListe'));

    Layout := BPNTLayoutGroup.ImportLayoutFromStream(
      IUnknown(Stream), WideString('Test Name'), WordBool(True));

    ExecLayout := Layout.doExport;
    ExecLayout.Execute;
  finally
    Stream := nil;
  end;
end;

COM-Instanzen in Services

Beim Einsatz der COM-Schnittstelle aus einem Windows-Dienst (Service) heraus können keine Meldungsfenster ausgegeben werden. Das Programm würde auf eine Eingabe warten und an dieser Stelle stehen bleiben.

Lösungsansätze:

  1. Bei Ihrem Dienst die Option „Datenaustausch zwischen Dienst und Desktop zulassen" aktivieren (Reiter „Anmelden" in den Diensteigenschaften). Diese Option stellt jedoch eine mögliche Sicherheitsverletzung dar.
  2. Die Callback-Schnittstelle für Meldungen verwenden (OnMeldFenster).
  3. Die COM-Instanz einmalig außerhalb des Dienstes starten, um die Meldung zu bestätigen. Beachten Sie, dass zehn Tage vor Ablauf eines Schlüssels immer eine Meldung erscheint.

NestedDataSet - häufiger Fehler

Die Fehlermeldung „Datenmenge weder im Einfüge- noch Editiermodus" tritt auf, wenn das NestedDataSet nicht korrekt verwendet wird.

Falsch
Set VorgangTbl = Vorgang.DataSet
VorgangTbl.NestedDataSets("VIBls").Edit
VorgangTbl.NestedDataSets("VIBls").Fields("Text").AsString = "Testeintrag"
' -> Fehler: "Datenmenge weder im Einfüge- noch Editiermodus"
VorgangTbl.NestedDataSets("VIBls").Post
VorgangTbl.Post
Richtig
Set VorgangTbl = Vorgang.DataSet
VorgangTbl.Edit  ' Master-Tabelle in den Edit-Modus versetzen

Set Nested = VorgangTbl.NestedDataSets("VIBls")  ' Eigene Variable zuweisen
Nested.Edit
Nested.Fields("Text").AsString = "Testeintrag"
Nested.Post
Nested.PostNestedDataSet  ' NestedDataSets in die Master-Tabelle schreiben

VorgangTbl.Post

Drei Regeln für NestedDataSets:

  • Die Master-Tabelle muss sich selbst im Edit- oder Insert-Modus befinden.
  • Jeder Aufruf von NestedDataSets("Name") erzeugt ein eigenständiges Objekt - das NestedDataSet daher immer einer eigenen Variablen zuweisen.
  • Damit Änderungen korrekt gespeichert werden, muss PostNestedDataSet aufgerufen werden.

Ranges - Beispiele

Grundlagen

Die grundlegende Dokumentation zu Ranges finden Sie unter Verbindung und Datenzugriff. Hier werden ergänzende Beispiele mit besonderem Fokus auf das Verhalten bei mehreren Indexfeldern gezeigt.

Verhalten bei zusammengesetzten Indices

Gegeben sei eine Anschriften-Tabelle mit dem Index AdrNrAnsNr (Felder: Adressnummer als String, Anschriftennummer als Integer):

Adressnummer Anschriftennummer Name
10000 0 Anton
10001 0 Bertha
10001 1 Cäsar
10002 0 Dora
10002 1 Emil
10002 3 Friedrich
10010 2 Gustav
10010 3 Heinrich
Range mit zwei Indexfeldern (Delphi)
tbl := BpApp.DatasetInfos['Anschriften'].CreateDataSet;
tbl.Indices.Item['AdrNrAnsNr'].Select;
tbl.SetRangeStart;
tbl.Fields['AdrNr'].AsString := '10000';
tbl.Fields['AnsNr'].AsInteger := 1;
tbl.SetRangeEnd;
tbl.Fields['AdrNr'].AsString := '10010';
tbl.Fields['AnsNr'].AsInteger := 2;
tbl.ApplyRange;

Aus SQL-Sicht würde man 3 Datensätze erwarten (Cäsar, Emil, Gustav). Tatsächlich liefert der Range 6 Datensätze: Bertha, Cäsar, Dora, Emil, Friedrich und Gustav. Die Erklärung liegt in der Range-Logik:

  • Von: (Adressnummer > 10000) ODER ((Adressnummer = 10000) UND (Anschriftennummer >= 1))
  • Bis: (Adressnummer < 10010) ODER ((Adressnummer = 10010) UND (Anschriftennummer <= 2))

Werden in einem Range mehrere Indexfelder übergeben, findet nur das erste Feld vollständige Beachtung. Alle weiteren Felder werden nur für die Datensätze ausgewertet, die über das erste Feld als Anfang oder Ende bestimmt wurden.

Gegenmaßnahmen: Das Ergebnis entweder mit einem Filter einschränken oder den Zugriff anders gestalten (z. B. mit KeyFieldCount).

Filter

Jedes DataSet kann über die Eigenschaft Filter bestimmte Datensätze aus der Ergebnismenge ausschließen. Filter können nur Felder verwenden, die in der Datenmenge enthalten und nicht berechnet sind (Calculated Fields entfallen).

Filter-Syntax
Art = '70' or Art = '100'

Leere Felder müssen explizit berücksichtigt werden:

Art <> '70' or PrjNr = NULL

Verfügbare Operatoren:

Operator Beschreibung
< Kleiner als
> Größer als
<= Kleiner oder gleich
>= Größer oder gleich
= Gleich
<> Ungleich
AND Beide Ausdrücke müssen wahr sein
NOT Nachfolgender Wert nicht wahr
OR Mindestens ein Ausdruck muss wahr sein

Performance-Tipp

Wenn nur ein Filter zur Eingrenzung verwendet wird, wird die gesamte Datenmenge erst zum Client transportiert und dort gefiltert. Die Anzahl der Datensätze sollte daher immer vorher über einen Range begrenzt werden.

Vorsicht bei aktiven Filtern

Ist ein Filter aktiv und werden Felder geändert, die zur Filterbedingung gehören, kann es sein, dass der aktuelle Datensatz die Bedingung nicht mehr erfüllt und „verschwindet". Der nächste passende Datensatz wird dann zum aktuellen Datensatz.

Funktionen zum schnellen Feldzugriff

Die Funktionen AcquireFieldList und ReadFieldListValues ermöglichen einen optimierten Feldzugriff bei der Iteration über große Datenmengen.

Schneller Feldzugriff (C#)
AutoDataSet ads = BpApp.DataSetInfos["Artikel"].CreateDataSet();
int iHandle = ads.AcquireFieldList(
    "ArtNr:String,Bez1:DataString,Kubez1:String,Einh:String,Vk0.Preis:Float");
object[] oValues = null;
int counter = 0;

ads.First();
while (!ads.Eof)
{
    counter++;
    oValues = ads.ReadFieldListValues(iHandle);
    Console.WriteLine("Datensatznummer: " + counter.ToString());
    Console.WriteLine("Artikelnummer: " + oValues[0].ToString());
    Console.WriteLine("KuBez1: " + oValues[2].ToString());
    Console.WriteLine("Preis: " + oValues[4].ToString());
    ads.Next();
}