├── src
├── ProgressDialogEx
│ ├── App.xaml.cs
│ ├── app.config
│ ├── packages.config
│ ├── ProgressDialog
│ │ ├── ProgressDialogOptions.cs
│ │ ├── ProgressDialogWindow.xaml.cs
│ │ ├── CancelCommand.cs
│ │ ├── ProgressDialogWindow.xaml
│ │ ├── ProgressDialogWindowViewModel.cs
│ │ ├── IProgressDialogService.cs
│ │ └── ProgressDialogService.cs
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ ├── App.xaml
│ ├── AttachedProperties
│ │ ├── DialogCloser.cs
│ │ └── WindowBehavior.cs
│ ├── MyLongRunningCommand.cs
│ ├── MainWindow.xaml
│ ├── ProgressDialogEx.csproj
│ └── MainWindow.xaml.cs
└── ProgressDialogEx.sln
├── .gitignore
└── README.md
/src/ProgressDialogEx/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace ProgressDialogEx
4 | {
5 | public partial class App : Application
6 | {
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/ProgressDialogEx/app.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/ProgressDialogEx/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/ProgressDialogEx/ProgressDialog/ProgressDialogOptions.cs:
--------------------------------------------------------------------------------
1 | namespace ProgressDialogEx.ProgressDialog
2 | {
3 | public class ProgressDialogOptions
4 | {
5 | public string WindowTitle { get; set; }
6 | public string Label { get; set; }
7 | }
8 | }
--------------------------------------------------------------------------------
/src/ProgressDialogEx/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | [assembly: AssemblyProduct("ProgressDialogEx")]
5 |
6 | [assembly: ComVisible(false)]
7 |
8 | [assembly: AssemblyVersion("1.0.0.0")]
9 | [assembly: AssemblyFileVersion("1.0.0.0")]
10 |
--------------------------------------------------------------------------------
/src/ProgressDialogEx/ProgressDialog/ProgressDialogWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace ProgressDialogEx.ProgressDialog
4 | {
5 | ///
6 | /// Interaction logic for ProgressDialog.xaml
7 | ///
8 | public partial class ProgressDialogWindow : Window
9 | {
10 | public ProgressDialogWindow()
11 | {
12 | InitializeComponent();
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/ProgressDialogEx/App.xaml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/ProgressDialogEx/AttachedProperties/DialogCloser.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace ProgressDialogEx.AttachedProperties
4 | {
5 | // From
6 | public static class DialogCloser
7 | {
8 | public static readonly DependencyProperty DialogResultProperty =
9 | DependencyProperty.RegisterAttached(
10 | "DialogResult",
11 | typeof(bool?),
12 | typeof(DialogCloser),
13 | new PropertyMetadata(DialogResultChanged));
14 |
15 | private static void DialogResultChanged(
16 | DependencyObject d,
17 | DependencyPropertyChangedEventArgs e)
18 | {
19 | var window = d as Window;
20 | if (window != null && (bool?) e.NewValue == true)
21 | window.Close();
22 | }
23 |
24 | public static void SetDialogResult(Window target, bool? value)
25 | {
26 | target.SetValue(DialogResultProperty, value);
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/src/ProgressDialogEx/ProgressDialog/CancelCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Windows.Input;
4 |
5 | namespace ProgressDialogEx.ProgressDialog
6 | {
7 | public class CancelCommand : ICommand
8 | {
9 | readonly CancellationTokenSource cancellationTokenSource;
10 |
11 | public event EventHandler CanExecuteChanged;
12 |
13 | public CancelCommand(CancellationTokenSource cancellationTokenSource)
14 | {
15 | if (cancellationTokenSource == null) throw new ArgumentNullException("cancellationTokenSource");
16 | this.cancellationTokenSource = cancellationTokenSource;
17 | }
18 |
19 | public bool CanExecute(object parameter)
20 | {
21 | return !cancellationTokenSource.IsCancellationRequested;
22 | }
23 |
24 | public void Execute(object parameter)
25 | {
26 | cancellationTokenSource.Cancel();
27 |
28 | if (CanExecuteChanged != null)
29 | CanExecuteChanged(this, EventArgs.Empty);
30 |
31 | CommandManager.InvalidateRequerySuggested();
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/src/ProgressDialogEx.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 2012
4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProgressDialogEx", "ProgressDialogEx\ProgressDialogEx.csproj", "{85C1B8BF-A213-4C8F-BD62-FBFD953CBB7C}"
5 | EndProject
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{D69881D8-E47F-4A83-ACD6-0D49E6D2464E}"
7 | ProjectSection(SolutionItems) = preProject
8 | .nuget\NuGet.Config = .nuget\NuGet.Config
9 | .nuget\NuGet.exe = .nuget\NuGet.exe
10 | .nuget\NuGet.targets = .nuget\NuGet.targets
11 | EndProjectSection
12 | EndProject
13 | Global
14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
15 | Debug|x86 = Debug|x86
16 | Release|x86 = Release|x86
17 | EndGlobalSection
18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
19 | {85C1B8BF-A213-4C8F-BD62-FBFD953CBB7C}.Debug|x86.ActiveCfg = Debug|x86
20 | {85C1B8BF-A213-4C8F-BD62-FBFD953CBB7C}.Debug|x86.Build.0 = Debug|x86
21 | {85C1B8BF-A213-4C8F-BD62-FBFD953CBB7C}.Release|x86.ActiveCfg = Release|x86
22 | {85C1B8BF-A213-4C8F-BD62-FBFD953CBB7C}.Release|x86.Build.0 = Release|x86
23 | EndGlobalSection
24 | GlobalSection(SolutionProperties) = preSolution
25 | HideSolutionNode = FALSE
26 | EndGlobalSection
27 | EndGlobal
28 |
--------------------------------------------------------------------------------
/src/ProgressDialogEx/MyLongRunningCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using System.Windows;
5 | using System.Windows.Input;
6 | using ProgressDialogEx.ProgressDialog;
7 |
8 | namespace ProgressDialogEx
9 | {
10 | public class MyLongRunningCommand : ICommand
11 | {
12 | readonly IProgressDialogService dialogService;
13 |
14 | public MyLongRunningCommand(IProgressDialogService dialogService)
15 | {
16 | this.dialogService = dialogService;
17 | }
18 |
19 | public void Execute(object parameter)
20 | {
21 | Task task = dialogService.ExecuteAsync(DoWork, new ProgressDialogOptions { WindowTitle = "Loading files" });
22 |
23 | MessageBox.Show(String.Format("Result = {0}", task.Result));
24 | }
25 |
26 | static async Task DoWork(CancellationToken cancellationToken, IProgress progress)
27 | {
28 | return await Task.Factory.StartNew(() => progress.Report("First"), cancellationToken)
29 | .ContinueWith(_ => Thread.Sleep(1000), cancellationToken)
30 | .ContinueWith(_ => progress.Report("Second"), cancellationToken)
31 | .ContinueWith(_ => Thread.Sleep(1000), cancellationToken)
32 | .ContinueWith(_ => 42);
33 | }
34 |
35 | public bool CanExecute(object parameter)
36 | {
37 | return true;
38 | }
39 |
40 | public event EventHandler CanExecuteChanged;
41 | }
42 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs)
2 | [Bb]in/
3 | [Oo]bj/
4 |
5 | # mstest test results
6 | TestResults
7 |
8 | ## Ignore Visual Studio temporary files, build results, and
9 | ## files generated by popular Visual Studio add-ons.
10 |
11 | # User-specific files
12 | *.suo
13 | *.user
14 | *.sln.docstates
15 |
16 | # Build results
17 | [Dd]ebug/
18 | [Rr]elease/
19 | x64/
20 | *_i.c
21 | *_p.c
22 | *.ilk
23 | *.meta
24 | *.obj
25 | *.pch
26 | *.pdb
27 | *.pgc
28 | *.pgd
29 | *.rsp
30 | *.sbr
31 | *.tlb
32 | *.tli
33 | *.tlh
34 | *.tmp
35 | *.log
36 | *.vspscc
37 | *.vssscc
38 | .builds
39 |
40 | # Visual C++ cache files
41 | ipch/
42 | *.aps
43 | *.ncb
44 | *.opensdf
45 | *.sdf
46 |
47 | # Visual Studio profiler
48 | *.psess
49 | *.vsp
50 | *.vspx
51 |
52 | # Guidance Automation Toolkit
53 | *.gpState
54 |
55 | # ReSharper is a .NET coding add-in
56 | _ReSharper*
57 |
58 | # NCrunch
59 | *.ncrunch*
60 | .*crunch*.local.xml
61 |
62 | # Installshield output folder
63 | [Ee]xpress
64 |
65 | # DocProject is a documentation generator add-in
66 | DocProject/buildhelp/
67 | DocProject/Help/*.HxT
68 | DocProject/Help/*.HxC
69 | DocProject/Help/*.hhc
70 | DocProject/Help/*.hhk
71 | DocProject/Help/*.hhp
72 | DocProject/Help/Html2
73 | DocProject/Help/html
74 |
75 | # Click-Once directory
76 | publish
77 |
78 | # Publish Web Output
79 | *.Publish.xml
80 |
81 | # NuGet Packages Directory
82 | packages
83 |
84 | # Windows Azure Build Output
85 | csx
86 | *.build.csdef
87 |
88 | # Windows Store app package directory
89 | AppPackages/
90 |
91 | # Others
92 | [Bb]in
93 | [Oo]bj
94 | sql
95 | TestResults
96 | [Tt]est[Rr]esult*
97 | *.Cache
98 | ClientBin
99 | [Ss]tyle[Cc]op.*
100 | ~$*
101 | *.dbmdl
102 | Generated_Code #added for RIA/Silverlight projects
103 |
104 | # Backup & report files from converting an old project file to a newer
105 | # Visual Studio version. Backup files are not needed, because we have git ;-)
106 | _UpgradeReport_Files/
107 | Backup*/
108 | UpgradeLog*.XML
109 |
--------------------------------------------------------------------------------
/src/ProgressDialogEx/ProgressDialog/ProgressDialogWindow.xaml:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
31 |
40 |
41 |
45 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/src/ProgressDialogEx/ProgressDialog/ProgressDialogWindowViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using GalaSoft.MvvmLight;
4 | using GalaSoft.MvvmLight.Threading;
5 |
6 | namespace ProgressDialogEx.ProgressDialog
7 | {
8 | public class ProgressDialogWindowViewModel : ViewModelBase
9 | {
10 | string windowTitle;
11 | string label;
12 | string subLabel;
13 | bool close;
14 |
15 | public string WindowTitle
16 | {
17 | get { return windowTitle; }
18 | private set
19 | {
20 | windowTitle = value;
21 | RaisePropertyChanged(() => WindowTitle);
22 | }
23 | }
24 |
25 | public string Label
26 | {
27 | get { return label; }
28 | private set
29 | {
30 | label = value;
31 | RaisePropertyChanged(() => Label);
32 | }
33 | }
34 |
35 | public string SubLabel
36 | {
37 | get { return subLabel; }
38 | private set
39 | {
40 | subLabel = value;
41 | RaisePropertyChanged(() => SubLabel);
42 | }
43 | }
44 |
45 | public bool Close
46 | {
47 | get { return close; }
48 | set
49 | {
50 | close = value;
51 | RaisePropertyChanged(() => Close);
52 | }
53 | }
54 |
55 | public bool IsCancellable { get { return CancelCommand != null; } }
56 |
57 | public CancelCommand CancelCommand { get; private set; }
58 | public IProgress Progress { get; private set; }
59 |
60 | public ProgressDialogWindowViewModel(
61 | ProgressDialogOptions options,
62 | CancellationToken cancellationToken,
63 | CancelCommand cancelCommand)
64 | {
65 | if (options == null) throw new ArgumentNullException("options");
66 | WindowTitle = options.WindowTitle;
67 | Label = options.Label;
68 | CancelCommand = cancelCommand; // can be null (not cancellable)
69 | cancellationToken.Register(OnCancelled);
70 | Progress = new Progress(OnProgress);
71 | }
72 |
73 | void OnCancelled()
74 | {
75 | // Cancellation may come from a background thread.
76 | if (DispatcherHelper.UIDispatcher != null)
77 | DispatcherHelper.CheckBeginInvokeOnUI(() => Close = true);
78 | else
79 | Close = true;
80 | }
81 |
82 | void OnProgress(string obj)
83 | {
84 | // Progress will probably come from a background thread.
85 | if (DispatcherHelper.UIDispatcher != null)
86 | DispatcherHelper.CheckBeginInvokeOnUI(() => SubLabel = obj);
87 | else
88 | SubLabel = obj;
89 | }
90 | }
91 | }
--------------------------------------------------------------------------------
/src/ProgressDialogEx/ProgressDialog/IProgressDialogService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | namespace ProgressDialogEx.ProgressDialog
6 | {
7 | public interface IProgressDialogService
8 | {
9 | ///
10 | /// Executes a non-cancellable task.
11 | ///
12 | void Execute(Action action, ProgressDialogOptions options);
13 |
14 | ///
15 | /// Executes a cancellable task.
16 | ///
17 | void Execute(Action action, ProgressDialogOptions options);
18 |
19 | ///
20 | /// Executes a non-cancellable task that reports progress.
21 | ///
22 | void Execute(Action> action, ProgressDialogOptions options);
23 |
24 | ///
25 | /// Executes a cancellable task that reports progress.
26 | ///
27 | void Execute(Action> action,
28 | ProgressDialogOptions options);
29 |
30 | ///
31 | /// Executes a non-cancellable task, that returns a value.
32 | ///
33 | bool TryExecute(Func action, ProgressDialogOptions options,
34 | out T result);
35 |
36 | ///
37 | /// Executes a cancellable task that returns a value.
38 | ///
39 | bool TryExecute(Func action,
40 | ProgressDialogOptions options, out T result);
41 |
42 | ///
43 | /// Executes a non-cancellable task that reports progress and returns a value.
44 | ///
45 | bool TryExecute(Func, T> action,
46 | ProgressDialogOptions options, out T result);
47 |
48 | ///
49 | /// Executes a cancellable task that reports progress and returns a value.
50 | ///
51 | bool TryExecute(Func, T> action,
52 | ProgressDialogOptions options, out T result);
53 |
54 | ///
55 | /// Executes a non-cancellable async task.
56 | ///
57 | Task ExecuteAsync(Func action, ProgressDialogOptions options);
58 |
59 | ///
60 | /// Executes a cancellable async task.
61 | ///
62 | Task ExecuteAsync(Func action, ProgressDialogOptions options);
63 |
64 | ///
65 | /// Executes a non-cancellable async task that reports progress.
66 | ///
67 | Task ExecuteAsync(Func, Task> action, ProgressDialogOptions options);
68 |
69 | ///
70 | /// Executes a cancellable async task that reports progress.
71 | ///
72 | Task ExecuteAsync(Func, Task> action,
73 | ProgressDialogOptions options);
74 |
75 | ///
76 | /// Executes a non-cancellable async task that returns a value.
77 | ///
78 | Task ExecuteAsync(Func> action, ProgressDialogOptions options);
79 |
80 | ///
81 | /// Executes a cancellable async task that returns a value.
82 | ///
83 | Task ExecuteAsync(Func> action, ProgressDialogOptions options);
84 |
85 | ///
86 | /// Executes a non-cancellable async task that reports progress and returns a value.
87 | ///
88 | Task ExecuteAsync(Func, Task> action, ProgressDialogOptions options);
89 |
90 | ///
91 | /// Executes a cancellable async task that reports progress and returns a value.
92 | ///
93 | Task ExecuteAsync(Func, Task> action,
94 | ProgressDialogOptions options);
95 | }
96 | }
--------------------------------------------------------------------------------
/src/ProgressDialogEx/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
24 |
30 |
36 |
37 |
43 |
49 |
55 |
56 |
62 |
68 |
74 |
75 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/src/ProgressDialogEx/AttachedProperties/WindowBehavior.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 | using System.Windows;
4 | using System.Windows.Interop;
5 |
6 | namespace ProgressDialogEx.AttachedProperties
7 | {
8 | // From http://stackoverflow.com/a/3247575/91551
9 | public class WindowBehavior
10 | {
11 | private static readonly Type OwnerType = typeof(WindowBehavior);
12 |
13 | #region HideCloseButton (attached property)
14 |
15 | public static readonly DependencyProperty HideCloseButtonProperty =
16 | DependencyProperty.RegisterAttached(
17 | "HideCloseButton",
18 | typeof(bool),
19 | OwnerType,
20 | new FrameworkPropertyMetadata(false, HideCloseButtonChangedCallback));
21 |
22 | [AttachedPropertyBrowsableForType(typeof(Window))]
23 | public static bool GetHideCloseButton(Window obj)
24 | {
25 | return (bool)obj.GetValue(HideCloseButtonProperty);
26 | }
27 |
28 | [AttachedPropertyBrowsableForType(typeof(Window))]
29 | public static void SetHideCloseButton(Window obj, bool value)
30 | {
31 | obj.SetValue(HideCloseButtonProperty, value);
32 | }
33 |
34 | private static void HideCloseButtonChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
35 | {
36 | var window = d as Window;
37 | if (window == null) return;
38 |
39 | var hideCloseButton = (bool)e.NewValue;
40 | if (hideCloseButton && !GetIsHiddenCloseButton(window))
41 | {
42 | if (!window.IsLoaded)
43 | {
44 | window.Loaded += HideWhenLoadedDelegate;
45 | }
46 | else
47 | {
48 | HideCloseButton(window);
49 | }
50 | SetIsHiddenCloseButton(window, true);
51 | }
52 | else if (!hideCloseButton && GetIsHiddenCloseButton(window))
53 | {
54 | if (!window.IsLoaded)
55 | {
56 | window.Loaded -= ShowWhenLoadedDelegate;
57 | }
58 | else
59 | {
60 | ShowCloseButton(window);
61 | }
62 | SetIsHiddenCloseButton(window, false);
63 | }
64 | }
65 |
66 | #region Win32 imports
67 |
68 | private const int GWL_STYLE = -16;
69 | private const int WS_SYSMENU = 0x80000;
70 | [DllImport("user32.dll", SetLastError = true)]
71 | private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
72 | [DllImport("user32.dll")]
73 | private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
74 |
75 | #endregion
76 |
77 | private static readonly RoutedEventHandler HideWhenLoadedDelegate = (sender, args) =>
78 | {
79 | if (sender is Window == false) return;
80 | var w = (Window)sender;
81 | HideCloseButton(w);
82 | w.Loaded -= HideWhenLoadedDelegate;
83 | };
84 |
85 | private static readonly RoutedEventHandler ShowWhenLoadedDelegate = (sender, args) =>
86 | {
87 | if (sender is Window == false) return;
88 | var w = (Window)sender;
89 | HideCloseButton(w);
90 | w.Loaded -= ShowWhenLoadedDelegate;
91 | };
92 |
93 | private static void HideCloseButton(Window w)
94 | {
95 | var hwnd = new WindowInteropHelper(w).Handle;
96 | SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) & ~WS_SYSMENU);
97 | }
98 |
99 | private static void ShowCloseButton(Window w)
100 | {
101 | var hwnd = new WindowInteropHelper(w).Handle;
102 | SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) | WS_SYSMENU);
103 | }
104 |
105 | #endregion
106 |
107 | #region IsHiddenCloseButton (readonly attached property)
108 |
109 | private static readonly DependencyPropertyKey IsHiddenCloseButtonKey =
110 | DependencyProperty.RegisterAttachedReadOnly(
111 | "IsHiddenCloseButton",
112 | typeof(bool),
113 | OwnerType,
114 | new FrameworkPropertyMetadata(false));
115 |
116 | public static readonly DependencyProperty IsHiddenCloseButtonProperty =
117 | IsHiddenCloseButtonKey.DependencyProperty;
118 |
119 | [AttachedPropertyBrowsableForType(typeof(Window))]
120 | public static bool GetIsHiddenCloseButton(Window obj)
121 | {
122 | return (bool)obj.GetValue(IsHiddenCloseButtonProperty);
123 | }
124 |
125 | private static void SetIsHiddenCloseButton(Window obj, bool value)
126 | {
127 | obj.SetValue(IsHiddenCloseButtonKey, value);
128 | }
129 |
130 | #endregion
131 |
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/ProgressDialogEx/ProgressDialogEx.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | x86
6 | 8.0.30703
7 | 2.0
8 | {85C1B8BF-A213-4C8F-BD62-FBFD953CBB7C}
9 | WinExe
10 | Properties
11 | ProgressDialogEx
12 | ProgressDialogEx
13 | v4.5
14 |
15 |
16 | 512
17 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
18 | 4
19 | .\
20 | true
21 |
22 |
23 | x86
24 | true
25 | full
26 | false
27 | bin\Debug\
28 | DEBUG;TRACE
29 | prompt
30 | 4
31 | false
32 |
33 |
34 | x86
35 | pdbonly
36 | true
37 | bin\Release\
38 | TRACE
39 | prompt
40 | 4
41 | false
42 |
43 |
44 |
45 | packages\MvvmLightLibs.4.1.27.0\lib\net45\GalaSoft.MvvmLight.Extras.WPF45.dll
46 |
47 |
48 | packages\MvvmLightLibs.4.1.27.0\lib\net45\GalaSoft.MvvmLight.WPF45.dll
49 |
50 |
51 | packages\CommonServiceLocator.1.0\lib\NET35\Microsoft.Practices.ServiceLocation.dll
52 |
53 |
54 |
55 | packages\MvvmLightLibs.4.1.27.0\lib\net45\System.Windows.Interactivity.dll
56 |
57 |
58 |
59 | 4.0
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | MSBuild:Compile
68 | Designer
69 |
70 |
71 |
72 |
73 | MSBuild:Compile
74 | Designer
75 |
76 |
77 | App.xaml
78 | Code
79 |
80 |
81 | MainWindow.xaml
82 | Code
83 |
84 |
85 | MSBuild:Compile
86 | Designer
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 | ProgressDialogWindow.xaml
97 |
98 |
99 |
100 | Code
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
117 |
--------------------------------------------------------------------------------
/src/ProgressDialogEx/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Windows;
4 | using ProgressDialogEx.ProgressDialog;
5 |
6 | namespace ProgressDialogEx
7 | {
8 | public partial class MainWindow : Window
9 | {
10 | readonly IProgressDialogService progressDialog = new ProgressDialogService();
11 |
12 | readonly ProgressDialogOptions options = new ProgressDialogOptions
13 | {
14 | WindowTitle = "Progress Dialog",
15 | Label = "Progress Dialog Label"
16 | };
17 |
18 | public MainWindow()
19 | {
20 | InitializeComponent();
21 | DataContext = this;
22 | MyLongRunningCommand = new MyLongRunningCommand(progressDialog);
23 | }
24 |
25 | public MyLongRunningCommand MyLongRunningCommand { get; private set; }
26 |
27 | // Using non-MVVM code-behind for purposes of example.
28 |
29 | void ShowProgressDialog(object sender, RoutedEventArgs e)
30 | {
31 | progressDialog.Execute(Sleep, options);
32 | }
33 |
34 | void ShowProgressDialogWithCancelButton(object sender, RoutedEventArgs e)
35 | {
36 | progressDialog.Execute(CancellableSleep, options);
37 | }
38 |
39 | void ShowProgressDialogWithCancelButtonAndProgress(object sender, RoutedEventArgs e)
40 | {
41 | progressDialog.Execute(CancellableSleepWithProgress, options);
42 | }
43 |
44 | void ShowProgressDialogWithResult(object sender, RoutedEventArgs e)
45 | {
46 | int result;
47 | if (progressDialog.TryExecute(SleepAndReturnResult, options, out result))
48 | MessageBox.Show(String.Format("Result = {0}", result));
49 | }
50 |
51 | void ShowProgressDialogWithResultAndCancelButton(object sender, RoutedEventArgs e)
52 | {
53 | int result;
54 | if (progressDialog.TryExecute(CancellableSleepAndReturnResult, options, out result))
55 | MessageBox.Show(String.Format("Result = {0}", result));
56 | }
57 |
58 | void ShowProgressDialogWithResultAndCancelButtonAndProgress(object sender, RoutedEventArgs e)
59 | {
60 | int result;
61 | if (progressDialog.TryExecute(CancellableSleepWithProgressAndReturnResult, options, out result))
62 | MessageBox.Show(String.Format("Result = {0}", result));
63 | }
64 |
65 | void ShowProgressDialogWithError(object sender, RoutedEventArgs e)
66 | {
67 | try
68 | {
69 | int result;
70 | if (progressDialog.TryExecute(SleepAndThrowError, options, out result))
71 | MessageBox.Show(String.Format("Result = {0}", result));
72 | }
73 | catch (Exception ex)
74 | {
75 | MessageBox.Show(ex.ToString());
76 | }
77 | }
78 |
79 | void ShowProgressDialogWithErrorAndCancelButton(object sender, RoutedEventArgs e)
80 | {
81 | try
82 | {
83 | int result;
84 | if (progressDialog.TryExecute(CancellableSleepAndThrowError, options, out result))
85 | MessageBox.Show(String.Format("Result = {0}", result));
86 | }
87 | catch (Exception ex)
88 | {
89 | MessageBox.Show(ex.ToString());
90 | }
91 | }
92 |
93 | void ShowProgressDialogWithErrorAndCancelButtonAndProgress(object sender, RoutedEventArgs e)
94 | {
95 | try
96 | {
97 | int result;
98 | if (progressDialog.TryExecute(CancellableSleepWithProgressAndThrowError, options, out result))
99 | MessageBox.Show(String.Format("Result = {0}", result));
100 | }
101 | catch (Exception ex)
102 | {
103 | MessageBox.Show(ex.ToString());
104 | }
105 | }
106 |
107 | #region Sleeps
108 |
109 | const int Limit = 20;
110 |
111 | static void CancellableSleep(CancellationToken cancellationtoken)
112 | {
113 | for (int i = 0; i < Limit; i++)
114 | {
115 | cancellationtoken.ThrowIfCancellationRequested();
116 | Thread.Sleep(TimeSpan.FromMilliseconds(100));
117 | }
118 | }
119 |
120 | static void CancellableSleepWithProgress(CancellationToken cancellationtoken,
121 | IProgress progress)
122 | {
123 | for (int i = 0; i < Limit; i++)
124 | {
125 | cancellationtoken.ThrowIfCancellationRequested();
126 | progress.Report(String.Format("Processing {0}/{1}", i, Limit));
127 | Thread.Sleep(TimeSpan.FromMilliseconds(100));
128 | }
129 | }
130 |
131 | static void Sleep()
132 | {
133 | Thread.Sleep(TimeSpan.FromSeconds(2));
134 | }
135 |
136 | static int SleepAndReturnResult()
137 | {
138 | Sleep();
139 | return 42;
140 | }
141 |
142 | static int CancellableSleepAndReturnResult(CancellationToken cancellationtoken)
143 | {
144 | CancellableSleep(cancellationtoken);
145 | return 42;
146 | }
147 |
148 | static int CancellableSleepWithProgressAndReturnResult(CancellationToken cancellationtoken, IProgress progress)
149 | {
150 | CancellableSleepWithProgress(cancellationtoken, progress);
151 | return 42;
152 | }
153 |
154 | static int CancellableSleepWithProgressAndThrowError(CancellationToken cancellationtoken, IProgress progress)
155 | {
156 | CancellableSleepWithProgress(cancellationtoken, progress);
157 | throw new Exception("Test exception");
158 | }
159 |
160 | static int CancellableSleepAndThrowError(CancellationToken cancellationtoken)
161 | {
162 | CancellableSleep(cancellationtoken);
163 | throw new Exception("Test exception");
164 | }
165 |
166 | static int SleepAndThrowError()
167 | {
168 | Sleep();
169 | throw new Exception("Test exception");
170 | }
171 |
172 | #endregion
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | WPF MVVM Progress Dialog Example
2 | =============================
3 |
4 | This is a quick example WPF application that shows an MVVM-style progress meter dialog, using the .NET 4.5 Task Parallel Library (TPL):
5 |
6 | * An injectable, stateless IProgressDialogService, that can be injected for unit testing purposes.
7 | * A progress meter using .NET 4 Tasks (not BackgroundWorkers).
8 | * Cancelling of Tasks via CancellationTokens.
9 | * Exception handling back to the original thread.
10 | * Reporting background progress updates via IProgress<T>.
11 | * Tasks can either be declared as void, or have a return value.
12 | * Progress can be reported from multiple tasks chained together.
13 | * Using an attached property to hide [the close button in the progress dialog's title bar](http://stackoverflow.com/questions/743906/how-to-hide-close-button-in-wpf-window) using Win32 interop.
14 | * Using an attached property to implement a [close window command using MVVM](http://stackoverflow.com/questions/11945821/implementing-close-window-command-with-mvvm/).
15 |
16 | Note it uses [MVVM light](http://mvvmlight.codeplex.com) for some INotifyPropertyChanged and DispatcherHelper stuff.
17 |
18 | ### Example Usage
19 | The following example shows how you might set up a simple progress dialog, that can be cancelled and reports progress.
20 |
21 | ```csharp
22 | public class MyLongRunningCommand : ICommand
23 | {
24 | IProgressDialogService dialogService;
25 |
26 | public void Execute(object parameter)
27 | {
28 | dialogService.Execute(DoWork, new ProgressDialogOptions { WindowTitle = "Loading files" });
29 | }
30 |
31 | void DoWork(CancellationToken cancellationToken, IProgress progress)
32 | {
33 | for (int i = 0; i < 100; i++)
34 | {
35 | cancellationToken.ThrowIfCancellationRequested();
36 |
37 | progress.Report(String.Format("Processing task {0}", i));
38 |
39 | // Do some work
40 | Thread.Sleep(TimeSpan.FromMilliseconds(100));
41 | }
42 | }
43 |
44 | ...
45 | }
46 | ```
47 |
48 | ### Example Usage - Async Task Composition
49 | The following example shows how you might set up a progress dialog that is composed of multiple tasks chained together (that are cancellable and each report progress), and provide a return value.
50 |
51 | ```csharp
52 | public class MyLongRunningCommand : ICommand
53 | {
54 | readonly IProgressDialogService dialogService;
55 |
56 | public void Execute(object parameter)
57 | {
58 | Task task = dialogService.ExecuteAsync(DoWork, new ProgressDialogOptions { WindowTitle = "Loading files" });
59 |
60 | MessageBox.Show(String.Format("Result = {0}", task.Result));
61 | }
62 |
63 | static async Task DoWork(CancellationToken cancellationToken, IProgress progress)
64 | {
65 | return await Task.Factory.StartNew(() => progress.Report("First"), cancellationToken)
66 | .ContinueWith(_ => Thread.Sleep(1000), cancellationToken)
67 | .ContinueWith(_ => progress.Report("Second"), cancellationToken)
68 | .ContinueWith(_ => Thread.Sleep(1000), cancellationToken)
69 | .ContinueWith(_ => 42);
70 | }
71 |
72 | ...
73 | }
74 | ```
75 |
76 | ### IProgressDialogService
77 | The following signatures are supported - Execute (no return value) and TryExecute (a return value is expected, but will return false if cancelled).
78 | ```csharp
79 | public interface IProgressDialogService
80 | {
81 | ///
82 | /// Executes a non-cancellable task.
83 | ///
84 | void Execute(Action action, ProgressDialogOptions options);
85 |
86 | ///
87 | /// Executes a cancellable task.
88 | ///
89 | void Execute(Action action, ProgressDialogOptions options);
90 |
91 | ///
92 | /// Executes a non-cancellable task that reports progress.
93 | ///
94 | void Execute(Action> action, ProgressDialogOptions options);
95 |
96 | ///
97 | /// Executes a cancellable task that reports progress.
98 | ///
99 | void Execute(Action> action,
100 | ProgressDialogOptions options);
101 |
102 | ///
103 | /// Executes a non-cancellable task, that returns a value.
104 | ///
105 | bool TryExecute(Func action, ProgressDialogOptions options,
106 | out T result);
107 |
108 | ///
109 | /// Executes a cancellable task that returns a value.
110 | ///
111 | bool TryExecute(Func action,
112 | ProgressDialogOptions options, out T result);
113 |
114 | ///
115 | /// Executes a non-cancellable task that reports progress and returns a value.
116 | ///
117 | bool TryExecute(Func, T> action,
118 | ProgressDialogOptions options, out T result);
119 |
120 | ///
121 | /// Executes a cancellable task that reports progress and returns a value.
122 | ///
123 | bool TryExecute(Func, T> action,
124 | ProgressDialogOptions options, out T result);
125 |
126 | ///
127 | /// Executes a non-cancellable async task.
128 | ///
129 | Task ExecuteAsync(Func action, ProgressDialogOptions options);
130 |
131 | ///
132 | /// Executes a cancellable async task.
133 | ///
134 | Task ExecuteAsync(Func action, ProgressDialogOptions options);
135 |
136 | ///
137 | /// Executes a non-cancellable async task that reports progress.
138 | ///
139 | Task ExecuteAsync(Func, Task> action, ProgressDialogOptions options);
140 |
141 | ///
142 | /// Executes a cancellable async task that reports progress.
143 | ///
144 | Task ExecuteAsync(Func, Task> action,
145 | ProgressDialogOptions options);
146 |
147 | ///
148 | /// Executes a non-cancellable async task that returns a value.
149 | ///
150 | Task ExecuteAsync(Func> action, ProgressDialogOptions options);
151 |
152 | ///
153 | /// Executes a cancellable async task that returns a value.
154 | ///
155 | Task ExecuteAsync(Func> action, ProgressDialogOptions options);
156 |
157 | ///
158 | /// Executes a non-cancellable async task that reports progress and returns a value.
159 | ///
160 | Task ExecuteAsync(Func, Task> action, ProgressDialogOptions options);
161 |
162 | ///
163 | /// Executes a cancellable async task that reports progress and returns a value.
164 | ///
165 | Task ExecuteAsync(Func, Task> action,
166 | ProgressDialogOptions options);
167 | }
168 | ```
169 |
170 | ### Credits
171 |
172 | This is based upon Jürgen Bäurle's original blog post here: http://www.parago.de/2011/04/how-to-implement-a-modern-progress-dialog-for-wpf-applications/
173 |
--------------------------------------------------------------------------------
/src/ProgressDialogEx/ProgressDialog/ProgressDialogService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | namespace ProgressDialogEx.ProgressDialog
6 | {
7 | public class ProgressDialogService : IProgressDialogService
8 | {
9 | readonly TaskFactory taskFactory;
10 |
11 | public ProgressDialogService() : this(Task.Factory) { }
12 |
13 | public ProgressDialogService(TaskFactory taskFactory)
14 | {
15 | if (taskFactory == null) throw new ArgumentNullException("taskFactory");
16 | this.taskFactory = taskFactory;
17 | }
18 |
19 | public void Execute(Action action, ProgressDialogOptions options)
20 | {
21 | if (action == null) throw new ArgumentNullException("action");
22 | ExecuteInternal((token, progress) => action(), options,
23 | isCancellable: false);
24 | }
25 |
26 | public void Execute(Action action, ProgressDialogOptions options)
27 | {
28 | if (action == null) throw new ArgumentNullException("action");
29 | ExecuteInternal((token, progress) => action(token), options);
30 | }
31 |
32 | public void Execute(Action> action, ProgressDialogOptions options)
33 | {
34 | if (action == null) throw new ArgumentNullException("action");
35 | ExecuteInternal((token, progress) => action(progress), options);
36 | }
37 |
38 | public void Execute(
39 | Action> action, ProgressDialogOptions options)
40 | {
41 | if (action == null) throw new ArgumentNullException("action");
42 | ExecuteInternal(action, options);
43 | }
44 |
45 | public bool TryExecute(Func action, ProgressDialogOptions options, out T result)
46 | {
47 | if (action == null) throw new ArgumentNullException("action");
48 | return TryExecuteInternal((token, progress) => action(), options, out result,
49 | isCancellable: false);
50 | }
51 |
52 | public bool TryExecute(Func action, ProgressDialogOptions options, out T result)
53 | {
54 | if (action == null) throw new ArgumentNullException("action");
55 | return TryExecuteInternal((token, progress) => action(token), options, out result);
56 | }
57 |
58 | public bool TryExecute(Func, T> action, ProgressDialogOptions options, out T result)
59 | {
60 | if (action == null) throw new ArgumentNullException("action");
61 | return TryExecuteInternal((token, progress) => action(progress), options, out result,
62 | isCancellable: false);
63 | }
64 |
65 | public bool TryExecute(Func, T> action,
66 | ProgressDialogOptions options, out T result)
67 | {
68 | if (action == null) throw new ArgumentNullException("action");
69 | return TryExecuteInternal(action, options, out result);
70 | }
71 |
72 | public async Task ExecuteAsync(Func action, ProgressDialogOptions options)
73 | {
74 | if (action == null) throw new ArgumentNullException("action");
75 | await ExecuteAsyncInternal((token, progress) => action(), options);
76 | }
77 |
78 | public async Task ExecuteAsync(Func action, ProgressDialogOptions options)
79 | {
80 | if (action == null) throw new ArgumentNullException("action");
81 | await ExecuteAsyncInternal((token, progress) => action(token), options);
82 | }
83 |
84 | public async Task ExecuteAsync(Func, Task> action, ProgressDialogOptions options)
85 | {
86 | if (action == null) throw new ArgumentNullException("action");
87 | await ExecuteAsyncInternal((token, progress) => action(progress), options,
88 | isCancellable: false);
89 | }
90 |
91 | public async Task ExecuteAsync(Func, Task> action,
92 | ProgressDialogOptions options)
93 | {
94 | if (action == null) throw new ArgumentNullException("action");
95 | await ExecuteAsyncInternal(action, options);
96 | }
97 |
98 | public async Task ExecuteAsync(Func> action, ProgressDialogOptions options)
99 | {
100 | if (action == null) throw new ArgumentNullException("action");
101 | return await ExecuteAsyncInternal((token, progress) => action(), options);
102 | }
103 |
104 | public async Task ExecuteAsync(Func> action, ProgressDialogOptions options)
105 | {
106 | if (action == null) throw new ArgumentNullException("action");
107 | return await ExecuteAsyncInternal((token, progress) => action(token), options);
108 | }
109 |
110 | public async Task ExecuteAsync(Func, Task> action, ProgressDialogOptions options)
111 | {
112 | if (action == null) throw new ArgumentNullException("action");
113 | return await ExecuteAsyncInternal((token, progress) => action(progress), options,
114 | isCancellable: false);
115 | }
116 |
117 | public async Task ExecuteAsync(Func, Task> action,
118 | ProgressDialogOptions options)
119 | {
120 | if (action == null) throw new ArgumentNullException("action");
121 | return await ExecuteAsyncInternal(action, options);
122 | }
123 |
124 | private void ExecuteInternal(Action> action,
125 | ProgressDialogOptions options, bool isCancellable = true)
126 | {
127 | if (action == null) throw new ArgumentNullException("action");
128 | if (options == null) throw new ArgumentNullException("options");
129 |
130 | using (var cancellationTokenSource = new CancellationTokenSource())
131 | {
132 | CancellationToken cancellationToken = cancellationTokenSource.Token;
133 |
134 | var cancelCommand = isCancellable ? new CancelCommand(cancellationTokenSource) : null;
135 |
136 | var viewModel = new ProgressDialogWindowViewModel(
137 | options, cancellationToken, cancelCommand);
138 |
139 | var window = new ProgressDialogWindow
140 | {
141 | DataContext = viewModel
142 | };
143 |
144 | var task = taskFactory
145 | .StartNew(() => action(cancellationToken, viewModel.Progress),
146 | cancellationToken);
147 |
148 | task.ContinueWith(_ => viewModel.Close = true);
149 |
150 | window.ShowDialog();
151 | }
152 | }
153 |
154 | private bool TryExecuteInternal(
155 | Func, T> action,
156 | ProgressDialogOptions options, out T result, bool isCancellable = true)
157 | {
158 | if (action == null) throw new ArgumentNullException("action");
159 | if (options == null) throw new ArgumentNullException("options");
160 |
161 | using (var cancellationTokenSource = new CancellationTokenSource())
162 | {
163 | CancellationToken cancellationToken = cancellationTokenSource.Token;
164 |
165 | var cancelCommand = isCancellable ? new CancelCommand(cancellationTokenSource) : null;
166 |
167 | var viewModel = new ProgressDialogWindowViewModel(
168 | options, cancellationToken, cancelCommand);
169 |
170 | var window = new ProgressDialogWindow
171 | {
172 | DataContext = viewModel
173 | };
174 |
175 | var task = taskFactory
176 | .StartNew(() => action(cancellationToken, viewModel.Progress),
177 | cancellationToken);
178 |
179 | task.ContinueWith(_ => viewModel.Close = true);
180 |
181 | window.ShowDialog();
182 |
183 | if (task.IsCanceled)
184 | {
185 | result = default(T);
186 | return false;
187 | }
188 |
189 | if (task.IsCompleted)
190 | {
191 | result = task.Result;
192 | return true;
193 | }
194 |
195 | result = default(T);
196 | return false;
197 | }
198 | }
199 |
200 | async private Task ExecuteAsyncInternal(
201 | Func, Task> action,
202 | ProgressDialogOptions options, bool isCancellable = true)
203 | {
204 | if (action == null) throw new ArgumentNullException("action");
205 | if (options == null) throw new ArgumentNullException("options");
206 |
207 | using (var cancellationTokenSource = new CancellationTokenSource())
208 | {
209 | CancellationToken cancellationToken = cancellationTokenSource.Token;
210 |
211 | var cancelCommand = isCancellable ? new CancelCommand(cancellationTokenSource) : null;
212 |
213 | var viewModel = new ProgressDialogWindowViewModel(
214 | options, cancellationToken, cancelCommand);
215 |
216 | var window = new ProgressDialogWindow
217 | {
218 | DataContext = viewModel
219 | };
220 |
221 | Task task = action(cancellationToken, viewModel.Progress);
222 |
223 | task.ContinueWith(_ => viewModel.Close = true);
224 |
225 | window.ShowDialog();
226 |
227 | return await task;
228 | }
229 | }
230 |
231 | async private Task ExecuteAsyncInternal(
232 | Func, Task> action,
233 | ProgressDialogOptions options, bool isCancellable = true)
234 | {
235 | if (action == null) throw new ArgumentNullException("action");
236 | if (options == null) throw new ArgumentNullException("options");
237 |
238 | using (var cancellationTokenSource = new CancellationTokenSource())
239 | {
240 | CancellationToken cancellationToken = cancellationTokenSource.Token;
241 |
242 | var cancelCommand = isCancellable ? new CancelCommand(cancellationTokenSource) : null;
243 |
244 | var viewModel = new ProgressDialogWindowViewModel(
245 | options, cancellationToken, cancelCommand);
246 |
247 | var window = new ProgressDialogWindow
248 | {
249 | DataContext = viewModel
250 | };
251 |
252 | Task task = action(cancellationToken, viewModel.Progress);
253 |
254 | task.ContinueWith(_ => viewModel.Close = true);
255 |
256 | window.ShowDialog();
257 |
258 | await task;
259 | }
260 | }
261 | }
262 | }
--------------------------------------------------------------------------------