Unsubscribe RadPropertyGrid CollectionEditor from INotifyCollectionChanged events

0 Answers 101 Views
PropertyGrid
Mikola
Top achievements
Rank 1
Mikola asked on 29 Sep 2022, 02:30 PM

Hi.

I have the following RadPropertyGrid:

XAML:

<Grid
        behaviors:CalloutTagBehavior.CalloutTag="F6C90D8E-9F1C-4372-87D0-17B40EE1E462"
        IsEnabled="{Binding StateDispatcher.EnvironmentState,
                            Converter={converters:ComparisonConverter},
                            ConverterParameter={x:Static runtime:EnvironmentState.Design}}">
        <telerik:RadPropertyGrid
            x:Name="RadPropertyGrid"
            telerik:PropertySet.ShouldAddNullForNonMatchingValues="True"
            AutoGeneratingPropertyDefinition="RadPropertyGrid_OnAutoGeneratingPropertyDefinition"
            EditEnded="RadPropertyGrid_OnEditEnded"
            EditMode="Single"
            FieldIndicatorVisibility="Collapsed"
            IsGrouped="False"
            Item="{Binding SelectedObject}"
            LabelColumnWidth="0.8*"
            Loaded="RadPropertyGrid_OnLoaded"
            PropertySetMode="None"
            RenderMode="Flat">
            <telerik:RadPropertyGrid.GroupStyle>
                <Style TargetType="telerik:RadToggleButton">
                    <Setter Property="Margin" Value="0,0,15,0" />
                </Style>
            </telerik:RadPropertyGrid.GroupStyle>
        </telerik:RadPropertyGrid>
    </Grid>

CodeBehinde:

public partial class PropertyGridPaneUserControl : UserControl
{
    public PropertyGridPaneUserControl(IEventAggregator eventAggregator)
    {
        if (eventAggregator == null) throw new ArgumentNullException(nameof(eventAggregator));
        InitializeComponent();
        eventAggregator.Register<RefreshPropertyGridMessage>(this, RefreshPropertyGridMessageHandler);
    }

    private void RefreshPropertyGridMessageHandler(RefreshPropertyGridMessage message)
    {
        RadPropertyGrid.ReloadData();
    }

    private void RadPropertyGrid_OnAutoGeneratingPropertyDefinition(object? sender, AutoGeneratingPropertyDefinitionEventArgs e)
    {
        ((Binding)e.PropertyDefinition.Binding).ValidatesOnExceptions = true;
    }

    private void RadPropertyGrid_OnLoaded(object sender, RoutedEventArgs e)
    {
        var virtualizingStackPanel = RadPropertyGrid
            .ChildrenOfType<VirtualizingStackPanel>().FirstOrDefault();
        if (virtualizingStackPanel != null)
        {
            virtualizingStackPanel.ScrollOwner.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled;
        }
    }

    private void RadPropertyGrid_OnEditEnded(object? sender, PropertyGridEditEndedEventArgs e)
    {
        if (e.EditAction != PropertyGridEditEndedAction.Commit ||
            Equals(e.NewValue, e.OldValue) ||
            DataContext is not PropertyGridViewModel {SelectedObjects: { }} propertyGridViewModel) return;

        ItemPropertyInfo sourceProperty = e.EditedPropertyDefinition.SourceProperty;

        foreach (var selectedObject in propertyGridViewModel.SelectedObjects)
        {
            if (selectedObject == e.EditedPropertyDefinition.Instance) continue;

            PropertyInfo? property = selectedObject.GetType()
                .GetProperty(sourceProperty.Name, sourceProperty.PropertyType);

            if (property != null)
            {
                property.SetValue(selectedObject, e.NewValue);
            }
        }
    }
}

And it worked fine up to a certain point. The problem occurs when processing an object with the ObservableCollection property of the following type:

public class A
{
    public ObservableCollection<object> Collection { get; }
}

