Je zde třeba čeština?

Vůbec jsem neplánoval zahájit nový rok takto, ale už se k tomu nějaký čas schylovalo… Chci se zeptat, kolik z vás závisí na české verzi tohoto blogu, tedy nevystačí si s anglickou mutací.

Můj první blog byl Dynamics AX česky, který byl pochopitelně v češtině. Před téměř třemi lety, už v Anglii, jsem založil tento blog, kde je každý příspěvek (až na výjimky) jak v češtině, tak i v angličtině. Psát v angličtině je prostě nutnost, pokud chci oslovit víc než pár lidí v České republice. Zároveň jsem se nechtěl vzdát českého publika – vím, že řada bývalých kolegů angličtinu neovládala.

Problém je v tom, že psaní ve dvou jazycích mě stojí překvapivě mnoho času, a nejsem si jistý, jestli to přináší dostatečnou hodnotu. Možná by bylo lepší, kdybych místo překládání raději napsal více článků v jediném jazyce. A to by rozhodně musela být angličtina.

Nechci jen tak jednostranně českou verzi zrušit, ale možná ji už nikdo zas tak moc nepotřebuje. Pokud ano, dejte mi vědět. Díky.

A required device isn’t connected

Microsoft nedávno uvolnil Dynamics AX 2012 R2 Solution Demo Package V4, tedy novou verzi virtuální počítače (Virtual Machine, dále jen VM) s nainstalovaným a nakonfigurovaným systémem Dynamics AX a souvisejícími komponentami. Obsahuje verzi AX 2012 R2 CU7.

Úspěšně jsem stáhl, rozbalil a naimportoval VM A (30 GB), ale při pokusu o boot jsem dostal následující chybu:

0xc000000e A required device isn’t connected or can’t be accessed

Zkusil jsem několik změn v nastavení a dokonce různé verze Hyper-V, ale marně. Nakonec jsem zkusil opravit master boot record a ostatní bootovací data – a to pomohlo!

Zde jsou instrukce:

  1. Připojte instalační disk nebo obraz disku s instalací Windows Server 2012 (v nastavení VM). Ujistěte se, že CD je první bootovací zařízení (sekce BIOS v Nastavení).
  2. Spusťte VM a potvrďte start z CD.
  3. Nastavte/potvrďte jazyk, rozložení klávesnice atd.
  4. Neklikejte na Install now – místo toho zvolte Repair you computer
  5. Klikněte na Troubleshoot a pak na Command Prompt
  6. Zavolejte tyto příkazy:
    bootrec /fixmbr
    bootrec /fixboot
    bootrec /rebuildbcd
  7. Potvrďte přidání instalace Windows do spouštění.
  8. Restartujte VM a nechte nabootovat z disku.

Na velikosti záleží

X++ normálně nerozlišuje velikost písmen – můžete zavolat warning(„x“) nebo WaRnInG(„x“) a oboje udělá to samé (ačkoli vaši kolegové nemusí být tak benevolentní jako kompilátor X++). Nicméně to samé naplatí pro .NET Interop z X++ – můžete sice použít například System.Math::Cos(0), ale pokud zkusíte zavolat System.Math::cos(0), dostanete kompilační chybu.

Existují dokonce případy, kdy změna velikosti písma zapříčiní volání jiné metody. Podívejte se na následující kus kódu – způsobí .NET výjimku, odchytne ji, převede na řetězec a odešle do infologu:

try
{
    System.IO.File::Open('neexistující soubor', System.IO.FileMode::Open);
}
catch (Exception::CLRError)
{
    error(CLRInterop::getLastException().ToString());
}

Výstup by měl být něco jako: „System.Reflection.TargetInvocationException: Exception has been thrown by the target of invocation. —> System.IO.FileNotFoundException: Could not find file ‚C:\Windows\system32\neexistující soubor.‘ (…)“ (Poznámka: nevím, co přesně dostanete v systému s češtinou.)

Nyní změňte ToString() na tostring(). Výstup bude „Class CLRObject“.

