WPF Dispatcher issue when adding tab items to tab control - c#

I'm getting an error when running this code:
tabControl1.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)(() => { tabControl1.Items.Add(tbItem); }));
the tabcontrol1 is hard coded into the xaml and the tab item/s are built at runtime.
I'm getting an error:
TargetInvocationException was
unhandled Exception has been thrown by
the target of an invocation.
I'd love to hear any thoughts on this.
Thanks
UPDATE
the inner exception:
{"The calling thread cannot access
this object because a different thread
owns it."}
the full method code:
TabItem tbItem = new TabItem();
tbItem.Header = worker;
Grid grid = new Grid();
ListBox listBox = new ListBox();
listBox.HorizontalAlignment = HorizontalAlignment.Stretch;
listBox.VerticalAlignment = VerticalAlignment.Stretch;
listBox.ItemsSource = datasource.Where(i => i.Category == worker);
grid.Children.Add(listBox);
tbItem.Content = grid;
tabControl1.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)(() => { tabControl1.Items.Add(tbItem); }));
The method is called with this:
Thread newThread = new Thread(myMethod);
newThread.SetApartmentState(ApartmentState.STA);
newThread.Start();
ANOTHER UPDATE
This works:
tabControl1.Dispatcher.Invoke(DispatcherPriority.Normal,
(Action)(() =>
{
TabItem tbItem = new TabItem();
tbItem.Header = worker;
//Grid & ListBox(within tab item)
Grid grid = new Grid();
ListBox listBox = new ListBox();
listBox.HorizontalAlignment = HorizontalAlignment.Stretch;
listBox.VerticalAlignment = VerticalAlignment.Stretch;
listBox.ItemsSource = datasource.Where(i => i.Category == worker);
grid.Children.Add(listBox);
tbItem.Content = grid;
tabControl1.Items.Add(tbItem);
}));

as you can see your tbItem is created on different thread, even through it tries to dispatch it back to the TAbControl's main gui thread.
why not extract out the part that takes longer (which you are usign thread for) and once you got result back, continue with creating tbItem and adding it to TabControl in the GUI thread
Example:
tabControl.Dispatcher.Invoke calls below function with dataSoruce result it gets
List<string> result = null;
tabControl.Dispatcher.Invoke((Action<IEnumerable<string>>)ProcessResult, result);
public void ProcessResult(IEnumerable<string> datasource)
{
//this is where you do creating TabItem and assigning data source to it and adding to TabControl.
}
Haven't compiled, pls check syntax

Check the InnerException property to find out the reason. TargetInvocationException is just a wrapper by the wpf runtime. Your lambda probably throws, but without the actual exception you can't tell.
edit
You are creating your TabItem in a different thread, thus the GUI thread can't access it, even though you use the dispatcher for the actual adding. As you already posted with your last snippet, you have to create the TabItem in the GUI thread. Only do the calculation in a different thread, and once the result returns, do the actual creation of the TabItem in the GUI thread (if necessary via the Dispatcher).

The problem is that you're creating your UIElements on a separate thread. This is not allowed.
You can do your processing on a background thread (the call to datasource.Where(i => i.Category == worker);), but unfortunately, every UI element needs to be constructed and used entirely on the main user interface thread.
In your case, this means constructing your ListBox and Grid on the UI thread, inside the Dispatcher call.
I would suggest rewriting this as:
// Load the data on the background...
var data = datasource.Where(i => i.Category == worker);
// Do all work on the UI thread
tabControl1.Dispatcher.Invoke(DispatcherPriority.Normal,
(Action)(() =>
{
TabItem tbItem = new TabItem();
tbItem.Header = worker;
//Grid & ListBox(within tab item)
Grid grid = new Grid();
ListBox listBox = new ListBox();
listBox.HorizontalAlignment = HorizontalAlignment.Stretch;
listBox.VerticalAlignment = VerticalAlignment.Stretch;
listBox.ItemsSource = data;
grid.Children.Add(listBox);
tbItem.Content = grid;
tabControl1.Items.Add(tbItem);
}));

Related

Calling thread cannot access object when it's created from the same thread [duplicate]

