Progressbar progress during long task - c#

It's my first post here, so I hope I'm doing everything correct.
I'm using the .NET Framework 4 Client Profile.
I want to load data from a .doc file into my program and work with this information. This can take a lot of time since I need to run through the tables of the document and check what's inside. That is already working, the only problem here is the screen is freezing and you can't see if something is happening.
Also I know this would be faster and way easier in excel, but since this type of data is and was always stored in word-documents in our company I have to keep it like that.
So what I want to do is count all rows from the tables that I have to read, set this as my Maximum Value for the Progress-Bar and then after each row I would count the value + 1.
I have my load Button with the Command bound to LoadWordDocCmd and the progress bar:
<Button Name="btnLoadFile"
Content="Load" Height="23"
Command="{Binding LoadWordDocCmd}"
HorizontalAlignment="Right" Margin="0,22,129,0"
VerticalAlignment="Top" Width="50"
Visibility="{Binding VisModeAddNew}"
/>
<ProgressBar HorizontalAlignment="Left" Height="24" Margin="574,52,0,0"
VerticalAlignment="Top" Width="306"
Name="prgBarAddNewLoadWord"
Minimum="0"
Maximum="{Binding AddNewProgressBarMaxVal, Mode=OneWay}"
Value="{Binding AddNewProgressBarValue, Mode=OneWay}"
Visibility="{Binding AddNewProgressBarVisible}"/>
Here is the RelayCommand:
/// <summary>
/// Relaycommand for Function loadWordDocument
/// </summary>
public RelayCommand LoadWordDocCmd
{
get
{
if (this.m_loadWordDocCmd == null)
{
this.m_loadWordDocCmd = new RelayCommand(this.loadWordDocument, canLoadWordDoc);
}
return m_loadWordDocCmd;
}
private set
{
this.m_loadWordDocCmd = value;
}
}
/// <summary>
/// checks if the Word Document can be loaded
/// </summary>
/// <param name="parameter">not used</param>
/// <returns>if it could Execute, then true, else false</returns>
private bool canLoadWordDoc(object parameter)
{
bool ret = false;
if (this.m_fileSelected)
{
ret = true;
}
return ret;
}
What I already did was to work with a BackgroundWorker.
I was able to bind the Button-Command to a function that has a RelayCommand with the BackgroundWorker, but then I wasn't able to check the canExecute function anymore.
I used this to test the Progress-Bar, that was working :
xaml:
<Button ...
Command="{Binding Path=InstigateWorkCommand}"
/>
cs :
private BackgroundWorker worker;
private ICommand instigateWorkCommand;
public ProggressbarSampleViewModel()
{
this.instigateWorkCommand = new
RelayCommand(o => this.worker.RunWorkerAsync(), o => !this.worker.IsBusy);
this.worker = new BackgroundWorker();
this.worker.DoWork += this.DoWork;
this.worker.ProgressChanged += this.ProgressChanged;
}
public ICommand InstigateWorkCommand
{
get { return this.instigateWorkCommand; }
}
private int _currentProgress;
public int CurrentProgress
{
get { return this._currentProgress; }
private set
{
if (this._currentProgress != value)
{
this._currentProgress = value;
OnPropertyChanged("CurrentProgress");
}
}
}
private void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.CurrentProgress = e.ProgressPercentage;
}
private void DoWork(object sender, DoWorkEventArgs e)
{
// do time-consuming work here, calling ReportProgress as and when you can
for (int i = 0; i < 100; i++)
{
Thread.Sleep(1000);
_currentProgress = i;
OnPropertyChanged("CurrentProgress");
}
}
But how can I get this to work with the canExecute ? Here is my function-Header:
/// <summary>
/// Function for Load Word Document
/// </summary>
/// <param name="parameter">not used</param>
private void loadWordDocument(object parameter)
Here is the Relay-Command Class:
public class RelayCommand : ICommand
{
private readonly Action<object> methodToExecute;
private readonly Func<object, bool> canExecute;
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
EventHandler handler = CanExecuteChanged;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
public RelayCommand(Action<object> execute)
: this(execute, null) { }
public RelayCommand(Action<object> methodToExecute, Func<object, bool> canExecute)
{
this.methodToExecute = methodToExecute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
// wird keine canExecute-Funktion übergeben, so liefert diese
// true zurück, ansonsten wird die custom canExecute-Funktion
// mit den übergebenen Parametern aufgerufen.
return canExecute == null ? true : canExecute.Invoke(parameter);
}
public void Execute(object parameter)
{
methodToExecute(parameter);
}
}
Thank you for your help and I hope I posted this question correct!

