I created new Window on a different thread because it has heavy UI operations, that was done to keep my main window run smoothly. Everything works perfectly. But here is the question:
How I can access newly created window?
After calling Dispatcher.Run() I can not manipulate visualisationWindow anymore. I want to keep access to that newly created window object.
Here is how my window created:
private void CreateVisualisationWindow()
{
Thread VisualisationWIndowThread = new Thread(new ThreadStart(ThreadStartingPoint));
VisualisationWIndowThread.SetApartmentState(ApartmentState.STA);
VisualisationWIndowThread.IsBackground = true;
VisualisationWIndowThread.Start();
}
private void ThreadStartingPoint()
{
Visualisation visualisationWindow = new Visualisation();
visualisationWindow.Show();
System.Windows.Threading.Dispatcher.Run();
}
Also I tried accessing it through System.Windows.Threading.Dispatcher.FromThread(VisualisationWIndowThread) but seems I misunderstand some core things.
I simulated your issue using two WPF Window objects and a timer to ensure that the Second Window was created before calling operations on it. Below is my code sample and it updates the second Windows TextBox every five seconds:
private Timer _timer;
private SecondWindow _secondWindow;
public MainWindow()
{
InitializeComponent();
CreateVisualisationWindow();
_timer = new Timer(Callback);
_timer.Change(5000, 5000);
}
private void Callback(object state)
{
UpdateSecondWindowText();
}
private void CreateVisualisationWindow()
{
Thread VisualisationWIndowThread = new Thread(ThreadStartingPoint);
VisualisationWIndowThread.SetApartmentState(ApartmentState.STA);
VisualisationWIndowThread.IsBackground = true;
VisualisationWIndowThread.Start();
}
private void ThreadStartingPoint()
{
_secondWindow = new SecondWindow();
_secondWindow.SecondWindowTextBlock.Text = "Hello";
_secondWindow.Show();
Dispatcher.Run();
}
private void UpdateSecondWindowText()
{
_secondWindow.Dispatcher.BeginInvoke(new Action(() =>
{
_secondWindow.SecondWindowTextBlock.Text = _secondWindow.SecondWindowTextBlock.Text + " World";
}));
}
So the trick is, you need to call the Dispatcher on the second Window in order to gain access to it.
Related
Info: I'm creating game using C# in Visual Studio 2017
How can I stop music thread? Is it possible even from different form?
I used this code to create thread which plays music in background
MediaPlayer bg;
public void main()
{
IntializeComponent();
Bg_music();
}
private void Bg_music()
{
new System.Threading.Thread(() =>
{
bg = new System.Windows.Media.MediaPlayer();
bg.Open(new System.Uri(path + "Foniqz_-_Spectrum_Subdiffusion_Mix_real.wav"));
bg.Play();
}).Start();
}
When I try to stop the thread using this code, it stops window which is currently open and music/thread keeps playing music
bg.Dispatcher.Invoke(() =>
{
bg.Close();
});
also this didn't work
bg.Dispatcher.Invoke(() =>
{
bg.Stop();
});
Assuming you really need a background thread (because the MediaPlayer it's non-blocking on WPF) you may want to use one of the following paths in C#:
Use Cancelation Token & Tasks:
MediaPlayer bg;
readonly CancellationTokenSource tokenSource = new CancellationTokenSource();
public MainWindow()
{
InitializeComponent();
Bg_music();
}
private void Bg_music()
{
Task.Run(() =>
{
bg = new MediaPlayer();
bg.Open(new Uri(#"D:\Songs\201145-Made_In_England__Elton_John__320.mp3"));
bg.Play();
bg.Play();
while (true)
{
if (tokenSource.Token.IsCancellationRequested)
{
bg.Stop();
break;
}
}
}, tokenSource.Token);
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
tokenSource.Cancel();
}
}
or
Use Events to communicate through Tasks. (Stop using threads, we have tasks now)
Cross-thread object access might be tricky.
Once you create MediaPlayer instance in another thread other than the UI thread, accessing this object inside the UI thread will throw InvalidOperationException since the object doesn't belong to UI thread.
private void Bg_music()
{
bg = new System.Windows.Media.MediaPlayer();
new System.Threading.Thread(() =>
{
bg.Dispatcher.Invoke(()=>{
bg.Open(new System.Uri(path + "Foniqz_-_Spectrum_Subdiffusion_Mix_real.wav"));
bg.Play();
});
}).Start();
}
Now you don't have to use Dispatcher to stop the MediaPlayer when calling it inside the UI thread.
Edit: Even if the implemented method is not the best practice, still worth to be answered to advert some theorical information.
I have a Main form, which is running a synchronous operation(thus freezing the form).
Before that starts to happen I call my function showWaitWindow().
private void showWaitWindow()
{
Wait x = new Wait();
x.Show(this); //"this" is allowing the form to later centralize itself to the parent
}
This is where it is exactly happening:
if (result)
{
System.Threading.Thread t = new System.Threading.Thread(new
System.Threading.ThreadStart(showWaitWindow));
t.Start();
}
else
{
return;
}
propertyGrid1.SelectedObject = z.bg_getAllPlugins(); //Heavy synchronous call
//This should be closing the form, which is not happening.
for (int index = Application.OpenForms.Count; index >= 0; index--)
{
if (Application.OpenForms[index].Name == "Wait")
{
MessageBox.Show("found");
Application.OpenForms[index].Close();
}
}
I've tried this without threading as well, which didn't work as well. Also, because it's trying to centralize to the parent, while being created in another thread, it throws an exception "tried to access in different thread that it was created in" rephrasing.
How do I approach that?
I would suggest using a BackgroundWorker -- available in the WinForms toolbox.
public partial class Form1 : Form
{
private BackgroundWorker backgroundWorker1;
public Form1()
{
InitializeComponent();
this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker();
this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(this.backgroundWorker1_DoWork);
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
//perform lengthy operation in here.
}
}
I am running a test unit (and learning about them). Quite simply, my unit creates a List and passes it to my MainWindow.
The issue I have is after I show() the main window the unit method ends. I want the unit to not finish until I close the MainWindow. This is what I've done (see below) - it obviously doesn't work and feels like I'm on the wrong path here. How can I do this properly?
[TestClass]
public class Logging
{
bool continueOn = true;
[TestMethod]
public void ShowLogs()
{
ShowResults(createLogList());
}
private void ShowResults(List<Log> logList)
{
MainWindow mw = new MainWindow(logList);
mw.Closed += mw_Closed;
mw.Show();
while (continueOn)
{ }
}
void mw_Closed(object sender, EventArgs e)
{
this.continueOn = false;
}
private List<Log> createLogList()
{
List<Log> listLog = new List<Log>();
//logic
return listLog;
}
Maybe I have to put this onto a background worker thread and monitor that - to be honest I've no idea and before I waste hours, I'd appreciate some guidance.
The WPF Window must be created and shown on a thread which supports the WPF window infrastructure (message pumping).
[TestMethod]
public void TestMethod1()
{
MainWindow window = null;
// The dispatcher thread
var t = new Thread(() =>
{
window = new MainWindow();
// Initiates the dispatcher thread shutdown when the window closes
window.Closed += (s, e) => window.Dispatcher.InvokeShutdown();
window.Show();
// Makes the thread support message pumping
System.Windows.Threading.Dispatcher.Run();
});
// Configure the thread
t.SetApartmentState(ApartmentState.STA);
t.Start();
t.Join();
}
Note that:
The window must be created and shown inside the new thread.
You must initiate a dispatcher (System.Windows.Threading.Dispatcher.Run()) before the ThreadStart returns, otherwise the window will show and die soon after.
The thread must be configured to run in STA apartment.
For more information, visit this link.
Of course, since it is only for testing, using
ShowDialog()
may be an option instead of 'Show()'
My application fetches data from a live feed, processes it and displays the results. This data is updated every 5 seconds. In the Load event of Main form I've created a thread to show the splash screen which is shown until the first data cycle is run .
The data fetching and processing thread (RecieverThread) calls RecieveFeed. The isue I'm facing is that form2 which displays data fetched in RecieveFeed is shown before the first cycle is run completely. How do I ensure that form2 is loaded only after the first cycle has completed fetching data.
Code in the Main form:
private void frmMain_Load(object sender, EventArgs e)
{
Hide();
// Create a new thread from which to start the splash screen form
Thread splashThread = new Thread(new ThreadStart(StartSplash));
splashThread.Start();
//Thread to call the live feed engine. This thread will run for the duration of application
ReceiverThread = new System.Threading.Thread(new System.Threading.ThreadStart(ReceiveFeed));
ReceiverThread.Start();
frmSecondForm form2 = new frmSecondForm();
form2.MdiParent = this;
form2.WindowState = FormWindowState.Maximized;
Show();
form2.Show();
}
public frmRaceRace()
{
InitializeComponent();
this.splash = new SplashScreen();
}
private void StartSplash()
{
splash.Show();
while (!done)
{
Application.DoEvents();
}
splash.Close();
this.splash.Dispose();
}
private void ReceiveFeed()
{
while (!StopReceivingData)
{
foreach (...)
{
//Fetches data from live engine
DLLImportClass.GetData1();
//Manipulates and stores the data fetched in datatables
ThreadPool.QueueUserWorkItem(new WaitCallback(delegate { StoreData(); }))
rowsProcessed++;
if (!done)
{
this.splash.UpdateProgress(100 * rowsProcessed / totalRows);
}
}
done = true;
Thread.Sleep(5000);
}
}
I think what you need to use here is System.Threading.AutoResetEvent. Basically, add a member of this to your form class:
private AutoResetEvent waitEvent_ = new AutoResetEvent(false); // create unininitialized
After showing your splash, you want to wait for this event to be signalled:
private void StartSplash()
{
splash.Show();
// this will time out after 10 seconds. Use WaitOne() to wait indefinitely.
if(waitEvent_.WaitOne(10000))
{
// WaitOne() returns true if the event was signalled.
}
} // eo StartSplash
Finally, in your processing function, when you're done, simply call:
waitEvent_.Set();
Looks like you've got some race conditions in your code.
When doing threading with WinForms and most (if not all) UI frameworks, you can ONLY access the UI objects (forms and controls) from a single thread.
All other threads can only access that thread using .InvokeRequired() and .BeginInvoke(). These calls can be used to run a delegate in the UI thread. See:
{REDACTED: StackOverflow will only allow me to post 1 hyperlink. Google these}
There is a builtin shortcut for this in the BackgroundWorker class.
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
Simply do this (Psuedocode):
public void StartSplash()
{
Splash.Show();
BackgroundWorker bgw = new BackgroundWorker();
// set up bgw Delegates
bgw.RunWorkerAsync();
}
public void bgw_DoWork( ... etc
{
// do stuff in background thread
// you cannot touch the UI from here
}
public void bgw_RunWorkerCompleted( ... etc
{
Splash.close();
// read data from background thread
this.show(); // and other stuff
}
Now, you're guaranteed not to close the SplashScreen and not to start the main window before your data is delivered.
Other considerations: you'll probably need to use locks to secure the data you might access in the background thread. You should never access data in more than 1 thread without locking it.
Change your frmMain_Load to this:
private void frmMain_Load(object sender, EventArgs e)
{
Hide();
//Thread to call the live feed engine. This thread will run for the duration of application
ReceiverThread = new System.Threading.Thread(new System.Threading.ThreadStart(ReceiveFeed));
ReceiverThread.Start();
frmSecondForm form2 = new frmSecondForm();
form2.MdiParent = this;
form2.WindowState = FormWindowState.Maximized;
StartSplash();
Show();
form2.Show();
}
I want to learn more about threading and created a little test application that change the backcolor of a label.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
//lblColor
public Color theLabel
{
get { return this.lblColor.BackColor; }
set { this.lblColor.BackColor = value; }
}
//btnStart
private void btnStart_Click(object sender, EventArgs e)
{
ThreadTest cColor = new ThreadTest();
Thread tColor = new Thread(new ThreadStart(cColor.ChangeColor));
tColor.Start();
}
}
And...
public class ThreadTest
{
public void ChangeColor()
{
Form1 foo = new Form1();
while (true)
{
foo.theLabel = Color.Aqua;
foo.theLabel = Color.Black;
foo.theLabel = Color.DarkKhaki;
foo.theLabel = Color.Green;
}
}
}
The only problem is why can't i make this code work? I can see that the code in ChangeColor runs but the color of the label don't change.
At first glance, you are constructing a new form
Form1 foo = new Form1();
inside ThreadTest and never displaying the form, my guess is you intended to change the form color on the form with btnStart? You have two options, either pass in the form in to a ParameterizedThreadStart or re-write the code to just operate on the existing form.
Also, based on the code written, you will likely need to use Invoke to update the state of the form as you cannot have worker threads update the UI. I will tweak your code and posted a revised example if someone doesn't beat me to it.
Edit
In this case you don't need the invoke... but here is what I think you were intending...
private void btnStart_Click(object sender, EventArgs e)
{
ThreadTest cColor = new ThreadTest();
Thread tColor = new Thread(new ParameterizedThreadStart(cColor.ChangeColor));
tColor.Start(this);
}
public class ThreadTest
{
public void ChangeColor(Object state)
{
Form1 foo = (Form1) state;
while (true)
{
foo.theLabel = Color.Aqua;
foo.theLabel = Color.Black;
foo.theLabel = Color.DarkKhaki;
foo.theLabel = Color.Green;
}
}
}
Also, it is important to set worker threads as background threads, otherwise when you close the form, the thread will keep that appliaction open.
tColor.IsBackground = true;
Additional Example
A slightly different example would be to have multiple threads trying to update the same value and see how they are interleaved... simple snippet to get the ball rolling.
private void btnStart_Click(object sender, EventArgs e)
{
CreateBackgroundColorSetter(Color.Aqua);
CreateBackgroundColorSetter(Color.Black);
CreateBackgroundColorSetter(Color.DarkKhaki);
CreateBackgroundColorSetter(Color.Green);
}
private void CreateBackgroundColorSetter(Color color)
{
var thread = new Thread(() =>
{
while (true)
{
theLabel = color;
Thread.Sleep(100);
}
});
thread.IsBackground = true;
thread.Start();
}
you have 2 issues
a) you are not supposed to update the UI from other threads
b) even if you were allowed to do it you need to tell the UI to repaint
These are non trivial issues
If you are just experimenting with threading then use Debug.WriteLine to see what the background threads are doing
If you actually need to update the UI in the background then lookup BeginInvoke and InvokeNeeded
If you really need to do threading in a GUI app you are going to need BackgroundWorker or something like it. As noted, other threads cannot update the GUI anyway so the sample shown there is a better model for moving long-running work items out of your main thread.
The BackgroundWorker class allows you
to run an operation on a separate,
dedicated thread. Time-consuming
operations like downloads and database
transactions can cause your user
interface (UI) to seem as though it
has stopped responding while they are
running. When you want a responsive UI
and you are faced with long delays
associated with such operations, the
BackgroundWorker class provides a
convenient solution.
If you want do experiments with threading just create a console application and google "threading tutorial c#" to get some example.
Regarding threading and winform you can have a look at the below link just to have an idea about the consideration you should make before access win form property from another thread
In WinForms, why can't you update UI controls from other threads?