This question already has answers here:
The calling thread cannot access this object because a different thread owns it
(15 answers)
Closed 5 years ago.
I'm dynamically building my WPF layout and for some reason, when I try to add a control to a grid using a different thread, I'm getting:
System.InvalidOperationException: 'The calling thread cannot access this object because a different thread owns it.'
I know that I need to be invoking my controls if they are being access from different threads and this is what I'm doing.
public void controlInvoke(Grid control, Action action)
{
control.Dispatcher.Invoke((Delegate)action);
}
When I add a new row definition to my grid from the thread, it works fine.
controlInvoke(installedApplicationGrid, () =>
{
installedApplicationGrid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
});
The children are built from another method, which is below.
private StackPanel BuildAppPanel()
{
StackPanel panel = new StackPanel();
Grid innerGrid = new Grid();
innerGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(50, GridUnitType.Star) });
innerGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(50, GridUnitType.Star) });
innerGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(100, GridUnitType.Star) });
panel.Children.Add(innerGrid);
return panel;
}
The actual code that generates the panel, and tries to add it to my Grid is.
StackPanel applicationPanel = BuildAppPanel();
controlInvoke(installedApplicationGrid, () =>
{
installedApplicationGrid.Children.Add(applicationPanel);
});
It is at this point, when the error message shows. What I don't understand is why this is happening when all of the controls are being dynamically built using this thread. It doesn't make sense to me why adding the row definitions is fine, but adding a new control is not.
Could someone shed some light on this?
So after looking at this a little more carefully, I fixed it by moving the BuildPanel inside of the action.
controlInvoke(installedApplicationGrid, () =>
{
StackPanel applicationPanel = BuildAppPanel();
installedApplicationGrid.Children.Add(applicationPanel);
});
All is working now. Thanks #Fildor for the suggestion.

Change value of dynamically created progressbar

I created dynamically a bunch of textboxes, labels, and progressbar in a TableLayoutPanel
These I want to control from a Parallel.Foreach threads.
I know the name of each control so I can access them like so:
Table1.Invoke(
new MethodInvoker(delegate() {
Table1.Controls[<name>].Text = "text to write";
}));
But when I try to change the value of a progressbar or I try to change the style(blocks->marquee) it doesn't know the .Value of .Style option.
Table1.Invoke(
new MethodInvoker(delegate() {
Table1.Controls[<progressbar_name>].Value = <percentage>;
}));
Table1.Invoke(
new MethodInvoker(delegate() {
Table1.Controls[<progressbar_name>].Style = ProgressBarStyle.Marquee;
}));
It gives an error that .Value and .Style cannot exist in the current context.
You might want cast Table1.Controls[<progressbar_name>] to a ProgressBar class.
You need to cast control to use ProgressBar properties.
Try this:
Table1.Invoke(new MethodInvoker(delegate()
{
((ProgressBar)Table1.Controls[<progressbar_name>]).Value = <percentage>;
((ProgressBar)Table1.Controls[<progressbar_name>]).Style = ProgressBarStyle.Marquee;
}));
Happy to help you!
When you refer Table1.Controls[<progressbar_name>], this is interpreted as a Control, instead of as a ProgressBar, which happens to be a subclass of Control.
A simple fix:
var progBar = Table1.Controls[<progressbar_name>] as ProgressBar;
if(progBar != null)
{
progBar.Value = = <percentage>;
}
The if-structure should not be necessary, strictly speaking, but might in some cases avoid exceptions by skipping the attempt to set the value instead (e.g. in case the controls in your Table were not loaded or instantiated correctly).

Understanding why TPL Task can update UI withOUT FromCurrentSynchronizationContext

