How to Display Progress During a Busy Loop

How do I display progress during a busy loop?

Move the work to a BackgroundWorker and use the ReportProgress method.

for (i = 0; i < count; i++)
{
... do analysis ...
worker.ReportProgress((100 * i) / count);
}

private void MyWorker_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
taskProgressBar.Value = Math.Min(e.ProgressPercentage, 100);
}

Display progress during matrix loop?

This solution has worked for me in the past.

ProgressWindowControl.xaml

<Window
x:Class="YourNamespace.ProgressWindowControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
WindowStartupLocation="CenterScreen" ShowInTaskbar="False" ResizeMode="NoResize"
SizeToContent="WidthAndHeight" WindowStyle="None"
mc:Ignorable="d">

<Window.Style>
<Style TargetType="Window">
<Setter Property="AllowsTransparency" Value="True"/>
<Setter Property="Background" Value="#00FFFFFF"/>
</Style>
</Window.Style>

<Grid>
<Grid Width="450" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0">
<Grid x:Name="Back">
<Border Background="Black" CornerRadius="3" Opacity="0.15"/>
<Border CornerRadius="2" Margin="1" Background="White"/>
</Grid>
<Grid x:Name="Content_Area" Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock x:Name="Info" TextWrapping="Wrap"
Text="{Binding Path=State,RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
Grid.Row="0" Margin="12,12,12,0" Foreground="#FF2D2D2D"/>
<ProgressBar Height="12"
Grid.Row="1"
Margin="12"
IsIndeterminate="{Binding Path=IsIndeterminate,RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
Value="{Binding Path=Progress,RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
Maximum="{Binding Path=MaxProgress,RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />

<Button x:Name="uxCancelBtn" Grid.Row="2" Height="22" Width="85" HorizontalAlignment="Right" Margin="0 0 12 0"
Click="CancelButton_Click" IsEnabled="False" Content="{x:Static resx:Strings.Cancel}">
</Button>
</Grid>
</Grid>
</Grid>
</Window>

ProgressWindowControl.cs

public sealed partial class ProgressWindowControl : Window
{
public static readonly DependencyProperty ProgressProperty =
DependencyProperty.Register("Progress", typeof(double), typeof(ProgressWindowControl), new PropertyMetadata(0d));

public static readonly DependencyProperty MaxProgressProperty =
DependencyProperty.Register("MaxProgress", typeof(double), typeof(ProgressWindowControl), new PropertyMetadata(100d));

public static readonly DependencyProperty IsIndeterminateProperty =
DependencyProperty.Register("IsIndeterminate", typeof(bool), typeof(ProgressWindowControl), new PropertyMetadata(true));

public static readonly DependencyProperty StateProperty =
DependencyProperty.Register("State", typeof(string), typeof(ProgressWindowControl), new PropertyMetadata(string.Empty));

public static readonly DependencyProperty IsCancelAllowedProperty =
DependencyProperty.Register("IsCancelAllowed", typeof(bool), typeof(ProgressWindowControl), new PropertyMetadata(false));

private ProgressWindowControl()
{
InitializeComponent();
}

public double Progress
{
get
{
return (double)GetValue(ProgressProperty);
}
set
{
SetValue(ProgressProperty, value);
}
}

public double MaxProgress
{
get
{
return (double)GetValue(MaxProgressProperty);
}
set
{
SetValue(MaxProgressProperty, value);
}
}

public bool IsIndeterminate
{
get
{
return (bool)GetValue(IsIndeterminateProperty);
}
set
{
SetValue(IsIndeterminateProperty, value);
}
}

public string State
{
get
{
return (string)GetValue(StateProperty);
}
set
{
SetValue(StateProperty, value);
}
}

public Action OnProgressWindowCancel { get; set; }

private void CancelButton_Click(object sender, RoutedEventArgs e)
{
if (OnProgressWindowCancel != null)
{
uxCancelBtn.IsEnabled = false;
uxCancelBtn.Content = Strings.Cancelling;
OnProgressWindowCancel();
}
}

[DllImport("user32.dll")]
private static extern int SetWindowLong(IntPtr hwnd, int index, int newStyle);

private const int GWL_HWNDPARENT = -8;

private static ProgressWindowControl _progressWindowControl;
private static bool _isVisible;
private static Window _owner;
private static ResizeMode? _ownerResizeMode;
private static bool _ownerIsHitTestVisible;
private static bool _ownerFocusable;

public static void ShowProgressWindow(Window owner = null)
{
if (!_isVisible)
{
IntPtr ownerHandle = IntPtr.Zero;
if (owner != null)
{
_owner = owner;
ownerHandle = GetHandler(_owner);
//Block owner window input while the progress bar is opened
_ownerResizeMode = _owner.ResizeMode;
_ownerIsHitTestVisible = _owner.IsHitTestVisible;
_ownerFocusable = _owner.Focusable;
_owner.ResizeMode = ResizeMode.NoResize;
_owner.IsHitTestVisible = false;
_owner.Focusable = false;
_owner.PreviewKeyDown += Owner_PreviewKeyDown;
_owner.PreviewMouseDown += Owner_PreviewMouseDown;
_owner.Closing += Owner_Closing;
}
//Run window in its own thread
Thread thread = new Thread(new ThreadStart(() =>
{
SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext(Dispatcher.CurrentDispatcher));
_progressWindowControl = new ProgressWindowControl();
// Shutdown the dispatcher when the window closes
_progressWindowControl.Closed += (s, e) =>
Dispatcher.CurrentDispatcher.BeginInvokeShutdown(DispatcherPriority.Background);

// When the progress window has loaded, if an owner has been specified, attach it to the window, otherwise set Topmost = true
ProgressWindowControl._progressWindowControl.Loaded += (s, e) =>
{
if (owner != null)
{
IntPtr ownedWindowHandle = GetHandler(_progressWindowControl);
SetOwnerWindowMultithread(ownedWindowHandle, ownerHandle);
}
else
{
_progressWindowControl.Topmost = true;
}
};
_progressWindowControl.Show();
_isVisible = true;
System.Windows.Threading.Dispatcher.Run();
}));
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();
}
}

