Eli Iannuzzo
Topic Author
Posts: 5
Joined: 26 Mar 2020, 06:01

Dynamic Menu3D options menu

07 Apr 2020, 06:18

We've been having trouble creating an options menu in our project with the following criteria.
  • Options list is generated from data.
  • Options can have different behaviors when interacted with, so we need a way of passing in an "interaction type" to each option, and based on that type it would use a different template & bind functionality to the view model. e.g.
    • -A label only option that can either do nothing or respond to a click event.
    • -A slider option that responds to specified inputs & has event bound to the view model for processing updates to the slider position.
    • -An array of entries that we go through using specified inputs (similar to the OptionSelector in the Menu3D example)
    • -An option containing an editable text box, again firing off an event bound to the view model when text is updated.
What advice can you give us & how might you go about updating the Menu3D example to take this dynamic approach?
 
User avatar
sfernandez
Site Admin
Posts: 2997
Joined: 22 Dec 2011, 19:20

Re: Dynamic Menu3D options menu

07 Apr 2020, 14:02

I think using typed DataTemplates here can help. You can have a list of heterogeneous options (sharing a base class), and different DataTemplates for each type:



The key here is, instead of explicitly setting the ItemTemplate on the ItemsControl, you let the resource lookup to automatically pick the appropriate DataTemplate based on the type of the item.
 
Eli Iannuzzo
Topic Author
Posts: 5
Joined: 26 Mar 2020, 06:01

Re: Dynamic Menu3D options menu

08 Apr 2020, 06:40

Could you give an example of doing this using an ObservableCollection as the ItemSource?
 
User avatar
sfernandez
Site Admin
Posts: 2997
Joined: 22 Dec 2011, 19:20

Re: Dynamic Menu3D options menu

08 Apr 2020, 11:11

The example will be pretty similar, but instead of having the items inside ItemsControl, they will be provided by the collection in the ItemsSource:
class Option: public BaseComponent, public INotifyPropertyChanged
{
public:
  Option(const char* label): _label(label) { }

  const char* GetLabel() const { return _label.Str(); }

  PropertyChangedEventHandler& PropertyChanged() override { return _changed; }

  NS_IMPLEMENT_INTERFACE_FIXUP

protected:
  void OnPropertyChanged(const char* name)
  {
    _changed(this, PropertyChangedEventArgs(Symbol(name)));
  }

private:
  String _label;
  PropertyChangedEventHandler _changed;

  NS_IMPLEMENT_INLINE_REFLECTION(Option, BaseComponent)
  {
    NsImpl<INotifyPropertyChanged>();
    NsProp("Label", &Option::GetLabel);
  }
};

class BoolOption: public Option
{
public:
  BoolOption(const char* label): Option(label) { }

  bool GetBool() const { return _bool; }
  void SetBool(bool value)
  {
    if (_bool != value)
    {
      _bool = value;
      OnPropertyChanged("Bool");
    }
  }

private:
  bool _bool;

  NS_IMPLEMENT_INLINE_REFLECTION(TextOption, Option)
  {
    NsProp("Bool", &BoolOption::GetBool, &BoolOption::SetBool);
  }
}

class TextOption: public Option
{
public:
  TextOption(const char* label): Option(label) { }

  const char* GetText() const { return _text.Str(); }
  void SetText(const char* value)
  {
    if (_text != value)
    {
      _text = value;
      OnPropertyChanged("Text");
    }
  }

private:
  String _text;

  NS_IMPLEMENT_INLINE_REFLECTION(TextOption, Option)
  {
    NsProp("Text", &TextOption::GetText, &TextOption::SetText);
  }
}

//...

typedef ObservableCollection<Option> OptionCollection;

class ViewModel: public BaseComponent
{
public:
  ViewModel(): _options(MakePtr<OptionCollection>()) { }

  OptionCollection* GetOptions() const { return _options; }

private:
  Ptr<OptionCollection> _options;

  NS_IMPLEMENT_INLINE_REFLECTION(ViewModel, BaseComponent)
  {
    NsProp("Options", &ViewModel::GetOptions);
  }
};
At any time you will fill the ViewModel's collection of options, and set the view model as DataContext of some control like this:
<UserControl
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <UserControl.Resources>
    <ControlTemplate x:Key="OptionTemplate" TargetType="HeaderedContentControl">
      <Grid>
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="2*" MaxWidth="200"/>
          <ColumnDefinition Width="5*/>
        <Grid.ColumnDefinitions>
        <TextBlock Grid.Column="0" Text="{TemplateBinding Header}" TextTrimming="CharacterEllipsis" VerticalAlignment="Center"/>
        <ContentPresenter Grid.Column="1" VerticalAlignment="Center"/>
      </Grid>
    </ControlTemplate>
    <DataTemplate DataType="{x:Type BoolOption}">
      <HeaderedContentControl Template="{StaticResource OptionTemplate}" Header="{Binding Label}">
        <CheckBox Grid.Column="1" IsChecked="{Binding Bool}"/>
      </HeaderedContentControl>
    </DataTemplate>
    <DataTemplate DataType="{x:Type TextOption}">
      <HeaderedContentControl Template="{StaticResource OptionTemplate}" Header="{Binding Label}">
        <TextBox Text="{Binding Text}"/>
      </HeaderedContentControl>
    </DataTemplate>
    <DataTemplate DataType="{x:Type RangeOption}">
      <HeaderedContentControl Template="{StaticResource OptionTemplate}" Header="{Binding Label}">
        <Slider Minimum="{Binding Minimum}" Maximum="{Binding Maximum}" Value="{Binding Value}"/>
      </HeaderedContentControl>
    </DataTemplate>
    <!-- ... -->
  </UserControl.Resources>
  <ItemsControl ItemsSource="{Binding Options}"/>
</UserControl>
Hope this helps.
 
Eli Iannuzzo
Topic Author
Posts: 5
Joined: 26 Mar 2020, 06:01

Re: Dynamic Menu3D options menu

09 Apr 2020, 07:51

Those option classes defined in the ViewModel.h don't seem to be accessible/valid in xaml, how can I expose them?
<DataTemplate DataType="{x:Type BoolOption}">

"The type reference cannot find a public type named 'BoolOption' ..."
<DataTemplate DataType="{x:Type local:BoolOption}">

"The name 'BoolOption' does not exist in the namespace ..."
 
User avatar
sfernandez
Site Admin
Posts: 2997
Joined: 22 Dec 2011, 19:20

Re: Dynamic Menu3D options menu

09 Apr 2020, 10:27

In order to make those classes available to the xaml parser they need to be at least registered in the reflection. So they need to have reflection marks with a TypeId, and you can force the types to be registered in reflection by calling TypeOf<T>() for those classes when you register your component types:
NS_IMPLEMENT_REFLECTION(BoolOption)
{
  NsMeta<TypeId>("Test.BoolOption");
  ...
}
void RegisterComponents()
{
  TypeOf<BoolOption>();
  ...
  NsRegisterComponent<MyControl>();
  ...
}
<UserControl x:Class="Test.MyControl"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="clr-namespace:Test">
  <UserControl.Resources>
    <DataTemplate DataType="{x:Type local:BoolOption}">
      ...
    </DataTemplate>
  </UserControl.Resources>
  ...
</UserControl>

Who is online

Users browsing this forum: Ahrefs [Bot] and 43 guests