I am doing some TPL in VS2012, WPF with MVVM. I have a question that I think I know the answer to but wanted to know for sure. Consider this snippet:
TaskCanceller = new CancellationTokenSource();
TaskLoader = Task<object>.Factory.StartNew(() =>
{
//Test the current query
DataRepository dr = new DataRepository(DataMappingSelected);
string test = dr.TestMappingConnection();
if (test.IsNotNullEmpty())
throw new DataConnectionException(test);
//Create the CSV File
DataQueryCsvFile CsvFile = new DataQueryCsvFile();
CsvFile.FileName = IO.Path.GetFileName(FilePath);
CsvFile.FilePath = IO.Path.GetDirectoryName(FilePath);
CsvFile.DataMapping = DataMappingSelected;
CsvFile.DataBrowserQuery = DataQueryHolder;
//Allow for updates to the UI
CsvFile.PageExportComplete += (s, e) =>
{
if (TaskCanceller.IsCancellationRequested)
(s as DataQueryCsvFile).IsSaveCancellationRequested = true;
StatusData = String.Format("{0} of {1} Complete", e.ProgressCount, e.TotalCount);
StatusProgress = (100 * e.ProgressCount / e.TotalCount);
};
CsvFile.SaveFile();
return CsvFile;
});
I have a class DataQueryCsvFile. Its intent is to create a CSV text file based off a passed set of query parameters the results of which can be very large. So the export "paginates" the table produced by the query so it does not blow the users memory. Among its members is an Event called PageExportComplete which is called whenever a "Page" is written to a file - say 1000 records at a time. The code below uses this event to update a progress indicator on the UI.
The progress indicators (StatusData and StatusProgress) are declared in the VM with appropriate Notification to let the View know when they are changed. For example:
public string StatusData
{
get { return _StatusData; }
set { NotifySetProperty(ref _StatusData, value, () => StatusData); }
}
private string _StatusData;
Here is my question - as is, this works very well. But why since I did NOT declare the Task to run or update via the UI thread (FromCurrentSynchronizationContext) in a ContinueWith.
Is it because the MVVM pattern? In other words, because the properties being updated are local to the VM and because they have the notification to update the View and because of the lose coupling via bindings its works? Or am I just lucky due to the circumstances and I should go through the trouble of declaring a ContinueWith to update progress on the UI thread?
UI related stuff can only be updated from UI thread whereas any CLR property binded to UI can be updated from background thread, they don't have thread affinity issue.
Like you posted in your sample, you are only updating View model properties from background thread which is perfectly fine but if you try updating Progress bar text directly, it will fall miserably since progressBar is UI component and can only be updated from UI thread.
Say you have TextBlock binded to Name property in ViewModel class:
<TextBlock x:Name="txt" Text="{Binding Name}"/>
If you try to update text directly, you will get famous thread affinity issue:
Task.Factory.StartNew(() => txt.Text = "From background");
But in case you try to update ViewModel Name property, it will work fine since no UI stuff is access from background thread here:
ViewModelClass vm = DataContext as ViewModelClass;
Task.Factory.StartNew(() => vm.Name = "From background");

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).
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!
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.
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.
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.

targeting dynamic items within a stackpanel

I'm a little new to this sort of coding, but i am trying to access dynamically created TextBlock properties (like, TextBlock.Tag, Name, etc) within a StackPanel every tick of a timer. What i intend to do with each TextBlock is to see what its tag property is, and if it matches a conditoinal, for the timer to alter the TextBlock property in some way.
So it's a matter of finding a way to code every timer Tick: "For every TextBlock.Tag in StackPanel, if TextBlock.Tag == this, do this to the TextBlock."
Here is some code to help visualize what I am doing:
Xaml code:
<StackPanel Name="StackP" Margin="6,0,6,0"/>
C# code:
{
for (var i = 0; i < MaxCountOfResults; ++i)
{
TextBlock SingleResult= new TextBlock { Text = Resultname.ToString(), FontSize = 20, Margin = new Thickness(30, -39, 0, 0) };
//a condition to alter certain TextBlock properties.
if (i == .... (irrelevant to this example))
{
SingleResult.Foreground = new SolidColorBrush(Colors.Yellow);
SingleResult.Tag = "00001";
}
//Add this dynamic TextBlock to the StackPanel StackP
StackP.Children.Add(SingleResult);
}
//the timer that starts when this entire function of adding the TextBlocks to the StackPanel StackP tree is done.
Atimer = new Timer(new TimerCallback(Atimer_tick), 0, 0, 100);
}
public void Atimer_tick(object state)
{
The area where I have no idea how to reference the Children of stackpanel StackP with every timer tick. I need help :(
}
Thank you guys. I am still learning this and the help is needed.
you should be able to use timer, but I'd recommend using BackgroundWorker to perform a loop instead of timer events, which could collide. Even better - use SilverLight-style animations with triggers.
On the non-UI thread you'd want to use Dispatcher call to invoke your async code back on the UI thread, something like:
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
try
{
foreach (TextBlock txb in StackP.Children){
txb.Text = "xyz";
}
}
catch (Exception ex)
{
Debug.WriteLine("error: "+ex);
}
});

Categories

Resources