I have a Task that update UI in a window. But I don't understand behavior of Thread in WinForm and WPF.
In MainWindow I have two buttons, one that starts Task and one that opens a new window.
private void btnOpenWindow_Click(object sender, RoutedEventArgs e)
{
var window = new Window();
OpenedWindow = window;
window.ShowDialog();
}
private void btnRunTask_Click(object sender, RoutedEventArgs e)
{
Task.Run(() =>
{
//If I call UpdateOpenedWindow() with Dispatcher, CheckAccess() is true
//this.Dispatcher.Invoke(() => UpdateOpendedWindow());
UpdateOpenedWindow();
});
}
private void UpdateOpenedWindow()
{
if (!OpenedWindow.Dispatcher.CheckAccess())
{
//Update UI like this really visual update UI
OpenedWindow.Dispatcher.Invoke(() => OpenedWindow.IsEnabled = false);
Thread.Sleep(1000);
OpenedWindow.Dispatcher.Invoke(() => OpenedWindow.IsEnabled = true);
//But if I use only one Invoke block, UI not visual update.. Why? In WinForm this work with no problem
//OpenedWindow.Dispatcher.Invoke(() =>
//{
// OpenedWindow.IsEnabled = false
// Thread.Sleep(1000);
// OpenedWindow.IsEnabled = true
//});
}
else
{
//This not work in WPF. Window seems to stay in enabled state ever
OpenedWindow.IsEnabled = false;
Thread.Sleep(1000);
OpenedWindow.IsEnabled = true;
}
}
In WinForm all comment statements work well and update UI. In WPF not happens. And I would like to understand why and why setting IsEnabled to false and then to true without invoke, task, ecc..., not really update UI in WPF. Thanks
Related
private void StartDate_LostFocus(object sender, RoutedEventArgs e)
{
if (!validate())
{
Dispatcher.BeginInvoke(
DispatcherPriority.ContextIdle,
new Action(delegate()
{
StartDate.Focus();
})
});
}
I'm validating a date in the lost focus of a textbox of a WPF application. Currently, I do some date validation - if it fails validation I reset the focus to the textbox. Is this the correct approach?
It seems to be working fine, I'm hoping I'm not creating any problems or memory leaks with the BeginInvoke.
Thanks in advance
As Adi suggested, please consider using more common approach to validation. However, if the application you're working on is something just for fun or just to get to know how WPF works, please consider:
validating asynchronously
interacting with UI synchronously in the UI thread
Also, validate before losing the focus (TextChanged might be an option). Something like this:
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
TextBox validatedTextbox = sender as TextBox;
validatedTextbox.IsEnabled = false;
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += (s, ea) => { Thread.Sleep(1000); }; // just to simulate really long validation
worker.RunWorkerCompleted += (s, ea) =>
{
if (validatedTextbox.Text.Contains('a'))
{
validatedTextbox.Background = new SolidColorBrush(Colors.Red);
}
else
{
validatedTextbox.Background = new SolidColorBrush(Colors.Green);
}
validatedTextbox.IsEnabled = true;
};
worker.RunWorkerAsync();
}
My WPF application has a window load animation which I created using Blend. The actual animation works fine, but if I add logic to my window load event (using C#) the animation skips to end when the window finally renders.
My initial plan was to use Threading to solve this, but this too didn't work:
private void MyWindow_Loaded(object sender, RoutedEventArgs e)
{
dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
dispatcherTimer.Interval = new TimeSpan(0, 0, 30);
dispatcherTimer.Start();
lstRecipients.Visibility = Visibility.Hidden;
windowAdorner = new TransparentAdorner(BorderGrid);
if (!StaticHelpers.AWSConfigurationExists())
{
this.IsEnabled = false;
GettingStarted gettingStarted = new GettingStarted(this);
gettingStarted.Owner = this;
gettingStarted.ShowDialog();
this.IsEnabled = true;
}
else
{
Task SetAWSLabelsTask = new Task(new Action(() => SetAWSLabels()));
SetAWSLabelsTask.Start();
}
Task bounceHandler = new Task(new Action(() => processBounce()));
bounceHandler.Start();
//processBounce();
Task unSubscribeHandler = new Task(new Action(() => handleUnsubscriptions()));
unSubscribeHandler.Start();
}
I'm assuming the system is so busy creating the threads, and the creation is handled by the UI thread, that the animation has already finished by the time the Window is rendered.
What I'm missing is a good way to coordinate the animation, so that any business logic I have in MyWindow_Loaded occurs only after the animation has finished loading.
Is this possible?
EDIT: I also tried a thread sleep, and this too didn't work.
Ok Solved the problem.
In the XAML added a new event handler to the storyboard:
<Storyboard x:Key="SESLogoLoad" Completed="StoryCompleted">
Then created a method manually using C#:
private void StoryCompleted(object sender, EventArgs e)
{
//Windows onload stuff goes here...
}
I have a WPF window with a button that spawns a BackgroundWorker thread to create and send an email. While this BackgroundWorker is running, I want to display a user control that displays some message followed by an animated "...". That animation is run by a timer inside the user control.
Even though my mail sending code is on a BackgroundWorker, the timer in the user control never gets called (well, it does but only when the Backgroundworker is finished, which kinda defeats the purpose...).
Relevant code in the WPF window:
private void button_Send_Click(object sender, RoutedEventArgs e)
{
busyLabel.Show(); // this should start the animation timer inside the user control
BackgroundWorker worker = new BackgroundWorker();
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerAsync();
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
this.Dispatcher.Invoke((Action)(() =>
{
string body = textBox_Details.Text;
body += "User-added addtional information:" + textBox_AdditionalInfo.Text;
var smtp = new SmtpClient
{
...
};
using (var message = new MailMessage(fromAddress, toAddress)
{
Subject = subject,
Body = body
})
{
smtp.Send(message);
}
}));
}
Relevant code in the user control ("BusyLabel"):
public void Show()
{
tb_Message.Text = Message;
mTimer = new System.Timers.Timer();
mTimer.Interval = Interval;
mTimer.Elapsed += new ElapsedEventHandler(mTimer_Elapsed);
mTimer.Start();
}
void mTimer_Elapsed(object sender, ElapsedEventArgs e)
{
this.Dispatcher.Invoke((Action)(() =>
{
int numPeriods = tb_Message.Text.Count(f => f == '.');
if (numPeriods >= NumPeriods)
{
tb_Message.Text = Message;
}
else
{
tb_Message.Text += '.';
}
}));
}
public void Hide()
{
mTimer.Stop();
}
Any ideas why it's locking up?
Using Dispatcher.Invoke in your worker_DoWork method is putting execution back on the UI thread, so you are not really doing the work asynchronously.
You should be able to just remove that, based on the code you are showing.
If there are result values that you need to show after the work is complete, put it in the DoWorkEventArgs and you will be able to access it (on the UI thread) in the worker_RunWorkerCompleted handler's event args.
A primary reason for using BackgroundWorker is that the marshalling is handled under the covers, so you shouldn't have to use Dispatcher.Invoke.
I have a BusyIndicator from the wpf extended toolkit and I'm running a function that takes a while to complete. If I run the time consuming task in a separate thread, I get a NotSupportedException because I'm attemping to insert objects into an ObservableCollection from that different thread. I don't really want to spend a lot of time refactoring the code, if possible... Is there a way that I can set the visibility of the indicator in a separate thread instead?
EDIT
ThreadStart start = delegate()
{
System.Windows.Application.Current.Dispatcher.Invoke((Action)(() =>
{
IsBusy = true;
}));
};
new Thread(start).Start();
longRunningFunction();
This did not work for me either.
You should be able to use the Dispatcher for things like that. e.g.
Application.Current.Dispatcher.Invoke((Action)(() =>
{
_indicator.Visibility = Visibility.Visible;
}));
This will cause the code to be run on the UI-Thread.
There is more info (including how to "properly" do this, with CheckAccess and such) on it in the threading model reference.
You cannot access UI controls from a background worker. What you normally do is set the IsBusy to true before you call BackgroundWorker.RunWorkerAync(), then in the BackgroundWorker.RunWorkerCompleted event handler you would set the IsBusy to false. Seomthing like:
Backgroundworker worker = new BackgroundWorker();
worker.DoWork += ...
worker.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs args)
{
IsBusy = false;
};
IsBusy = true;
worker.RunWorkerAsync();
You can use the Dispatcher to add items to your ObservableCollection while in the DoWork event hanlder.
EDIT: Here is the complete solution
private void Button_Click(object sender, RoutedEventArgs e)
{
//on UI thread
ObservableCollection<string> collection;
ThreadStart start = delegate()
{
List<string> items = new List<string>();
for (int i = 0; i < 5000000; i++)
{
items.Add(String.Format("Item {0}", i));
}
System.Windows.Application.Current.Dispatcher.Invoke((Action)(() =>
{
//propogate items to UI
collection = new ObservableCollection<string>(items);
//hide indicator
_indicator.IsBusy = false;
}));
};
//show indicator before calling start
_indicator.IsBusy = true;
new Thread(start).Start();
}
I have developed an interesting WPF control that is currently slowing down my entire application :) On my custom control, I have an image control that I need to update everytime a backend event occurs. This backend event is firing twice a second (very fast). When the event fires I need to pull a Bitmap object out of a 3rd party control, convert to a BitmapSource object and then bind it to my Image control. Each time my event is fired, I am queuing a new work item in the ThreadPool. The item will then fetch the Bitmap and do the conversion in a background worker object. This is done everytime the event fires. I am using the dispatcher to update my image control source with BeginInvoke but I still get an unresponsive app. Please let me know what I can do to make this process better performing and help to make my app more responsive:
Here is the code in my event:
void inSight_ResultsChanged(object sender, EventArgs e)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessEvent), ((InSightViewer)this.DataContext).CvsDisplay);
}
Here is the code from delegate:
void ProcessEvent(object display)
{
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
bw.RunWorkerAsync(display);
}
Here is the code in my background worker DoWork event:
void bw_DoWork(object sender, DoWorkEventArgs e)
{
3rdPartyControl displayControl = new 3rdPartyControl();
displayControl.ImageHost = (ImgHost)e.Argument;
Bitmap b = displayControl.GetBitmap();
var mBitmap = b.GetHbitmap();
BitmapSource bs;
try
{
bs = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
mBitmap,
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
bs.Freeze();
}
catch (System.Exception ex) { throw ex; }
finally
{
DeleteObject(mBitmap);
}
e.Result = bs;
}
Here is the code in RunWorkerCompleted event:
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Application.Current.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.ApplicationIdle, (ThreadStart)delegate()
{
this.imgSource.Source = (BitmapSource)e.Result;
});
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Application.Current.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.ApplicationIdle, (ThreadStart)delegate()
{
this.imgSource.Source = (BitmapSource)e.Result;
});
}
System.Windows.Threading.DispatcherPriority.ApplicationIdle means Operations are processed when the application is idle.
What if the application is always busy and never idle? The requests will be queued and the app slows down.
However I haven't tested this because I don't have the source code of your app.