Yaron
Yaron

Reputation: 233

Wpf application and Threads

I have problem with my GUI and Threads. The GUI contains DataGrid. Every X time the program do some query and getting a list of items that I want to fill into the DataGrid.

So far so good:

private void loadTaskList() //Call every X time
{
    List<myObject> myList = myquery();
    this.Dispatcher.Invoke((Action)(() =>
    {
        TaskListTable.Items.Clear(); //Clear the DataGrid
        foreach (myObject O in myList) //Add the items from the new query.
        {
            TaskListTable.Items.Add(O);
        }
    }));                    
    FindSelectionObject(); // <-- see next explanation.
}

When the user click on one of the objects in the datagrid, the line color changed (it works fine), but when the program reload the table,The painted line disappears (Becuse I clear and add new objects).

To deal with it, I created the function FindSelectionObject():

private void FindSelectionObject()
{
    this.Dispatcher.Invoke((Action)(() =>
    {
       this.SelectedIndex = TaskListTable.Items.IndexOf((myObject)lastSelectionObject); //find index of the new object that equels to the last selection object.
       var row = TaskListTable.ItemContainerGenerator.ContainerFromIndex(SelectedIndex) as DataGridRow; //get the row with the index
       row.Background = Brushes.LightGoldenrodYellow; //repaint
    }));
}

The problem: Everything works fine, but sometimes when the program reloads, the line flashes per second and then highlighted back, and sometimes it's not painting it at all (untill the next reload).

enter image description here

I can't understand why this is happening. I think maybe the FindSelectionObject() begins to run before the loadTaskList() ends to invoke all and add the new objects into the datagrid. But if so - Why? And how can I fix it?

In the bottom line, I want that after every reload the line re-paint immediately..

Thanks for any advice!

Upvotes: 1

Views: 136

Answers (3)

odyss-jii
odyss-jii

Reputation: 2719

A few things to think about:

You should keep in mind that the DataGrid uses virtualization, which means that each item in your items source does not get its very own UI element. The UI elements are created to fill the visible area, and then re-used depending on which data-source item is currently bound to each one (this changes when you scroll for instance or change the items source). This may cause you problems in the future if you use your current approach, so keep this in mind.

The other thing is that the DataGrid may require more "cycles" of the layout process in order to update its UI. You may simply be calling FindSelectionObject prematurely. You have queued FindSelectionObject right after the invocation in loadTaskList. If the DataGrid needs to perform some actions which are queued on the dispatcher after the items source has changed, these will execute after the invocation in FindSelectionObject. Try this instead:

private void loadTaskList() //Call every X time
{
    List<myObject> myList = myquery();
    this.Dispatcher.Invoke((Action)(() =>
    {
        TaskListTable.Items.Clear(); //Clear the DataGrid
        foreach (myObject O in myList) //Add the items from the new query.
        {
            TaskListTable.Items.Add(O);
        }

        // The items of the grid have changed, NOW we QUEUE the FindSelectionObject
        // operation on the dispatcher.

        FindSelectionObject(); // <-- (( MOVE IT HERE )) !!
    }));
}

EDIT: OK, so if this fails then maybe this will cover the case in which the above solution fails: subscribe to the LoadingRow event of DataGrid and set the appropriate background color if the row is the selected one. So in the cases when new rows are created this event will be called (due to virtualization it is not called per item in items source, but per actual row UI element). In the event args you will have access to the created DataGridRow instance.

Upvotes: 1

Ra&#250;l Ota&#241;o
Ra&#250;l Ota&#241;o

Reputation: 4770

I think this issue could be a visual thread synchronization. For this you can create and use a method similar like this:

public void LockAndDoInBackground(Action action, string text, Action beforeVisualAction = null, Action afterVisualAction = null)
    {
        var currentSyncContext = SynchronizationContext.Current;
        var backgroundWorker = new BackgroundWorker();
        backgroundWorker.DoWork += (_, __) =>
        {
            Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");
            Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US");
            currentSyncContext.Send((t) =>
            {
                IsBusy = true;
                BusyText = string.IsNullOrEmpty(text) ? "Espere por favor..." : text;
                if (beforeVisualAction != null)
                    beforeVisualAction();
            }, null);
            action();
            currentSyncContext.Send((t) =>
            {
                IsBusy = false;
                BusyText = "";
                if (afterVisualAction != null)
                    afterVisualAction();
            }, null);
        };
        backgroundWorker.RunWorkerAsync();
    }

IsBusy and BusyText are particular properties, that you can remove. The action variable will be the action to do in background (load your items for instance). beforeVisualAction and afterVisualAction are the visual actions you may want to do before and after the background action. Here are any visual update, for instance select your item, change color, set a view model variable that raise a binding update,... (any action that update the view). Hope this method helps.

Upvotes: 1

Allan Elder
Allan Elder

Reputation: 4104

Are you maintaining the reference to lastSelectionObject somewhere? You say you're adding new objects, if they are truly new then the reference will be different and the reference comparison happening in IndexOf will not find it.

Upvotes: 0

Related Questions