My oneway binding isn't working as I'm expecting.
When I click a button to add a new "person" it doesn't add the newly entered person to the listview until I shut it down and restart the application (so the value gets added to the DB just not to the UI)
What am I doing incorrectly? I have the INotifyPropertyChanged, I have the ObservableCollection... What am I missing?
I have my Model:
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _personName;
private string _personEmail;
private string _personPhone;
private DateTime _personDOB;
[PrimaryKey, AutoIncrement]
public int personId { get; set; }
[MaxLength(25)]
public string personName {
get { return _personName; }
set
{
_personName = value;
OnPropertyChanged("personName");
}
}
[MaxLength(50)]
public string personEmail {
get { return _personEmail; }
set
{
_personEmail = value;
OnPropertyChanged("personEmail");
}
}
[MaxLength(13)]
public string personPhone {
get { return _personPhone; }
set
{
_personPhone = value;
OnPropertyChanged("personPhone");
}
}
public DateTime personDOB {
get { return _personDOB;}
set
{
_personDOB = value;
OnPropertyChanged("personDOB");
}
}
public Boolean isPersonActive { get; set; }
public string Summary
{
get { return string.Format("{0} - {1} : {2} -- {3}", personName, personEmail, personPhone, personDOB); }
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
In my ViewModel I have this:
public class ChoresVM
{
private ObservableCollection<win8Chores.Model.databaseTables.Person> _personList;
public ObservableCollection<win8Chores.Model.databaseTables.Person> personList
{
get { return _personList; }
set { _personList = value; }
}
...
public ObservableCollection<win8Chores.Model.databaseTables.Person> selectAllPerson()
{
using (var db = new SQLiteConnection(dbPath))
{
ObservableCollection<win8Chores.Model.databaseTables.Person> pList = new ObservableCollection<win8Chores.Model.databaseTables.Person>(db.Query<win8Chores.Model.databaseTables.Person>("select personId,personName,personEmail,personDOB from Person"));
_personList = new ObservableCollection<Model.databaseTables.Person>(db.Query<win8Chores.Model.databaseTables.Person>("select personId,personName,personEmail,personDOB from Person"));
return _personList;
}
}
public void insertPerson(string name, string email, string phone, DateTime dob, Boolean isActive = true)
{
dbPath = Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "myDB");
db = new SQLiteConnection(dbPath);
using (db)
{
var p = db.Insert(new win8Chores.Model.databaseTables.Person()
{
personName = name,
personEmail = email,
personPhone = phone,
personDOB = dob,
isPersonActive = isActive
});
}
selectAllPerson();
}
Then in my View:
public MainPage()
{
this.InitializeComponent();
VM = new ViewModel.ChoresVM();
DataContext = VM;
}
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
DateTime myDOB = new DateTime(1955, 02, 28);
VM.insertPerson("test","test#live.com","123-456-7890", myDOB);
}
With my XAML like this:
<ListView HorizontalAlignment="Left" Height="224" Margin="287,344,0,0" VerticalAlignment="Top" Width="740" x:Name="test" DisplayMemberPath="Summary" ItemsSource="{Binding personList, Mode=OneWay}" />
Try to implement INotifyPropertyChanged on ChoresVM and raise PropertyChanged("personList") when you initialize the collection (in selectAllPerson() or personList setter). This way itemssource binding will be notified, that collection property was changed and will pickup new collection. Also you have a typo. In Binding you have "PersonList" (Pascal case) but you property is in camel case (personList)
Related
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.
I want to read some data from database and do some process on them and then view them in the view.
I read a lot about MVVM and now I am confused.
Imaging I read a person entity from database with Name attribute.
please make a small code and show me how should I make my model and ViewModel.
I guess it we will be something like this :
public class PersonModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string Name;
public string name
{
get
{
return Name;
}
set
{
Name = value;
onpropertychanged("name");
}
}
public PersonModel( string s)
{
name = s;
}
public void onpropertychanged(string PName)
{
if (PropertyChanged !=null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PName));
}
}
}
public class PersonViewModel
{
public ObservableCollection <PersonModel> list { get; set; }
public PersonViewModel()
{
list = new ObservableCollection<model>();
list.Add(new model("abc"));
list.Add(new model("def"));
}
public void change()
{
list[1].name = "changed";
}
}
public class ViewModelBase
{
public PersonViewModel vperson { get; set; }
public ViewModelBase()
{
vperson = new PersonViewModel();
vperson.change();
}
}
Edite : Where should database connections be?
Edite :
<Grid>
<TextBox Text="{Binding vperson.list[1].name}" />
</Grid>
</Window>
I edited your classes and is working
public class PersonModel : INotifyPropertyChanged
{
private string _name;
public string Name
{
get => _name;
set
{
if (_name == value) return;
_name = value;
OnPropertyChanged();
}
}
public PersonModel(string name)
{
_name = name;
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class PersonViewModel
{
public ObservableCollection<PersonModel> Items { get; set; }
public PersonViewModel()
{
Items = new ObservableCollection<PersonModel> { new PersonModel("abc"), new PersonModel("def") };
}
public void Change()
{
Items[1].Name = "changed";
}
}
public class ViewModelBase
{
public PersonViewModel PersonViewModel { get; set; }
public ViewModelBase()
{
PersonViewModel = new PersonViewModel();
PersonViewModel.Change();
}
}
//Use the dataContext in this way, will help you with the strong type
xmlns:viewModels="clr-namespace:WpfApp1.ViewModels"
<Window.DataContext>
<viewModels:ViewModelBase />
</Window.DataContext>
<Grid>
<TextBox Text="{Binding PersonViewModel.Items[1].Name}" />
</Grid>
I used MVVM.
How I can get the selected item from DataGrid?
It is my Model
Person.cs
public class Person : INotifyPropertyChanged
{
#region Fields
private string _firstName;
private string _middleName;
private string _lastName;
private DateTime _dateOfBirth;
private Gender _gender;
#endregion Fields
#region Properties
public string FirstName
{
get { return _firstName; }
set
{
if (value == _firstName) return;
_firstName = value;
nPropertyChanged();
}
}
public string MiddleName
{
get { return _middleName; }
set
{
if (value == _middleName) return;
_middleName = value;
OnPropertyChanged();
}
}
public string LastName
{
get { return _lastName; }
set
{
if (value == _lastName) return;
_lastName = value;
OnPropertyChanged();
}
}
public DateTime DateOfBirth
{
get { return _dateOfBirth; }
set
{
if (value.Equals(_dateOfBirth)) return;
_dateOfBirth = value;
OnPropertyChanged();
}
}
public Gender Gender
{
get { return _gender; }
set
{
if (value == _gender) return;
_gender = value;
OnPropertyChanged();
}
}
#endregion Properties
#region Constructors
public Person()
{
}
public Person(string firstName, string middleName, string lastName, DateTime dateOfBirth, Gender gender)
{
FirstName = firstName;
MiddleName = middleName;
LastName = lastName;
DateOfBirth = dateOfBirth;
Gender = gender;
}
#endregion Constructors
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
//[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion INotifyPropertyChanged
}
public enum Gender
{
Male,
Female
}
}
It is my ViewModel
Ii is PersonsViewModel
namespace Learn.MVVM.Example.ViewModels
{
public class PersonsViewModel<TViewType> : INotifyPropertyChanged, IViewModel where TViewType : IView, new()
{
private readonly IView _view;
private readonly PersonModel _model;
public ObservableCollection<Person> Persons { get; set; }
public RelayCommand OkCommand { get; private set; }
private string _str;
public PersonsViewModel()
{
this._view = new TViewType();
this._model = new PersonModel();
this.Persons = new ObservableCollection<Person>(this._model.GetPersons());
this.OkCommand = new RelayCommand(o => this.OKRun());
_str = "Кнопка";
this._view.SetDataContext(this);
this._view.ShowView();
}
public string Str
{
get { return _str; }
set
{
if (_str == value)
return;
_str = value;
OnPropertyChanged("Str");
}
}
public ObservableCollection<Person> Observ
{
get { return Persons; }
set
{
if (Persons == value)
return;
Persons = value;
OnPropertyChanged("Observ");
}
}
public event PropertyChangedEventHandler PropertyChanged;
//[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
private void OKRun()
{
Str = "Refresh";
//this.Persons = new ObservableCollection<Person>(this._model.SetPersons());
this.Observ = new ObservableCollection<Person>(this._model.SetPersons());
//OnPropertyChanged("Observ");
}
}
}
How i can get value current row from datagrid?
How i can get index current row from datagrid?
In my experience, a nice method to achieve this is by binding the SelectedItem to a property in your view model:
public Person SelectedPerson { get; set; }
And your DataGrid will look like this:
<DataGrid SelectedItem="{Binding SelectedPerson}" ... >
I am trying to bind the data displayed in a DataGrid to a dynamic list of object (WhisperModel) which is inside another object(WhisperReader). The DataGrid only displays the headers, but no values. How can I make the DataGrid dynamically update itself when the list "whispers" is changed?
Main Window XAML:
<DataGrid x:Name="whisperDataGrid" Margin="10,69,10,10" IsReadOnly="True" ItemsSource="{Binding}"/>
Main Window C#
public partial class MainWindow : Window
{
private WhisperReader wr;
public MainWindow()
{
InitializeComponent();
wr = new WhisperReader();
whisperDataGrid.DataContext = wr.whispers;
}
WhisperReader:
class WhisperReader
{
public ObservableCollection<WhisperModel> whispers { get; private set; }
public WhisperReader()
{
whispers = new ObservableCollection<WhisperModel>();
}
WhisperModel:
class WhisperModel
{
public DateTime sentTime { get; set; }
public string sender { get; set; }
public string message { get; set; }
}
I think your problem is that it doesn't know when to update itself because:
You have made the whispers list the data context.
The properties that you are binding to don't use INotifyPropertyChanged.
WhisperReader and WhisperModel are not public
All bindings must be public, must be properties, and must call the PropertyChanged method.
The PropertyChanged function triggers the binding updates.
Try this...
public partial class MainWindow : Window
{
private WhisperReader wr;
public MainWindow()
{
InitializeComponent();
wr = new WhisperReader();
whisperDataGrid.DataContext = wr;
}
public class WhisperReader : INotifyPropertyChanged
{
ObservableCollection<WhisperModel> _whispers;
public ObservableCollection<WhisperModel> whispers
{
get { return _whispers; }
private set
{
_whispers = value;
NotifyPropertyChanged();
}
}
public WhisperReader()
{
whispers = new ObservableCollection<WhisperModel>();
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class WhisperModel : INotifyPropertyChanged
{
public DateTime sentTime { get; set; }
private string _sender;
public string sender
{
get { return _sender; }
set { _sender = value; NotifyPropertyChanged();
}
private string _message;
public string message
{
get { return _message; }
set { _message = value; NotifyPropertyChanged();
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
<DataGrid x:Name="whisperDataGrid" Margin="10,69,10,10" IsReadOnly="True" AutoGenerateColumns="True" ItemsSource="{Binding whispers}"/>
I'm have a TextBox which gets added to a Window alongside a Checkbox. I've managed to bind the TextBox to property Order of the CheckedListItem handler so when a change is made it binds correctly and updates.
My problem I'm having is that I cannot get it to initialise with starting values. My constructor is as follows
public partial class OwnerSettingWindow : Window
{
public ObservableCollection<CheckedListItem<Owner>> Owners { get; set; }
public class Owner
{
public String OwnerName { get; set; }
public String OwnerOrder { get; set; }
}
public OwnerSettingWindow()
{
InitializeComponent();
Owners = new ObservableCollection<CheckedListItem<Owner>>();
string testString = #"Item1,true,1:Item2,true,2:Item3,false,24"; ;
string[] splitOwners = testString.Split(':');
foreach (string item in splitOwners)
{
string[] spOwnerSetting = item.Split(',');
bool bchecked = bool.Parse(spOwnerSetting[1].ToString());
string norder = spOwnerSetting[2].ToString();
Owners.Add(new CheckedListItem<Owner>(new Owner() { OwnerName = spOwnerSetting[0].ToString(), OwnerOrder = norder },
isChecked: bchecked));
}
DataContext = this;
}
public class CheckedListItem<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isChecked;
private T item;
private string order;
public CheckedListItem()
{ }
public CheckedListItem(T item, bool isChecked = false)
{
this.item = item;
this.isChecked = isChecked;
}
public T Item
{
get { return item; }
set
{
item = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Item"));
}
}
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("IsChecked"));
}
}
public string Order
{
get { return order; }
set
{
order = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Order"));
}
}
}
}
The following line not working as intended
Owners.Add(new CheckedListItem<Owner>(new Owner() { OwnerName =
spOwnerSetting[0].ToString(), OwnerOrder = norder },
isChecked: bchecked));
OwnerOrder = norder is not showing when the Window is opened. My TextBox binding in XAML is simply <TextBox Text ="{Binding Order}"/>
I've also tried the following with no success
Owners.Add(new CheckedListItem<Owner>(new Owner() { OwnerName = spOwnerSetting[0].ToString() },
isChecked: bchecked, order: norder));
Any ideas?
Order Property in CheckedListItem is never initialized. you can add a new ctor
public CheckedListItem(T item, string nrorder, bool isChecked = false)
{
this.item = item;
this.isChecked = isChecked;
this.order = nrorder;
}
and change your adding method
Owners.Add(new CheckedListItem<Owner>(new Owner() { OwnerName = spOwnerSetting[0].ToString(), OwnerOrder = norder }, norder, isChecked: bchecked));
In my test app now is working.