I have the following view model used in MainWindow.xaml, the view model is called MainViewModel:
public abstract class AbstractPropNotifier : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
public sealed class MainViewModel : AbstractPropNotifier
{
private bool _editEnabled;
private bool _deleteEnabled;
private ICommand _editCommand;
private ICommand _deleteCommand;
private IRssViewModel _selectedIrssi;
private IAsyncCommand _addCommand;
private readonly Dispatcher _dispatcher;
public MainViewModel(Dispatcher dispatcher)
{
_dispatcher = dispatcher;
IrssItems = new ObservableCollection<IRssViewModel>();
Log = new ObservableCollection<string>();
EditEnabled = false;
DeleteEnabled = false;
EditCommand = new RelayCommand(c => EditItem(), p => EditEnabled);
DeleteCommand = new RelayCommand(DeleteItems, p => DeleteEnabled);
AddCommand = new AsyncCommand(AddItem, () => true);
}
public ObservableCollection<IRssViewModel> IrssItems { get; set; }
public IRssViewModel SelectedIrssi
{
get
{
return _selectedIrssi;
}
set
{
_selectedIrssi = value;
OnPropertyChanged(nameof(SelectedIrssi));
EditEnabled = DeleteEnabled = true;
}
}
public ObservableCollection<string> Log { get; set; }
public bool EditEnabled
{
get
{
return _editEnabled;
}
set
{
_editEnabled = value || SelectedIrssi != null;
OnPropertyChanged(nameof(EditEnabled));
}
}
public bool DeleteEnabled
{
get
{
return _deleteEnabled;
}
set
{
_deleteEnabled = value || SelectedIrssi != null;
OnPropertyChanged(nameof(DeleteEnabled));
}
}
public ICommand EditCommand
{
get
{
return _editCommand;
}
set
{
_editCommand = value;
}
}
public ICommand DeleteCommand
{
get
{
return _deleteCommand;
}
set
{
_deleteCommand = value;
}
}
public IAsyncCommand AddCommand
{
get
{
return _addCommand;
}
set
{
_addCommand = value;
}
}
private void EditItem()
{
}
private void DeleteItems(object selectedItems)
{
var list = selectedItems as IList;
var newList = new List<IRssViewModel>(list.Cast<IRssViewModel>());
if (MessageBox.Show($"Are you sure that want to delete {newList.Count} item{(newList.Count > 1 ? "s" : "")} ?", "Deletion", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
foreach (var item in newList)
{
IrssItems.Remove(item as IRssViewModel);
}
EditEnabled = DeleteEnabled = false;
}
}
private async Task AddItem()
{
var win = new ManageIrssi("Add item");
var result = win.ShowDialog();
if (result.HasValue && result.Value)
{
foreach (var data in win.Model.Items)
{
//check stuff
IrssItems.Add(data);
await CreateConnection(data);
}
}
}
private async Task CreateConnection(IRssViewModel data)
{
await Task.Run(() =>
{
IrcManager manager = new IrcManager(new CustomLogger(), data);
manager.Build(s => _dispatcher.Invoke(() => Log.Add(s)));
data.IsConnected = true;
});
}
}
and AsynCommand is got from https://johnthiriet.com/mvvm-going-async-with-async-command/
public class AsyncCommand : IAsyncCommand
{
public event EventHandler CanExecuteChanged;
private bool _isExecuting;
private readonly Func<Task> _execute;
private readonly Func<bool> _canExecute;
private readonly IErrorHandler _errorHandler;
public AsyncCommand(
Func<Task> execute,
Func<bool> canExecute = null,
IErrorHandler errorHandler = null)
{
_execute = execute;
_canExecute = canExecute;
_errorHandler = errorHandler;
}
public bool CanExecute()
{
return !_isExecuting && (_canExecute?.Invoke() ?? true);
}
public async Task ExecuteAsync()
{
if (CanExecute())
{
try
{
_isExecuting = true;
await _execute();
}
finally
{
_isExecuting = false;
}
}
RaiseCanExecuteChanged();
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
#region Explicit implementations
bool ICommand.CanExecute(object parameter)
{
return CanExecute();
}
void ICommand.Execute(object parameter)
{
ExecuteAsync().GetAwaiter().GetResult();
}
#endregion
}
The problem I met is that After press a button Add, the last line data.IsConnected = true; is executed and then nothing happens means UI is frozen and no item is added in UI datagrid.
I removed also part _dispatcher.Invoke(() => Log.Add(s), same issue, UI frozen.
Why ? Where is my mistake ? Seems the problem is in await CreateConnection(data)
Your sample code is neither compilable or minimal, but I can spot a flaw in the Execute method of your command:
void ICommand.Execute(object parameter)
{
ExecuteAsync().GetAwaiter().GetResult();
}
Calling Result on a Task may deadlock and is a big no-no, especially in GUI applications. Try to fire away the Task and then return from the method:
async void ICommand.Execute(object parameter)
{
await ExecuteAsync().ConfigureAwait(false);
}
Problem is AddItem is on UI thread and since it is Awaits on UI Thread, your UI stalls.
Take AddItem on new thread and release UI thread, dispatch it to main thread once it is complete and update the UI
Related
I have following ICommand implementation, which works great, but I want to expand it so I can pass external canExecute parameter
public class AsyncRelayCommand : ICommand
{
private readonly Func<object, Task> callback;
private readonly Action<Exception> onException;
private bool isExecuting;
public bool IsExecuting
{
get => isExecuting;
set
{
isExecuting = value;
CanExecuteChanged?.Invoke(this, new EventArgs());
}
}
public event EventHandler CanExecuteChanged;
public AsyncRelayCommand(Func<object, Task> callback, Action<Exception> onException = null)
{
this.callback = callback;
this.onException = onException;
}
public bool CanExecute(object parameter) => !IsExecuting;
public async void Execute(object parameter)
{
IsExecuting = true;
try
{
await callback(parameter);
}
catch (Exception e)
{
onException?.Invoke(e);
}
IsExecuting = false;
}
}
Can this implementation be extended in a way so when caller's CanExecute() changes, both Execute1AsyncCommand and Execute2AsyncCommand will acknowledge that? Here is my caller class:
public class Caller : ObservableObject
{
public ObservableTask Execute1Task { get; } = new ObservableTask();
public ObservableTask Execute2Task { get; } = new ObservableTask();
public ICommand Execute1AsyncCommand { get; }
public ICommand Execute2AsyncCommand { get; }
public Caller()
{
Execute1AsyncCommand = new AsyncRelayCommand(Execute1Async);
Execute2AsyncCommand = new AsyncRelayCommand(Execute2Async);
}
private bool CanExecute(object o)
{
return Task1?.Running != true && Task2?.Running != true;
}
private async Task Execute1Async(object o)
{
Task1.Running = true;
try
{
await Task.Run(()=>Thread.Sleep(2000)).ConfigureAwait(true);
Task1.RanToCompletion = true;
}
catch (Exception e)
{
Task1.Faulted = true;
}
}
private async Task Execute2Async(object o)
{
Task2.Running = true;
try
{
await Task.Run(() => Thread.Sleep(2000)).ConfigureAwait(true);
Task2.RanToCompletion = true;
}
catch (Exception e)
{
Task2.Faulted = true;
}
}
}
In other callers I still want to be able to use AsyncRelayCommand() with just callback being mandatory. In this case CanExecute should be evaluated internally from AsyncRelayCommand as in my original implementation.
For completeness, here is my view:
<StackPanel>
<Button Content="Execute Task 1"
Command="{Binding Execute1AsyncCommand}" />
<Button Content="Execute Task 2"
Command="{Binding Execute2AsyncCommand}" />
<TextBlock Text="Task 1 running:" />
<TextBlock Text="{Binding Task1.Running}" />
<TextBlock Text="Task 2 running:" />
<TextBlock Text="{Binding Task2.Running}" />
</StackPanel>
And ObservableTask class:
public class ObservableTask : ObservableObject
{
private bool running;
private bool ranToCompletion;
private bool faulted;
public Task Task { get; set; }
public bool WaitingForActivation => !Running && !RanToCompletion && !Faulted;
public bool Running
{
get => running;
set
{
running = value;
if (running)
{
RanToCompletion = false;
Faulted = false;
}
}
}
public bool RanToCompletion
{
get => ranToCompletion;
set
{
ranToCompletion = value;
if (ranToCompletion)
{
Running = false;
}
}
}
public bool Faulted
{
get => faulted;
set
{
faulted = value;
if (faulted)
{
Running = false;
}
}
}
}
What I want to achieve is after user press one button both become disabled until all tasks are done.
Solution
I ended up with the following implementation which so far works as intended:
public class AsyncRelayCommand : ICommand
{
private bool isExecuting;
private readonly Func<object, Task> execute;
private readonly Predicate<object> canExecute;
private readonly Action<Exception, object> onException;
private Dispatcher Dispatcher { get; }
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public AsyncRelayCommand(Func<object, Task> execute, Predicate<object> canExecute = null, Action<Exception, object> onException = null)
{
this.execute = execute;
this.canExecute = canExecute;
this.onException = onException;
Dispatcher = Application.Current.Dispatcher;
}
private void InvalidateRequerySuggested()
{
if (Dispatcher.CheckAccess())
CommandManager.InvalidateRequerySuggested();
else
Dispatcher.Invoke(CommandManager.InvalidateRequerySuggested);
}
public bool CanExecute(object parameter) => !isExecuting && (canExecute == null || canExecute(parameter));
private async Task ExecuteAsync(object parameter)
{
if (CanExecute(parameter))
{
try
{
isExecuting = true;
InvalidateRequerySuggested();
await execute(parameter);
}
catch (Exception e)
{
onException?.Invoke(e, parameter);
}
finally
{
isExecuting = false;
InvalidateRequerySuggested();
}
}
}
public void Execute(object parameter) => _ = ExecuteAsync(parameter);
}
Usage:
public class Caller: ObservableObject
{
public ObservableTask Task1 { get; } = new ObservableTask();
public ObservableTask Task2 { get; } = new ObservableTask();
public ObservableTask Task3 { get; } = new ObservableTask();
public ICommand Execute1AsyncCommand { get; }
public ICommand Execute2AsyncCommand { get; }
public ICommand Execute3AsyncCommand { get; }
public Caller()
{
// Command with callers CanExecute method and error handled by callers method.
Execute1AsyncCommand = new AsyncRelayCommand(Execute1Async, CanExecuteAsMethod, Execute1ErrorHandler);
// Command with callers CanExecute parameter and error handled inside task therefore not needed.
Execute2AsyncCommand = new AsyncRelayCommand(Execute2Async, _=>CanExecuteAsParam);
// Some other, independent command.
// Minimum example - CanExecute is evaluated inside command, error handled inside task.
Execute3AsyncCommand = new AsyncRelayCommand(Execute3Async);
}
public bool CanExecuteAsParam => !(Task1.Running || Task2.Running);
private bool CanExecuteAsMethod(object o)
{
return !(Task1.Running || Task2.Running);
}
private async Task Execute1Async(object o)
{
Task1.Running = true;
await Task.Run(() => { Thread.Sleep(2000); }).ConfigureAwait(true);
Task1.RanToCompletion = true;
}
private void Execute1ErrorHandler(Exception e, object o)
{
Task1.Faulted = true;
}
private async Task Execute2Async(object o)
{
try
{
Task2.Running = true;
await Task.Run(() => { Thread.Sleep(2000); }).ConfigureAwait(true);
Task2.RanToCompletion = true;
}
catch (Exception e)
{
Task2.Faulted = true;
}
}
private async Task Execute3Async(object o)
{
try
{
Task3.Running = true;
await Task.Run(() => { Thread.Sleep(2000); }).ConfigureAwait(true);
Task3.RanToCompletion = true;
}
catch (Exception e)
{
Task3.Faulted = true;
}
}
}
Thank you everybody for invaluable help!
I have some ready-to-use solution.
Regular synchronous delegate, thus it can replace simple RelayCommand.
Delegate executed on a pooled Thread.
CanExecute is false while command is executing, thus it will disable the control automatically.
Implementation
public interface IAsyncCommand : ICommand
{
Task ExecuteAsync(object param);
}
public class AsyncRelayCommand : IAsyncCommand
{
private bool _isExecuting;
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
private Dispatcher Dispatcher { get; }
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public AsyncRelayCommand(Action<object> execute, Predicate<object> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
Dispatcher = Application.Current.Dispatcher;
}
private void InvalidateRequerySuggested()
{
if (Dispatcher.CheckAccess())
CommandManager.InvalidateRequerySuggested();
else
Dispatcher.Invoke(CommandManager.InvalidateRequerySuggested);
}
public bool CanExecute(object parameter) => !_isExecuting && (_canExecute == null || _canExecute(parameter));
public async Task ExecuteAsync(object parameter)
{
if (CanExecute(parameter))
{
try
{
_isExecuting = true;
InvalidateRequerySuggested();
await Task.Run(() => _execute(parameter));
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
finally
{
_isExecuting = false;
InvalidateRequerySuggested();
}
}
}
public void Execute(object parameter) => _ = ExecuteAsync(parameter);
}
Usage
private IAsyncCommand _myAsyncCommand;
public IAsyncCommand MyAsyncCommand => _myAsyncCommand ?? (_myAsyncCommand = new AsyncRelayCommand(parameter =>
{
Thread.Sleep(2000);
}));
Note: you can't deal with ObservableCollection from non-UI Thread, as workaround I suggest this one.
Asynchronous delegate version
public class AsyncRelayCommand : IAsyncCommand
{
private bool _isExecuting;
private readonly Func<object, Task> _executeAsync;
private readonly Predicate<object> _canExecute;
private Dispatcher Dispatcher { get; }
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public AsyncRelayCommand(Func<object, Task> executeAsync, Predicate<object> canExecute = null)
{
_executeAsync = executeAsync;
_canExecute = canExecute;
Dispatcher = Application.Current.Dispatcher;
}
private void InvalidateRequerySuggested()
{
if (Dispatcher.CheckAccess())
CommandManager.InvalidateRequerySuggested();
else
Dispatcher.Invoke(CommandManager.InvalidateRequerySuggested);
}
public bool CanExecute(object parameter) => !_isExecuting && (_canExecute == null || _canExecute(parameter));
public async Task ExecuteAsync(object parameter)
{
if (CanExecute(parameter))
{
try
{
_isExecuting = true;
InvalidateRequerySuggested();
await _executeAsync(parameter);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
finally
{
_isExecuting = false;
InvalidateRequerySuggested();
}
}
}
public void Execute(object parameter) => _ = ExecuteAsync(parameter);
}
Usage
private IAsyncCommand _myAsyncCommand;
public IAsyncCommand MyAsyncCommand => _myAsyncCommand ?? (_myAsyncCommand = new AsyncRelayCommand(async parameter =>
{
await Task.Delay(2000);
}));
If your Caller had a method called CanExecute like this:
private bool CanExecute()
{
return SomeCondition && OtherCondition;
}
Then you would be able to pass it to your AsyncRelayCommand as an instance of delegate type Func<bool>, of course, if your AsyncRelayCommand defined constructor with the needed parameter:
public AsyncRelayCommand(Func<object, Task> callback, Func<bool> canExecute, Action<Exception> onException = null)
{
this.callback = callback;
this.onException = onException;
this.canExecute = canExecute;
}
Then you pass it to the constructor like this:
MyAsyncCommand = new AsyncRelayCommand(ExecuteAsync, CanExecute, ErrorHandler);
Thus, your AsyncRelayCommand would be able to invoke canExecute delegate and will get the actual results.
Or you can leave CanExecute as the property, but when you create AsyncRelayCommand, wrap it to the lambda expression like this
MyAsyncCommand = new AsyncRelayCommand(ExecuteAsync, () => CanExecute, ErrorHandler);
To apply fallback logic to your CanExecute for AsyncRelayCommand you can change the code in the following way:
have an instance variable of type Func<bool> called, let's say, _canExecute. Then assign it in the constructor with whatever value accepted as the argument Func<bool> canExecute even if it's null. Then in your public CanExecute(object param) just check if _canExecute is null, just return !IsExecuting as you're doing it now, if it's not null, then return whatever _canExecute return.
For example I have something like this. When I am clicking on first button it start's async process and then I am clicking second button it start's second process. But I need only one process to work after clicking on each button. How can I cancel other process?
namespace WpfApplication55
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
TestCombo TC = new TestCombo();
public MainWindow()
{
DataContext = TC;
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
TC.Begin(60);
}
private void Button_Click1(object sender, RoutedEventArgs e)
{
TC.Begin(120);
}
}
public class TestCombo:INotifyPropertyChanged
{
private int someData;
public int SomeData
{
get { return someData; }
set { someData = value; RaisePropertyChanged("SomeData"); }
}
public void StartCount(int input)
{
SomeData = input;
while (input>0)
{
System.Threading.Thread.Sleep(1000);
input -= 1;
SomeData = input;
}
}
public void Begin(int input)
{
Action<int> Start = new Action<int>(StartCount);
IAsyncResult result = Start.BeginInvoke(input, null, null);
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged (string info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
I'm not exactly sure how you want the while condition in StartCount to work but as long as you evaluating the new CancellationToken you should be good to cancel. Remember the Thread.Sleep won't cancel while its sleeping. So you may have up to a 1s delay.
public void StartCount(int input, CancellationToken token)
{
SomeData = input;
while (input > 0 && !token.IsCancellationRequested)
{
System.Threading.Thread.Sleep(1000);
input -= 1;
SomeData = input;
}
}
IAsyncResult process;
public void Begin(int input)
{
if (process != null && !process.IsCompleted)
((CancellationTokenSource)process.AsyncState).Cancel();
Action<int, CancellationToken> Start = new Action<int, CancellationToken>(StartCount);
var cancelSource = new CancellationTokenSource();
process = Start.BeginInvoke(input,cancelSource.Token, null, cancelSource);
}
I would use Microsoft's Reactive Framework for this.
Here's your class:
public class TestCombo : INotifyPropertyChanged
{
private int someData;
public int SomeData
{
get { return someData; }
set { someData = value; RaisePropertyChanged("SomeData"); }
}
private SingleAssignmentDisposable _subscription = new SingleAssignmentDisposable();
public void Begin(int input)
{
_subscription.Disposable =
Observable
.Interval(TimeSpan.FromSeconds(1.0))
.Select(x => input - (int)x)
.Take(input)
.ObserveOnDispatcher()
.Subscribe(x => this.SomeData = x);
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged (string info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
The two key parts to this solution is first the observable query subscription which does all of the timing, computes the value to assign to SomeData and marshals the assignment to the UI thread.
The second is the SingleAssignmentDisposable. When you assign a new IDisposable to its Disposable property it will dispose any previously assigned IDisposable.
The disposing cancels the previous subscription.
Just NuGet "Rx-WPF" to get the WPF bits for Rx.
Try something like this:
namespace WpfApplication55
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
TestCombo TC = new TestCombo();
CancellationTokenSource cts;
public MainWindow()
{
DataContext = TC;
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
if (cts != null)
{
cts.Cancel();
}
cts = new CancellationTokenSource();
await TC.DoAsync(60, cts.Token);
}
private void Button_Click1(object sender, RoutedEventArgs e)
{
if (cts != null)
{
cts.Cancel();
}
cts = new CancellationTokenSource();
await TC.DoAsync(120, cts.Token);
}
}
public class TestCombo:INotifyPropertyChanged
{
private int someData;
public int SomeData
{
get { return someData; }
set { someData = value; RaisePropertyChanged("SomeData"); }
}
public void StartCount(int input)
{
SomeData = input;
while (input>0)
{
System.Threading.Thread.Sleep(1000);
input -= 1;
SomeData = input;
}
}
public Task DoAsync(int input, CancellationToken cancellationToken)
{
return Task.Run(StartCount, cancellationToken);
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged (string info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Try using class CancellationTokenSource;
See code below-
CancellationTokenSource ctstask = new CancellationTokenSource();
ctstask.Cancel();//This line should be called from 2nd button click.
I'm currently creating a WPF form which retrieve some manual information and then perform some activities through PowerShell.
Activity is a class that implement INotifyPropertyChanged in order to reflect property changes (for example when an activity fails, its status changes from "running" to "Error").
I've created a listView (datagrid) which itemsSource is an ObservableCollection named Sequence. Sequence is declared in mainWindows class.
Activity Class is that :
public class Activity : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int step;
public int Step
{
get { return this.step; }
set
{
if (this.step != value)
{
this.step = value;
this.NotifyPropertyChanged("Step");
}
}
}
private String group;
public String Group
{
get { return this.group; }
set
{
if (this.group != value)
{
this.group = value;
this.NotifyPropertyChanged("Group");
}
}
}
private String activityName;
public String ActivityName
{
get { return this.activityName; }
set
{
if (this.activityName != value)
{
this.activityName = value;
this.NotifyPropertyChanged("ActivityName");
}
}
}
private Model.Status status;
public Model.Status Status
{
get { return this.status; }
set
{
if (this.status != value)
{
this.status = value;
this.NotifyPropertyChanged("Status");
}
}
}
private String cmdlet;
public String Cmdlet
{
get { return this.cmdlet; }
set
{
if (this.cmdlet != value)
{
this.cmdlet = value;
this.NotifyPropertyChanged("Cmdlet");
}
}
}
private Dictionary<String, Object> parameters;
public Dictionary<String, Object> Parameters
{
get { return this.parameters; }
set { this.parameters = value; }
}
public Activity(int _Step, String _ActivityName, String _Group , Model.Status _Status, String _Cmdlet, Dictionary<String, Object> _Parameters)
{
this.Step = _Step;
this.Group = _Group;
this.ActivityName = _ActivityName;
this.Status = _Status;
this.Cmdlet = _Cmdlet;
this.Parameters = _Parameters;
}
public void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
internal void Run(ref Runspace _rs)
{
using (PowerShell ps = PowerShell.Create())
{
ps.Runspace = _rs;
//ps.AddCommand(this.cmdlet).AddParameter("Site", this._Selection.SelectedSite);
ps.AddCommand(this.cmdlet).AddParameters(this.Parameters);
Collection<PSObject> results = ps.Invoke();
if (results.Count > 0 && ps.HadErrors == false)
{
foreach (PSObject item in results)
{
this.status = (Model.Status)Enum.Parse(typeof(Model.Status), item.ToString(), true);
}
}
else
{
this.Status = Model.Status.Error;
}
}
}
}
Deployment start when I click on the Last "Next" button.
A function Deploy is started :
private void DeploySite()
{
// Build list of activities
//
int i = 0;
Dictionary<String, Object> _params = new Dictionary<string,object>();
_params.Add("Site", this._Selection.SelectedSite);
this.Sequence.Add(new Model.Activity(i++, "Update System Discovery Method", "Configure Administration Node", Model.Status.NoStatus, "Update-SystemDiscoveryMethod", _params));
this.Sequence.Add(new Model.Activity(i++, "Update User Discovery Method", "Configure Administration Node", Model.Status.NoStatus, "Update-SystemDiscoveryMethod", _params));
// Start Execution of activities
//
foreach (Model.Activity activity in this.Sequence)
{
activity.Status = Model.Status.Running;
activity.Run(ref rs);
}
}
My problem is that the view is updated only when every activities are finished, in other words when the DeploySite() function ends.
I would like to view progression of each activities when there are executed.
Any help will be greatly appreciated.
Since you are continuously doing work in the UI thread in DeploySite(), there is no chance for the View to do anything. You should do the work in a background thread, and only do the UI updates themselves in the UI thread.
Run your activities in a new thread an dispatch results/updates to the UI thread. Current implementation is blocking the UI, as mentioned by #Daniel Rose.
Here, I hope this meta code helps you solve it.
private void DeploySite()
{
// Fire up a new Task
Task.Run(() =>
{
for (int i = 0; i < 10; i++)
{
int i1 = i;
// Dispatch
System.Windows.Application.Current.Dispatcher.BeginInvoke(
new Action(() => Sequence.Add(i1)), null);
Thread.Sleep(100);
}
});
}
I have a simple button that uses a command when executed, this is all working fine but I would like to pass a text parameter when the button is clicked.
I think my XAML is ok, but I'm unsure how to edit my RelayCommand class to receive a parameter:
<Button x:Name="AddCommand" Content="Add"
Command="{Binding AddPhoneCommand}"
CommandParameter="{Binding Text, ElementName=txtAddPhone}" />
public class RelayCommand : ICommand
{
private readonly Action _handler;
private bool _isEnabled;
public RelayCommand(Action handler)
{
_handler = handler;
}
public bool IsEnabled
{
get { return _isEnabled; }
set
{
if (value != _isEnabled)
{
_isEnabled = value;
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
}
}
public bool CanExecute(object parameter)
{
return IsEnabled;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_handler();
}
}
Change Action to Action<T> so that it takes a parameter (probably just Action<object> is easiest).
private readonly Action<object> _handler;
And then simply pass it the parameter:
public void Execute(object parameter)
{
_handler(parameter);
}
You could just do
public ICommand AddPhoneCommand
{
get
{
return new Command<string>((x) =>
{
if(x != null) { AddPhone(x); }
};
}
}
Then, of course have your AddPhone:
public void AddPhone(string x)
{
//handle x
}
You can simply do this (no change to RelayCommand or ICommand required):
private RelayCommand _addPhoneCommand;
public RelayCommand AddPhoneCommand
{
get
{
if (_addPhoneCommand == null)
{
_addPhoneCommand = new RelayCommand(
(parameter) => AddPhone(parameter),
(parameter) => IsValidPhone(parameter)
);
}
return _addPhoneCommand;
}
}
public void AddPhone(object parameter)
{
var text = (string)parameter;
...
}
public void IsValidPhone(object parameter)
var text = (string)parameter;
...
}
Currenlty, I'm using as Below.
In xaml,
<Button Content="X" Width="33" Height="16" Padding="1,-2,1,0"
Command="{Binding ElementName=UserControlName, Path=DataContext.DenyCommand}"
<Button.CommandParameter>
<wpfext:UICommandParameter UICommandCallerCallback="{Binding ElementName=UserControlName, Path=UIDenyCallBackCommand}"/>
</Button.CommandParameter>
</Button>
In xaml.cs,
public UICommandCallerCallback UIDenyCallBackCommand
{
get;
private set;
}
public UserControlName()
{
this.UIDenyCallBackCommand = this.UIAccessDenyCallBack;
this.InitializeComponent();
}
public void UIAccessDenyCallBack(object commandParameter, object callbackData)
{
ShowADenyMsgBox();
}
private void ShowDenyMsgBox()
{
RightsDenied win = new RightsDenied(); //xaml window
win.Owner = GetImmediateWindow();
win.WindowStartupLocation = WindowStartupLocation.CenterScreen;
win.ShowDialog();
}
In ViewModel.cs,
internal ViewModel()
{
this.DenyCommand= new DenyCommand(this.AccessDeny);
}
public void AccessDeny(ICommandState commandState)
{
commandState.InvokeCallerCallback("AccessDenied");
}
public CommandCallback DenyCommand
{
get;
private set;
}
UICommandCallerCallback is declared as below.
public delegate void UICommandCallerCallback(object commandParameter, object callbackData);
CommandCallback class is as below.
public class CommandCallback:ICommand
{
private readonly Action<ICommandState> executeMethod;
private readonly Func<ICommandState, bool> canExecuteMethod;
public CommandCallback(Action<ICommandState> executeMethod)
: this(executeMethod, null)
{
}
public CommandCallback(Action<ICommandState> executeMethod, Func<ICommandState, bool> canExecuteMethod)
{
if (executeMethod == null)
{
throw new ArgumentNullException("executeMethod");
}
this.executeMethod = executeMethod;
this.canExecuteMethod = canExecuteMethod;
}
public bool CanExecute(object parameter)
{
return this.canExecuteMethod != null ? this.canExecuteMethod((ICommandState)parameter) : true;
}
public void Execute(object parameter)
{
if (parameter == null)
{
throw new ArgumentNullException("parameter","CommandCallback parameter cannot be null");
}
if (!(parameter is ICommandState))
{
throw new ArgumentException("expects a parameter of type ICommandState","parameter");
}
ICommandState state = (ICommandState)parameter;
this.executeMethod.Invoke(state);
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
}
It's working fine if it just to pop up the dialog box, but I want to wait for the result of the dialog and want to continue AccessDeny() function. For eg.
public void AccessDeny(ICommandState commandState)
{
1. processs
2. open xaml window and wait for the dialogresult. (i.e Yes No or Cancel)
3. Based on the result, continue processing.
}
What could be the best way to do this work flow? Please advise. Thanks.
Read through User Interaction Patterns in this documentation.