I have a RelayCommand class:
public class RelayCommand : ICommand
{
private readonly Func<bool> canExecuteEvaluator;
private readonly Action methodToExecute;
public RelayCommand(Action methodToExecute, Func<bool> canExecuteEvaluator)
{
this.methodToExecute = methodToExecute;
this.canExecuteEvaluator = canExecuteEvaluator;
}
public RelayCommand(Action methodToExecute)
: this(methodToExecute, null)
{
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
if (this.canExecuteEvaluator == null)
{
return true;
}
else
{
bool result = this.canExecuteEvaluator.Invoke();
return result;
}
}
public void Execute(object parameter)
{
this.methodToExecute.Invoke();
}
In my view model I create my commands like this:
public RelayCommand CmdUpdate { get; private set; }
CmdUpdate = new RelayCommand(() => Updater.CheckForUpdate());
Updater.CheckForUpdate() simply checks if there is an update available and shows an message box.
I then bind the command to a menu item in my view.
When I press the item, the message box pops up for a split second and disappears again. Where is the propblem. An explanation is highly appreciated, so I can understand it.
Related
I am working on WPF (MVVM) ..
using this tutorial :
https://www.tutorialspoint.com/mvvm/mvvm_validations.htm
when trying to implement "AddEditCustomerViewModel"
I received two errors for this class:
namespace MVVMHierarchiesDemo.ViewModel
{
class AddEditCustomerViewModel : BindableBase
{
public AddEditCustomerViewModel()
{
CancelCommand = new MyICommand(OnCancel);
SaveCommand = new MyICommand(OnSave, CanSave);
}
private bool _EditMode;
public bool EditMode
{
get { return _EditMode; }
set { SetProperty(ref _EditMode, value); }
}
private SimpleEditableCustomer _Customer;
public SimpleEditableCustomer Customer
{
get { return _Customer; }
set { SetProperty(ref _Customer, value); }
}
private Customer _editingCustomer = null;
public void SetCustomer(Customer cust)
{
_editingCustomer = cust;
if (Customer != null) Customer.ErrorsChanged -= RaiseCanExecuteChanged;
Customer = new SimpleEditableCustomer();
Customer.ErrorsChanged += RaiseCanExecuteChanged;
CopyCustomer(cust, Customer);
}
private void RaiseCanExecuteChanged(object sender, EventArgs e)
{
SaveCommand.RaiseCanExecuteChanged();
}
public MyICommand CancelCommand { get; private set; }
public MyICommand SaveCommand { get; private set; }
public event Action Done = delegate { };
private void OnCancel()
{
Done();
}
private async void OnSave()
{
Done();
}
private bool CanSave()
{
return !Customer.HasErrors;
}
}
}
First one;on this lines :
CancelCommand = new MyICommand(OnCancel);
SaveCommand = new MyICommand(OnSave, CanSave);
an error appear on MyICommand constructor as in :
Error CS0305 Using the generic type 'MyICommand' requires 1 type
arguments
Second one , on this line :
CopyCustomer(cust, Customer);
an error appear for CopyCustomer function as in :
Error CS0103 The name 'CopyCustomer' does not exist in the current
context
the implementation of MyICommand as in :
public class MyICommand<T> : ICommand
{
Action<T> _TargetExecuteMethod;
Func<T, bool> _TargetCanExecuteMethod;
public MyICommand(Action<T> executeMethod)
{
_TargetExecuteMethod = executeMethod;
}
public MyICommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod)
{
_TargetExecuteMethod = executeMethod;
_TargetCanExecuteMethod = canExecuteMethod;
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged(this, EventArgs.Empty);
}
#region ICommand Members
bool ICommand.CanExecute(object parameter)
{
if (_TargetCanExecuteMethod != null)
{
T tparm = (T)parameter;
return _TargetCanExecuteMethod(tparm);
}
if (_TargetExecuteMethod != null)
{
return true;
}
return false;
}
// Beware - should use weak references if command instance lifetime is
//longer than lifetime of UI objects that get hooked up to command
// Prism commands solve this in their implementation
public event EventHandler CanExecuteChanged = delegate { };
void ICommand.Execute(object parameter)
{
if (_TargetExecuteMethod != null)
{
_TargetExecuteMethod((T)parameter);
}
}
#endregion
}
}
to see the whole Project folder ; please see this link :
https://drive.google.com/drive/folders/1jFDU6vDMW_TO0J88NT53oLVWVYbAYyTv?usp=sharing
I'm trying to implement a datepicker and bind it to a model I've created.
I've made a global tournament object in App.xaml.cs:
private Tournament _tournament;
public Tournament tournament {
get { return _tournament; }
set { _tournament = value; OnPropertyChanged("tournament"); }
}
And I've made an OnStartup override to launch my windows:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
//setup score
score = new Score();
// Tournament setup
tournament = new Tournament();
tournament.GamesToWin = 1;
tournament.Games = new List<Game>(1);
tournament.Players = new List<Player>(2) { new Player(), new Player() };
//tournament.TimeAndDate = new DateTime(2021, 11, 22);
tournament.Winner = null;
// Initializing the UserInput
UserInput userInput = new UserInput();
UserInputWindowViewModel userinputViewModel = new UserInputWindowViewModel();
userInput.DataContext = userinputViewModel;
// Opening the UserInput Window
bool? res = userInput.ShowDialog();
// If the UserInput Window is closed, open the next Window
if (res == true)
{
// Opening the MainWindow
MainWindow main = new MainWindow();
main.Show();
}
else
{
Shutdown();
}
}
Model.cs (Tournament.cs):
public class Tournament : INotifyPropertyChanged
{
private Player _winner;
private DateTime? _timeAndDate;
private List<Player> _players;
public List<Player> Players
{
get { return _players; }
set { _players = value; OnPropertyChanged("Players"); }
}
private List<Game> _games;
public List<Game> Games
{
get { return _games; }
set { _games = value; OnPropertyChanged("Games"); }
}
private int _gamesToWin;
public int GamesToWin
{
get { return _gamesToWin; }
set { _gamesToWin = value; OnPropertyChanged("GamesToWin"); }
}
public Player Winner
{
get { return _winner; }
set { _winner = value; OnPropertyChanged("Winner"); }
}
public DateTime? TimeAndDate
{
get { return _timeAndDate; }
set { _timeAndDate = value; OnPropertyChanged("TimeAndDate"); }
}
#region PropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
ViewModel.cs (UserInputWindowViewModel.cs):
public class UserInputWindowViewModel
{
// Calling the current app to access the tournament object globally
public App currentApp = Application.Current as App;
#region The players object
private List<Player> _players;
public List<Player> Players
{
get { return _players; }
set { _players = value; }
}
#endregion
#region The DateTime object
private DateTime? _dateTime;
public DateTime? TournamentDateTime
{
get { return _dateTime; }
set { _dateTime = value; }
}
#endregion
public UserInputWindowViewModel()
{
Tournament tournament = currentApp.tournament;
Players = tournament.Players;
TournamentDateTime = new DateTime(2021, 11, 22);
tournament.TimeAndDate = TournamentDateTime;
//TournamentDateTime = tournament.TimeAndDate;
}
#region ICommand Members
private ICommand mUpdater;
public ICommand UpdateCommand
{
get
{
if (mUpdater == null)
mUpdater = new Updater();
return mUpdater;
}
set
{
mUpdater = value;
}
}
private class Updater : ICommand
{
public bool CanExecute(object parameter) => true;
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
}
}
#endregion
}
Inside my window (UserInput.xaml) I have the following Datepicker:
<DatePicker SelectedDate="{Binding TournamentDateTime, Mode=TwoWay}"/>
I've set the date inside my viewmodel to 22-11-2021 as test, but when I change the date, it's not changing. What am I doing wrong?
EDIT: I also tried the OnPropertyChanged in the ViewModel, which didn't work
You forgot to implement INotifyPropertyChanged under UserInputWindowViewModel.cs.
public class UserInputWindowViewModel : INotifyPropertyChanged
{
}
Also make sure you are triggering PropertyChanged event in property setter, it's important for two-way binding
public DateTime? TournamentDateTime
{
get { return _dateTime; }
set
{
_dateTime = value;
OnPropertyChanged(nameof(TournamentDateTime);
}
}
I'd also would suggest not to access Application directly from a view model, better pass tournament object as a dependency to a view model as soon it would be better separation of concerns
I managed to fix my problem:
I made a small function to update my global model:
public void updateModel(DateTime? s)
{
currentApp.tournament.TimeAndDate = s;
}
I've added this small bit of code to my getter/setter inside my viewmodel (notice I used DateTime.Now to set it to the current date):
private DateTime? _dateTime = DateTime.Now;
public DateTime? TournamentDateTime
{
get { return _dateTime; }
set { _dateTime = value; updateModel(value); }
}
This fixed all my problems with the datepicker. And I didn't have to use INotifyPropertychanged inside my viewmodel because my model already had it implemented.
MainWindow.xaml:
<TextBox HorizontalAlignment="Left" Height="23" Margin="308,90,0,0" TextWrapping="Wrap" Text = "{Binding Login, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="120"/>
MainWindow.cs:
public partial class MainWindow : Window
{
UserViewModel userViewModel = new UserViewModel();
UserService userService = new UserService();
public MainWindow()
{
InitializeComponent();
DataContext = userViewModel;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
userService.CheckUserExist();
}
Model:
public string Login { get; set; }
ViewModel:
public class UserViewModel : INotifyPropertyChanged
{
private string _login;
public string Login
{
get { return _login; }
set
{
_login = value;
OnPropertyChange("Login");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChange(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Service:
public string Login { get; set; }
public void CheckUserExist()
{
using (PrincipalContext principalContext = new PrincipalContext(ContextType.Domain))
{
var user = UserPrincipal.FindByIdentity(principalContext, Login);
if (user == null)
{ UserMessageText = "xx"; }
{
}
}
}
Why Login in method CheckUserExist() is always null when i start it?
I tried with UpdateSourceTrigger=LostFocus and RaisePropertyChanged();
From method data are correctly sent to VM i View.
The value is always null because you never set it. Also when following the MVVM pattern, your view is not allowed to have a dependency to the model or the UserService to be specific.
It's the responsibility of the view model to pass the value from UserViewModel.Login to UserService.Login:
MainWindow.xaml.cs
public partial class MainWindow : Window
{
private UserViewModel userViewModel = new UserViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = this.userViewModel;
}
}
MainWindow.xaml
<StackPanel>
<TextBox Text="{Binding Login}"/>
<Button Content="Submit" Command="{Binding SubmitLoginDataCommand}"/>
</StackPanel>
UserViewModel.cs
public class UserViewModel
{
public void SubmitLoginData(object loginData)
{
this.userService.CheckUserExist(this.Login);
}
public ICommand SubmitLoginDataCommand => new RelayCommand(SubmitLoginData, param => true);
public string Login { get; set; }
private UserService userService { get; set; }
}
UserService.cs
public void CheckUserExist(string login)
{
using (PrincipalContext principalContext = new PrincipalContext(ContextType.Domain))
{
var user = UserPrincipal.FindByIdentity(principalContext, login);
if (user == null)
{
this.UserMessageText = "xx";
}
}
}
RelayCommand.cs
Implementation taken from Microsoft Docs: Relaying Command Logic
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
public RelayCommand(Action<object> execute) : this(execute, null) { }
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
this._execute = execute; this._canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return this._canExecute == null ? true : this._canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter) { this._execute(parameter); }
#endregion // ICommand Members
}
The following code is an example to explain my issue.
I have a textbox in binding. When I click on a button, executes a function. (In this case it's a for loop).
Now I want, the text box updated with the content of the i variable. (public void MyAction())
So, I made a thread, but this doesn't work.
Why ?
Thanks in advance
XAML code
<TextBox Text ="{Binding MyValue}" HorizontalAlignment="Left" Height="47" Margin="4,4,4,4" VerticalAlignment="Top" Width="342"/>
<Button Command="{Binding ClickCommand}" Content="Run" Margin="114,69,283,216"/>
C# code
class Vm_Main : ViewModelBase
{
public string _MyValue { get; set; } //String in my XAML
public string MyValue
{
get { return _MyValue; }
set
{
_MyValue = value;
base.OnPropertyChanged("MyValue");
}
}
private bool _canExecute=true;
private ICommand _clickCommand;
public ICommand ClickCommand
{
get
{
return _clickCommand ?? (_clickCommand = new CommandHandler(() => MyAction(), _canExecute));
}
}
public Vm_Main()
{
MyValue = "Hallo"; //Default value
}
public void MyAction() // This is the function where I want update the TextBox in Binding
{
Worker workerObject = new Worker();
Thread workerThread = new Thread(workerObject.DoWork);
workerThread.Start();
for (int i = 1; i <= 10; i++)
{
MyValue = i.ToString();
workerObject.Value = MyValue;
Thread.Sleep(500);
}
workerObject.RequestStop();
workerThread.Join();
MessageBox.Show("End");
}
}
// The THREAD
public class Worker : ViewModelBase
{
// This method will be called when the thread is started.
public string _Value { get; set; }
public string Value
{
get { return _Value; }
set
{
_Value = value;
base.OnPropertyChanged("Value");
}
}
public void DoWork()
{
while (!_shouldStop)
{
Console.WriteLine("Value is..." + _Value);
}
Console.WriteLine("End.");
}
public void RequestStop()
{
_shouldStop = true;
}
// Volatile is used as hint to the compiler that this data
// member will be accessed by multiple threads.
private volatile bool _shouldStop;
}
And Class ViewModelBase and Class Command
public abstract class ViewModelBase : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string strPropertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(strPropertyName));
}
#endregion
#region Messages and Progressbar
private string _errorMessage;
public string ErrorMessage
{
get { return _errorMessage; }
set
{
if (_errorMessage == value) return;
_errorMessage = value;
OnPropertyChanged("ErrorMessage");
}
}
private string _errorTooltip;
public string ErrorTooltip
{
get { return _errorTooltip; }
set
{
if (_errorTooltip == value) return;
_errorTooltip = value;
this.OnPropertyChanged("ErrorTooltip");
}
}
private string _statusMessage;
public string StatusMessage
{
get { return _statusMessage; }
set
{
if (_statusMessage == value) return;
_statusMessage = value;
//pulizia dei messaggi di errore
ErrorMessage = string.Empty;
ErrorTooltip = string.Empty;
OnPropertyChanged("StatusMessage");
}
}
protected void ClearMessage()
{
ErrorMessage = string.Empty;
StatusMessage = string.Empty;
}
private int _currentProgress;
public int CurrentProgress
{
get { return _currentProgress; }
set
{
_currentProgress = value;
OnPropertyChanged("CurrentProgress");
}
}
#endregion
protected ViewModelBase()
{
}
}
public class CommandHandler : ICommand
{
private Action _action;
private bool _canExecute;
public CommandHandler(Action action, bool canExecute)
{
_action = action;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_action();
}
}
So I think you have to issues.
First use async await pattern.
It goes like this - to not block ur UI thread.
public async Task<object> MyAsyncMethod()
{
return await Task.Run<object>(() =>
{
return null;
});
}
or for ICommand just:
public async void MyAsyncMethod()
{
await Task.Run(() =>
{
});
}
Second is that you may want to update your UI - while processing async. This is a common problem for updating progress for example. You can solve this with SynchronizationContext.
public interface IUpdateProgess
{
void SendMessage(string val);
}
public async Task<object> MyAsyncMethod(IUpdateProgess progress)
{
//UI thread
SynchronizationContext context = SynchronizationContext.Current;
return await Task.Run<object>(() =>
{
//other thread
if (context != null && progress != null)
{
context.Post(new SendOrPostCallback((o) =>
{
progress.SendMessage("Progress");
}), null);
}
return null;
});
}
You can use this obviously to not just update progress - i think you get the idea.
I am using this code to make a Simple Command:
public class SimpleCommand : ICommand
{
public Predicate<object> CanExecuteDelegate { get; set; }
public Action<object> ExecuteDelegate { get; set; }
#region ICommand Members
public bool CanExecute(object parameter)
{
if (CanExecuteDelegate != null)
return CanExecuteDelegate(parameter);
return true;// if there is no can execute default to true
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
if (ExecuteDelegate != null)
ExecuteDelegate(parameter);
}
#endregion
}
I did not write this. But I enjoy using it. When I use it it ends up being like this:
// This is the value that gets set to the command in the UI
public SimpleCommand DoSomethingCommand { get; set; }
public DoSomethingCommandConstructor()
{
DoSomethingCommand = new SimpleCommand
{
ExecuteDelegate = x => RunCommand(x)
};
}
private void RunCommand(object o)
{
// Run the command.
}
The only problem with this is that the parameter of RunCommand is an object. I think I have been spoiled by generics. I always want the IDE/compiler to just know what the type I am working with is with out casting.
Is it possible to change this SimpleCommand class to be implemented using generics?
Sure. Was gonna point you to Prism's implementation, but CodePlex source tab seems to not be working. It would look something like:
public class SimpleCommand<T> : ICommand
{
public Predicate<T> CanExecuteDelegate { get; set; }
public Action<T> ExecuteDelegate { get; set; }
#region ICommand Members
public bool CanExecute(object parameter)
{
if (CanExecuteDelegate != null)
return CanExecuteDelegate((T)parameter);
return true;// if there is no can execute default to true
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
if (ExecuteDelegate != null)
ExecuteDelegate((T)parameter);
}
#endregion
}
Incidentally, your usage of SimpleCommand in your question is a little roundabout. Instead of this:
DoSomethingCommand = new SimpleCommand
{
ExecuteDelegate = x => RunCommand(x)
};
You could just have:
DoSomethingCommand = new SimpleCommand
{
ExecuteDelegate = this.RunCommand
};
Specifying a lambda is really only useful if you're doing the work inline like this:
DoSomethingCommand = new SimpleCommand
{
ExecuteDelegate = o => this.SelectedItem = o,
CanExecuteDelegate = o => o != null
};