Tutoriál: WPF User Control pro AX2012

Jedna z mnoha nových vlastností v Dynamics AX 2012, které přináší fantastické možnosti, je schopnost používat prvky uživatelského rozhraní vytvořené ve Windows Forms nebo WPF. Microsoft vynaložil značné úsilí na integrovaci prostředí .NET a AX, nejen ve smyslu běhu aplikace, ale také pro vývojáře, pro snadné zpracovávání událostí a tak dále.

Pojďme se podívat na stručný tutoriál, jak vytvořit WPF User Control a formulář v Dynamics AX demonstrující obousměrnou komunikaci mezi AX a WPF.

Konkrétně vytvoříme následující ovládací prvek:

Jde o jednoduchý WPF prvek typu User Control, složený z ovládacích prvků Label a Slider. Hodnoty posuvníku jsou definovány výčovým typem (enum) z AX a je také zobrazen popisek právě vybraného prvku. Ovládací prvek umožňuje nastavit jméno enumu a vybranou hodnotu a vyvolává událost při změně hodnoty.

Také vytvoříme formulář demonstrující použití vlastností a událostí:

Formulář obsahuje prvek WPF popsaný výše a nativní AX ComboBox. Změní-li  se hodnota v jednom ovládacím prvku, druhý prvek je automaticky aktualizován.

Formulář přijímá výčtový typ a hodnotu v objektu Args, takže můžete použít různé výčtové typy pouhým nastavením vlastností položky menu.

 

Tutoriál je dost stručný, ale kompletní zdrojový kód naleznete na konci příspěvku. Byl vytvořen ve Visual Studiu 2010 SP1 a Dynamics AX 2012 RTM.

Vytvoření projektu ve Visual Studiu

Ve Visual Studiu 2010 klikněte na File > New > Project a zvolte šablonu WPF User Control Library (Visual C#). Nastavte vhodné jméno projektu a řešení – ve zbytku tutoriálu předpokládám, že se oboje (a sestavení také) jmenuje AxUserControls.

Potvrďte dialog, otevřete nově vytvořený projekt v Solution Exploreru a přejmenujte automaticky vytvořený ovládací prvek UserControl1 na EnumSlider (soubory i třídu).

Musíte také změnit cílový framework z .NET Framework 4 Client Profile na .NET Framework 4. (Klikněte pravým myšítkem na projekt a zvolte Properties. Target Framework je na první záložce.)

Znovu klikněte pravým myšítkem na projektu a klikněte na Add AxUserControls to AOT.

Implementace ovládacího prvku WPF

Nejprve definujte uživatelské rozhraní v XAML. Jen zaměňte Grid ve vygenerovaném XAML za StackPanel a přidejte Label a Slider jako v následující ukázce:

<UserControl x:Class="AxUserControls.EnumSlider"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300">
    <StackPanel>
        <Label Name="ValueLabel" FontWeight="Bold" />
        <Slider Name="Slider" IsSnapToTickEnabled="True" TickFrequency="1"
            TickPlacement="BottomRight" ValueChanged="Slider_ValueChanged" />
    </StackPanel>
</UserControl>

Label a Slider mají oba přiřazené jméno, posuvník navíc definuje ovladač pro událost ValueChanged a pár vlastností upřesňujících jeho chování.

Nyní pojďme přidat nějaký kód do EnumSlider.xaml.cs.

Nejprve vytvořte dvě vlastnosti – EnumName and Value. EnumName se použije pro generování dostupných hodnot a k získání popisků elementů. Všimněte si, že hodnoty prvků enumu mohou být nastaveny ve vlastnostech v AOT a nemusí odpovídat indexu prvku (například enum EPCTagType používá hodnoty {2,48,49,50}). Použijte následující kód k definici jak normálních tak dependency properties:

public string EnumName
{
    get { return (string)GetValue(EnumNameProperty); }
    set { SetValue(EnumNameProperty, value); }
}
public static DependencyProperty EnumNameProperty = DependencyProperty.Register(
    "EnumName", typeof(string), typeof(EnumSlider),
    new FrameworkPropertyMetadata(new PropertyChangedCallback(OnEnumNameChanged)),
    new ValidateValueCallback(ValidateEnumName));
 
public int Value
{
    get { return (int)GetValue(ValueProperty); }
    set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
    "Value", typeof(int), typeof(EnumSlider),
    new FrameworkPropertyMetadata(new PropertyChangedCallback(OnValueChanged), new CoerceValueCallback(CoerceValue)));

Pokud nejste obeznámeni s dependency properties, považujte je prostě za „chytřejší“ vlastnosti podporující informování o změnách, validace atd.

Obě dependency properties v předchozím kódu mají definovány určité metody pro zpětné volaní při změně hodnoty (OnEnumNameChanged, OnValueChanged), pro validaci hodnoty (ValidateEnumName) nebo pro opravu neplatné hodnoty (CoerceValue).

Pozn. Při definování dependency properties můžete využít code snippet propdp. Prostě napište v editoru kódu propdp a stiskněte klávesu Tab – většina kódu bude vygenerována za vás.

Dále potřebujeme událost, která bude vyvolána při změně hodnoty:

public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent(
    "ValueChanged", RoutingStrategy.Bubble,
    typeof(RoutedEventHandler), typeof(EnumSlider));
 
public event RoutedEventHandler ValueChanged
{
    add { AddHandler(ValueChangedEvent, value); }
    remove { RemoveHandler(ValueChangedEvent, value); }
}

Potřebujeme také privátní proměnnou pro uložení instance třídy SysDictEnum. Vytvořte proxy třídu přetažením třídy SysDictEnum z Application Exploreru do projektu AxUserControls

a deklarujte instanční proměnnou v kódu:

private SysDictEnum dictEnum;

Nyní musíte ještě implementovat nějaké metody. Pojďme se podívat na ty zásadní (pro zbytek prosím nahlédněte do zdrojového kódu).

Když se změní jméno enumu, dependency property automaticky zavolá metodu OnEnumNameChanged. Je vytvořena nová instance AX třídy SysDictEnum a hodnoty posuvníku, rozměry atd. jsou odpovídajícím způsobem aktualizovány:

private static void OnEnumNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    EnumSlider control = (EnumSlider)d;
    control.dictEnum = SysDictEnum.newName((string)e.NewValue);
    control.OnDictEnumChanged();
}
private void OnDictEnumChanged()
{
    this.Slider.Maximum = this.dictEnum.values() - 1;
    this.UpdatePreferedLabelWidth();
    this.UpdateLabel();
}

