microsoft / microsoft-ui-xaml

Windows UI Library: the latest Windows 10 native controls and Fluent styles for your applications
MIT License
6.34k stars 677 forks source link

Proposal: implicit DataTemplates #1049

Open weitzhandler opened 5 years ago

weitzhandler commented 5 years ago

Proposal: Enable implicit DataTemplate in UWP

In WPF, you can add DataTemplate resources without a key, so at runtime, they get resolved according to their DataType property. Sadly, looks like this functionality isn't implemented in UWP. This feature has been proposed centuries ago. Maybe now that WinUI is on, it's about time to implement this. Like DataTemplateSelector, but per type.

Scope

Capability Priority
This proposal will allow users to define data-templates which will resolve according to their types Should

Meanwhile, here's my workaround:

[ContentProperty(Name = nameof(Templates))]
public class TypedDataTemplateSelector : DataTemplateSelector
{
  public IList<TypedDataTemplate> Templates { get; } 
    = new ObservableCollection<TypedDataTemplate>();

  public TypedDataTemplateSelector()
  {
    var incc = (INotifyCollectionChanged)Templates;
    incc.CollectionChanged += (sender, e) =>
    {
      if (e?.NewItems.Cast<TypedDataTemplate>()
          .Any(tdt => tdt?.DataType == null || tdt?.Template == null) == true)
        throw new InvalidOperationException("All items must have all properties set.");
    };
  }

  protected override DataTemplate SelectTemplateCore(object item, 
      DependencyObject container)
  {
    if (item == null) return null;
    if (!Templates.Any()) throw new InvalidOperationException("No DataTemplates found.");

    var result =
      Templates.FirstOrDefault(t => t.DataType.IsAssignableFrom(item.GetType()));
    if (result == null)
      throw new ArgumentOutOfRangeException(
        $"Could not find a matching template for type '{item.GetType()}'.");

    return result.Template;
  }
}

[ContentProperty(Name = nameof(Template))]
public class TypedDataTemplate
{
  public Type DataType { get; set; }
  public DataTemplate Template { get; set; }
}

Usage:

<ContentControl Content="{Binding}">
  <ContentControl.ContentTemplateSelector>
    <v:TypedDataTemplateSelector>
      <v:TypedDataTemplate DataType="data:Person">
        <DataTemplate>
          <StackPanel>
            <TextBox Header="First name" Text="{Binding FirstName}" />
            <TextBox Header="Last name" Text="{Binding LastName}" />
          </StackPanel>
        </DataTemplate>
      </v:TypedDataTemplate>
      <v:TypedDataTemplate DataType="data:Company">
        <DataTemplate>
          <StackPanel>
            <TextBox Header="Company name" Text="{Binding CompanyName}" />
          </StackPanel>
        </DataTemplate>
      </v:TypedDataTemplate>
    </v:TypedDataTemplateSelector>
  </ContentControl.ContentTemplateSelector>
</ContentControl>
huoyaoyuan commented 5 years ago

Note: your code currently DOES work in CoreCLR (Debug) build, but DOES NOT work in .Net Native (Release) build. The AOT system cannot create a System.Type from xaml. I would like this feature, and wrote almost same code in my app. But it seems needs xaml enhancement.

jevansaks commented 5 years ago

.NET Native requires that you explicitly list the types you want reflection on in the .rd.xml file, but otherwise the feature should in AOT I think.

huoyaoyuan commented 5 years ago

@weitzhandler I think this should be considered blocked now, until the xaml platform is open sourced, which is announced for future in WinUI 3 roadmap. And also, the announcement of .NET 5 contains a full-featured AOT solution, to get rid of differences between Debug and Release in UWP.

huoyaoyuan commented 5 years ago

Currently I just check the type name, instead of the System.Type object, as there are only 2 or 3 choises.

micahl commented 5 years ago

Another data point to include in this conversation (not meant to detract from the proposal, just add some extra info)... a while back I took a look at how DataTemplateSelector was being used in 1st party UI across the system and one thing that stood out was the different pattern used in C++ versus C# code. Managed code overwhelmingly would have a base type for the data and various derived types. Selecting which DataTemplate to use was based on the type information. Unmanaged code, however, would have only a few types and would select the template to use based on something like the value of a property.

Qiu233 commented 1 year ago

Sadly this workaround even breaks for winui. And, now that ResourceDictionary is of Dict<object, object>, why can't we put a general object as key, no matter it's Type or other stuff? The most annoying point of using Selector is that both DataTemplate definition and consumption are bound to xaml. And there's no reason to select xaml keys which are compile-time static, depending on some value in code.