Caliburn CanExecute not working even though firing - wpf

Trying to implement simple validation with caliburn. All I want is to enable/disable save button based on certain conditions.
View:
`<xctk:MaskedTextBox x:Name="pm_personId" cal:Message.Attach="[Event LostFocus] = [Action CanSave()]" Mask="00-000-000?"/>
<Button Content="Save" x:Name="Save" />`
Model:
public class PersonModel
{
public String personId { get; set; }
public PersonModel() {}
public PersonModel(String id)
{
this.id = personId;
}
}
ViewModel:
[ImplementPropertyChanged]
public class PersonViewModel : Screen
{
public PersonModel pm { get; set; }
public PersonViewModel()
{
pm = new PersonModel();
}
public bool CanSave()
{
MessageBox.Show(pm.personId);
if (pm.personId != null)
return true;
else return false;
}
}
The MessageBox is fired with the right value but button is not enable. Am I missing anything. Either am missing something with caliburn or it's doing too much magic. Am beginning to suspect that the time it may save you initially will be lost in debugging, just my exeprience.

Thanks #CCamilo but your answer was incomplete. For other people who encounter a similar problem, below is my final working code:
[ImplementPropertyChanged]
public class PersonModel
{
public String personId { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public PersonModel() {}
public PersonModel(String id)
{
this.id = personId;
}
}
[ImplementPropertyChanged]
public class PersonViewModel : Screen
{
public PersonModel pm { get; set; }
public PersonViewModel()
{
pm = new PersonModel();
this.pm.PropertyChanged += pm_PropertyChanged;
}
void pm_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyOfPropertyChange(() => CanSave);
}
public bool CanSave
{
get { return pm.personId != null; }
}
}

The error you have is with CanSave() method.. It should be a property instead:
public bool CanSave
{
get
{
if (pm.personId != null)
return true;
else return false;
}
}

Related

INotifyPropertyChanged won't update grid in the window [duplicate]

