Loading asynchronously in a UserControl WPF with MVVM pattern - c#

I have a small problem async loader in a view with WPF MVVM pattern .
I wish that when I click the button Read from UserControl View , send a Command to view model , and then it was off a loading before making execute and it disappeared when I return data .
The data is read by a machine via optical USB .
All works , the program runs perfectly, just can not bring up the loading so async . The loading is displayed along with the return of reading , because synchronous . How can I do asynchronous ? I tried it with the task but it seems he does not consider the code .
class ReadAndPrintFromDevice : ICommand
{
async Task<int> showLoader()
{
model.ShowLoader = true;
return 1;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public async void Execute(object parameter)
{
showLoader async here
//other code..
showloader async shadow here, after other code
}
}
if you need more information tell in the comments that I add all .

I would suggest something like this:
public class ReadAndPrintFromDeviceAsyncCommand : PropertyChangedBase, ICommand
{
private bool _IsBusy;
public bool IsBusy
{
get { return _IsBusy; }
private set
{
_IsBusy = value;
NotifyOfPropertyChange();
}
}
public bool CanExecute(object parameter)
{
return !IsBusy;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public async void Execute(object parameter)
{
IsBusy = true;
await ...
IsBusy = false;
}
}
By using an IsBusy property, you can simply set that to be true before executing the asynchronous operation, and once it's done, set it back to false.
You can then bind your control to the IsBusy property:
<MyControl Visibility="{Binding SomeCommand.IsBusy, ...}"
The above example would require a BooleanToVisibilityConverter to work, but I think you get the idea.
Also, the command uses a PropertyChangedBase which I have created to simply make implementing INotifyPropertyChanged a bit easier, the code for this class is here:
public abstract class PropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyOfPropertyChange([CallerMemberName]string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

Related

DataBinding and UpdateSourceTrigger on Content

Okay, so I've been attempting to learn a bit of MVVM. It's been only three days and have come to a dead end. The application simply retrieves the client's external ip address and updates the new ip address in a label found in my UI. However, rather than just going the old-fashioned way of simply doing,
ipAdd.Content = getNewIp();
and ending the story end there. Rather, I decided to use some MVVM to achieve the following:
Retrieve ip address, then display it to the user in a label
Query the database and update the user's ip address
Rinse, repeat every five minutes.
The reason I chose to go more MVVM is to use UpdateSourceTrigger and UpdateCommand. Essentially, when the ip address gets updated, I want to do something with it (as mentioned above). I've seen in many tutorials where UpdateCommand is used on buttons and UpdateSourceTrigger used on input boxes, but nothing that shows how to do it with labels, yet it seems that it should be tied in to the same concept; it might not be explicity a button that is being clicked, but some back code that executes and performs the same task as a button. For starters, here's what I have so far:
Model
namespace IPdevices
{
class IP : INotifyPropertyChanged
{
private string _add;
public string add
{
get
{
return _add;
}
set
{
_add = value;
OnPropertyChange("add");
}
}
private void OnPropertyChange(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
ViewModel
namespace IPdevices
{
class IPViewModel
{
private IP _ip;
public IPViewModel()
{
UpdateCommand = new IPUpdateCommand(this);
}
public ICommand UpdateCommand
{
get; private set;
}
public bool CanUpdate
{
get
{
return true; // just say yes for now
}
}
public IP IP
{
get
{
return _ip;
}
set
{
_ip = value;
}
}
public void updateClientIp()
{
Console.WriteLine("_updating client ip and query database");
}
}
}
ICommand
namespace IPdevices
{
class IPUpdateCommand : ICommand
{
private IPViewModel _viewModel;
public IPUpdateCommand(IPViewModel vm)
{
_viewModel = vm;
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
public bool CanExecute(object parameter)
{
return _viewModel.CanUpdate;
}
public void Execute(object parameter)
{
_viewModel.updateClientIp();
}
}
}
MainWindow
public Main()
{
InitializeComponent();
ipAdd.DataContext = new IPViewModel();
}
View
<Label x:Name="ipAdd" Content="{Binding Path=IP.add}"/>
What I'm trying to achieve here can easily be done without going MVVM (perhaps overkill as well) and simply calling a few helper classes to update the user's ip on the db side, but for the sake of getting used to doing things MVVM, I tried incorporating the way I did, though with no luck.
Is there any way to do this, or even better, should it be done this way?

Data binding in MVVM WPF OnButtonClick

I am using MVVM in my WPF application and I have a problem with data binding. I am considering binding user actions to data operations (in my case adding record to database). If I use heavy coupling between CommandClass and ViewModelClass everything works fine. My CommandClass in this case looks like this:
public class ButtonCommand : ICommand
{
private readonly UserViewModel _userViewModel;
public ButtonCommand(UserViewModel viewModel)
{
_userViewModel = viewModel;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_userViewModel.AddUser();
}
public event EventHandler CanExecuteChanged;
}
My heavy coupling in ViewModelClass looks like this:
private readonly ButtonCommand _buttonCommand;
public UserViewModel()
{
_buttonCommand = new ButtonCommand(this);
}
public ICommand btnClick
{
get { return _buttonCommand; }
}
My XAML coupling on button click (take a look on a Command section):
<Page.Resources>
<viewModel:UserViewModel x:Key="UserObj" TxtFirstName="" TxtLastName="" TxtEmail="" TxtPassword=""/>
</Page.Resources>
....
<Button Content="Submit" HorizontalAlignment="Left" Margin="42,231,0,0" VerticalAlignment="Top" Width="75" Command="{Binding btnClick, Mode=OneWay, Source={StaticResource UserObj}}"/>
And I have such an output (take a look at Submit button): Window.
After I make changes to my CommandClass and ViewModelClass (to make them more general and reusable), but leave my XAML coupling the same the Submit button becomes unavailable after runnig my application. After changes CommandClass looks like this:
public class ButtonCommand : ICommand
{
private readonly Action _executionMethod;
private readonly Func<bool> _executeOrNot;
public ButtonCommand(Action executionMethod, Func<bool> executeOrNot)
{
_executionMethod = executionMethod;
_executeOrNot = executeOrNot;
}
public bool CanExecute(object parameter)
{
return _executeOrNot();
}
public void Execute(object parameter)
{
_executionMethod();
}
public event EventHandler CanExecuteChanged;
}
My ViewModelClass after changes:
private readonly ButtonCommand _buttonCommand;
public UserViewModel()
{
_buttonCommand = new ButtonCommand(AddUser, IsValidInputForRegistration);
}
public ICommand btnClick
{
get { return _buttonCommand; }
}
XAML I leave the same. The output I have is next (take a look at Submit button): WindowWithChanges.
Can anyone provide me with some information, why button became unavailable and where do I mess up?
First, try IsValidInputForRegistration to always return true. That will prove that your implementation of IButton (i.e. your ButtonCommand class) works fine.
If that works, what's happening to your program is the IsValidInputForRegistration passes the state for your _buttonCommand during initialization and it will stay on that state since it doesn't query if the IsValidInputForRegistration have changed states.
To achieve querying of states, you can implement the EventHandler CanExecuteChanged like so:
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
You can look into msdn for what CommandManager.RequerySuggested does. But I think the description says it all. :)
Occurs when the CommandManager detects conditions that might change
the ability of a command to execute.

Wiring up the code behind of the View with some Action in the ViewModel

In a Silverlight app that is written with MVVM I want to enable/disable my view based on some stuff.
In the constructor of the View class in code behind I can say something like this and it disables the form:
public MyForm1View()
{
InitializeComponent();
if(this.DataContext == null)
{
this.IsEnabled = False;
}
}
The issue is when there is no data to load, I am showing a gray overlay screen on top of my form to the user with a link on that gray overlay that says "Create a New Record"....now the problem is that if I disable my form like that above then How can I re-enable it when they click that CreateNewRecord link?
But how can I reenable it again from the view-model? Maybe I should have an Action on my ViewModel and when it's called on the ViewModel, it calls a method that's wired up in the code behind of the View ? But how to code this idea?
I would suggest few things:
simple wrapper for ICommand Interface:
public class DelegateCommand : ICommand
{
private readonly Action execute;
private readonly Func<bool> canExecute;
public DelegateCommand(Action execute, Func<bool> canExecute = null)
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
if (this.canExecute != null)
{
return this.canExecute();
}
else
{
return true;
}
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
execute();
}
public void RaiseExecuteChanged()
{
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, new EventArgs());
}
}
}
ViewModel:
public class ViewModel : INotifyPropertyChanged {
public void ViewModel() {
SwitchCommand = new DelegateCommand(() => this.IsEnabled = true, () => true);
}
public DelegateCommand SwitchCommand {get;set;}
private bool isEnabled;
public bool IsEnabled {
get {
return isEnabled;
}
set {
isEnabled = value;
NotifyPropertyChanged("IsEnabled");
}
// here, InotifyPropertyChanged implementation, dozens of sample available
}
Xaml:
as example:
<Button Command={Binding SwitchCommand} /> bind command to click.
So, what's left is to set ViewModel to View, via view constructor, of IoC if you use it.
hope that help.

Binding Start/Stop text on Stopwatch.IsRunning - Property Changed

I have something like this in xaml:
<Button Content="{Binding MyStopwatch.IsRunning,
Converter={StaticResource BoolToStr}}"/>
I need to display Start, when IsRunning is false and Stop, when IsRunning is true.
I have no problem with converter or binding itself.
I have problem with refreshing IsRunning property.
When IsRunning property change while programm is running - it does not change Start/Stop text.
I know how to implement INotifyPropertyChange on my own properties.
But I dont know how to implement (something like) property change on IsRunning
If you want to update your bindings, you can call the PropertyChanged on property MyStopwatch whenever you start or stop a stopwatch.
OnPropertyChanged("MyStopwatch");
You can't make StopWatch implement INotifyPropertyChanged. What you can do, is create your own wrapper for it, and use that instead. For example:
public class StopwatchWrapper : INotifyPropertyChanged
{
Stopwatch _stopwatch;
private bool _isRunning;
public bool IsRunning
{
get { return _isRunning; }
set
{
if (_isRunning != value)
{
_isRunning = value;
OnPropertyChanged("IsRunning");
}
}
}
public StopwatchWrapper()
{
_stopwatch = new Stopwatch();
_isRunning = false;
}
public void Start()
{
_stopwatch.Start();
IsRunning = _stopwatch.IsRunning;
}
public void Stop()
{
_stopwatch.Stop();
IsRunning = _stopwatch.IsRunning;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}

WPF UI not updating after incoming WCF message

I have an MVVM app, where my ViewModel PingerViewModel processes incoming WCF Ping() messages. Processing such a message happens on a thread of Scheduler.Default's thread pool.
Semantically, incoming WCF messages change a bound property CanPing and raise the PropertyChanged event for said property.
But my UI is not updating until it receives some UI event, e.g. focusing/clicking the window, etc.
How do I make it update as soon as the event is fired?
I have tried raising the PropertyChanged event...
on the Application's Dispatcher,
using a SynchronizationContext
without any luck.
I also verified that the bound property is indeed set to the proper value, and that there is indeed a listener consuming my PropertyChanged event.
Here's some code (full code on github):
part of my view's MainWindow.xaml:
It might be worth noting that the bound Command does not actually play a role in producing the incoming WCF message.
<Button Content="Ping" Height="23" HorizontalAlignment="Left" Margin="10,10,0,0" Name="PingBtn" VerticalAlignment="Top" Width="75" AutomationProperties.AutomationId="Ping"
IsEnabled="{Binding CanPing}"
Command="{Binding PingCommand}" />
part of my views MainWindow.xaml.cs
public MainWindow()
{
DataContext = new PingerViewModel();
InitializeComponent();
}
part of my ViewModel
public class PingerViewModel : INotifyPropertyChanged
public PingerViewModel()
{
Pinger = new Pinger(true);
PingCommand = new PingerPingCommand(this);
//...
}
public bool CanPing
{
get
{
if (Pinger == null) return false;
return Pinger.CanPing;
}
}
public void Ping()
{
_pingClient.Channel.Ping();
Pinger.CanPing = false;
OnPropertyChanged("CanPing");
}
protected virtual void OnPong(PongEventArgs e)
{
Pinger.CanPing = true;
OnPropertyChanged("CanPing");
}
public Pinger Pinger { get; private set; }
public ICommand PingCommand { get; private set; }
//...
}
I think you need to remove IsEnabled="{Binding CanPing}" from your button.
Binding to the command is enough as the ICommand object contains CanExecute and the CanExecuteChanged event handler.
I would create a CanExecute boolean inside your Command class, and implement INotifyPropertyChanged also on this class. Something like this:
public class PingCommand : ICommand, INotifyPropertyChanged
{
private bool _canExecute;
public bool CanExecute1
{
get { return _canExecute; }
set
{
if (value.Equals(_canExecute)) return;
_canExecute = value;
CanExecuteChanged.Invoke(null, null);
OnPropertyChanged("CanExecute1");
}
}
public void Execute(object parameter)
{
//whatever
}
public bool CanExecute(object parameter)
{
return _canExecute;
}
public event EventHandler CanExecuteChanged;
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Then, on the Ping/Pong methods inside your ViewModel, update this property inside your Command:
public void Ping()
{
_pingClient.Channel.Ping();
Pinger.CanPing = false;
PingCommand.CanExecute1 = false;
OnPropertyChanged("CanPing");
}
protected virtual void OnPong(PongEventArgs e)
{
Pinger.CanPing = true;
PingCommand.CanExecute1 = true;
OnPropertyChanged("CanPing");
}
if your CanPing Property and the CanExecute methode for your PingCommand both return TRUE it should work.
sometimes the Delegate/RelayCommand implementation give the possibility to call RaiseCanExecuteChanged() - try this if the statement above is true for both and its not working
btw this is called within the RaiseCanExecuteChanged()
CommandManagerHelper.CallWeakReferenceHandlers(_canExecuteChangedHandlers);
You can use RaiseCanExecuteChanged() method of the said property to update.
Eg:
this.PingCommand.RaiseCanExecuteChanged();
Try this, I hope it will solve your problem.

Categories