private static void Owner_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
e.Cancel = true;
}

private static void Owner_PreviewMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
e.Handled = true;
}

private static void Owner_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
e.Handled = true;
}

private static void SetOwnerWindowMultithread(IntPtr windowHandleOwned, IntPtr intPtrOwner)
{
if (windowHandleOwned != IntPtr.Zero && intPtrOwner != IntPtr.Zero)
{
SetWindowLong(windowHandleOwned, GWL_HWNDPARENT, intPtrOwner.ToInt32());
}
}

private static IntPtr GetHandler(Window window)
{
var interop = new WindowInteropHelper(window);
return interop.Handle;
}

public static void CloseProgressWindow()
{
if (_progressWindowControl != null && _isVisible)
{
if (_progressWindowControl.Dispatcher.CheckAccess())
{
_progressWindowControl.Close();
}
else
{
_progressWindowControl.Dispatcher.Invoke(DispatcherPriority.Normal,
new ThreadStart(_progressWindowControl.Close));
}
if (_owner != null)
{
//Unblock owner input
_owner.ResizeMode = _ownerResizeMode ?? ResizeMode.CanResize;
_owner.IsHitTestVisible = _ownerIsHitTestVisible;
_owner.Focusable = _ownerFocusable;
_owner.PreviewKeyDown -= Owner_PreviewKeyDown;
_owner.PreviewMouseDown -= Owner_PreviewMouseDown;
_owner.Closing -= Owner_Closing;
}
//Reset fields
_ownerResizeMode = null;
_ownerIsHitTestVisible = false;
_ownerFocusable = false;
_progressWindowControl = null;
_owner = null;
_isVisible = false;
}
}

public static void SetProgress(double progress, double maxProgress)
{
if (_progressWindowControl != null)
{
if (_progressWindowControl.Dispatcher.CheckAccess())
{
_progressWindowControl.IsIndeterminate = false;
_progressWindowControl.Progress = progress;
_progressWindowControl.MaxProgress = maxProgress;
}
else
{
_progressWindowControl.Dispatcher.Invoke(DispatcherPriority.Normal,
new ThreadStart(() =>
{
_progressWindowControl.IsIndeterminate = false;
_progressWindowControl.Progress = progress;
_progressWindowControl.MaxProgress = maxProgress;
}));
}
}
}

public static void SetIsIndeterminate(bool isIndeterminate)
{
if (_progressWindowControl != null)
{
if (_progressWindowControl.Dispatcher.CheckAccess())
{
_progressWindowControl.IsIndeterminate = isIndeterminate;
}
else
{
_progressWindowControl.Dispatcher.Invoke(DispatcherPriority.Normal,
new ThreadStart(() =>
{
_progressWindowControl.IsIndeterminate = isIndeterminate;
}));
}
}
}

