jeudi 22 juin 2023

Binding on dynamically generated controls using reflection

I am trying to make a page to display and edit the details on a subject. Depending on the type of subject, we want to display different fields to the user. Some subject types need to display a barcode field, some needs a Tutor name, etc etc.

Some may have a total of 5-6 fields, another one can have 12 fields to display, and this is all configurable, so my page has to be dynamic, I cannot have anything hardcoded.

What I did so far is that I created this ItemsControl on my page (which is a UserControl):

<!--  Subject Info section  -->
<Grid Grid.Row="2" Grid.Column="0">
    <ItemsControl ItemsSource="{Binding SubjectDataFields}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <UniformGrid Columns="2" />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Grid Margin="5">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="100" />
                        <ColumnDefinition Width="*" />
                    </Grid.ColumnDefinitions>

                    <TextBlock Text="{Binding Name}" />
                    <TextBox Grid.Column="1">
                        <TextBox.Text>
                            <MultiBinding Converter="{StaticResource SubjectFieldValueConverter}">
                                <Binding Path="DataContext.Subject" RelativeSource="{RelativeSource AncestorType=UserControl}" />
                                <Binding Path="Field" />
                            </MultiBinding>
                        </TextBox.Text>
                    </TextBox>
                </Grid>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Grid>

SubjectDataFields is a List<SubjectDataField>. SubjectDataField is defined as such:

public class SubjectDataField
{
    [Key, Column(Order = 0)]
    public string SubjectTypeIDFromServer { get; set; }
    [Key, Column(Order = 1)]
    public string Field { get; set; }
    public string Name { get; set; }
    public int DisplayOrder { get; set; }
    public bool Required { get; set; }

    
    public SubjectDataField()
    {
    }
}

I have created a converter to use multibinding in order to get the correct fields bound:

public class SubjectFieldValueConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (values.Length >= 2 && values[0] is Subject subject && values[1] is string fieldName)
        {
            string dynamicFieldPrefix = "dynamic_";

            // Take dynamic fields into consideration
            if (fieldName.StartsWith(dynamicFieldPrefix))
            {
                fieldName = fieldName.Substring(dynamicFieldPrefix.Length);
                using (DbContext db = new DbContext())
                {
                    return db.SubjectCustomDatas.Where(scd => scd.SubjectId == subject.Id && scd.Key == fieldName).FirstOrDefault()?.Value ?? "";
                }
            }

            // Not a dynamic field, Retrieve the property, if it exists.
            var propertyInfo = subject.GetType().GetProperty(fieldName);
            if (propertyInfo != null)
                return propertyInfo?.GetValue(subject);

            return "";
        }

        return null;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        return new object[] { Binding.DoNothing, Binding.DoNothing };
    }

}

As you can see, some fields are actual properties from the object and are fetched using reflection, and some other have to go look into a dynamic fields table. The Convert function is working as intended. Both my properties and my dynamic fields are fetched properly, and performances look good.

However, when I wanted to implement the ConvertBack method, I am having an issue. The value contains the new value as entered in the text box, but I don't get neither the Subject object, nor am I getting the Field name. The Convert back receives a Type[] targetTypes, but as I understand, this is nothing but the definition of types, so I do se Subject for the subject and String for the field name, but I don't see how I can actually access either the Subject itself or the Filed name itself.

I have asked GPT for help, but he quickly went into a circle. It tried to get me to add ConverterParameter to the binding in the xaml and bind it with the subject, but then my page doesn't load at all, but I am not getting any error message... I am unsure how to debug this part. I am using the correct approach? What am I doing wrong?





Aucun commentaire:

Enregistrer un commentaire