locked
Lightswitch Custom control binding nested itemscollection RRS feed

  • Question

  • I want to create a custom control for my SalesOrderLine object so i can visualise the different article sizes in a horizontal table. To do this i need to bind SalesorderLine and the containing collection SalesorderLinePacks in xaml

    I tried 

        <ItemsControl ItemsSource="{Binding Path=Screen.SalesOrderLines}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="White">
                        <ItemsControl.ItemTemplate>
                            <DataTemplate DataType="lightSwitchApplication:SalesOrderLine">
                                <Grid>
                                    <Grid Background="Green" Margin="5" Width="500" Height="200">
                                        <StackPanel Orientation="Vertical">
                                            <TextBlock Text="{Binding Path=Article.Code, Mode=TwoWay}"/>
                                            
                                            
                                            <ItemsControl ItemsSource="{Binding Path=SalesOrderLinePacks}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="White">
                                                <ItemsControl.ItemTemplate>
                                                    <DataTemplate DataType="lightSwitchApplication:SalesOrderLinePack">
                                                        <Grid Height="50" Width="50" Background="Blue">
                                                            <TextBlock Text="{Binding Path=Pack.Code, Mode=TwoWay}"/>
                                                        </Grid>
                                                    </DataTemplate>
                                                </ItemsControl.ItemTemplate>
                                            </ItemsControl>
                                            
                                            
                                        </StackPanel>
                                    </Grid>
                                </Grid>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>

    Then i read about having to bind the containing collection to a property on the screen. Becouse of the different viewmodel and having it to thread-change.

    What would the correct aproach look like?

    (this one throws thread exception)

    Friday, March 7, 2014 8:26 AM

Answers

  • The likely problem is that you are trying to iterate the SalesOrderLinePacks property value while on the main thread of the application (controls always belong to the main thread). However, LightSwitch data must be access on a data dispatcher (which the main thread is not). So you get an InvalidOperationException about invalid cross-thread access.

    Please read my answer in this post about threading to get a better idea of why this occurs: http://social.msdn.microsoft.com/Forums/vstudio/en-US/f948da2f-c8be-4864-9c21-7dc1b0937c38/understanding-threading?forum=lightswitch

    To solve this problem in your case, you should build a converter that, for a given parent entity, enumerates the end of the navigation property while on the logic dispatcher, and then updates the returned collection on the main dispatcher. For example:

    public class NavigationPropertyConverter :
        IValueConverter
    {
        object IValueConverter.Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            IEntityObject parentEntity = value as IEntityObject;
            if (null != value && null == parentEntity)
                throw new ArgumentException();
    
            IList<IEntityObject> childEntities = new ObservableCollection<IEntityObject>();
            if (!String.IsNullOrEmpty(this.PropertyName))
            {
                IEntityCollectionProperty property = parentEntity.Details.Properties[this.PropertyName] as IEntityCollectionProperty;
    
                // Dispatch to logic dispatcher to enumerate child entities
                parentEntity.Details.Dispatcher.BeginInvoke(() =>
                {
                    IEnumerable<IEntityObject> propertyValue = property.Value.Cast<IEntityObject>().ToList();
    
                    // Dispatch to main dispatcher to update collection returned by converter
                    Dispatchers.Main.Invoke(() =>
                    {
                        foreach (IEntityObject childEntity in propertyValue)
                            childEntities.Add(childEntity);
                    });
                });
            }
            return childEntities;
        }
    
        object IValueConverter.ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    
        public string PropertyName { get; set; }
    }


    The way you'd use this converter is to declare an instance of this converter at the top of your user control:

    <UserControl.Resources>
      <me:NavigationPropertyConverter x:Key="PropertyConverter" PropertyName="SalesOrderLinePacks"/>
    </UserControl.Resources>

    and update your inner ItemsControl.ItemsSource binding to be:

    <ItemsControl ItemsSource="{Binding Converter={StaticResource PropertyConverter}}">


    Justin Anderson, LightSwitch Development Team

    • Marked as answer by Angie Xu Monday, March 17, 2014 6:59 AM
    Friday, March 7, 2014 8:39 PM
    Moderator

All replies

  • The likely problem is that you are trying to iterate the SalesOrderLinePacks property value while on the main thread of the application (controls always belong to the main thread). However, LightSwitch data must be access on a data dispatcher (which the main thread is not). So you get an InvalidOperationException about invalid cross-thread access.

    Please read my answer in this post about threading to get a better idea of why this occurs: http://social.msdn.microsoft.com/Forums/vstudio/en-US/f948da2f-c8be-4864-9c21-7dc1b0937c38/understanding-threading?forum=lightswitch

    To solve this problem in your case, you should build a converter that, for a given parent entity, enumerates the end of the navigation property while on the logic dispatcher, and then updates the returned collection on the main dispatcher. For example:

    public class NavigationPropertyConverter :
        IValueConverter
    {
        object IValueConverter.Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            IEntityObject parentEntity = value as IEntityObject;
            if (null != value && null == parentEntity)
                throw new ArgumentException();
    
            IList<IEntityObject> childEntities = new ObservableCollection<IEntityObject>();
            if (!String.IsNullOrEmpty(this.PropertyName))
            {
                IEntityCollectionProperty property = parentEntity.Details.Properties[this.PropertyName] as IEntityCollectionProperty;
    
                // Dispatch to logic dispatcher to enumerate child entities
                parentEntity.Details.Dispatcher.BeginInvoke(() =>
                {
                    IEnumerable<IEntityObject> propertyValue = property.Value.Cast<IEntityObject>().ToList();
    
                    // Dispatch to main dispatcher to update collection returned by converter
                    Dispatchers.Main.Invoke(() =>
                    {
                        foreach (IEntityObject childEntity in propertyValue)
                            childEntities.Add(childEntity);
                    });
                });
            }
            return childEntities;
        }
    
        object IValueConverter.ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    
        public string PropertyName { get; set; }
    }


    The way you'd use this converter is to declare an instance of this converter at the top of your user control:

    <UserControl.Resources>
      <me:NavigationPropertyConverter x:Key="PropertyConverter" PropertyName="SalesOrderLinePacks"/>
    </UserControl.Resources>

    and update your inner ItemsControl.ItemsSource binding to be:

    <ItemsControl ItemsSource="{Binding Converter={StaticResource PropertyConverter}}">


    Justin Anderson, LightSwitch Development Team

    • Marked as answer by Angie Xu Monday, March 17, 2014 6:59 AM
    Friday, March 7, 2014 8:39 PM
    Moderator
  • Monday, April 7, 2014 4:16 PM