public static void SetState(string state)
{
if (_progressWindowControl != null)
{
if (_progressWindowControl.Dispatcher.CheckAccess())
{
_progressWindowControl.State = state;
}
else
{
_progressWindowControl.Dispatcher.Invoke(DispatcherPriority.Normal,
new ThreadStart(() =>
{
_progressWindowControl.State = state;
}));
}
}
}

public static void SetIsCancelAllowed(bool isCancelAllowed, Action progressWindowCancel)
{
if (_progressWindowControl != null)
{
if (_progressWindowControl.Dispatcher.CheckAccess())
{
_progressWindowControl.OnProgressWindowCancel = progressWindowCancel;
_progressWindowControl.uxCancelBtn.IsEnabled = isCancelAllowed;
_progressWindowControl.uxCancelBtn.Content = Strings.Cancel;
}
else
{
_progressWindowControl.Dispatcher.Invoke(DispatcherPriority.Normal,
new ThreadStart(() =>
{
_progressWindowControl.OnProgressWindowCancel = progressWindowCancel;
_progressWindowControl.uxCancelBtn.IsEnabled = isCancelAllowed;
_progressWindowControl.uxCancelBtn.Content = Strings.Cancel;
}));
}
}
}
}

A helper class to open the window:

public static class ProgressWindowHelper
{
public static void Show(Window owner = null)
{
ProgressWindowControl.ShowProgressWindow(owner);
}

public static void Close()
{
ProgressWindowControl.CloseProgressWindow();
}

public static void SetProgress(double progress, double maxProgress)
{
ProgressWindowControl.SetProgress(progress, maxProgress);
}

public static void SetIsIndeterminate(bool isIndeterminate)
{
ProgressWindowControl.SetIsIndeterminate(isIndeterminate);
}

public static void SetState(string state)
{
ProgressWindowControl.SetState(state);
}

public static void SetIsCancelAllowed(bool isCancelAllowed, Action progressWindowCancel)
{
ProgressWindowControl.SetIsCancelAllowed(isCancelAllowed, progressWindowCancel);
}
}