My task is to perform asynchronous addition of elements to a nested collection. If I didn't open the CollectionEditor in the UI, then the addition happens without problems. But if you open CollectionEditor in UI at least once, InvalidOperationnException is generated when adding asynchronously: The calling thread cannot access this object because a different thread owns it.  Stack trace part:

 at System.Windows.Threading.Dispatcher.VerifyAccess()
  at System.Windows.DependencyObject.GetValue(DependencyProperty dp)
  at System.Windows.Controls.Panel.get_IsItemsHost()
  at System.Windows.Controls.ItemsControl.GetItemsOwnerInternal(DependencyObject element, ItemsControl& itemsControl)
  at System.Windows.Controls.Panel.VerifyBoundState()
  at System.Windows.Controls.Panel.OnItemsChanged(Object sender, ItemsChangedEventArgs args)
  at System.Windows.Controls.ItemContainerGenerator.OnItemAdded(Object item, Int32 index)
  at System.Windows.Controls.ItemContainerGenerator.OnCollectionChanged(Object sender, NotifyCollectionChangedEventArgs args)
  at System.Windows.WeakEventManager.ListenerList`1.DeliverEvent(Object sender, EventArgs e, Type managerType)
  at System.Windows.WeakEventManager.DeliverEvent(Object sender, EventArgs args)
  at System.Windows.Data.CollectionView.OnCollectionChanged(NotifyCollectionChangedEventArgs args)
  at System.Windows.WeakEventManager.ListenerList`1.DeliverEvent(Object sender, EventArgs e, Type managerType)
  at System.Windows.WeakEventManager.DeliverEvent(Object sender, EventArgs args)
  at System.Windows.Data.CollectionView.OnCollectionChanged(NotifyCollectionChangedEventArgs args)
  at MS.Internal.Data.CollectionViewProxy._OnViewChanged(Object sender, NotifyCollectionChangedEventArgs args)
  at Telerik.Windows.Data.QueryableCollectionView.OnCollectionChanged(NotifyCollectionChangedEventArgs args)
  at Telerik.Windows.Data.QueryableCollectionView.ProcessSynchronousCollectionChangedWithAdjustedArgs(NotifyCollectionChangedEventArgs originalArguments, Int32 adjustedOldIndex, Int32 adjustedNewIndex)
  at Telerik.Windows.Data.QueryableCollectionView.ProcessSynchronousCollectionChanged(NotifyCollectionChangedEventArgs args)
  at Telerik.Windows.Data.QueryableCollectionView.ProcessCollectionChanged(NotifyCollectionChangedEventArgs args)
  at Telerik.Windows.Data.QueryableCollectionView.OnSourceCollectionChanged(Object sender, NotifyCollectionChangedEventArgs args)
  at Telerik.Windows.Data.QueryableCollectionView.Telerik.Windows.Data.IWeakEventListener<System.Collections.Specialized.NotifyCollectionChangedEventArgs>.ReceiveWeakEvent(Object sender, NotifyCollectionChangedEventArgs args)
  at System.Collections.ObjectModel.ObservableCollection`1.OnCollectionChanged(NotifyCollectionChangedEventArgs e)

At the same time, an exception occurs even if in Rad PropertyGrid.Item set another instance of the object or null. I would like to know how to correct this misunderstanding

Martin Ivanov
Telerik team
commented on 04 Oct 2022, 08:41 AM

The WPF framework doesn't allow making modifications to the UI elements on another thread (different than the UI thread). This leads to InvalidOperationnException (" The calling thread cannot access this object because a different thread owns it.").

To avoid this type of error, you should move all operations related to the UI from the thread that executes it to the WPF UI thread. This includes updates of properties bound to the UI. The moving of the code is done using a Dispatcher. For example:

public void UpdateCollectionOnAnotherThread()
{
	// some actions here
	App.Current.Dispatcher.BeginInvoke(new Action(() =>
	{
		this.Collection.Add(newItem);
	}));
	// some actions here 
}

 

 

 

No answers yet. Maybe you can help?

Tags
PropertyGrid
Asked by
Mikola
Top achievements
Rank 1
Share this question
or