I have a WPF application where a data-intensive operation is called by a context menu selection.
I'm using Dispatcher to run the data-intensive operation in the background, but the context menu does not fully close before it begins - it starts to close (i.e. fade) but doesn't disappear completely before the operation begins. The result is a sloppy looking half-faded context menu sitting open on the screen while my "executing" message displays and the data operation completes.
I understand that there is a separate "rendering" thread that runs in the background of a WPF application, and it seems to me like the Close operation of the context menu is completing but the rendering event of fading the context menu away is not finishing before the data-intensive operation begins.
Here is my code:
private void mnuAddProblem_Click(object sender, RoutedEventArgs e)
{
CareGuide selectedCareGuide = (CareGuide)grdCareGuides.SelectedItem;
List<Problem> selectedProblems = new List<Problem>();
for (int i = 0; i < grdSuggestedProblems.SelectedItems.Count; i++)
{
selectedProblems.Add((Problem)grdSuggestedProblems.SelectedItems[i]);
}
LoadingWindow loader = new LoadingWindow();
loader.Owner = this;
loader.WindowStartupLocation = WindowStartupLocation.CenterOwner;
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += delegate
{
Dispatcher.BeginInvoke(new Action(delegate { loader.ShowDialog(); }));
Dispatcher.Invoke((Action)(() => { dataWorker.AddSuggestedProblems(selectedProblems, selectedCareGuide); }));
Dispatcher.BeginInvoke(new Action(delegate() { loader.Close(); }));
};
worker.RunWorkerAsync();
}
I was able to fix this by disabling the "fading" animation that occurs when opening and closing the context menu by overriding the system parameter that controls popup animation:
<PopupAnimation x:Key="{x:Static SystemParameters.MenuPopupAnimationKey}">None</PopupAnimation>
Related
I'm writing a 3D wpf application using Viewport3D. When user push a button, I must start DoubleAnimation on AxisAngleRotation3D, but it must be done synchronous. I can't do it on animation.Completed, because this animation run the next one and the next one recursively.
ButtonHandler must work on UI thread, because to calculate animation I hardly use Viewport3D.
So I must in UI thread start synchronous animation and after it finished continoue work.
I tried this code, but it cause deadlock:
AxisAngleRotation3D axis;
DoubleAnimation animation;
DependencyProperty dp;
var worker = new Thread(() =>
{
Dispatcher.CurrentDispatcher.InvokeShutdown();
Dispatcher.Run();
});
worker.SetApartmentState(ApartmentState.STA);
worker.Start();
AutoResetEvent animationDone = new AutoResetEvent(false);
EventHandler handler = null;
handler = (sender, args) =>
{
animation.Completed -= handler; // remove self
animationDone.Set(); // signal completion
};
animation.Completed += handler;
Dispatcher.CurrentDispatcher.Invoke(new Action(() =>
{
axis.BeginAnimation(dp, animation);
}));
animationDone.WaitOne();
Ok, it's just an idea. Why don't you create this animations in few steps. First start first batch of animation and then, when they'll finish run another set of animations. You could synchronize it with some kind of timer.
You can first create list of animations and objects to run in each step and then just invoke them.
My app has a UserControl that wraps a ServiceController to expose start/stop/restart win service functionality to the user. My concern at the moment is restarting. It takes a small amount of time and I want to reflect the restarting status inside of the control. This is roughly what I have for the restart button click handler
private void RestartButton_Click(object sender, RoutedEventArgs e)
{
startStopButton.Visibility = Visibility.Hidden;
restartButton.Visibility = Visibility.Hidden;
statusTextBlock.Text = "Restarting...";
Controller.Stop();
Controller.WaitForStatus(ServiceControllerStatus.Stopped);
Controller.Start();
Controller.WaitForStatus(ServiceControllerStatus.Running);
startStopButton.Visibility = Visibility.Visible;
restartButton.Visibility = Visibility.Visible;
statusTextBlock.Text = Controller.Status.ToString();
}
Even when I step through the debugger I don't see these changes reflected in the application. Must be something that I'm missing. Also, I've tried disabling the buttons instead of hiding them and that does not work either.
You're doing everything on the UI thread, so the UI isn't updated until this code completes. You should do the heavy lifting on a background thread. The BackgroundWorker component makes this easy:
private void RestartButton_Click(object sender, RoutedEventArgs e)
{
startStopButton.Visibility = Visibility.Hidden;
restartButton.Visibility = Visibility.Hidden;
statusTextBlock.Text = "Restarting...";
var backgroundWorker = new BackgroundWorker();
// this delegate will run on a background thread
backgroundWorker.DoWork += delegate
{
Controller.Stop();
Controller.WaitForStatus(ServiceControllerStatus.Stopped);
Controller.Start();
Controller.WaitForStatus(ServiceControllerStatus.Running);
};
// this delegate will run on the UI thread once the work is complete
backgroundWorker.RunWorkerCompleted += delegate
{
startStopButton.Visibility = Visibility.Visible;
restartButton.Visibility = Visibility.Visible;
statusTextBlock.Text = Controller.Status.ToString();
};
backgroundWorker.RunWorkerAsync();
}
That's because execution is happening in the UI thread. Your button won't update because between { and } the UI thread is busy doing your work and it cannot update the button.
I know that there are several implementations here and there, but i was still not able to 'lock' on something really useful...
Whenever i set some component DataContext or ItemsSource to some big object, there is this 'render time frozen GUI' which make the app real annoying (even when using Virtualization).
I know i can iterate the object and set the items one by one and show progress, but i am looking for some other approach which can let me show some moving indication while GUI is rendering. I also prefer to have some progress bar and not only make the mouse cursor change.
Is there a decent way to achieve the followings?
Many Thanks
Zamboni example is a very good one, but still does not solve the frozen GUI problem.
As mentioned, there is no currently simple way of having something 'alive' to update a gui control while GUI is busy rendering.
I currently found some event that is 'alive and kicking' while gui is rendering, althogh it should be turned off when not needed as it can fire something like 60 times per second.
CompositionTarget.Rendering += ReportRenderProgress;
You can then implement ReportRenderProgress() anyway you like to signal you progress bar to update. Currently, i dont see any better solution available in WPF to update a progress indication while rendering so i am marking this as the answer.
This is actually a problem. You are using the GUI thread to fill the data (from object structure into GUI). The GUI thread is required both to read Windows message queue (prevent app from freezing, allow app to be moved/respond) and it is required to do any updates to the GUI.
One solution could be to slowly fill the the object structure after binding. This would have to be done from the GUI thread, so you could add DoEvents() and/or some percent indicator+forced refresh to make application seem alive.
I am interested to hear if anyone has a better solution though.
BackgroundWorker has everything you need.
EDIT
In WPF the Dispatcher is being employed automatically to invoke cross-thread method calls.
Check out Build More Responsive Apps With The Dispatcher in MSDN magazine.
I also put together some code fragments from a ViewModel that shows a BackgroundWorker updating a progress bar.
<ProgressBar
VerticalContentAlignment="Stretch" VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Minimum="0" Maximum="100"
Value="{Binding Path=BarPosition, Mode=TwoWay}"/>
// configure the background worker...
_backgroundWorker = new BackgroundWorker();
_backgroundWorker.WorkerReportsProgress = true;
_backgroundWorker.WorkerSupportsCancellation = true;
_backgroundWorker.DoWork += new DoWorkEventHandler(_backgroundWorker_DoWork);
_backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_backgroundWorker_RunWorkerCompleted);
_backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(_backgroundWorker_ProgressChanged);
// control progress bar position
private int _barPosition = 0;
public int BarPosition
{
get { return _barPosition; }
set
{
_barPosition = value;
OnPropertyChanged("BarPosition");
}
}
// long operation
void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bw = sender as BackgroundWorker;
if (bw != null)
{
int pos;
for (int i = 0; i < 100; ++i
{
// report progress here for our long running operation..
pos = i/100;
bw.ReportProgress(pos);
Thread.Sleep(1000); // fake long operation
}
}
}
// report progress,,,
void _backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
BackgroundWorker bw = sender as BackgroundWorker;
if (bw != null)
{
BarPosition = e.ProgressPercentage;
}
}
// reset scroll bar position
void _backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
BackgroundWorker bw = sender as BackgroundWorker;
if (bw != null)
{
BarPosition = 0;
// Forcing the CommandManager to raise the RequerySuggested event to refresh UI...
CommandManager.InvalidateRequerySuggested();
}
}
I have the following method in my WPF project (.net 4):
private void MyMethod(){
imgMyImage.Visibility = Visibility.Visible;
DoWork();
imgMyImage.Visibility = Visibility.Collapsed;
}
The image is in a DockPanel, and I want it to appear while the "DoWork()" method is being executed, but it does not change state until after the "MyMethod()" method exits. Can someone explain how to make this work correctly?
Thank you for any help.
Your "DoWork" method is blocking the UI thread. Until it completes, nothing in the UI will change (and the UI will remain unresponsive).
A better option is to push the DoWork into a background thread. For example, using the new Task framework in .NET 4, you could write this as:
private void MyMethod()
{
imgMyImage.Visibility = Visibility.Visible;
// Create a background task for your work
var task = Task.Factory.StartNew( () => DoWork() );
// When it completes, have it hide (on the UI thread), imgMyImage element
task.ContinueWith( t => imgMyImage.Visibility = Visibility.Collapsed,
TaskScheduler.FromCurrentSynchronizationContext() );
}
I have a WPF app, upon clicking a button, the app goes into a calculation that can take 4-10 seconds. I'd like to update the opacity of the background and show a progress bar, during that operation.
To do that, I use this code:
this.Cursor = System.Windows.Input.Cursors.Wait;
// grey-out the main window
SolidColorBrush brush1 = new SolidColorBrush(Colors.Black);
brush1.Opacity = 0.65;
b1 = LogicalTreeHelper.FindLogicalNode(this, "border1") as Border;
b1.Opacity = 0.7;
b1.Background = brush1;
// long running computation happens here ....
// show a modal dialog to confirm results here
// restore background and opacity here.
When I run the code, the background and opacity doesn't change until the modal dialog appears. How can I get those visual changes to happen right now, before the calculation begins? In Windows Forms there was an Update() method on each control, that did this as necessary, as I recall. What's the WPF analog?
What if you would do long running computation in the background thread? Once they are done dispatch results back to UI thread...
Honestly, I suspect there is nothing else there, that can solve your problem. Maybe nested pumping will do the trick, but I really doubt it.
Just in case this reference is helpful: Build More Responsive Apps With The Dispatcher
Use the DoEvents() code as shown here:
http://blogs.microsoft.co.il/blogs/tamir/archive/2007/08/21/How-to-DoEvents-in-WPF_3F00_.aspx
My actual code:
private void GreyOverlay()
{
// make the overlay window visible - the effect is to grey out the display
if (_greyOverlay == null)
_greyOverlay = LogicalTreeHelper.FindLogicalNode(this, "overlay") as System.Windows.Shapes.Rectangle;
if (_greyOverlay != null)
{
_greyOverlay.Visibility = Visibility.Visible;
DoEvents();
}
}
private void DoEvents()
{
// Allow UI to Update...
DispatcherFrame f = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
new Action<object>((arg)=> {
DispatcherFrame fr = arg as DispatcherFrame;
fr.Continue= false;
}), f);
Dispatcher.PushFrame(f);
}