0

I'm not sure if this is possible, but I couldn't find anything when I searched about it.

I have a visual schedule made in WPF that loads and displays appointments. The problem is that it takes a while to load all the visuals and the program becomes unresponsive during that time.

Is it possible to load the appointment visuals and modify the schedule grid in a separate thread while leaving the main thread open for other things? Or possibly keep the schedule grid permanently in a second STA thread so it can do its own thing without interfering with the window?

edit:

Currently what I have:

    private static void FillWeek()
    { 
        BindingOperations.EnableCollectionSynchronization(ObservableAppointments, _lockobject);
        for (int i = 1; i < 6; i++)
        {
            FillDay(Date.GetFirstDayOfWeek().AddDays(i).Date);
        }
    }
    private static ObservableCollection<AppointmentUIElement> ObservableAppointments = new ObservableCollection<AppointmentUIElement>();
    private static object _lockobject = new object();
    public static async Task FillDay(DateTime date)
    {
        ClearDay(date);
        Appointment[] Appointments;
        var date2 = date.AddDays(1);
        using (var db = new DataBaseEntities())
        {
            Appointments = (from Appointment a in db.GetDailyAppointments(2, date.Date) select a).ToArray();
        }
        await Task.Run(()=>
        {
            foreach (Appointment a in Appointments)
            {
                var b = new AppointmentUIElement(a, Grid);
                ObservableAppointments.Add(b);
            }
        });      
    }
    private static void ObservableAppointments_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
        {
            var a = e.NewItems[0] as AppointmentUIElement;
            a.Display();
        }
    }
    private static void ClearDay(DateTime date)
    {
        var Queue = new Queue<AppointmentUIElement>(Grid.Children.OfType<AppointmentUIElement>().Where(a => a.Appointment.Start.DayOfWeek == date.DayOfWeek));
        while (Queue.Count > 0)
        {
            var x = Queue.Dequeue();
            Grid.Children.Remove(x);
            ObservableAppointments.Remove(x);
        }
        var Queue2 = new Queue<GridCell>(Grid.Children.OfType<GridCell>().Where(g => g.Date.Date == date));
        while (Queue2.Count > 0)
        {
            Queue2.Dequeue().AppointmentUIElements.RemoveAll(a => true);
        }
    }

AppointmentUIElement is derived from Border

2 Answers 2

2

Yes

Now the challenge of all this is that visual elements and bound ObservableCollections can only be modified by the UI thread without some additional work. Bound properties that are not collections do not require this.

So lets say you have the "appointment visuals" from the UI bound to an ObservableCollection that has you appointment data in it. What you can do is make your 'search appointments' function async and register your collection for thread synchronization as below. I'm leaving out anything related to INotifyPropertyChange for brevity.

  public ObservableCollection<Appointments> Appointments = new ObservableCollection<Appointments>();
  private static object _lockobject = new object();
  public async Task Load()
  {
        await Task.Run(() => { /*load stuff into the Appointments collection here */ });
        ///possibly more code to execute after the task is complete.
  }

  //in constructor or similar, this is REQUIRED because the collection is bound and must be synchronized for mulththreading operations
  BindingOperations.EnableCollectionSynchronization(YourCollection, _lockobject);

There is also a much nastier and not recommended way of modifying UI thread created visual elements.

 this.Dispatcher.Invoke(() => {/* do stuff with ui elements or bound things*/});

The gist of what happens is that you call load from the UI thread and when it hits the 'await task.run' it will work the contents of the task in a seperate thread while allowing the UI thread to continue responding to the user. Once the task completes it will then under the hood return to the ui thread to execute whatever else was under it in the load method.

If you forget the EnableCollectionSynchronization part then any attempts to add or remove items inside the task.run will throw an error complainging that you cannot change the contents of a collection in a different thread then the same one it was created with (almost same error as trying to modify a ui element directly).

Comment reply -> the problem with what your doing is here

  AppointmentUIElement(a,Grid)

What you really should be doing here is putting the Grid into a custom control that has a bound item template defined that binds to items from the ObservableAppointments which should actually be the appointment data, not UI elements. All of this should be happening through ViewModels on context. The way your doing it will ONLY work if there is just a single thread managing EVERYTHING, as soon as another thread gets involved it will all fall apart on you.

Sign up to request clarification or add additional context in comments.

4 Comments

Don't use async void except for event handlers. It should be public async Task Load() instead.
My VisualAppointment item has wpf elements that are created inside it and they are causing the STA error. Am I supposed to have only non-visual things in the collection?
I added an edit to the question with my current code
Ouch, I would not do it like that. I would go with creating custom controls that only knows about drawing the UI and the models which only know about the data and use MVVM binding to put the two together. The way your doing it everything is completely coupled, I'm not sure there is a good way to get this to work consistently the way your doing it. See update for some thoughts.
0

Is it possible to load the appointment visuals and modify the schedule grid in a separate thread while leaving the main thread open for other things? Or possibly keep the schedule grid permanently in a second STA thread so it can do its own thing without interfering with the window?

You could load and display the schedule grid in a separate window that runs on a dedicated dispatcher thread. Please refer to this blog post for an example of how to launch a WPF window in a separate thread.

Keep in mind that an element created on the new thread won't be able to interact with an element created on the main thread though. So you can't simply load the schedule on another thread and then bring it back to the main thread. A visual element can only be accessed from the thread on which it was originally created on.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.