I am just writing my first program using WPF and C#. My windows just contains a simple canvas control:
<StackPanel Height="311" HorizontalAlignment="Left" Name="PitchPanel" VerticalAlignment="Top" Width="503" Background="Black" x:FieldModifier="public"></StackPanel>
This works fine and from the Window.Loaded event I can access this Canvas called PitchPanel.
Now I have added a class called Game which is initialized like this:
public Game(System.Windows.Window Window, System.Windows.Controls.Canvas Canvas)
{
this.Window = Window;
this.Canvas = Canvas;
this.GraphicsThread = new System.Threading.Thread(Draw);
this.GraphicsThread.SetApartmentState(System.Threading.ApartmentState.STA);
this.GraphicsThread.Priority = System.Threading.ThreadPriority.Highest;
this.GraphicsThread.Start();
//...
}
As you can see, there is a thread called GraphicsThread. This should redraw the current game state at the highest possible rate like this:
private void Draw() //and calculate
{
//... (Calculation of player positions occurs here)
for (int i = 0; i < Players.Count; i++)
{
System.Windows.Shapes.Ellipse PlayerEllipse = new System.Windows.Shapes.Ellipse();
//... (Modifying the ellipse)
Window.Dispatcher.Invoke(new Action(
delegate()
{
this.Canvas.Children.Add(PlayerEllipse);
}));
}
}
But although I have used a dispatcher which is invoked by the main window which is passed at the creation of the game instance, an unhandled exception occurs: [System.Reflection.TargetInvocationException], the inner exceptions says that I cannot access the object as it is owned by another thread (the main thread).
The Game is initialized in the Window_Loaded-event of the application:
GameInstance = new TeamBall.Game(this, PitchPanel);
I think this is the same principle as given in this answer.
So why does this not work? Does anybody know how to make calls to a control from another thread?
You cannot create a WPF object on a different thread - it must also be created on the Dispatcher thread.
This:
System.Windows.Shapes.Ellipse PlayerEllipse = new System.Windows.Shapes.Ellipse();
must go into the delegate.
Related
I'm trying to do what is said in the title of the question. I've looked on MSDN that if you used a thread to access the UI thread you need to use the Dispatch call. Anyways that hasn't solved my problem.
This is my code, what do I need to do to solve it? Thanks!:
static void example()
{
for (int i = 0; i < amountRows; i++)
{
RowDefinition rowD = new RowDefinition();
gridSizes.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() => { gridSizes.RowDefinitions.Add(rowD); }));
Console.WriteLine("added"); // never gets to here cuz the dispatcher produces an exception
}
}
Reproduce:
new Thread(() =>
{
example();
}).Start();
This code has been called from the public void of a Window from a WPF project, MainWindow --> Open new Window --> Thread --> Code --> Exception
I believe because RowDefinition is also a DependencyObject that you need to create it on the UI thread as well for it to work with your Grid. Moving RowDefinition rowD = new RowDefinition() into your Dispatcher.Invoke call should fix that.
Of course, in the above example that makes the second threat pointless, since it's doing nothing itself, but I'm assuming that in your full code you have the second thread actually doing something.
As #Keith Stein said, RowDefinition is a DependencyObject, so in order to call it from a thread Dispatcher.Invoke must be used.
In order to solve the issue I used the next block of code which Invokes the Dispatcher for all the code inside there.
App.Current.Dispatcher.Invoke((Action)delegate ()
{
//your code here
}
I have a usertaskpane in VSTO add-in. I'm adding there winformshost and elementhost to be able to use wpf controls inside usertaskpane.
I managed to add a main wpf control, but I am failing with adding child user control to that.
I have such method that initiates adding new wpf control:
private void MasterCheck()
{
this.pnlProgress.Visibility = System.Windows.Visibility.Visible;
//I'm using progress bar functionality in ReturnMasters method
Thread myNewThread = new Thread(() => Auditor.AuditMasterSlides(Globals.ThisAddIn.Application.ActivePresentation, this.pnlMaster, this, token));
token = new CancellationTokenSource();
myNewThread.Start();
this.pnlProgress.Visibility = System.Windows.Visibility.Collapsed;
}
public static void AuditMasterSlides(PPT.Presentation pres, Panel panel, MainProofingTaskPaneControl control, CancellationTokenSource cancToken)
{
IDictionary<string,MasterSlide> masterSlides = ReturnMasters(pres, cancToken, control);
control.ShowAndCollapse(panel);
control.RemovePanelChildren(panel);
if (masterSlides.Count>1)
{
//control.AddControlToPanel(panel, new MasterCheckControlOK());
}
else
{
control.AddControlToPanel(panel, new MasterCheckControlOK());
}
}
internal void RemovePanelChildren(Panel panel)
{
this.Dispatcher.Invoke(() =>
{
for (int i = panel.Children.Count - 1; i >= 0; i--)
{
panel.Children.RemoveAt(i);
}
});
}
internal void AddControlToPanel(Panel panel, Control control)
{
MasterCheckControlOK newControl = new MasterCheckControlOK();
this.Dispatcher.Invoke(() =>
{
panel.Children.Add(newControl);
});
}
And I'm getting error here:
public MasterCheckControlOK()
{
InitializeComponent();
}
How can I solve it to be able to:
use progress bar functionality (currently works)
add new wpf controls (does not work)
modify/remove controls (currently works)
You can only create UI controls on STA (single-threaded apartment) threads:
The calling thread must be STA, because many UI components require this
You can only access a control on the thread on which it was originally created. For example, you cannot create a control on a thread B and then try to add it to the Children collection of a control that was created on thread A.
So it makes no sense to create a control on a background thread if you intend to interact with it one way or another from the main thread. Then you will get this exception.
Bottom line: You should create all controls on the same thread and this thread should in most cases be the main UI/dispatcher thread. This will save you a whole lot of trouble.
When you create a control it has to happen in the main UI thread. Currently you are creating the control in another thread and then adding it to another. This will cause an exception.
You need to move the creation of the control to happen inside the invoke so it happens on the main UI thread.
You can't create UI controls in separate threads. The control needs to exist on the UI thread.
You might try having your threaded function do its work through your window's Dispatcher using its .Invoke() methods.
You probably want to make sure that ONLY the manipulation of your UI controls is done with the dispatcher otherwise you'll probably lock up the UI anyway.
public static void AuditMasterSlides(PPT.Presentation pres, Panel panel, MainProofingTaskPaneControl control, CancellationTokenSource cancToken)
{
IDictionary<string,MasterSlide> masterSlides = ReturnMasters(pres, cancToken, control);
this.Dispatcher.Invoke((() => control.ShowAndCollapse(panel));
...
}
As for the STA thread issue, you need to specify that your thread is an STA thread before you start it.
I did this by calling .SetApartmentState() on my thread:
thread1.SetApartmentState(ApartmentState.STA);
thread1.Start();
I have an image control in a Xaml file as follows:
<Viewbox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" VerticalAlignment="Top" HorizontalAlignment="Center">
<Image Name="Content"/>
</Viewbox>
I'd like to update this image with a different image every 10 seconds.
I create a system.threading.Timer instance, initialize it with a callback, pass in the UI control as a state object and set the interval to 10 seconds as follows:
contentTimer = new Timer(OnContentTimerElapsed, Content , 0 , (long) CONTENT_DISPLAY_TIME);
The callback looks as follows:
private void OnContentTimerElapsed( object sender )
{
Image content = (Image)sender;
//update the next content to be displayed
nCurrentImage = (nCurrentImage % NUM_IMAGES) + 1;
//get the url of the image file, and create bitmap from the jpeg
var path = System.IO.Path.Combine(Environment.CurrentDirectory, "../../DisplayContent/Image_" + nCurrentImage.ToString() + ".jpg");
Uri ContentURI = new Uri(path);
var bitmap = new BitmapImage(ContentURI);
//update the image control, by launching this code in the UI thread
content.Dispatcher.BeginInvoke(new Action(() => { content.Source = bitmap; }));
}
I still keep getting the following exception:
An unhandled exception of type 'System.InvalidOperationException' occurred in WindowsBase.dll
Additional information: The calling thread cannot access this object because a different thread owns it.
I was able to get a solution by updating just the numCurrentImage variable, and then updating the Content.Source in the MainWindow class in callbacks running on the UI thread, something as follows (note, I'm getting frames at 30fps from a kinect):
int nCurrentImage;
Public MainWindow()
{
InitializeComponent();
nCurrentImage = 1;
System.Timers.Timer contentTimer = new System.Timers.Timer(OnContentTimerElapsed, CONTENT_DISPLAY_TIME);
contentTimer.Elapsed += OnContentTimerElapsed;
...
//Some kinect related initializations
...
kinect.multiSourceReader.MultiSourceFrameArrived += OnMultiSourceFrameArrived;
}
private void OnContentTimerElapsed( object sender )
{
//update the next content to be displayed
nCurrentImage = (nCurrentImage % NUM_IMAGES) + 1;
}
private void OnMultiSourceFrameArrived(object sender, MultiSourceFrameArrivedEventArgs e)
{
UpdateContent(nCurrentImage);
}
private void UpdateContent(int numImage)
{
var path = System.IO.Path.Combine(Environment.CurrentDirectory, "../../DisplayContent/Image_" + numImage.ToString() + ".jpg");
Uri ContentURI = new Uri(path);
var bitmap = new BitmapImage(ContentURI);
Content.Source = bitmap;
}
Even though that works, it just doesn't make good programming sense to update it that way, since half of the work is being done by one thread, and the rest by the UI thread.
Any Ideas what I'm doing wrong?
Even though that works, it just doesn't make good programming sense to update it that way, since half of the work is being done by one thread, and the rest by the UI thread.
Actually this is exactly what you want to be doing. You want to be doing your non-UI work in a non-UI thread, and doing your UI work in a UI thread.
That said, while this is fundamentally what you want to be doing, you don't need to do all of this yourself so explicitly. You can simply use a DispatcherTimerand it will fire the callback in the UI thread, rather than a thread pool thread. It is, more or less, doing much of what you're doing manually.
Update the XAML image element, I like to name them all with an X to remind me it's a XAML element.
<Image Name="XContent"/>
When timer fires,
...
bitmap.Freeze();
XContent.Dispatcher.Invoke(()=>{
XContent.Source = bitmap;
}
I'm working on a project about PDF rendering in the C# language. I convert each page of PDF file to Image and Adds it to a ObservableCollection with a new thread by the below code:
ThreadStart myThreadDelegate = new ThreadStart(DoWork);
myThread = new Thread(myThreadDelegate);
myThread.SetApartmentState(ApartmentState.STA);
void DoWork()
{
for (int i = 0; i < pdfFile.Pages.Count; i++)
{
PdfPage page=pdfFile.LoadPage(i);
myObservableCollection[i]=page;
}
}
then pass the custom item of myObservableCollection to another UserControl for render it but I got an exception:
The calling thread cannot access this object because a different
thread owns it.
I know if I use UI thread my problem could be solved but I want load pdf pages in the background and user doesn't wait for loading all pages and this is possible with a new thread.
You can use threads but have to use the Dispatcher to access UI elements. Only the part, where you pass the item to the UserControl has to be done by the dispatcher.
Application.Current.Dispatcher.BeginInvoke(new Action(() => AddItem()));
BeginInvoke is a asynchronous call and won't block the execution of the following code.
Edit: I'm still not 100% sure if I unterstood the whole idea of your application but made a small sample which demonstrates how you can use threads and UI elements.
I made a Window (that would be your UserControl) which contains a Button and a ListBox. When clicking the Button a thread is started and processes some items. In my case it just adds some texts into a list, I added Thread.Sleep(1000) to simulate the processing of lots of stuff. When the text is prepared, it will be added to the ObservableCollection, which has to be done by the UI thread (Dispatcher). There is nothing blocking the UI but this adding and this is done very fast. You can also start multiple threads at the same time.
This is the code-behind of the Window (the Window itsself just contains a Button and a ListBox):
public partial class MainWindow : Window
{
private ObservableCollection<string> textList;
public MainWindow()
{
textList = new ObservableCollection<string>();
InitializeComponent();
btnStartWork.Click += BtnStartWorkClick;
lstTextList.ItemsSource = textList;
}
private void BtnStartWorkClick(object sender, RoutedEventArgs e)
{
Thread myThread;
ThreadStart myThreadDelegate = DoWork;
myThread = new Thread(myThreadDelegate);
myThread.SetApartmentState(ApartmentState.STA);
myThread.Start();
}
private void DoWork()
{
for (int i = 0; i < 5; i++)
{
string text = string.Format("Text {0}", i);
// block the thread (but not the UI)
Thread.Sleep(1000);
// use the dispatcher to add the item to the list, which will block the UI, but just for a very short time
Application.Current.Dispatcher.BeginInvoke(new Action(() => textList.Add(text)));
}
}
}
I've been struggling with this for quite a while:
I have a function designed to add control to a panel with cross-thread handling, the problem is that though the panel and the control are in "InvokeRequired=false" - I get an exception telling me that one of the controls inner controls are accessed from a thread other than the thread it was created on, the snippet goes like this:
public delegate void AddControlToPanelDlgt(Panel panel, Control ctrl);
public void AddControlToPanel(Panel panel, Control ctrl)
{
if (panel.InvokeRequired)
{
panel.Invoke(new AddControlToPanelDlgt(AddControlToPanel),panel,ctrl);
return;
}
if (ctrl.InvokeRequired)
{
ctrl.Invoke(new AddControlToPanelDlgt(AddControlToPanel),panel,ctrl);
return;
}
panel.Controls.Add(ctrl); //<-- here is where the exception is raised
}
the exception message goes like this:
"Cross-thread operation not valid: Control 'pnlFoo' accessed from a thread other than the thread it was created on"
('pnlFoo' is under ctrl.Controls)
How can I add ctrl to panel?!
When the code reaches the "panel.Controls.Add(ctrl);" line - both panel and ctrl "InvokeRequired" property is set to false, the problem is the the controls inside ctrl has "InvokeRequired" set to true. To clarify things: "panel" is created on the base thread and "ctrl" on the new thread, therefore, "panel" has to be invoked (causing "ctrl" to need invoke again). Once both of the invokes are done, it reaches the panel.Controls.Add(ctrl) command (both "panel" and "ctrl" doesn't need invocation in this state)
Here is a small snippet of the full program:
public class ucFoo : UserControl
{
private Panel pnlFoo = new Panel();
public ucFoo()
{
this.Controls.Add(pnlFoo);
}
}
public class ucFoo2 : UserControl
{
private Panel pnlFooContainer = new Panel();
public ucFoo2()
{
this.Controls.Add(pnlFooContainer);
Thread t = new Thread(new ThreadStart(AddFooControlToFooConatiner());
t.Start()
}
private AddFooControlToFooConatiner()
{
ucFoo foo = new ucFoo();
this.pnlFooContainer.Controls.Add(ucFoo); //<-- this is where the exception is raised
}
}
As an aside - to save yourself having to create countless delegate types:
if (panel.InvokeRequired)
{
panel.Invoke((MethodInvoker) delegate { AddControlToPanel(panel,ctrl); } );
return;
}
Additionally, this now does regular static checks on the inner call to AddControlToPanel, so you can't get it wrong.
'panel' and 'ctrl' must be created on the same thread, ie. you cannot have panel.InvokeRequired return different value than ctrl.InvokeRequired. That is if both panel and ctrl have the handles created or belong to a container with the handle created. From MSDN:
If the control's handle does not yet
exist, InvokeRequired searches up the
control's parent chain until it finds
a control or form that does have a
window handle. If no appropriate
handle can be found, the
InvokeRequired method returns false.
As it is right now your code is open to race conditions because the panel.InvokeNeeded can return false because the panel is not yet created, then ctrl.InvokeNeeded will certainly return false because most likely ctrl is not yet added to any container and then by the time you reach panel.Controls.Add the panel was created in the main thread, so the call will fail.
Where is pnlFoo being created, and in which thread? Do you know when its handle is being created? If it's being created in the original (non-UI) thread, that's the problem.
All control handles in the same window should be created and accessed on the same thread. At that point, you shouldn't need two checks for whether Invoke is required, because ctrl and panel should be using the same thread.
If this doesn't help, please provide a short but complete program to demonstrate the problem.
Here is a working piece of code :
public delegate void AddControlToPanelDlg(Panel p, Control c);
private void AddControlToPanel(Panel p, Control c)
{
p.Controls.Add(c);
}
private void AddNewContol(object state)
{
object[] param = (object[])state;
Panel p = (Panel)param[0];
Control c = (Control)param[1]
if (p.InvokeRequired)
{
p.Invoke(new AddControlToPanelDlg(AddControlToPanel), p, c);
}
else
{
AddControlToPanel(p, c);
}
}
And here is how I tested it. You need to have a form with 2 buttons and one flowLayoutPanel (I chose this so I didn't have to care about location hwhen dinamically adding controls in the panel)
private void button1_Click(object sender, EventArgs e)
{
AddNewContol(new object[]{flowLayoutPanel1, CreateButton(DateTime.Now.Ticks.ToString())});
}
private void button2_Click(object sender, EventArgs e)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(AddNewContol), new object[] { flowLayoutPanel1, CreateButton(DateTime.Now.Ticks.ToString()) });
}
I that he probem with your exaple is that when you get in the InvokeRequired branch you invoke the same function wou are in, resulting in a strange case of recurssion.
In your own answer you state:
To clarify things: "panel" is created on the base thread and "ctrl" on the new thread
I think this might be the cause of your problem. All UI elements should be created on the same thread (the base one). If you need to create "ctrl" as a consequence of some action in the new thread, then fire an event back to the base thread and do the creation there.
Lots of interesting answers here, but one key item for any multithreading in a Winform app is using the BackgroundWorker to initiate threads, and communicate back to the main Winform thread.
Here is a small snippet of the full program:
public class ucFoo : UserControl
{
private Panel pnlFoo = new Panel();
public ucFoo()
{
this.Controls.Add(pnlFoo);
}
}
public class ucFoo2 : UserControl
{
private Panel pnlFooContainer = new Panel();
public ucFoo2()
{
this.Controls.Add(pnlFooContainer);
Thread t = new Thread(new ThreadStart(AddFooControlToFooConatiner());
t.Start()
}
private AddFooControlToFooConatiner()
{
ucFoo foo = new ucFoo();
this.pnlFooContainer.Controls.Add(ucFoo); //<-- this is where the exception is raised
}
}