I hope I understand your issue correctly.
The basic rule for a GUI application is: don't use the GUI thread for (time-consuming) data processing. You have to perform this task on a background thread.
Since you're using .NET 4.0 Client Profile, the async/await feature is not available to you. That would be the easiest solution, however.
You can do this with a ThreadPool instead. The BackgroundWorker is not recommended anymore.
In your XAML, you're binding the ProgressBar.Value property to a AddNewProgressBarValue property, so I assume you have a view-model with that property already. You have to ensure that changing AddNewProgressBarValue will raise the PropertyChanged event. And the good news is, the WPF Binding Engine automatically marshals the property value transfer operation to the GUI thread, so you don't need to care about which thread is changing a property your progress bar is bound to.
So the solution might look like this (not a production code, just an idea!):
class ViewModel : INotifyPropertyChanged
{
private bool isProcessing;
public bool AddNewProgressBarVisible
{
get { return this.isProcessing; }
// SetProperty here is a PRISM-like helper to set the backing field value
// and to raise the PropertyChanged event when needed.
// You might be using something similar.
private set { this.SetProperty(ref this.isProcessing, value, "AddNewProgressBarVisible");
}
private int progressValue;
public int AddNewProgressBarValue
{
get { return this.progressValue; }
private set { this.SetProperty(ref this.progressValue, value, "AddNewProgressBarValue");
}
// This is your command handler
private void LoadWordDocument(object parameter)
{
if (this.isProcessing)
{
// don't allow multiple operations at the same time
return;
}
// indicate that we're staring an operation:
// AddNewProgressBarVisible will set isProcessing = true
this.AddNewProgressBarVisible = true;
this.AddNewProgressBarValue = 0;
// Notify the bound button, that it has to re-evaluate its state.
// Effectively, this disables the button.
this.LoadWordDocCmd.RaiseCanExecuteChanged();
// Run the processing on a background thread.
ThreadPool.QueueUserWorkItem(this.DoLoadWordDocument);
}
private void DoLoadWordDocument(object state)
{
// Do your document loading here,
// this method will run on a background thread.
// ...
// You can update the progress bar value directly:
this.AddNewProgressBarValue = 42; // ...estimate the value first
// When you're done, don't forget to enable the button.
this.AddNewProgressBarVisible = false;
// We have to marshal this to the GUI thread since your ICommand
// implementation doesn't do this automatically
Application.Current.Dispatcher.Invoke(() => this.LoadWordDocCmd.RaiseCanExecuteChanged());
}
// this is your command enabler method
private bool CanLoadWordDoc(object parameter)
{
// if we're already loading a document, the command should be disabled
return this.m_fileSelected && !this.isProcessing;
}
}

I think that your ProggressbarSampleViewModel code sample is ok. I tested it and it works.
I am assuming that you want to change LoadWordDocCmd to have the behavior of InstigateWorkCommand. If you put the code from ProgressbarSampleViewModel into your actual ViewModel, you should have no problem accessing loadWordDocument and canLoadWordDoc. In addition, as mm8 mentioned, in your DoWork method you need to call RaiseCanExecuteChanged or else WPF will not check the CanExecute method.
Your ViewModel should look like bellow. See comments in upper case.
private BackgroundWorker worker;
private RelayCommand instigateWorkCommand; //CHANGE HERE
bool isBusy = false; // ADD THIS
public ProggressbarSampleViewModel()
{
//CHANGE NEXT LINE
this.instigateWorkCommand = new RelayCommand(
o => this.worker.RunWorkerAsync(),
o => !isBusy && canLoadWordDoc(null));
this.worker = new BackgroundWorker();
this.worker.DoWork += this.DoWork;
//REMOVE
//this.worker.ProgressChanged += this.ProgressChanged;
}
public ICommand InstigateWorkCommand
{
get { return this.instigateWorkCommand; }
}
private int _currentProgress;
public int CurrentProgress
{
get { return this._currentProgress; }
private set
{
if (this._currentProgress != value)
{
this._currentProgress = value;
OnPropertyChanged("CurrentProgress");
}
}
}
//REMOVE
//private void ProgressChanged(object sender, ProgressChangedEventArgs e)
//{
// this.CurrentProgress = e.ProgressPercentage;
//}
private void DoWork(object sender, DoWorkEventArgs e)
{
//ADD NEXT LINES
isBusy = true;
Application.Current.Dispatcher.BeginInvoke(
(Action)instigateWorkCommand.RaiseCanExecuteChanged);
// do time-consuming work here, calling ReportProgress as and when you can
for (int i = 0; i <= 100; i++)
{
Thread.Sleep(10);
_currentProgress = i;
OnPropertyChanged("CurrentProgress");
}
//ADD NEXT LINES
isBusy = false;
Application.Current.Dispatcher.BeginInvoke(
(Action)instigateWorkCommand.RaiseCanExecuteChanged);
}
bool m_fileSelected = true; //CHANGE TO SEE THE EFFECT
//REMOVE
//RelayCommand m_loadWordDocCmd;
///// <summary>
///// Relaycommand for Function loadWordDocument
///// </summary>
//public RelayCommand LoadWordDocCmd
//{
// get
// {
// if (this.m_loadWordDocCmd == null)
// {
// this.m_loadWordDocCmd = new RelayCommand(this.loadWordDocument, canLoadWordDoc);
// }
// return m_loadWordDocCmd;
// }
// private set
// {
// this.m_loadWordDocCmd = value;
// }
//}
/// <summary>
/// checks if the Word Document can be loaded
/// </summary>
/// <param name="parameter">not used</param>
/// <returns>if it could Execute, then true, else false</returns>
private bool canLoadWordDoc(object parameter)
{
bool ret = false;
if (this.m_fileSelected)
{
ret = true;
}
return ret;
}
/// <summary>
/// Function for Load Word Document
/// </summary>
/// <param name="parameter">not used</param>
private void loadWordDocument(object parameter)
{
}
Hope this helps.

Related

How can I await for a button click in an async method?

I try to write a code to read a JSON File and allows user to input all the parametes for the objects in the JSON File one by one.
I try to write something like an "awaitable Button", but I failed to write a "GetAwaiter" extension for the button, although I found informations about how to do it.
https://learn.microsoft.com/en-us/dotnet/desktop/winforms/controls/how-to-inherit-from-existing-windows-forms-controls?view=netframeworkdesktop-4.8
how can I combine await.WhenAny() with GetAwaiter extension method
http://blog.roboblob.com/2014/10/23/awaiting-for-that-button-click/
So here is my code after clicking a button "loadJSON":
for (int i = 0; i<templist_net.Count; i++)
{
GeneratorFunctions.GetNetworkParameterList(templist_net[i].Type, templist_net[i], treeViewPath.SelectedPath, SolutionFolder);
cBoxPouItem.Text = templist_net[i].Type;
ListViewParam2.ItemsSource = GeneratorFunctions.TempList; // Parameter list binding
temp = GeneratorFunctions.TempList;
ListViewParam2.Visibility = Visibility.Visible; // Set list 2 visible
ListViewParam.Visibility = Visibility.Collapsed; // Set list 1 invisible
//something stop loop, and wait user to type parameters in Listview, and click Button, Then the loop move on.
}
And Here is code trying to write a Button with extension. I add a new class for custom control, and write the extension.
public partial class CustomControl2 : System.Windows.Forms.Button
{
static CustomControl2()
{
}
public static TaskAwaiter GetAwaiter(this Button self)
{
ArgumentNullException.ThrowIfNull(self);
TaskCompletionSource tcs = new();
self.Click += OnClick;
return tcs.Task.GetAwaiter();
void OnClick(object sender, EventArgs args)
{
self.Click -= OnClick;
tcs.SetResult();
}
}
}
But I can't write a extension, which inherit System.Windows.Forms.Button. What should I do?
UPDATE:
here is what i tried.
private async Task Btn_loadJsonAsync(object sender, RoutedEventArgs e) {
// Initialize an open file dialog, whose filter has a extend name ".json"
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "(*.json)|*.json";
TextBoxInformation.Text += "Opening project ...\n";
if (openFileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
networks = GeneratorFunctions.ReadjsonNetwork(openFileDialog.FileName);
for (int i = 0; i < networks.Count; i++)
{
if (temp != null)
{
if (networks[i].Type == "Network")
{
templist_net.Add(networks[i]);
i = 1;
}
if (networks[i].Type == "Subsystem")
{
templist_sub.Add(networks[i]);
i = 1;
}
if (networks[i].Type == "Component: Data Point Based Control")
{
templist_com.Add(networks[i]);
i = 1;
}
}
}
using (SemaphoreSlim semaphore = new SemaphoreSlim(0, 1))
{
void OnClick(object sender, RoutedEventArgs e) => semaphore.Release();
btn.Click += OnClick;
for (int i = 0; i < templist_net.Count; i++)
{
//...
//wait here until [btn] is clicked...
await semaphore.WaitAsync();
}
btn.Click -= OnClick;
}}}
You can wait for a button click asynchronously using a SemaphoreSlim, e.g.:
using (SemaphoreSlim semaphore = new SemaphoreSlim(0, 1))
{
void OnClick(object sender, RoutedEventArgs e) => semaphore.Release();
btn.Click += OnClick;
for (int i = 0; i < templist_net.Count; i++)
{
//...
//wait here until [btn] is clicked...
await semaphore.WaitAsync();
}
btn.Click -= OnClick;
}
Although you may want to redesign the way you are doing things, a quick an dirty solution would be to use a dialog box in modal mode and upon the dialog box closing, capture the data that got input and continue looping. The loop will block until the dialog box is closed.
First of all I must insist that your request goes against the principles of the MVVM pattern which is based on events.
Your logic should be in a separate class and expose an OnNext method which should be called from the model through an ActionCommand
Anyway, to conform (as much as possible) to the MVVM pattern, you don't want to await on the button but more on the command bound to the button.
So let's build an awaitable command :
public class AwaitableCommand : ICommand
{
private readonly object _lock = new();
private TaskCompletionSource? _taskCompletionSource;
/// <summary>
/// null-event since it's never raised
/// </summary>
public event EventHandler? CanExecuteChanged
{
add { }
remove { }
}
/// <summary>
/// Always executable
/// </summary>
public bool CanExecute(object? parameter) => true;
public void Execute(object? parameter)
{
lock (_lock)
{
if (_taskCompletionSource is null)
return;
_taskCompletionSource.SetResult();
// reset the cycle
_taskCompletionSource = null;
}
}
public Task WaitAsync()
{
lock (_lock)
{
// start a new cycle if needed
_taskCompletionSource ??= new TaskCompletionSource();
return _taskCompletionSource.Task;
}
}
}
Then you can create your logic with it (I put it in the model, wich is a bad practice):
public class Model : NotifyPropertyChangedBase
{
private int _count;
public Model()
{
RunLogicAsync();
}
public int Count
{
get => _count;
private set => Update(ref _count, value);
}
public AwaitableCommand OnNextCommand { get; } = new();
/// <summary>
/// I know, I know, we should avoid async void
/// </summary>
private async void RunLogicAsync()
{
try
{
for (;;)
{
await OnNextCommand.WaitAsync();
Count++;
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}
And your view:
<Window ...>
<Window.DataContext>
<viewModel:Model />
</Window.DataContext>
<Window.Resources>
<system:String x:Key="StringFormat">You clicked it {0} times</system:String>
</Window.Resources>
<Grid>
<Button Content="{Binding Count}"
ContentStringFormat="{StaticResource StringFormat}"
Command="{Binding OnNextCommand}"
Padding="10 5"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
</Window>
Working demo available here.

Is this call really async?

Following:
Converting Action method call to async Action method call
and
The 'await' operator can only be used within an async lambda expression
I've come up with the following code:
CommandHandler.cs:
public class CommandHandler : ICommand
{
private Func<Task> _action;
private Func<bool> _canExecute;
/// <summary>
/// Creates instance of the command handler
/// </summary>
/// <param name="action">Action to be executed by the command</param>
/// <param name="canExecute">A bolean property to containing current permissions to execute the command</param>
public CommandHandler(Func<Task> action, Func<bool> canExecute)
{
_action = action;
_canExecute = canExecute;
}
/// <summary>
/// Wires CanExecuteChanged event
/// </summary>
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
/// <summary>
/// Forcess checking if execute is allowed
/// </summary>
/// <param name="parameter"></param>
/// <returns></returns>
public bool CanExecute(object parameter)
{
return _canExecute.Invoke();
}
public void Execute(object parameter)
{
_action(); // IS THIS REALLY ASYNC? VS DOESN'T COMPLAIN
}
}
AppliedJobsViewModel.cs:
class AppliedJobsViewModel
{
private TexParser texParser;
private ObservableCollection<AppliedJob> appliedJobsCollection;
public AppliedJobsViewModel() {
// TODO:
// -- do nothing here
}
public ObservableCollection<AppliedJob> AppliedJobsCollection
{
get
{
if (appliedJobsCollection == null)
{
appliedJobsCollection = new ObservableCollection<AppliedJob>();
}
return appliedJobsCollection;
}
set
{
if (value != null)
{
appliedJobsCollection = value;
}
}
}
private ICommand _openTexClick;
public ICommand OpenTexClick
{
get
{
return _openTexClick ?? (_openTexClick = new CommandHandler(() => ReadAndParseTexFile(), () => CanExecute));
}
}
public bool CanExecute
{
get
{
// check if executing is allowed, i.e., validate, check if a process is running, etc.
return true;
}
}
public async Task ReadAndParseTexFile()
{
if (texParser == null)
{
texParser = new TexParser();
}
// Read file asynchronously here
await Task.Run(() => ReadFileAndUpdateUI()); // LARGE SYNC CHUNK OF CODE
}
private void ReadFileAndUpdateUI()
{
texParser.ReadTexFile();
string[][] appliedJobsArray = texParser.getCleanTable();
// Update collection here
List<AppliedJob> appliedJobsList = createAppliedJobsListFromTable(appliedJobsArray);
appliedJobsCollection = new ObservableCollection<AppliedJob>(appliedJobsList);
}
private List<AppliedJob> createAppliedJobsListFromTable(string[][] table)
{
List<AppliedJob> jobsList = new List<AppliedJob>();
for (int i = 0; i < table.Length; i++)
{
jobsList.Add(new AppliedJob(table[i]));
}
return jobsList;
}
public ObservableCollection<AppliedJob> AppliedJobs {
get;
set;
}
}
Is the code "entirely"(from the start of the Func action) async? If yes, is it "fully" MVVM? If not, why?

MultiThreading c# WPF

I am facing an issue while binding value from c# code to WPF UI.
I have gone through the basic of threading and come to know that I have to use the Dispatcher to bind the ui-thread from my custom background thread.
I have a requirement of like, I want to update my WPF UI continuously by hitting the nse-stockmarket api every second and the do some logic accordingly so that I can show weather share price is increasing or decreasing.
Below is the code how I am trying to achieve this...
Note: I am not getting any kind of exception not even "CROSS-Thread"
//globally declared var stockName = "";
//wpf button click
private void Button_Click(object sender, RoutedEventArgs e)
{
stockName = "LUPIN";
new Thread(() =>
{
RunStockParallel(share.Key);
Action action = new Action(SetTextBoxValues);
}).Start();
}
public void RunStockParallel(string stockName){
var count = 0 ;
do
{
HttpWebRequest stocks = null;
try
{
//your logic will be here..
}
catch (Exception e)
{
//throw e;
}
//It will call the delegate method so that UI can update.
Action action = new Action(SetTextBoxValues);
stockName = count++;
} while (true);
}
private void SetTextBoxValues()
{
this.Dispatcher.Invoke(() =>
{
this.text1.Text = stockName;
});
}
As I am using do-while loop, it will keep looping until I terminate the application. In this do-while loop I am continuously trying to update the WPF ui by update the Text1 textbox with this "counter++;".
But its not working as expected. Need suggestion or solution. :)
You are not invoking the delegate that you are creating. Also, the variable that you are incrementing is not the variable that you are using to update the UI. You are only upgrading the local variable of the method RunStockParallel().
Below is a working version. Hopefully it helps.
PS: I would suggest not to use the below piece of code in production. When you close your application, SetTextBoxValues() will throw a TaskCanceledException which is not at all ideal. As someone has already mentioned, this is probably a very old fashioned way to perform concurrent tasks. You might want to switch to using a Task-based or async/await approach, where you can avoid such exceptions very effectively by using CancellationToken.
private void Button_Click(object sender, RoutedEventArgs e)
{
stockName = "LUPIN";
new Thread(() =>
{
RunStockParallel(stockName);
Action action = new Action(SetTextBoxValues); // Maybe this is not required? But this was present in your original code, so I left it as is.
}).Start();
}
public void RunStockParallel(string stockName)
{
var count = 0;
do
{
HttpWebRequest stocks = null;
try
{
//your logic will be here..
}
catch (Exception e)
{
//throw e;
}
//It will call the delegate method so that UI can update.
Action action = new Action(SetTextBoxValues);
//Invoke the delegate
action();
//Increment the globally declared var stockname
this.stockName = count++.ToString();
} while (true);
}
private void SetTextBoxValues()
{
this.Dispatcher.Invoke(() =>
{
this.text1.Text = stockName;
});
}
#tushardevsharma
I got an answer for this.. I have added just below peace of code in the RunStockParallel Method , inside the try catch block.. my main logic part with this
HttpWebRequest stocks = null;
try
{
//your logic will be here..
Dispatcher.BeginInvoke(new Action(() =>
{
txtName.Text = stockName;
}), DispatcherPriority.Background);
}
catch (Exception e)
{
//throw e;
}
I would do it the WPF way, so you don't need to care about using Dispatcher...
XAML Code:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525" d:DataContext="{d:DesignInstance local:ViewModel}">
<StackPanel>
<Button Command="{Binding StartPollingCommand}" Content="Start Polling" />
<TextBlock Text="{Binding StockValue}" />
</StackPanel>
</Window>
Your C# Code:
public partial class MainWindow {
public MainWindow() {
InitializeComponent();
DataContext = new ViewModel();
}
}
public class ViewModel : INotifyPropertyChanged {
private string _stockValue;
public event PropertyChangedEventHandler PropertyChanged;
public ICommand StartPollingCommand {
get { return new RelayCommand(param => DoExecuteStartPollingCommand()); }
}
private void DoExecuteStartPollingCommand() {
try {
Task.Run(() => RunStockParallel("StockName"));
} catch (Exception ex) {
//TODO
}
}
private void RunStockParallel(string stockName) {
var count = 0;
do {
try {
// Do Something to get your Data
//HttpWebRequest stocks = null;
var stockresults = DateTime.Now;
StockValue = stockresults.ToString();
} catch (Exception e) {
//throw e;
}
//Wait some time before getting the next stockresults
Thread.Sleep(1000);
} while (true);
}
public string StockValue {
get => _stockValue;
set {
_stockValue = value;
OnPropertyChanged();
}
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class RelayCommand : ICommand {
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action<object> execute)
: this(execute, null) { }
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<object> execute, Predicate<object> canExecute) {
if (execute == null)
throw new ArgumentNullException("execute"); //NOTTOTRANS
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameter) {
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged {
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public void Execute(object parameter) {
_execute(parameter);
}
#endregion // ICommand Members
}
You should end the running Task smoothly before closeing the application...
I don't know what error you have encountered but assuming you had a Cross Thread Operation Not Valid Error. It is probably due to this line
this.text1.Text = stockName;
You are accessing the text1 directly and thus will show you an error about Cros Threading. A safe way was to invoke the .Text method using a delegate function
this.text1.Invoke(new Action(() => this.text1.Text = stockName));
I have not tested it but you had the idea.
If you want to have a cross-threading safe call. You may refer to this
Cross-thread operation not valid: Control 'textBox1' accessed from a thread other than the thread it was created on
Your problem in the code is that there is no sleep. The thread is too busy.
You need to either add a Thread.Sleep(100) in the while loop or use a semaphore.

Set up button's IsEnabled property with ICommand [duplicate]

This question already has answers here:
WPF MVVM command canexecute enable/disable button
(4 answers)
Disable button in WPF?
(5 answers)
How does one "disable" a button in WPF using the MVVM pattern?
(5 answers)
Command source disabling and enabling
(1 answer)
Closed 5 years ago.
I have the command
public class RelayActionCommand : ICommand
{
/// <summary>
/// The Action Delegate representing a method with input parameter
/// </summary>
public Action<object> ExecuteAction { get; set; }
/// <summary>
/// The Delegate, used to represent the method which defines criteria for the execution
/// </summary>
public Predicate<object> CanExecuteAction { get; set; }
public bool CanExecute(object parameter)
{
if (CanExecuteAction != null)
{
return CanExecuteAction(parameter);
}
return true;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
if (ExecuteAction != null)
{
ExecuteAction(parameter);
}
}
}
To use it,
public RelayActionCommand SearchPersonCommnad { get; set; }
DataAccess objds;
public PersonViewModel()
{
Persons = new ObservableCollection<PersonInfo>();
objds = new DataAccess();
Persons = new ObservableCollection<PersonInfo>(objds.GetPersonData());
var defaultView = CollectionViewSource.GetDefaultView(Persons);
//based upon the data entered in the TextBox
SearchPersonCommnad = new RelayActionCommand()
{
CanExecuteAction = n=> !String.IsNullOrEmpty(Name),
ExecuteAction = n => defaultView.Filter = name => ((PersonInfo)name).FirstName.StartsWith(Name)
|| ((PersonInfo)name).LastName.StartsWith(Name)
|| ((PersonInfo)name).City==Name
};
At the beginning, the button is disabled. But in running time, it changes by different situations. My question is how to set up the button's IsEnabled property with it? Which means, when ExecuteAction I have to set up the property correctly.
UPDATE:
I use ICommand not DelegateCommand.
You can use the CanExecute method, but it is good practice is actually to avoid this, and bind the button's enabled state to a separate boolean property of the view model. Most other solutions will have unexpected effects, or be suboptimal. Why?
CanExecute is a method. This means that it needs to be polled for the button state to change. You can force the control that's using the command to re-poll on a status change, but the code is much cleaner and more straightforward if you just use a property on the view model. This is because as a method, you can't use INotifyPropertyChanged to notify for changes, whereas with a property you can.
The danger in using CanExecute is that the user will manage to click the button after the method would return false, but before the button's enablement has changed.
Edit: Code to do what you want:
public class ViewModel : INotifyPropertyChanged
{
private int someValue;
private bool isEnabled;
public ViewModel()
{
MyCommand = new RelayActionCommand(Click);
}
private void Click(object obj)
{
//Do something.
}
/// <summary>
/// Bind this to the IsEnabled property of the button, and
/// also the background using a convertor or see ButtonBackground.
/// </summary>
public bool IsEnabled => SomeValue < 5;
/// <summary>
/// Option 2 - use this property to bind to the background of the button.
/// </summary>
public Brush ButtonBackground => IsEnabled ? Brushes.SeaShell : Brushes.AntiqueWhite;
public int SomeValue
{
get { return someValue; }
set
{
if (value == someValue) return;
someValue = value;
OnPropertyChanged();
OnPropertyChanged(nameof(IsEnabled));
OnPropertyChanged(nameof(ButtonBackground));
}
}
/// <summary>
/// Bind this to the command of the button.
/// </summary>
public RelayActionCommand MyCommand { get; }
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged
([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Relay command fixed up a bit to avoid using CanExecute:
public class RelayActionCommand : ICommand
{
public RelayActionCommand(Action<object> executeAction)
{
ExecuteAction = executeAction;
}
/// <summary>
/// The Action Delegate representing a method with input parameter
/// </summary>
public Action<object> ExecuteAction { get; }
/// <summary>
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
ExecuteAction?.Invoke(parameter);
}
//Deliberately empty.
public event EventHandler CanExecuteChanged
{
add { }
remove { }
}
}
EDIT 2: Code to do what you want using a DelegateCommand
Note, this does not use InvalidateRequerySuggested - mainly because it refreshes all buttons when any CanExecute changes, which is a poor solution. As you can see, this is less immediately straightforward than putting the code in the view model directly, but whatever floats your boat I guess.
public sealed class ViewModel : INotifyPropertyChanged
{
private int calls;
public ViewModel()
{
SafeOnceCommand = new RelayCommand(DoItOnce, HasDoneIt);
}
private bool HasDoneIt()
{
return Calls == 0;
}
private void DoItOnce()
{
if (Calls > 0) throw new InvalidOperationException();
Calls++;
}
public int Calls
{
get { return calls; }
set
{
if (value == calls) return;
calls = value;
OnPropertyChanged();
SafeOnceCommand.RaiseCanExecuteChanged();
}
}
public RelayCommand SafeOnceCommand { get; }
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public sealed class RelayCommand : ICommand
{
private readonly Action execute;
private readonly Func<bool> canExecute;
private readonly List<EventHandler> invocationList = new List<EventHandler>();
public RelayCommand(Action execute, Func<bool> canExecute)
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return canExecute();
}
public void Execute(object parameter)
{
execute();
}
/// <summary>
/// Method to raise CanExecuteChanged event
/// </summary>
public void RaiseCanExecuteChanged()
{
foreach (var elem in invocationList)
{
elem(null, EventArgs.Empty);
}
}
public event EventHandler CanExecuteChanged
{
add { invocationList.Add(value); }
remove { invocationList.Remove(value); }
}
}

Using “Get Only” Static property as Binding source

I have class structures as mentioned below.
//On the design aspects, I know It may not be the advisable approach,
//but something of this kind is only required.
/// <summary>
/// Paper Class
/// </summary>
public class Paper
{
public string PaperName { get; set; }
public bool IsPending { get; set; }
}
/// <summary>
/// PaperChecking class, Individual papers will be processed here.
/// </summary>
public class PaperChecking
{
public static List<Paper> ListPapers { get; set; }
public static void AddPapers()
{
ListPapers = new List<Paper>();
ListPapers.Add(new Paper() { PaperName = "Paper1", IsPending = false });
ListPapers.Add(new Paper() { PaperName = "Paper2", IsPending = false });
ListPapers.Add(new Paper() { PaperName = "Paper3", IsPending = false });
ListPapers.Add(new Paper() { PaperName = "Paper4", IsPending = false });
ListPapers.Add(new Paper() { PaperName = "Paper5", IsPending = false });
}
public static bool IsCheckingPending
{
get
{
//List has items and it is not null, so intentionally removed the checks.
return ListPapers.Count(paper => paper.IsPending == true) > 0 ? true : false;
}
}
}
/// <summary>
/// This class will select papers for processing
/// </summary>
public class ChangePaperSetting
{
public void SelectPaper(string paperName)
{
//It can be assumed that Paper object will never be NULL
PaperChecking.ListPapers.FirstOrDefault(paper => paper.PaperName.Equals(paperName)).IsPending = true;
}
}
Now,
I want to use property PaperChecking.IsCheckingPending to display some controls in my WPF window. I have bound the same property with Visibility of my controls. When window loads for the first time behavior is expected because Collection is already there. But at run-time when I am changing the Pending status of Paper object as below :
ChangePaperSetting changePaperSetting = new ChangePaperSetting();
changePaperSetting.SelectPaper("Paper1");
changePaperSetting.SelectPaper("Paper2");
changePaperSetting.SelectPaper("Paper5");
In my collection, now I have papers which have IsPending as true. So now PaperChecking.IsCheckingPending will return TRUE and according to that my controls should be visible now.
In a normal object I could have implemented INotifyPropertyChanged , but in above case I do not have a Setter on the property. Is there any way of doing this or any other neat approach using same kind class structures.
//-------------------------------------------------------------------------------//
Update
As suggested by Josh, I tried something like this :
/// <summary>
/// Paper Class
/// </summary>
public class Paper : INotifyPropertyChanged
{
public string PaperName { get; set; }
private bool isPending;
public bool IsPending
{
get
{
return isPending;
}
set
{
if (isPending != value)
{
isPending = value;
PropertyChanged(this, new PropertyChangedEventArgs("IsPending"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
/// <summary>
/// PaperChecking class, Individual papers will be processed here.
/// </summary>
public class PaperChecking : Control
{
public static List<Paper> listOfPapers { get; set; }
public static bool IsCheckingPending
{
get
{
//List has items and it is not null, so intentionally removed the checks.
try
{
return listOfPapers.Count(paper => paper.IsPending == true) > 0 ? true : false;
}
catch (Exception ex) { return false; }
}
}
public static event PropertyChangedEventHandler PropertyChanged;
public static void PendingStatusChanged(object sender,PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsPending")
{
//If I keep it static, It given Null Reference Error
//and If I implement INotifyPropertyChanged interface
//in this Class, it gives compilation error because
//I am doing so in my Static property.
PropertyChanged(null,new PropertyChangedEventArgs("IsCheckingPending"));
}
}
}
/// <summary>
/// This class will select papers for processing
/// </summary>
public class ChangePaperSetting
{
public static void AddPapers()
{
var listOfPapers = new List<Paper>();
for (int i = 0; i < 5; i++)
{
var paper = new Paper() { PaperName = "Paper"+i.ToString(),
IsPending = false };
paper.PropertyChanged+=PaperChecking.PendingStatusChanged;
listOfPapers.Add(paper);
}
PaperChecking.listOfPapers = listOfPapers;
}
public void SelectPaper(string paperName)
{
//It can be assumed that Paper object will never be NULL
PaperChecking.listOfPapers.FirstOrDefault(paper => paper.PaperName.Equals(paperName)).IsPending = true;
}
}
Here is my XAML Code :
<Window xmlns:my="clr-namespace:LearningWpf" x:Class="LearningWpf.Window4"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window4" Height="300" Width="300"
>
<Window.Resources>
<my:PaperChecking x:Key="paperChecking"/>
<BooleanToVisibilityConverter x:Key="bvc" />
</Window.Resources>
<StackPanel>
<Button Name="btn1" Content="Button1" Height="20" Width="80" Click="btn1_Click"></Button>
<Button Name="btn2" Content="Button2" Height="20" Width="80" Click="btn2_Click"></Button>
<Button Name="btn3" Content="Button3" Height="20" Width="80"
Visibility="{Binding Source={StaticResource paperChecking},
Path=IsCheckingPending,
Converter={StaticResource bvc}}"></Button>
</StackPanel>
Here is my CodeBehind.cs
public partial class Window4 : Window
{
public Window4()
{
InitializeComponent();
}
private void btn1_Click(object sender, RoutedEventArgs e)
{
ChangePaperSetting.AddPapers();
}
private void btn2_Click(object sender, RoutedEventArgs e)
{
var v = PaperChecking.listOfPapers.FirstOrDefault(paper =>
paper.PaperName == "Paper1");
v.IsPending = true;
}
}
But this code is giving error, righlty so because I am using Static variable without initializing it. If there is any correction or any other approach to achieve the same target. Your help is highly appreciated.
Since you are using CLR property, so its your duty to notify the UI that underlying binding property has changed which is only achievable by raising PropertyChanged event from your code.
First of all make the collection as ObservableCollection since it implements INotifyCollectionChanged and INotifyPropertyChanged. Hook the collection changed event with your collection and in handler simply raise the propertyChanged event for your property like this -
ObservableCollection<Paper> listOfPapers = new ObservableCollection<Paper>();
listOfPapers.CollectionChanged += new NotifyCollectionChangedEventHandler(listOfPapers_CollectionChanged);
void listOfPapers_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged("IsCheckingPending");
}
With this approach you won't have to worry if you need to add items in the collection from method other than SelectPaper().
Other possible solution could have been use Dependency Property instead of plain CLR property that way you won't have to worry about raising the property changed explicitly.
http://msdn.microsoft.com/en-us/library/ms752914.aspx
Something like this, maybe?
private static bool _isCheckingPending;
public static bool IsCheckingPending
{
get
{
bool pending = ListPapers.Count(paper => paper.IsPending == true) > 0;
if (pending != _isCheckingPending)
{
PropertyChanged("IsCheckingPending");
_isCheckingPending = pending;
}
//List has items and it is not null, so intentionally removed the checks.
return _isCheckingPending;
}
}
The idea is that it remembers the result from last time, and if it differs from the result this time, raise the PropertyChanged event (and of course you will ave implemented INotifyPropertyChanged).
Off topic, but why are all the properties and functions within PaperChecking static?
To solve your problem though, add a new function to PaperChecking and implement INotifyPropertyChanged.
public void SelectPaper(string paperName)
{
var paper = ListPapers.FirstOrDefault(paper => paper.PaperName.Equals(paperName));
if (paper != null)
{
paper.IsPending = true;
PropertyChanged("IsCheckingPending");
}
}
I'm assuming you know how to implement INotifyPropertyChanged, and have your own way of raising the event. There is nothing that says you must raise the event from a setter of a Property.
Having your property getter cycle through your entire list of papers each time it is queried is very very inefficient. It can and will be called very very often. Pretty much with any mouse or keyboard event. You should try caching the value of the count each time the pending status is changed on a Paper. It will take a bit more work, but it might be worth doing.
Edit:
Actually Objects can be updated from multiple interfaces and they will not call same method, they have reference to PAPER object and will update property directly, instead of calling method in PaperChecking class
In that case, you should implement INotifyPropertyChanged on the Paper class, and then listen for those updates within PaperChecking.
public void PaperChanged(object sender, PropertyChangedEventArgs args)
{
if (args.PropertyName == 'IsPending') PropertyChanged("IsCheckingPending");
}
public void AddPapers()
{
ListPapers = new List<Paper>();
ListPapers.Add(new Paper() { PaperName = "Paper1", IsPending = false });
ListPapers.Add(new Paper() { PaperName = "Paper2", IsPending = false });
ListPapers.Add(new Paper() { PaperName = "Paper3", IsPending = false });
ListPapers.Add(new Paper() { PaperName = "Paper4", IsPending = false });
ListPapers.Add(new Paper() { PaperName = "Paper5", IsPending = false });
foreach(var paper in ListPapers)
{
paper.PropertyChanged += PaperChanged;
}
}
You also need to transform PaperChecking into a class that uses instance methods and properties rather than static ones. If you don't already know about MVVM, I suggest reading up on it. Essentially, what you'd do, is create an instance of PaperChecking, and set it as the DataSource in your code behind of the View. Then, in your XAML, you can simply bind like this:
<Button Name="btn3" Content="Button3" Height="20" Width="80"
Visibility="{Binding IsCheckingPending, Converter={StaticResource bvc}}" />
Static properties and methods are nearly always wrong when starting out with WPF. Know when you need to use them, and when you're trying to make things easier on yourself.

Categories