question

EmonHaque-1485 avatar image
0 Votes"
EmonHaque-1485 asked PeterFleischer-3316 answered

How to load parts of Grid content with different UIThread?

Here's the example I was looking at and I wanted to try that method on it:

93049-capture.png

The main visual of AddView is a Grid and each of those component, i.e. Plot, Space, Tenant, etc., is FrameworkElement, CardView. Without threading I do it like this:

 class AddView : View
 {
         var plot = new AddPlot();
         var space = new AddSpace();
         var tenant = new AddTenant();
         var head = new AddHead();
         var lease = new AddLease();
         Grid.SetColumn(space, 1);
         Grid.SetColumn(lease, 2);
         Grid.SetRowSpan(lease, 2);
         Grid.SetRow(tenant, 1);
         Grid.SetRow(head, 1);
         Grid.SetColumn(head, 1);

         grid = new Grid() {
             RowDefinitions = {
                 new RowDefinition(),
                 new RowDefinition()
             },
             ColumnDefinitions = {
                 new ColumnDefinition(),
                 new ColumnDefinition(),
                 new ColumnDefinition()
             },
             Children = { plot, space, lease, tenant, head }
         };
         AddVisualChild(grid);
     }    
 }

My View class is:

 abstract class View : FrameworkElement
 {
     public virtual string Icon { get; }
     public abstract FrameworkElement container { get; }
     protected override Size MeasureOverride(Size availableSize) {
         container.Measure(availableSize);
         return container.DesiredSize;
     }
     protected override Size ArrangeOverride(Size finalSize) {
         container.Arrange(new Rect(container.DesiredSize));
         return finalSize;
     }
     protected override Visual GetVisualChild(int index) => container;
     protected override int VisualChildrenCount => 1;
 }

and the CardView class is:

 abstract class CardView : View
 {
     public abstract string Header { get; }
     public override FrameworkElement container => card;
     Card card;
     public CardView() {
         card = new Card() {
             IsRound = true,
             Header = Header
         };
         AddVisualChild(card);
         KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle);
     }
     protected void setContent(FrameworkElement element) => card.Content = element;
 }

Here you can see how I load content in CardView. In my AddView, I've tried with the following way to give each CardView a separate thread:

 class AddView : View
 {
     override public string Icon => Icons.Add;
     public override FrameworkElement container => grid;
     Grid grid;
     public AddView() {
         grid = new Grid() {
             RowDefinitions = {
                 new RowDefinition(),
                 new RowDefinition()
             },
             ColumnDefinitions = {
                 new ColumnDefinition(),
                 new ColumnDefinition(),
                 new ColumnDefinition()
             }
         };
         AddVisualChild(grid);
         var plotThread = new Thread(() => {
             var plot = new AddPlot();
             Dispatcher.Invoke(() => grid.Children.Add(plot));
             System.Windows.Threading.Dispatcher.Run();
         }) { IsBackground = true };
         plotThread.SetApartmentState(ApartmentState.STA);
         plotThread.Start();

         var spaceThread = new Thread(() => {
             var space = new AddSpace();
             Grid.SetColumn(space, 1);
             Dispatcher.Invoke(() => grid.Children.Add(space));
             System.Windows.Threading.Dispatcher.Run();
         }) { IsBackground = true }; ;
         spaceThread.SetApartmentState(ApartmentState.STA);
         spaceThread.Start();

         var tenantThread = new Thread(() => {
             var tenant = new AddTenant();
             Grid.SetRow(tenant, 1);
             Dispatcher.Invoke(() => grid.Children.Add(tenant));
             System.Windows.Threading.Dispatcher.Run();
         }) { IsBackground = true }; ;
         tenantThread.SetApartmentState(ApartmentState.STA);
         tenantThread.Start();

         var headThread = new Thread(() => {
             var head = new AddHead();
             Grid.SetRow(head, 1);
             Grid.SetRow(head, 1);
             Dispatcher.Invoke(() => grid.Children.Add(head));
             System.Windows.Threading.Dispatcher.Run();
         }) { IsBackground = true }; ;
         headThread.SetApartmentState(ApartmentState.STA);
         headThread.Start();

         var leaseThread = new Thread(() => {
             var lease = new AddLease();
             Grid.SetColumn(lease, 2);
             Grid.SetRowSpan(lease, 2);
             Dispatcher.Invoke(() => grid.Children.Add(lease));
             System.Windows.Threading.Dispatcher.Run();
         }) { IsBackground = true }; ;
         leaseThread.SetApartmentState(ApartmentState.STA);
         leaseThread.Start();
     }    
 }

and that doesn't work! I get System.InvalidOperationException: 'The calling thread cannot access this object because a different thread owns it.'

windows-wpf
capture.png (47.6 KiB)
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

1 Answer

PeterFleischer-3316 avatar image
1 Vote"
PeterFleischer-3316 answered

Hi,
all framework elements in the window must be instantiated and executed in the same thread. The example "Running a WPF application with multiple UI threads" shows the possibility of running windows in separate threads. The message pump is created for windows only.

In your code you create Grid in main thread "grid = new Grid()", then you create control in other thread "var plot = new AddPlot();" and use the main thread for Add "Dispatcher.Invoke(() => grid.Children.Add(plot));". That is impossible.



5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.