I'm new with C# and I want to show a loading window with an indeterminate progressbar during the execution of a Dispatcher.BeginInvoke, I try something like this:
progressbarwin = new Windows1();
Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.ContextIdle, (Action)delegate
{
//Do something
progressbarwin.Close();
});
progressbarwin.ShowDialog();
The window is showed (before the delegate) but the progressbar is blocked. What can I do?
#Sinatr
I tried to open the Windows from a new thread UI:
Thread loadT = new Thread(new ThreadStart(() =>
{
Loading progressbarwin = new Loading();
progressbarwin.ShowDialog();
}));
// Set the apartment state
loadT.SetApartmentState(ApartmentState.STA);
// Make the thread a background thread
loadT.IsBackground = true;
// Start the thread
loadT.Start();
//Do something
loadT.Abort();
but it gives me a cross thread exception when i create the window (new Loading())
#Mitra M
It's the same :(
Related
I'm trying to implement a task as described in the caption.
The HeaviWindow performs some heavy task and so not displayed until the task finished. This time I want to display a splash screen with animated loader.
Till now I used the following construction:
private RunHeavyTask()
{
ShowSplashScreen("Please wait...");
var dialog = new HeaviWindow();
dialog.ShowDialog();
}
private void ShowSplashScreen(string str)
{
Thread newWindowThread = new Thread(new ParameterizedThreadStart(ThreadStartingPoint));
newWindowThread.SetApartmentState(ApartmentState.STA);
newWindowThread.IsBackground = true;
newWindowThread.Start(str);
}
private void ThreadStartingPoint(object str)
{
splash = new WaitWindow((string)str);
splash.Show();
System.Windows.Threading.Dispatcher.Run();
}
But suddenly it turned out that while running the thread all other background threads stop to work.
I've tried to use Task for this purpose:
private void ShowSplashScreen(string str)
Task.Run(() =>
{
Application.Current.Dispatcher.Invoke(delegate
{
splash = new WaitWindow((string)str);
splash.ShowDialog();
});
});
}
But splash screen not displayed until the heavy dialog finish the task. The same result if I use BackgroundWorker.
So my question - how can I display a splash dialog while running a heavy task in another one. The splash dialog use some animation so it need to be updated.
Try this:
Window splash;
private void RunHeavyTask()
{
ShowSplashScreen("Please wait...");
//simulate "heavy task" by sleeping for 5 seconds
Thread.Sleep(5000);
//close the splash window
splash.Dispatcher.BeginInvoke(() => splash.Close());
}
private void ShowSplashScreen(string str)
{
Thread newWindowThread = new Thread(new ThreadStart(() =>
{
SynchronizationContext.SetSynchronizationContext(
new DispatcherSynchronizationContext(
Dispatcher.CurrentDispatcher));
//pass str to your custom Window here...
splash = new Window() { Content = new ProgressBar() { IsIndeterminate = true } };
splash.Closed += (s, e) =>
Dispatcher.CurrentDispatcher.BeginInvokeShutdown(DispatcherPriority.Background);
splash.Show();
Dispatcher.Run();
}));
newWindowThread.SetApartmentState(ApartmentState.STA);
newWindowThread.IsBackground = true;
newWindowThread.Start();
}
It will display a window with an indeterminate ProgressBar on a background thread while the thread that calls RunHeavyTask() is blocked.
It's not very clear what exactly you are trying to do. More details would be nice as it seems that your general approach needs to be optimized.
Generally you don't start the Window in a separate thread to execute heavy work (CPU bound) on the UI thread. Rather would you execute the CPU intensive task on a background thread. You can wait for this task asynchronously and then close the splash screen afterwards when the task has completed.
The following simple example shows the pattern to be used to execute the CPU bound work asynchronously on a background thread to keep the UI thread responsive, while using IProgress<T> to access the UI thread from the background thread using a delegate registered with an instance of Progress<T>.
The example assumes that the splash screen is showing some progress and for this purpose exposes a Progress property:
// Show a splash screen,
// while executing CPU bound work asynchronously on a background thread
private async Task RunHeavyTaskAsync()
{
splashScreen = new Window();
splashScreen.Show();
// Use IProgress<T> to access the UI thread via a delegate,
// which is registered using the constructor of Progress<T>
var progressReporter = new Progress<double>(value => splashScreen.Progress = value);
// Pass the IProgress<T> instance to the background thread
// and wait non-blocking (asynchronously) for the thread to complete
await Task.Run(() => DoWork(progressReporter));
// Close splash screen after 5s heavy work
splashScreen.Close();
}
// Will be executed on a background thread
private void DoWork(IProgress<double> progressReporter)
{
// Post progress from background thread (current context)
// to the UI thread (splash screen).
// IProgress<T>.Report() will execute the previously registered delegate
progressReporter.Report(50);
// Do 5s heavy work
Thread.Sleep(5000);
progressReporter.Report(100);
}
I have a c++ application and use clr to call the below c# method. Everything is going good except for one thing. As wpf window needs STA thread, I'm trying to create a new thread with STA state and start it. This makes the wpf window modeless, even if the window is started with show dialog. So I tried using thread.join() to make the caller thread to wait until the thread completes or window closes. using thread.join() shows server busy window after a few seconds. (I'm not doing any operation related to the internet in my application).
How to make the caller thread to wait until the window closes? or How to get rid of Server busy window?
void ShowWindow()
{
_ownerHandle = Process.GetCurrentProcess().MainWindowHandle;
Thread newWindowThread = new Thread(new ThreadStart(() =>
{
MyWpfWindow window = new MyWpfWindow();
MyWpfWindowViewModel vm = new MyWpfWindowViewModel();
WindowInteropHelper helper = new WindowInteropHelper(window);
helper.Owner = _ownerHandle;
window.DataContext = vm;
window.ShowDialog();
}));
// set the apartment state this will only invoke the WPF window
newWindowThread.SetApartmentState(ApartmentState.STA);
newWindowThread.IsBackground = true;
// start the thread
newWindowThread.Start();
//waits this thread here untill the newWindowThread execution completes.
newWindowThread.Join();
}
Also, I tried with a while loop as below to block the current main thread.
while(!windowShown)
{
}
in the place of newWindowThread.join(). After using this, my window is not visible.
I'm getting a server busy window as below
Calling Join() blocks the current thread. If you want to wait asynchronously, you could use a SemaphoreSlim:
static async Task ShowWindowAsync()
{
SemaphoreSlim semaphore = new SemaphoreSlim(0, 1);
_ownerHandle = Process.GetCurrentProcess().MainWindowHandle;
Thread newWindowThread = new Thread(new ThreadStart(() =>
{
MyWpfWindow window = new MyWpfWindow();
MyWpfWindowViewModel vm = new MyWpfWindowViewModel();
WindowInteropHelper helper = new WindowInteropHelper(window);
helper.Owner = _ownerHandle;
window.DataContext = vm;
window.Closed += (s, e) =>
{
semaphore.Release();
semaphore.Dispose();
};
window.ShowDialog();
}));
// set the apartment state this will only invoke the WPF window
newWindowThread.SetApartmentState(ApartmentState.STA);
newWindowThread.IsBackground = true;
// start the thread
newWindowThread.Start();
//waits this thread here untill the newWindowThread execution completes.
await semaphore.WaitAsync();
}
I have following Code:
Thread thread = new Thread(new ThreadStart(CreateSplashScrn));
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();
OpenSplashScrn();
ChangeSplashScrnMessageText("String");
public void CreateSplashScrn()
{
splash = new SplashScreen(this);
System.Windows.Threading.Dispatcher.Run();
}
public void OpenSplashScrn()
{
splash.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
new Action(() => { splash.Show(); }));
}
public void ChangeSplashScrnMessageText(string messageText)
{
splash.messageLabel.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
new Action(() => { splash.messageLabel.Content = messageText; }));
}
However, this returns a Null Reference Exception at OpenSplashScrn().
How can I open this in another thread and change the Label content?
Is this possible over a Task?
You should NOT open splach screen in background thread and perform the long running initialization in UI thread.
You should open splash screen in UI thread and perform the long running initialization in non UI thread.
var splash = new SplashScreen(this);
splash.Show();
Thread thread = new Thread(new ThreadStart(Initialize));
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();
public void Initialize()
{
//move your long running logic from your app here..
ChangeSplashScrnMessageText("Initialization Started");
Thread.Sleep(1000);
ChangeSplashScrnMessageText("Initialize finished");
}
public void ChangeSplashScrnMessageText(string messageText)
{
splash.messageLabel.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
new Action(() => { splash.messageLabel.Content = messageText; }));
}
EDIT: Why you should not open Splash Screen in another thread?
Because it complicates things a log and in 99% there is no reason to do so. You can have multiple windows running in single thread and still perform some long running task in the background.
I guess, that in your main window you are trying to perform the long running task in UI thread. Just move it to background thread...
This is a function that calls a custom C# message box. The function is called from a simulation process that runs inside a thread that is not the UI thread.
public Output.ButtonResult msgboxYesNo(string Text, string title) {
Output.Message_Box msg = new Output.Message_Box();
msg.Dispatcher.Invoke(new Action(() => {
msg.seeQuestion(Text, title);
msg.Topmost = true;
Application.Current.MainWindow.Dispatcher.Invoke(new Action(()
=> { msg.Owner = Application.Current.MainWindow; }));
msg.ShowDialog();
}));
return msg.result;
}
The problematic line is this one:
Application.Current.MainWindow.Dispatcher.Invoke(new Action(() =>
{ msg.Owner = Application.Current.MainWindow; }));
And it throws this:
The calling thread cannot access this object because a different
thread owns it
Because I want to set the main window as the owner of the custom message box, which is called within a separated thread.
How can I set the main form as the owner of the message box?
(I hope I explained the issue clear enough, the forms are WPF)
Output.Message_Box is an UI component so it should be created from UI thread only and not from background thread.
Problem in your code -
msg is created on background thread but you are trying to access it from UI thread here
msg.Owner = Application.Current.MainWindow;
Instead you should even create message box on UI thread only:
Application.Current.Dispatcher.Invoke(new Action(() =>
{
Output.Message_Box msg = new Output.Message_Box();
msg.seeQuestion(Text, title);
msg.Topmost = true;
msg.Owner = Application.Current.MainWindow;
msg.ShowDialog();
return msg.result;
}));
Also you are trying to access MainWindow from background thread which is created on UI thread.
You can get UI dispatcher like this if app launched from main thread only: Application.Current.Dispatcher.
I have a background thread that generates a series of BitmapImage objects. Each time the background thread finishes generating a bitmap, I would like to show this bitmap to the user. The problem is figuring out how to pass the BitmapImage from the background thread to the UI thread.
This is an MVVM project, so my view has an Image element:
<Image Source="{Binding GeneratedImage}" />
My view-model has a property GeneratedImage:
private BitmapImage _generatedImage;
public BitmapImage GeneratedImage
{
get { return _generatedImage; }
set
{
if (value == _generatedImage) return;
_generatedImage= value;
RaisePropertyChanged("GeneratedImage");
}
}
My view-model also has the code that creates the background thread:
public void InitiateGenerateImages(List<Coordinate> coordinates)
{
ThreadStart generatorThreadStarter = delegate { GenerateImages(coordinates); };
var generatorThread = new Thread(generatorThreadStarter);
generatorThread.ApartmentState = ApartmentState.STA;
generatorThread.IsBackground = true;
generatorThread.Start();
}
private void GenerateImages(List<Coordinate> coordinates)
{
foreach (var coordinate in coordinates)
{
var backgroundThreadImage = GenerateImage(coordinate);
// I'm stuck here...how do I pass this to the UI thread?
}
}
I'd like to somehow pass backgroundThreadImage to the UI thread, where it will become uiThreadImage, then set GeneratedImage = uiThreadImage so the view can update. I've looked at some examples dealing with the WPF Dispatcher, but I can't seem to come up with an example that addresses this issue. Please advise.
The following uses the dispatcher to execute an Action delegate on the UI thread. This uses a synchronous model, the alternate Dispatcher.BeginInvoke will execute the delegate asynchronously.
var backgroundThreadImage = GenerateImage(coordinate);
GeneratedImage.Dispatcher.Invoke(
DispatcherPriority.Normal,
new Action(() =>
{
GeneratedImage = backgroundThreadImage;
}));
UPDATE
As discussed in the comments, the above alone will not work as the BitmapImage is not being created on the UI thread. If you have no intention of modifying the image once you have created it you can freeze it using Freezable.Freeze and then assign to GeneratedImage in the dispatcher delegate (the BitmapImage becomes read-only and thus threadsafe as a result of the Freeze). The other option would be to load the image into a MemoryStream on the background thread and then create the BitmapImage on the UI thread in the dispatcher delegate with that stream and the StreamSource property of BitmapImage.
You need to do two things:
Freeze your BitmapImage so it can be moved to the UI thread, then
Use the Dispatcher to transition to the UI thread to set the GeneratedImage
You'll need to access the UI thread's Dispatcher from the generator thread. The most flexible way to do this is to capturing the the value Dispatcher.CurrentDispatcher in the main thread and passing it into the generator thread:
public void InitiateGenerateImages(List<Coordinate> coordinates)
{
var dispatcher = Dispatcher.CurrentDispatcher;
var generatorThreadStarter = new ThreadStart(() =>
GenerateImages(coordinates, dispatcher));
...
If you know you will only use this within a running Application and that the application will only have one UI thread, you can just call Application.Current.Dispatcher to get the current dispatcher. The disadvantages are:
You loose the ability to use your view model independently of a constructed Application object.
You can only have one UI thread in your application.
In the generator thread, add a call to Freeze after the image is generated, then use the Dispatcher to transition to the UI thread to set the image:
var backgroundThreadImage = GenerateImage(coordinate);
backgroundThreadImage.Freeze();
dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
{
GeneratedImage = backgroundThreadImage;
}));
Clarification
In the above code it is critical that Dispatcher.CurrentDispatcher be accessed from the UI thread, not from the generator thread. Every thread has its own Dispatcher. If you call Dispatcher.CurrentDispatcher from the generator thread you will get its Dispatcher instead of the one you want.
In other words, you must do this:
var dispatcher = Dispatcher.CurrentDispatcher;
var generatorThreadStarter = new ThreadStart(() =>
GenerateImages(coordinates, dispatcher));
and not this:
var generatorThreadStarter = new ThreadStart(() =>
GenerateImages(coordinates, Dispatcher.CurrentDispatcher));
In the background thread work with Streams.
For example, in the background thread:
var artUri = new Uri("MyProject;component/../Images/artwork.placeholder.png", UriKind.Relative);
StreamResourceInfo albumArtPlaceholder = Application.GetResourceStream(artUri);
var _defaultArtPlaceholderStream = albumArtPlaceholder.Stream;
SendStreamToDispatcher(_defaultArtPlaceholderStream);
In the UI thread:
void SendStreamToDispatcher(Stream imgStream)
{
dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
{
var imageToDisplay = new BitmapImage();
imageToDisplay.SetSource(imgStream);
//Use you bitmap image obtained from a background thread as you wish!
}));
}