This question already has answers here:
INotifyPropertyChanged WPF
(3 answers)
using of INotifyPropertyChanged
(3 answers)
Closed last year.
I've been this for a while now but I can't seem to be figuring out why my code won't update the window. The events are called and the invoke methods are invoked, but still, my window won't update
public class RaceContext : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Race CurrentRace { get; set; }
public string TrackName { get => CurrentRace == null ? "" : "Track name : " + CurrentRace.Track.Name; }
public List<IParticipant> Participants { get; set; }
public ObservableCollection<DriverListView> DriverListViews
{
get { return _DriverListViews; }
set { _DriverListViews = value; OnPropertyChanged(nameof(DriverListViews)); }
}
public ObservableCollection<DriverListView> _DriverListViews = new ObservableCollection<DriverListView>();
public void OnNextRace(object sender, NextRaceEventArgs e)
{
CurrentRace = e.Race;
e.Race.DriverChanged += OndriversChanged;
e.Race.DriverMoved += OnDriverMoved;
DriverListViews = new ObservableCollection<DriverListView>();
CurrentRace.Participants.ForEach(item =>
{
DriverListViews.Add(new DriverListView()
{
Driver = item
});
});
OnPropertyChanged();
}
public void OndriversChanged (object sender, DriversChangedEventArgs e)
{
Participants = CurrentRace.Participants;
e.DriverTimers.ForEach(timer =>
{
var view = DriverListViews.Where(item => item.Driver.Equals(timer.Driver)).First();
view.RaceStopwatch = timer.TotalRaceTimer.Elapsed;
view.LapStopwatch = timer.LapTimer.Elapsed;
});
OnPropertyChanged(nameof(DriverListViews));
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
This is the list view class
public class DriverListView
{
public IParticipant Driver { get; set; }
public int Position { get; set; }
public TimeSpan RaceStopwatch { get; set; }
public int LapCount { get; set; }
public TimeSpan LapStopwatch { get; set; }
public string LapPercentage { get; set; }
public int SectionCount { get; set; }
public string ParticipantName { get => Driver.Name; }
public TeamColors TeamColor { get => Driver.TeamColor; }
public int ParticpantPoints { get => Driver.Points; }
}
So when OnDriverChanged is called, the INotifyPropertyChanged is called. the window does not update the records
Thanks

MVVM Datagrid Binding SelectedItem not updating

I'm new to WPF and MVVM and i've an applicaton that uses Entity Framework to connect to database and a datagrid to show the users of the application.
The users CRUD operations are made in a separate window and not in the datagrid.
My problems are related with the update of datagrid.
The insert operation is ok but the update is not.
View 1 (Users List):
<DataGrid Grid.Row="1"
ItemsSource="{Binding Users, Mode=TwoWay}"
SelectedItem="{Binding SelectedUser, Mode=TwoWay}"
AutoGenerateColumns="False"
CanUserAddRows="False">
</DataGrid>
ViewModel :
class UserListViewModel: NotificationClass
{
UserDBContext _db = null;
public UserListViewModel()
{
_db = new UserDBContext();
Users = new ObservableCollection<User>(_db.User.ToList());
SelectedUser = Users.FirstOrDefault();
}
private ObservableCollection<User> _users;
public ObservableCollection<User> Users
{
get { return _users; }
set
{
_users = value;
OnProprtyChanged();
}
}
private User _selectedUser;
public User SelectedUser
{
get
{
return _selectedUser;
}
set
{
_selectedUser = value;
OnProprtyChanged();
}
}
public RelayCommand Edit
{
get
{
return new RelayCommand(EditUser, true);
}
}
private void EditUser()
{
try
{
UserView view = new UserView();
view.DataContext = SelectedUser;
view.ShowDialog();
if (view.DialogResult.HasValue && view.DialogResult.Value)
{
if (SelectedUser.Id > 0){
User updatedUser = _db.User.First(p => p.Id == SelectedUser.Id);
updatedUser.Username = SelectedUser.Username; //this doesn't do nothing, object is already with the new username ?!
}
_db.SaveChanges();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
after _db.SaveChanges(), datagrid should not be updated ?
Model:
class UserDBContext: DbContext
{
public UserDBContext() : base("name=DefaultConnection")
{
}
public DbSet<User> User { get; set; }
}
View 2 (User detail)
public partial class UserView : Window
{
public UserView()
{
InitializeComponent();
}
private void SaveButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
}
}
User object
class User: NotificationClass
{
public int Id { get; set; }
public string Username { get; set; }
public string CreatedBy { get; set; }
public DateTime? CreatedOn { get; set; }
}
NotificationClass
public class NotificationClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public void OnProprtyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
if i close and open view 1, the new username is updated..
could someone help ? thanks
Just implementing INotifyPropertyChanged isn't enough, you have to explicitly invoke PropertyChanged (or in your case OnPropertyChanged) when a property changed.
See also https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-implement-property-change-notification
You can do it like so
class User : NotificationClass
{
private int _id;
private string _username;
private string _createdBy;
private DateTime? _createdOn;
public int Id
{
get => _id;
set
{
if (value == _id) return;
_id = value;
OnPropertyChanged();
}
}
public string Username
{
get => _username;
set
{
if (value == _username) return;
_username = value;
OnPropertyChanged();
}
}
public string CreatedBy
{
get => _createdBy;
set
{
if (value == _createdBy) return;
_createdBy = value;
OnPropertyChanged();
}
}
public DateTime? CreatedOn
{
get => _createdOn;
set
{
if (value.Equals(_createdOn)) return;
_createdOn = value;
OnPropertyChanged();
}
}
}
it worked ! many thanks #nosale !
what about the change made to SelectedUser being reflected in my context ?
if i do this :
SelectedUser.Username = "test";
User updatedUser = _db.User.First(p => p.Id == SelectedUser.Id);
i was thinking that SelectedUser object has the "test" username and updatedUser has the old username, but not .. updatedUser already have "test"

I need your suggestion regarding loop in Model to ViewModel MVVM

I'am new to WPF and MVVM and I was given the task to continue working on one of the unfinished project that is made using the said technology. I've written a sample code below that is similar to the structure of the project.
My concern is, the loop used in GetBookPages() to display the details on the grid might take some time to finish.
public class BookModel
{
public string BookTitle { get; set; }
public List<BookDetailModel> BookDetails { get; set; }
}
public class BookDetailModel
{
public int Pages { get; set; }
public string Others { get; set; }
// ....
}
public class BookViewModel : INotifyPropertyChanged
{
private BookModel _model;
private ObservableCollection<BookDetailViewModel> _bookDetailSource;
private BookService _service;
public BookViewModel()
{
_model = new BookModel();
_service = new BookService();
GetBookPages();
}
/// <summary>
/// This is the item source of datagrid that is located in view
/// </summary>
public ObservableCollection<BookDetailViewModel> BookDetailSource
{
get { return _bookDetailSource; }
set
{
if (value == _bookDetailSource)
return;
_bookDetailSource = value;
OnPropertyChanged();
}
}
private void GetBookPages()
{
BookModel bookModel = _service.GetBookData();
var listOf = new List<BookDetailViewModel>();
bookModel.BookDetails.ForEach(e =>
{
// This is were the system's bottle neck is.
// can someone please suggests me a good work around.
listOf.Add(
new BookDetailViewModel
{
Others = e.Others,
// ....
});
});
BookDetailSource = new ObservableCollection<BookDetailViewModel>(listOf);
}
public string BookTitle
{
get { return _model.BookTitle; }
set
{
if (value == _model.BookTitle)
return;
_model.BookTitle = value;
OnPropertyChanged();
}
}
#region Property Change
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
public class BookDetailViewModel : INotifyPropertyChanged
{
private BookDetailModel _model;
#region Constructor
public BookDetailViewModel()
{
_model = new BookDetailModel();
ViewPageDataCommand = new RelayCommand(x => ViewPageData());
RemovePageCommdand = new RelayCommand(x => RemovePage());
}
#endregion
#region Properties
public int Page
{
get { return _model.Pages; }
set
{
if (value == _model.Pages)
return;
_model.Pages = value;
OnPropertyChanged();
}
}
public string Others
{
get { return _model.Others; }
set
{
if (value == _model.Others)
return;
_model.Others = value;
OnPropertyChanged();
}
}
#endregion
// These are the button command inside the grid's row
public ICommand ViewPageDataCommand { get; private set; }
public ICommand RemovePageCommdand { get; private set; }
private void ViewPageData()
{
// view the page data by clicking the row button inside the grid
}
private void RemovePage()
{
// will remove the currently selected row inside the grid
}
#region Property Change
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
public class BookService
{
public BookModel GetBookData()
{
var data = GetBookData(99);
data.BookDetails = GetBookDetail(99);
return data;
}
private BookModel GetBookData(int bookId)
{
// return 1 row only
}
private List<BookDetailModel> GetBookDetail(int bookId)
{
// return List<BookDetailModel> that might consists of more than 100 index's
}
}
I hope you understand what I mean. Your suggestion will be much appreciated. Thanks in advance!

Cannot bind to the property or column States on the DataSource

Why this work :
public Form1()
{
InitializeComponent();
exCheckedListBox1.DataSource = Profiles;
this.exCheckedListBox1.DataBindings.Add(new System.Windows.Forms.Binding("Tag", this, "States", true));
}
CheckedBindingList Profiles = new CheckedBindingList();
public int States
{
get
{
return Profiles.States;
}
set
{
Profiles.States = value;
}
}
}
public class CheckedBindingList : List<string>
{
public int States { get; set; }
}
but when change binding to
this.exCheckedListBox1.DataBindings.Add(new System.Windows.Forms.Binding("Tag", this.Profiles, "States", true));
throw the Exception ?
Thanks all very very very. I try to bind filed from my custom list class that inherit form List.
Exception - Cannot bind to the property or column States on the DataSource.
Parameter name: dataMember
Seems System.Windows.Forms.Binding parameter dataSource cannot be a class that inherits from List< T >
This solves the problem:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Load += new EventHandler(Form1_Load);
}
CheckedBindingList Profiles = new CheckedBindingList();
SomeClass some_class = new SomeClass();
public int States
{
get
{
return Profiles.States;
}
set
{
Profiles.States = value;
}
}
private void Form1_Load(object sender, EventArgs e)
{
exCheckedListBox1.DataSource = Profiles;
exCheckedListBox1.DataBindings.Add(new System.Windows.Forms.Binding("Tag", some_class, "States", true));
}
}
public class CheckedBindingList : List<string>
{
public int States { get; set; }
}
public class SomeClass
{
public int States { get; set; }
}

How to pass CommandParameters to the ViewModel?

I have a command that should switch the current view when it's executed. I binded this command to my buttons like this:
<Button Style="{StaticResource TextButton}" Command="{Binding ViewModel:MainViewModel.OpenItemCommand}" CommandParameter="{Binding Link}"/>
I want to pass Link (the link of the currently selected article) to my command. My command is defined like this:
public class Command : ICommand
{
public event EventHandler CanExecuteChanged;
readonly Predicate<Object> _canExecute;
readonly Action<Object> _executeAction;
public Command(Predicate<Object> canExecute, Action<object> executeAction)
{
_canExecute = canExecute;
_executeAction = executeAction;
}
public bool CanExecute(object parameter)
{
if (_canExecute != null)
return _canExecute(parameter);
return true;
}
public void UpdateCanExecuteState()
{
if (CanExecuteChanged != null)
CanExecuteChanged(this, new EventArgs());
}
public void Execute(object parameter)
{
if (_executeAction != null)
_executeAction(parameter);
UpdateCanExecuteState();
}
}
In my ViewModel I have this:
public ICommand OpenItemCommand
{
get
{
if (_openItemCommand == null)
{
_openItemCommand = new Command.Command(
p => true,
p => OpenItem(_HOW_DO_I_GET_THE_PARAMETER?_)
);
}
return _openItemCommand;
}
set
{
if (_openItemCommand != value)
{
_openItemCommand = value;
RaisePropertyChanged("OpenItemCommand");
}
}
}
private void OpenItem(Uri link)
{
throw new NotImplementedException();
}
When I create the command I need to pass the command parameter (the link) to the Execute method. But how do I get the value of this? I defined the CommandParameter in XAML, but I don't know how to access it.
I really searched through a huge amount of websites but I can't really find the answer.
You should look at the implementation of Prism's DelegateCommand or MVVM light's RelayCommand. With these you would write code like this:
public class ViewModel
{
public ViewModel()
{
OpenItemCommand = new RelayCommand<string>(OpenItem);
}
public RelayCommand<string> OpenItemCommand { get; private set; }
private void OpenItem(string link)
{
Debug.WriteLine(link);
}
}
where string in this case is the type of the parameter.
I'm not sure where the link parameter is coming from but if it's from a control, the value of the control could be bound to a property of your view model, then you don't need a parameter, for example:
public class ViewModel
{
public ViewModel()
{
OpenItemCommand = new RelayCommand(OpenItem);
}
public RelayCommand OpenItemCommand { get; private set; }
public string Link { get; set; }
private void OpenItem()
{
Debug.WriteLine(Link);
}
}
replace
p => OpenItem(_HOW_DO_I_GET_THE_PARAMETER?_)
with
p => OpenItem(p)
that is what the p stands for: parameter

Resources