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.
Related
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.
I am trying to find a proper way of running existing methods using threads created in the ViewModel. The main purpose is to provide a responsive UI. I decided I want to use the Task-based Asynchronous Pattern, but I need to properly integrate it with WPF and MVVM.
So far, I found a way to run a lengthy task in another thread and report its progress. However, I couldn't find a way of updating the Button that starts the task so that it IsEnabled only when the task is not running. The following ViewModel describes what I have done:
public class MainViewModel : INotifyPropertyChanged
{
public void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
public event PropertyChangedEventHandler PropertyChanged;
// Do some time consuming work.
int SomeTask()
{
//SCENARIO: Consider that it takes longer than usual to start the worker thread.
Thread.Sleep(1000);
// Prevent executing the task by two threads at the same time.
lock ("IsReady")
{
if (IsReady == false)
throw new ApplicationException("Task is already running");
IsReady = false;
}
// The actual work that this task consists of.
TaskProgress = 0;
for (int i = 1; i <= 100; i++)
{
Thread.Sleep(50);
TaskProgress = i;
}
// Mark task as completed to allow rerunning it.
IsReady = true;
return 123;
}
// True when not started or completed.
bool _isReady = true;
public bool IsReady
{
get { return _isReady; }
set
{
_isReady = value;
NotifyPropertyChanged("IsReady");
StartTaskCommand.RaiseCanExecuteChanged();
}
}
// Indicate the current progress when running SomeTask.
int _taskProgress;
public int TaskProgress
{
get { return _taskProgress; }
set
{
_taskProgress = value;
NotifyPropertyChanged("TaskProgress");
}
}
// ICommand to start task asynchronously.
RelayCommand _startTask;
public RelayCommand StartTaskCommand
{
get
{
if (_startTask == null)
{
_startTask = new RelayCommand(
obj =>
{
Task<int> task = Task.Run((Func<int>)SomeTask);
task.ContinueWith(t =>
{
// SomeTask method may throw an ApplicationException.
if (!t.IsFaulted)
Result = t.Result.ToString();
});
},
obj => IsReady);
}
return _startTask;
}
}
string _result;
public string Result
{
get { return _result; }
set { _result = value; NotifyPropertyChanged("Result"); }
}
}
I use the following implementation of RelayCommand:
public class RelayCommand : ICommand
{
private Action<object> execute;
private Func<object, bool> canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return this.canExecute == null || this.canExecute(parameter);
}
public void Execute(object parameter)
{
this.execute(parameter);
}
}
The main problem is that the Button that executes the command does not update its status based on IsReady. I also tried setting it explicitly with IsEnabled="{Binding IsReady}", but it still doesn't work. The best article I found related to this problem is this one: Raising CanExecuteChanged.
The XAML is quite simple:
<DockPanel Margin="4">
<TextBox DockPanel.Dock="Right" Text="{Binding Result}" Width="100"/>
<Button DockPanel.Dock="Right" Content="Start" Margin="5,0"
Command="{Binding StartTaskCommand}"/>
<ProgressBar Value="{Binding TaskProgress}"/>
</DockPanel>
How do I fix IsReady to be reflected by the Button's state?
Does anyone recommend a minimalistic working implementation for what I need?
Thanks for taking your time to read.
Set the IsReady property to false on the UI thread before you start the task, and then set it back to true once the task has completed:
public RelayCommand StartTaskCommand
{
get
{
if (_startTask == null)
{
_startTask = new RelayCommand(
obj =>
{
if (IsReady)
{
//1. Disable the button on the UI thread
IsReady = false;
//2. Execute SomeTask on a background thread
Task.Factory.StartNew(SomeTask)
.ContinueWith(t =>
{
//3. Enable the button back on the UI thread
if (!t.IsFaulted)
Result = t.Result.ToString();
IsReady = true;
}, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
}
},
obj => IsReady);
}
return _startTask;
}
}
int SomeTask()
{
Thread.Sleep(1000);
TaskProgress = 0;
for (int i = 1; i <= 100; i++)
{
Thread.Sleep(50);
TaskProgress = i;
}
return 123;
}
You have to update your IsRady flag on the UI thread. I modify your sample to achieve the expected behavior:
int SomeTask()
{
// Prevent executing the task by two threads at the same time.
lock ("IsReady")
{
if (IsReady == false)
throw new ApplicationException("Task is already running");
Application.Current.Dispatcher.Invoke(() => { IsReady = false; });
}
//SCENARIO: Consider that it takes longer than usual to start the worker thread.
Thread.Sleep(1000);
// The actual work that this task consists of.
TaskProgress = 0;
for (int i = 1; i <= 100; i++)
{
Thread.Sleep(50);
TaskProgress = i;
}
// Mark task as completed to allow rerunning it.
Application.Current.Dispatcher.Invoke(() => { IsReady = true; });
return 123;
}
You were firing PropertyChanged event from other thread, that was the problem.
I put also the thread sleeping below the lock since I was getting your ApplicationExceptions lol
EDIT
Since you are raising canExecuteChanged you could solve this also by raising that event on the UI thread:
public bool IsReady
{
get { return _isReady; }
set
{
_isReady = value;
NotifyPropertyChanged("IsReady");
Application.Current.Dispatcher.Invoke(() =>
{
StartTaskCommand.RaiseCanExecuteChanged();
});
}
}
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.
this is my first proper C# application that I wrote to help me at work (I'm on helpdesk for an MSP with a passing interest in scripting and code) and I'm using UWP just to make it look pretty without much effort. Our time tracking software is a web service written in ASP.Net so generally the built in timer is fine but it won't survive a browser refresh, so I wrote my own that fits into the format that we need for our tickets.
I have taken some code from other Stack questions and my dad (A C# framework dev for a multinational) helped re-write some of the timer code so it wasn't using stopwatch. He just isn't available to fix this issue at the moment. I do understand how it works now, just not how to debug the issue I'm getting.
It supports multiple timers running at the same time and creating a new timer auto-pauses all others. It handles two time formats, minutes and decimal hours, so that will explain some of the properties you see in the code.
My issue is that when I add a new timer, it pauses all others, but then when I press start on an older timer (Returning back to an earlier ticket) the time instantly jumps up to how long the new timer was running for, with about 10% difference (It's never exactly how long it was running).
This is the class that tracks notes and the current time (Tidied up a bit for neatness):
public sealed class JobTimer:INotifyPropertyChanged
{
private DateTime _created; // When the timer was created
private DateTime _started; // When it was most recently started
private TimeSpan _offset; // The saved value to offset the currently running timer
Timer _swTimer; // The actual tick that updates the screen
public JobTimer() : this(TimeSpan.Zero)
{ }
public JobTimer(TimeSpan offset)
{
_offset = offset;
_created = DateTime.Now;
IsNotLocked = true;
}
// Time in seconds
public string TimeMin => string.Format("{0:00}:{1:00}:{2:00}", ElapsedTime.Hours, ElapsedTime.Minutes, ElapsedTime.Seconds);
// Time in decimal hours
public string TimeDec => string.Format("{0}", 0.1 * Math.Ceiling(10 * ElapsedTime.TotalHours));
public DateTime Created => _created;
public TimeSpan ElapsedTime => GetElapsed();
public void Start()
{
_started = DateTime.Now;
_swTimer = new Timer(TimerChanged, null, 0, 1000);
NotifyPropertyChanged("IsRunning");
}
public void Stop()
{
if (_swTimer != null)
{
_swTimer.Dispose();
_swTimer = null;
}
_offset = _offset.Add(DateTime.Now.Subtract(_started));
NotifyPropertyChanged("IsRunning");
}
private TimeSpan GetElapsed()
{
// This was made as part of my own debugging, the ElaspsedTime property used to just be the if return
if (IsRunning)
{
return _offset.Add(DateTime.Now.Subtract(_started));
}
else
{
return _offset;
}
}
// Updates the UI
private void TimerChanged(object state)
{
NotifyPropertyChanged("TimeDec");
NotifyPropertyChanged("TimeMin");
}
public bool IsRunning
{
get { return _swTimer != null; }
}
public void ToggleRunning()
{
if (IsRunning)
{
Stop();
}
else
{
Start();
}
}
}
This goes into the ViewModel:
public class JobListViewModel
{
private readonly ObservableCollection<JobTimer> _list = new ObservableCollection<JobTimer>();
public ObservableCollection<JobTimer> JobTimers => _list;
public JobListViewModel()
{
AddTimer();
}
public void AddTimer()
{
JobTimer t = new JobTimer();
JobTimers.Add(t);
t.Start();
}
public void PauseAll()
{
foreach(JobTimer timer in JobTimers)
{
timer.Stop();
}
}
// Other functions unrelated
}
And this is the UI button click that adds a new timer
private void AddTimer_Click(object sender, RoutedEventArgs e)
{
// Create JobTimer
ViewModel.PauseAll();
ViewModel.AddTimer();
// Scroll to newly created timer
JobTimer lastTimer = ViewModel.JobTimers.Last();
viewTimers.UpdateLayout();
viewTimers.ScrollIntoView(lastTimer);
}
I realise it's a lot of code to dump into a post but I can't pinpoint where the issue is being caused. I was able to find that something alters the offset when I hit the AddTimer button whether the existing timer is running or not, but I can't find what's altering it.
After building enough other code to support the code you posted, I was able to reproduce your problem.
The issue in your code is that you unconditionally call the Stop() method, whether the timer is already stopped or not. And the Stop() method unconditionally resets the _offset field, whether or not the timer is already running. So, if you add a timer when any other timer is already stopped, its _offset value is incorrectly reset.
IMHO, the right fix is for the Start() and Stop() methods to only perform their work when the timer is in the appropriate state to be started or stopped. I.e. to check the IsRunning property before actually doing the operation.
See below for an actual Minimal, Complete, and Verifiable version of the code you posted, but without the bug.
In addition to fixing the bug, I removed all of the unused elements (that is, all the code that did not appear to be used or discussed in your scenario) and refactored the code so that it is more idiomatic of a typical WPF implementation (see helper/base classes at the end). When I run the program, I am able to start and stop the timer objects without any trouble, even after adding new timers to the list.
Notable modifications:
Use of NotifyPropertyChangedBase class as base class for model classes.
Leverage of said base class features for property change notification, by keeping public properties as simple value-storing properties modified as needed.
Use of ICommand implementation for user actions (i.e. "commands").
Separation of timer-specific start/stop functionality when adding timers from view-specific scrolling-into-view behavior.
Remove time-formatting logic from non-UI model object, and put it in the XAML instead
Use conventional (and more readable) - and + operators for DateTime and TimeSpan math
JobTimer.cs:
class JobTimer : NotifyPropertyChangedBase
{
private DateTime _started; // When it was most recently started
private TimeSpan _offset; // The saved value to offset the currently running timer
Timer _swTimer; // The actual tick that updates the screen
private readonly DelegateCommand _startCommand;
private readonly DelegateCommand _stopCommand;
public ICommand StartCommand => _startCommand;
public ICommand StopCommand => _stopCommand;
public JobTimer() : this(TimeSpan.Zero)
{ }
public JobTimer(TimeSpan offset)
{
_offset = offset;
_startCommand = new DelegateCommand(Start, () => !IsRunning);
_stopCommand = new DelegateCommand(Stop, () => IsRunning);
}
private TimeSpan _elapsedTime;
public TimeSpan ElapsedTime
{
get { return _elapsedTime; }
set { _UpdateField(ref _elapsedTime, value); }
}
public void Start()
{
_started = DateTime.UtcNow;
_swTimer = new Timer(TimerChanged, null, 0, 1000);
IsRunning = true;
}
public void Stop()
{
if (_swTimer != null)
{
_swTimer.Dispose();
_swTimer = null;
}
_offset += DateTime.UtcNow - _started;
IsRunning = false;
}
private TimeSpan GetElapsed()
{
return IsRunning ? DateTime.UtcNow - _started + _offset : _offset;
}
// Updates the UI
private void TimerChanged(object state)
{
ElapsedTime = GetElapsed();
}
private bool _isRunning;
public bool IsRunning
{
get { return _isRunning; }
set { _UpdateField(ref _isRunning, value, _OnIsRunningChanged); }
}
private void _OnIsRunningChanged(bool obj)
{
_startCommand.RaiseCanExecuteChanged();
_stopCommand.RaiseCanExecuteChanged();
}
}
MainViewModel.cs:
class MainViewModel : NotifyPropertyChangedBase
{
public ObservableCollection<JobTimer> JobTimers { get; } = new ObservableCollection<JobTimer>();
public ICommand AddTimerCommand { get; }
public MainViewModel()
{
AddTimerCommand = new DelegateCommand(_AddTimer);
_AddTimer();
}
private void _AddTimer()
{
foreach (JobTimer timer in JobTimers)
{
timer.Stop();
}
JobTimer t = new JobTimer();
JobTimers.Add(t);
t.Start();
}
}
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MainViewModel model = (MainViewModel)DataContext;
model.JobTimers.CollectionChanged += _OnJobTimersCollectionChanged;
}
private void _OnJobTimersCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
ObservableCollection<JobTimer> jobTimers = (ObservableCollection<JobTimer>)sender;
// Scroll to newly created timer
JobTimer lastTimer = jobTimers.Last();
listBox1.ScrollIntoView(lastTimer);
}
}
MainWindow.xaml:
<Window x:Class="TestSO46416275DateTimeTimer.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:l="clr-namespace:TestSO46416275DateTimeTimer"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<l:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type l:JobTimer}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding ElapsedTime, StringFormat=hh\\:mm\\:ss}"/>
<Button Content="Start" Command="{Binding StartCommand}"/>
<Button Content="Stop" Command="{Binding StopCommand}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Content="Add Timer" Command="{Binding AddTimerCommand}" HorizontalAlignment="Left"/>
<ListBox x:Name="listBox1" ItemsSource="{Binding JobTimers}" Grid.Row="1"/>
</Grid>
</Window>
NotifyPropertyChangedBase.cs:
class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void _UpdateField<T>(ref T field, T newValue,
Action<T> onChangedCallback = null,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return;
}
T oldValue = field;
field = newValue;
onChangedCallback?.Invoke(oldValue);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
DelegateCommand.cs:
class DelegateCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
public DelegateCommand(Action execute) : this(execute, null)
{ }
public DelegateCommand(Action execute, Func<bool> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute();
}
public void Execute(object parameter)
{
_execute();
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
I have this console application which I now want to make into a WPF application. I'm new to WPF, so I'm not sure where to go from here. I currently have the following function to start a server:
public static void StartListening(string[] prefixes)
{
HttpListener listener = new HttpListener();
if (prefixes == null || prefixes.Length == 0)
throw new ArgumentException("prefixes");
foreach (string s in prefixes)
{
listener.Prefixes.Add("http://" + s + "/");
}
listener.Start();
Console.WriteLine("\nListening...");
listener.BeginGetContext(new AsyncCallback(OnRequest), listener);
}
Now I want to be able to do this in WPF with the click of a button. I already have the following in my MainWindow.xaml.cs but I could use a hint of how to bind the StartServerButton_Click to my StartListening() method. I've been looking at using ICommand but it just seems like an overly complicated solution to this fairly simple problem.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
try
{
IPAddress[] addrs = Array.FindAll(Dns.GetHostEntry(string.Empty).AddressList,
a => a.AddressFamily == AddressFamily.InterNetwork);
ServerOutputTextBox.AppendText("Your IPv4 address is: ");
foreach (IPAddress addr in addrs)
{
ServerOutputTextBox.AppendText(addr.ToString());
}
//Automatically set the IP address
string[] ips = addrs.Select(ip => ip.ToString()).ToArray();
Response.StartListening(ips);
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
}
private void StartServerButton_Click(object sender, RoutedEventArgs e)
{
}
Both answers so far show you how to use the code-behind click event to start your method, however since your title asks about binding the Button.Command property to your DataContext, I figured I'd post an answer on how to do that.
You do need an ICommand value in order to bind Button.Command. Furthermore, for binding purposes you typically want what is called a RelayCommand or DelegateCommand, which is just a fancy way of saying a command that is able to point to some unrelated code somewhere else to execute.
If you're using a 3rd party framework like Microsoft PRISM or MVVM Light, they both have a class specifically for this already, or you can create your own version of a RelayCommand for use with bindings.
Here's the class for a RelayCommand that I usually use when I don't want to use 3rd party libraries:
/// <summary>
/// A command whose sole purpose is to relay its functionality to other
/// objects by invoking delegates. The default return value for the
/// CanExecute method is 'true'.
/// </summary>
public class RelayCommand : ICommand
{
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
/// <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");
_execute = execute;
_canExecute = canExecute;
}
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameters)
{
return _canExecute == null ? true : _canExecute(parameters);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameters)
{
_execute(parameters);
}
#endregion // ICommand Members
}
Your DataContext class would then expose an instance of this RelayCommand that goes to your method for binding purposes, like this
public ICommand StartServerCommand
{
get
{
// If command hasn't been created yet, create it
if (_startServerCommand == null)
{
_startServerCommand = new RelayCommand(
param => StartServer()
);
}
return _startServerCommand;
}
}
private void StartServer()
{
var ips = GetIpAddresses();
Response.StartListening(ips);
}
Now that said, from the code example you've given so far you don't look like you are taking advantage of WPF's binding system correctly, so this solution may not be for you and it might be simpler to just go with an OnClick method like others have shown.
In an ideal world, your data and business logic would all exist in classes unrelated to the UI which are used as the DataContext behind your UI components, and you'd use bindings to pull data from your data class for display in the UI.
If you're looking to learn more about how to use WPF properly, I have some beginners articles on my blog that may help you:
What is this "DataContext" you speak of?
A Simple MVVM Example
Judging from your code, after the window is loaded, IPs are not changed. So make ips private and use it in the button click handler:
public partial class MainWindow : Window {
private string[] ips;
public MainWindow()
{
InitializeComponent();
try
{
IPAddress[] addrs = Array.FindAll(Dns.GetHostEntry(string.Empty).AddressList,
a => a.AddressFamily == AddressFamily.InterNetwork);
ServerOutputTextBox.AppendText("Your IPv4 address is: ");
foreach (IPAddress addr in addrs)
{
ServerOutputTextBox.AppendText(addr.ToString());
}
//Automatically set the IP address
ips = addrs.Select(ip => ip.ToString()).ToArray();
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
}
private void StartServerButton_Click(object sender, RoutedEventArgs e)
{
Response.StartListening(ips);
}
I'm not quite sure if I got your question correct. But is this what you're looking for?
Xaml:
<Button Click=StartServerButton_Click/>
Code behind:
private void StartServerButton_Click(object sender, RoutedEventArgs e)
{
DoSomething();
}