A service so you can use Dependency Injection (I didn't include the interface, just create one as needed):

 public class ProgressWindowService : IProgressWindowService
{
public void Show(Window owner = null)
{
ProgressWindowHelper.Show(owner);
}

public void Close()
{
ProgressWindowHelper.Close();
}

public void SetProgress(double progress, double maxProgress)
{
ProgressWindowHelper.SetProgress(progress, maxProgress);
}

public void SetIsIndeterminate(bool isIndeterminate)
{
ProgressWindowHelper.SetIsIndeterminate(isIndeterminate);
}

public void SetState(string state)
{
ProgressWindowHelper.SetState(state);
}

public void SetIsCancelAllowed(bool isCancelAllowed, Action progressWindowCancel)
{
ProgressWindowHelper.SetIsCancelAllowed(isCancelAllowed, progressWindowCancel);
}
}

And finally in your ViewModel (assuming you have injected the service):

ProgressBarService.SetProgress(current, total);

ProgressBarService.SetState(state);

ProgressBarService.Show();

You can also pass a window to the Show method and then the window will be attached to it and input will be blocked while the progress window is shown, if no window is provided, the progress bar is shown on top of any other window (TopMost=true):

ProgressBarService.Show(YourWindow);

You could also use a messenger to trigger the progress window.

EDIT

Removed DevExpress dependency.

How to display a loop's progress without pausing the loop?

Since the provided answers didn't meet my requirements, I did some research on events and solved the issue the way I initially wanted.

I declared an event in the class that is starting the loop:

public delegate void ProgressChangedEvHandler(int progress);

public event ProgressChangedEvHandler ProgressChanged;

private void OnProgressChanged(int progress)
{
var handler = ProgressChanged;
if (handler != null) handler(progress);
}

Then I invoked the event from within the loop:

for (var index = 0; index < arrayListCount; index++)
{
var progress = (int) (100*(double) index/(double) arrayListCount);
OnProgressChanged(progress);
}

and then I just created the listener (LoopClassInstance.ProgressChanged += LoopClassInstance_ProgressChanged;) in a different class (on a different thread):

private void LoopClassInstance_ProgressChanged(int progress)
{
toolStripProgressBar1.Value = progress;
}

Show a progress bar while a function runs in a loop until it returns a value

My comments as an answer...

Put that code into a different thread, then use a ProgressBar in "Marquee" mode to indicate an operation that is ongoing, but has no known ending time.

Yes...but you still need to put the query/loop in a different thread...otherwise the main UI thread will to be to busy to animate and remain responsive to the user.

Look at the BackgroundWorker control, or using a Task, with Async/Await.

You'd show the form, start the worker, wait for worker to finish, then close the form. The BackgroundWorker() has UI friendly events like RunWorkerCompleted that are already marshaled to the UI thread for you.

Update progress bar from for loop

You should use BackgroundWorker combined with a ProgressBar control. Here is a simple example.

How to show GUI progress for a long process?

Your question seems to be about determining an efficient way of evaluating an interval at which to issue a progress update. If you are dead-set against a threaded approach then what you are doing is fine - there is not much in the way of improving it. In fact, the efficiency of determining an update interval is just as relevant in threaded code as well so whether threaded or not the problem is the same.

You don't seem to have an issue with the contents of FProgress(self) (ie : how to perform the status update), nor does this seem to be about "un-freezing" the GUI so I will not address those points.

Your options are

  • GetTickCount -- update based on time
  • if x mod 1000 = 0 -- update based on loop variable
  • (something like) if x shl 22 = 0 -- same as above, but a tricksy mod 1024 without division
  • Per @LURD, you could also use something like if i and $3FF = 0 for similar performance

These are almost identical in performance if your loop has any meat to it at all. To test :

program Project1;

uses Windows, SysUtils;

{$APPTYPE CONSOLE}

var
i : integer;
t0 : cardinal;
begin
t0 := GetTickCount;
for i := 0 to 1000000000 do begin
if t0 - GetTickCount > 1000 then;
end;
t0 := GetTickCount - t0;
WriteLn('GetTickCount : ' + IntToStr(t0));

t0 := GetTickCount;
for i := 0 to 1000000000 do begin
if i mod 1000 = 0 then;
end;
t0 := GetTickCount - t0;
WriteLn('i mod 1000 : ' + IntToStr(t0));

t0 := GetTickCount;
for i := 0 to 1000000000 do begin
if i shl 22 = 0 then;
end;
t0 := GetTickCount - t0;
WriteLn('i shl 22 : ' + IntToStr(t0));

ReadLn;
end.

Produces output (for me)

 GetTickCount : 3978   
i mod 1000 : 3386
i shl 22 : 2184

The last option is about twice as performant, but if your loop is doing anything substantial at all you probably won't notice any difference.


Appendix :

If you need to satisfy yourself that bitshifting is an accurate way to do this :

program Project1;

uses SysUtils;

{$APPTYPE CONSOLE}

var
i : integer;
begin
for i := 0 to 1000000000 do begin
if (i shl 22 = 0) <> (i mod 1024 = 0) then
WriteLn('not the same! : ' + IntToStr(i));
end;
WriteLn('complete.');
ReadLn;
end.

Note that i shl 22 does not equal i mod 1024, but they are both zero for the same values of i.

Adding a progress bar to indicate ggplotting progress in Shiny

It's not actually a progress bar as you'd like to generate. But you can display a loading message within a banner instead, which is why I suppose it could be useful here. Just copy the following code-snippet into the ui-part of your app and adjust the colors as needed.

info_loading <- "Shiny is busy. Please wait."
your_color01 <- # define a color for the text
your_color02 <- # define a color for the background of the banner

tags$head(tags$style(type="text/css",
paste0("
#loadmessage {
position: fixed;
top: 0px;
left: 0px;
width: 100%;
padding: 5px 0px 5px 0px;
text-align: center;
font-weight: bold;
font-size: 100%;
color: ", your_color01,";
background-color: ", your_color02,";
z-index: 105;
}
"))),
conditionalPanel(condition="$('html').hasClass('shiny-busy')",
tags$div(info_loading,id="loadmessage"))

Don't hesitate to adjust the parameters (e.g. top position) as needed.
You may further see: shiny loading bar for htmlwidgets

How to Display a progress bar whilst 'work' is 'working'

private void Button_Click(object sender, RoutedEventArgs e)
{
Thread th = new Thread(DoSomething);
th.Start();
}

private void DoSomething()
{
ShowBusy();
while (true) // No Exit Clause ???
{
System.Threading.Thread.Sleep(100);
}
HideBusy();
}


Related Topics



Leave a reply



Submit