Když se změní vlastnost Value EnumSlideru, aktualizuje se GUI a vyvolá se událost (a ta může být následně zpracována v AX):

private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    EnumSlider control = (EnumSlider)d;
    int elementIndex = control.dictEnum.value2Index((int)e.NewValue);
    control.Slider.Value = elementIndex;
    control.UpdateLabel(elementIndex);
    control.RaiseEvent(new RoutedEventArgs(ValueChangedEvent));
}

Když se změní hodnota posuvníku, je aktualizována vlastnost Value EnumSlideru (a ta vyvolá událost atd.).

private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs e)
{
    int sliderValue = (int)e.NewValue;
    this.Value = dictEnum.index2Value(sliderValue);
}

Nasazení ovládacího prvku WPF

Když je ovládací prvek dokončen, sestavte projekt, klikněte na něj pravým myšítkem a zvolte Open Folder in Windows Explorer. Jděte do složky \bin\Debug a zkopírujte AxUserControl.dll do Bin adresáře klienta AX (např. c:\Program Files (x86)\Microsoft Dynamics AX\60\Client\Bin).

(Reálně byste měli použít konfiguraci Release a sestavení podepsat, ale nekomplikujme to.)

Implementace AX formuláře

Posledním krokem je vytvoření AX formuláře využívající nový ovládací prvek WPF. Bude obsahovat dva ovládací prvky – WPF EnumSlider a AX ComboBox – a jejich hodnoty budou synchronizovány.

Vytvořte běžným způsobem formulář a menu item a nazvěte je EnumSlider. Do designu formuláře přidejte nový prvek typu ManagedHost:

Automaticky se otevře další formulář zvaný Managed control selector. Musíte zvolit sestavení obsahující řízené ovládací prvky a konkrétní prvek, který bude použit v ManagedHost.

Protože jsme dosud nevytvořili referenci na sestavení, klikněte na tlačítko Add reference, ve formuláři Add reference klikněte na Browse, najděte AxUserControls.dll a potvrďte vytvoření reference.

Zpátky ve formuláři Managed control selector vyberte sestavení AxUserControls a ovládací prvek EnumSlider:

Potvrďte výběr tlačítkem OK, přejmenujte ManagedHost na SliderHost a nastavte jeho vlastnost Sizing na SizeToContent.

Dále přidejte AX ComboBox a nastavte ho jako automaticky deklarovaný. Struktura formuláře by pak měla vypadat takto:

Pak přidejte ovladač události, který bude volán při změně hodnoty posuvníku. Klikněte pravým tlačítkem myši na prvku SliderHost, otevřete form Events kliknutím na Events, v gridu najděte událost ValueChanged a stiskněte tlačítko Add. Tím se vytvoří X++ metoda SliderHost_ValueChanged a přiřadí se jako ovladač události ValueChanged definované na ovládacím prvku WPF.

Můžete otevřít metodu init() na fomuláři a podívat se, jak je přihlášení ovladače události implementováno:

_SliderHost_Control = SliderHost.control();
_SliderHost_Control.add_ValueChanged(new ManagedEventHandler(this, 'SliderHost_ValueChanged'));

V SliderHost_ValueChanged() prostě nastavte tu samou hodnotu i comboBoxu:

void SliderHost_ValueChanged(System.Object sender, System.Windows.RoutedEventArgs e)
{
    AxUserControls.EnumSlider slider = sender;
    int newValue = slider.get_Value();
    comboBox.selection(newValue);
}

Synchronizace v opačném směru je také jednoduchá – přidejte do ComboBox.selectionChange() volání

_SliderHost_Control.set_Value(this.selection())

Poslední krok je použití výčtového typu a hodnoty definované na položce menu volající EnumSlider form. Do init() přidejte nastavení těchto hodnot ovládacím prvkům AX i WPF:

comboBox.enumType(element.args().parmEnumType());
_SliderHost_Control.set_EnumName(enumId2Name(element.args().parmEnumType()));
_SliderHost_Control.set_Value(enum2int(element.args().parmEnum()));

Na menu itemu EnumSlider nastavte vlastnosti EnumTypeParameter a (volitelně) EnumParameter (můj příklad používá enum SysAccessRights), otevřete menu item a ověřte, že všechno funguje dle očekávání, včetně obousměrné synchronizace.

Závěr

V tomto tutoriálu jsme vytvořili WPF user control, využili metadata o AX výčtových typech k definování hodnot ovládacího prvku, četli a zapisovali jsme hodnoty CLR vlastností z AX and zpracovávali vlastní routed event v X++.

Integrace Dynamics AX 2012 a Windows Forms/WPF je zcela hladká a umožňuje snadno využít existující řízené ovládací prvky, vytvářet různé vlastní prvky, snadno využívat animace, přetahování myší a mnoho dalších konceptů podporovaných ve Windows Forms a WPF. To nabízí úplně nové možnosti pro design uživatelského rozhraní Dynamics AX.

Zdrojový kód

Download (zazipované .xpo)