Co se stalo? To, co dostaneme z CLRInterop::getLastException() je instance (X++) třídy CLRObject, která reprezentuje .NET objekt (v tomto případě FileNotFoundException). Když zavoláme metodu ToString(), AX rozpozná, že FileNotFoundException takovou metodu obsahuje a zavolá ji. Ale pokud zavoláte tostring(), AX nemůže použít metodu na FileNotFoundException kvůli různé velikosti písmen. Ale je zde metoda toString() na samotné třídě CLRObject (zděděná z třídy Object), která může být zavolána bez potíží. Tudíž zde nejde o jednu metodu vracející různé výsledky, jsou to metody na úplně jiných objektech.

Můžete to být trochu matoucí, ale dává to smysl.

Řádky pro více hlaviček

Cílem dnešního cvičení je vytvoření formuláře s dvěma gridy (obsahující hlavičky a odpovídající řádky). Trik je v tom, že pokud vyberete více hlaviček, formulář zobrazí řádky pro všechny.

Následující formulář využívá prodejní objednávky. Všimněte si, že vybrané objednávky mají čísla 1, 3 a 4 a zobrazené řádky také patří k objednávkám 1, 3 a 4:

Form

Vytvořil jsem dvě implementace: jednu pro AX2012 a jednu pro AX2009. Nejsou úplně stejné – nejprve popíšu AX2012 a pak zmíním změny nutné pro AX2009. Link na stažení obou verzí najdete na konci.

Formulář má dva datové zdroje (SalesTable a SalesLine), které spolu nejsou spojeny. Fitrování a spouštění dotazu nezajišťuje AX, je spouštěno explicitně z metody selectionChanged() na datovém zdroji SalesTable.

Pro každou zvolenou hlavičku je přidán nový filtr do datového zdroje SalesLine:

Ranges

Mohli byste použít jen jeden filtr a vytvořit jednu dlouhou hodnotu spojením čísel objednávek oddělených čárkou (např. „000001, 000002″), nicméně to má jeden velký problém: podporovaná délka hodnoty filtru není neomezená (použitím dostatečně dlouhé hodnoty se mi dokonce podařilo sestřelit klienta AX2012). Mít více krátkých hodnot je také výhodné při zobrazování fitrů v gridu (jako na předchozím obrázku).

Implementace je docela přímočará, neměli byste v ní jinak tápat:

public class FormRun extends ObjectRun
{
    MultiSelectionHelper headerSelection;
    QueryBuildDataSource lineQueryDs;
}
 
public void init()
{
    super();
 
    // Initalizuj MultiSelectionHelper pro pozdější použití
    headerSelection = MultiSelectionHelper::construct();
    headerSelection.parmDatasource(salesTable_ds);
 
    // Pro pohodlný přístup k instanci QueryBuildDataSource pro řádky
    // (v AX2012 byste mohli použít salesLine_ds.queryBuildDataSource())
    lineQueryDs = salesLine_ds.query().dataSourceNo(1);
}
 
public void selectionChanged() // Datový zdroj SalesTable
{
    super();
    SalesLine_ds.executeQuery();
}
 
public void executeQuery() // Datový zdroj SalesLine
{
    lineQueryDs.clearRanges();
    this.filterBySelectedHeaders();
 
    super();
}
 
void filterBySelectedHeaders() // Datový zdroj SalesLine
{
    SalesTable st = headerSelection.getFirst();
 
    while (st.RecId)
    {
        // Přidej jeden filtr pro každé vybrané SalesId
        lineQueryDs.addRange(fieldNum(SalesLine, SalesId)).value(queryValue(st.SalesId));
        st = headerSelection.getNext();
    }
}

V AX2009 nemáme metodu selectionChanged(), takže jsem zkombinoval metodu markChanged() a „delayed link“ mezi datovými zdroji, který při změně hlavičky volá executeQuery() pro řádky. Jen jsem musel odebrat dynamický odkaz (dynamic link), jinak by AX ukázala řádky jen pro jednu hlavičku.

Obě verze můžete stáhnout zde: LinesForMultipleHeaders.zip.