[MSCE U17 C#, C++] Create dynamic conditional hierarchical picklists.

 Want to control the content of a picklist from another picklist or other ItemType property values? Check the following video to find out how this can be done:

In Update 16, we introduced PickList Source and Settings for extracting PickList values from different sources. Please refer to the PickListProvider blog to learn about it.  MicroStation U17 adds API, GetValueFromContext, to achieve workflow as mentioned in the video. This API gets you to the value for a fully qualified accessString from an instance to which GetStandardValues:ecProperty belongs. GetStandardValues has reference to the WIP instance on which StandardValues are requested. In your GetStandardValues implementation, you can use the GetValueFromContext method to get the value of some other property belonging to the same instance of the GetStandardValues "ecProperty" argument. In this blog,  you will learn how to use these APIs to achieve a filtered or conditional picklist.

SDK Sample:

  1. Managed implementation. This is available at: ..\examples\DgnEC\PickListProviderExample\ManagedExample\PickListConditionalProvider.cs

  2. Native implemetation. This is available at: ..\examples\DgnEC\PickListProviderExample\NativeExample\PickListCSVProvider.h::ConditionalPickListProvider

Implementation: 

Step 1: Implement IPickListNetProvider/IPickListProvider :

Managed:

IPickListNetProvider is an abstract class, available under Bentley.DgnPlatformNET namespace in Bentley.DgnPlatformNET.dll. In the class, the constructer sets the unique value of the provider and display label.
Implement GetStandardValues() and IsValidSettings(). Use GetValueFromContext() in GetStandardValues() to control picklist values.

  

/*=================================================================================**//**
* This class implements IPickListNetProvider interface. 
* For CSV based picklists the value extraction will be done here. 
* @bsiclass                                                               Bentley Systems
+===============+===============+===============+===============+===============+======*/
class ConditionalPickListProvider : IPickListNetProvider
{
private IECProperty m_wipProperty = null;
//--------------------------------------------------------------------------------------
// @description   Constrcutor for PickListCSVNetProvider
// @bsimethod                                                    Bentley
//+---------------+---------------+---------------+---------------+---------------+------
public ConditionalPickListProvider() : base ("ConditionalPickList", "ConditionalPickList")
    {
    }

//--------------------------------------------------------------------------------------
// @description   This function has CSV provider specifc picklist value extraction code. 
// @bsimethod                                                    Bentley
//+---------------+---------------+---------------+---------------+---------------+------
public override IList<string> GetStandardValues(DgnFile dgnFile, IECProperty ecProperty, IECPrimitiveType primitiveType)
    {
    List <string> values = new List<string>();
    //validate provider
    if (!IPickListNetProvider.GetProviderName(dgnFile, ecProperty).Equals(this.Name))
        return values;

    string providerSettings = IPickListNetProvider.GetProviderSettings(dgnFile, ecProperty);

    if (!ecProperty.ClassDefinition.Contains(providerSettings))
        return values;
    
    IECPrimitiveType pType = null;
    object pValue = this.GetValueFromContext(providerSettings, ref pType);
    if (pValue == null)
        return values;
    
    string foodType = pValue.ToString().ToLower();
    if (foodType == "fruits")
        values = new List<string>(){"Mango", "Banana", "Apple"};
    else if (foodType == "veggis")
        values = new List<string>() { "Tomato", "Brinjal", "carrot", "corn"};
    else if (foodType == "all")
        values = new List<string>() { "Mango", "Banana", "Apple", "Tomato", "Brinjal", "carrot", "corn"};
    else
        values.Add("sorry no food");
    
    return values;
    }

//--------------------------------------------------------------------------------------
// @description   Check picklist settings are valid or not for CSV Provider.
// @bsimethod                                                    Bentley
//+---------------+---------------+---------------+---------------+---------------+------
public override bool IsValidSettings(string providerSettings)
    {
    return true;
    }
    
}

Native:

IPickListProvider is an abstract class, available under Bentley::DgnPlatform namespace in Bentley.DgnPlatform5.dll.
In the class, the constructer sets the unique value of the provider and display label.
Implement GetStandardValues() and IsValidSettings().  Use GetValueFromContext() in GetStandardValues() to control picklist values.

/*=================================================================================**//**
* This class implements IPickListProvider interface.
* It will give out filtered values
* @bsiclass                                                               Bentley Systems
+===============+===============+===============+===============+===============+======*/
struct ConditionalPickListProvider : Bentley::DgnPlatform::IPickListProvider
{
private:
    static WString _ProviderName; // = L"ConditionalPickListNative";

public:
    ConditionalPickListProvider ();
    virtual bool                GetStandardValues (DgnFileP dgnFile, ECN::ECPropertyCP ecProperty, ECN::PrimitiveType primitiveType, StandardValuesCollection& values) override;
    virtual bool                IsValidSettings (WCharCP settings) const override;
private:
    bool AddValuesFromDgn (WCharCP pSettings, DgnFileP dgnFile, ECN::PrimitiveType primitiveType, StandardValuesCollection& values);
};

/*---------------------------------------------------------------------------------**//**
*  PickListCSVProvider constructer
* @bsimethod                                                            Bentley Systems
+---------------+---------------+---------------+---------------+---------------+------*/
WString ConditionalPickListProvider::_ProviderName = L"ConditionalPickListNative";
ConditionalPickListProvider::ConditionalPickListProvider () : Bentley::DgnPlatform::IPickListProvider (_ProviderName.c_str ())
    {
    SetDisplayLabel (_ProviderName.c_str ());
    }

/*---------------------------------------------------------------------------------**//**
*  This function has CSV provider specifc picklist value extraction code.
* @bsimethod                                                            Bentley Systems
+---------------+---------------+---------------+---------------+---------------+------*/
bool  ConditionalPickListProvider::GetStandardValues (DgnFileP dgnFile, ECN::ECPropertyCP ecProperty, ECN::PrimitiveType primitiveType, StandardValuesCollection& values)
    {
    ECN::ECValue ecValueProviderName;
    bool status = IPickListProvider::GetProviderName (ecProperty, ecValueProviderName);
    if (!status || ecValueProviderName.IsNull () || !_ProviderName.Equals (ecValueProviderName.GetString ()))
        return false;

    ECN::ECValue ecValueProviderSetting;
    status = IPickListProvider::GetProviderSettings (ecProperty, ecValueProviderSetting);
    if (!status || ecValueProviderSetting.IsNull () || WString::IsNullOrEmpty (ecValueProviderSetting.GetString ()))
        return false;
    return AddValuesFromDgn(ecValueProviderSetting.GetString(), dgnFile, primitiveType, values);
    }

/*---------------------------------------------------------------------------------**//**
* Check picklist settings are valid or not for CSV Provider.
* @bsimethod                                                            Bentley Systems
+---------------+---------------+---------------+---------------+---------------+------*/
bool  ConditionalPickListProvider::IsValidSettings (WCharCP settings) const
    {
    return true;
    }
/*---------------------------------------------------------------------------------**//**
* Check picklist settings are valid or not for CSV Provider.
* @bsimethod                                                            Bentley Systems
+---------------+---------------+---------------+---------------+---------------+------*/
bool ConditionalPickListProvider::AddValuesFromDgn (WCharCP pSettings, DgnFileP dgnFile, ECN::PrimitiveType primitiveType, StandardValuesCollection& values)
    {
    ECN::ECValue filter;
    values.clear ();
    if (SUCCESS == this->GetValueFromContext (filter, pSettings))
        {
        if (filter.IsNull () || filter.IsUninitialized ())
            return false;
        WString foodType = filter.GetString();
        foodType.ToLower();
        if (0 == wcscmp (foodType.c_str(), L"fruits"))
            {
            values.push_back (L"Mango");
            values.push_back (L"Banana");
            values.push_back (L"Grapes");
            }
        else if (0 == wcscmp (foodType.c_str (), L"veggis"))
            {
            values.push_back (L"Tomato");
            values.push_back (L"Brinjal");
            values.push_back (L"carrot");
            values.push_back (L"corn");
            }
        else if (0 == wcscmp (foodType.c_str (), L"all"))
            {
            values.push_back (L"Mango");
            values.push_back (L"Banana");
            values.push_back (L"Grapes");
            values.push_back (L"Tomato");
            values.push_back (L"Brinjal");
            values.push_back (L"carrot");
            values.push_back (L"corn");
            }
        else
            {
            values.push_back(L"Sorry No food");
            }
        }
    return (values.size () > 0);
}

Step 2: Register Provider:
Register provider implementation:

Managed:

bool status = Bentley.DgnPlatformNET.IPickListNetProvider.RegisterProvider(new ConditionalPickListProvider());

Native:

s_conditionalPickListProvider = new ConditionalPickListProvider();
Bentley::DgnPlatform::DgnECManager::GetManager ().RegisterPickListProvider (s_conditionalPickListProvider);

Step 3: Create Custom User control by implementing IPickListDataSettings:

Note: UI will be always in Managed code.
IPickListDataSettings is an interface available in Bentley.DgnPlatformNET namespace in Bentley.DgnDisplayNet.dll.

XAML:

<UserControl x:Class="PickListProviderManagedExample.ConditionProviderUI"
             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="450" d:DesignWidth="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Row="0" Grid.Column="0" Text="ItemType property: "></TextBlock>
        <TextBox Grid.Row="0"
                 Grid.Column="1"
                 HorizontalAlignment="Stretch"
                 Margin="2"
                 MinWidth="100"
                 Height="25"
                 Text="{Binding AccessString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
        </TextBox>

        <!--<ComboBox Grid.Row="1"
                 Grid.Column="0"
                 MinWidth="100"
                 Height="25"
                 HorizontalAlignment="Stretch"
                 Margin="2"></ComboBox>-->
    </Grid>
</UserControl>

XAML.cs

/*=================================================================================**//**
* This class is UserControl class which is used for CSV provider  
* @bsiclass                                                               Bentley Systems
+===============+===============+===============+===============+===============+======*/
public partial class ConditionProviderUI : UserControl
{
internal ConditionProviderVm ViewModel { get; set; }

/*---------------------------------------------------------------------------------**//**
* Constructor for PickListCSVProvider
* @bsimethod                                                              Bentley Systems
/*--------------+---------------+---------------+---------------+---------------+------*/
public ConditionProviderUI()
    {
    InitializeComponent();
    ViewModel = new ConditionProviderVm(this);
    DataContext = ViewModel;
    }
}

ViewModel:

/*=================================================================================**//**
* This class implements IPickListDataSettings interface, Bentley.UI.Mvvm.ViewModelBase class. 
* ViewModel for VS based picklist which will provider Usercontrol.
* @bsiclass                                                               Bentley Systems
+===============+===============+===============+===============+===============+======*/
internal class ConditionProviderVm : Bentley.UI.Mvvm.ViewModelBase, IPickListDataSettings
{
private DependencyObject m_dependencyObject;
private string           m_accessString;

/// <summary>
/// Fully qualified property access string.
/// </summary>
public string AccessString 
    {
    set 
        {
        m_accessString = value;
        OnPropertyChanged("AccessString");
        }
    get { return m_accessString; }
    }

//--------------------------------------------------------------------------------------
// @description   Constrcutor for PickListCSVProvider
// @bsimethod                                                    Bentley
//+---------------+---------------+---------------+---------------+---------------+------
public ConditionProviderVm(DependencyObject d)
    {
    m_dependencyObject = d;
    }

//--------------------------------------------------------------------------------------
// @description   Conditon to enable/disable UI with apprpriate error message.
// @bsimethod                                                    Bentley
//+---------------+---------------+---------------+---------------+---------------+------
public bool CanShowUIForProperty(CustomProperty customProperty, ITypeDescriptorContext context, out string errorMessage)
    {
    errorMessage = string.Empty;
    return true;
    }

//--------------------------------------------------------------------------------------
// @description   Send the string which will be stored as PickList Setting
// @bsimethod                                                    Bentley
//+---------------+---------------+---------------+---------------+---------------+------
public string GetPickListSetting()
    {
    return AccessString;
    }

//--------------------------------------------------------------------------------------
// @description   Send the UserControl used for CSV provider
// @bsimethod                                                    Bentley
//+---------------+---------------+---------------+---------------+---------------+------
public System.Windows.Controls.UserControl GetUI(CustomProperty customProperty, ITypeDescriptorContext context)
    {
    string pickListSettingVal = customProperty.PickListSettings;
    AccessString = string.IsNullOrEmpty(pickListSettingVal) ? string.Empty : pickListSettingVal;
       
    return (System.Windows.Controls.UserControl)m_dependencyObject;
    }
}

4. Register UI class:

IPickListDataSettings csvUI = new ConditionProviderUI().ViewModel;
PickListUIManager.Instance.RegisterUI(pickListUIName, csvUI);

5. See it running:

Step 1: Register providers:

Execute following key-ins either for managed or native.

    - For managed
        - mdl Load PickListProviderManagedExample.dll
        - picklistprovidermanagedexample initializeprovider
        - picklistprovidermanagedexample initializeui ConditionalPickList
    - For native
        - mdl Load PickListProviderManagedExample.dll
        - mdl load PickListProviderNativeExample
        - PickListProviderNativeExample InitializeProvider
        - picklistprovidermanagedexample initializeui ConditionalPickListNative

Step 2: Create DGN picklist.

Open PickList manager. Create DGN picklist with values: All, Fruits, Veggis

Step 3: Create ItemType Library and ItemType with Following Properties

1. FoodType: Text Property. Set DGN picklist as created in step#2.

2. Food: Text property. Select PickList source as ConditionalPickList for managed or ConditionalPickListNative for native sample.

Click the Settings button. And, write "FoodType" in UI.

Step 4: Try Attach item

Open Attach item dialog. You will observe that, if you select FoodType as "Fruit", you will see "Food" values as "Apple, Banana & Mango". And, when you select "FoodType" as "Veggies", you will see "Food" values as "Brinjal, Carrot, Corn and Tomato".

Note: SDK sample should be available starting with the Microstation U17 release.