C# SQLite INotifyPropertyChanged implementation - c#

I'm using SQLite in my application to store some Message objects. I display a list of Message items in "View 1". When I change a property in the Edit View "View 2", I want the property to also change in the list.
Classes
Message.cs
class Message : INotifyPropertyChanged
{
private uint _id;
public uint Id
{
get
{
return _id;
}
set
{
// Trigger INotifyPropertyChanged
Set("Id", ref _id, value);
}
}
private string _content;
public string Content
{
get
{
return _content;
}
set
{
// Trigger INotifyPropertyChanged
Set("Content", ref _content, value);
}
}
...
}
MessageViewModel.cs
class MessageViewModel : INotifyPropertyChanged
{
private Message message;
...
private string _content;
public string Content
{
get
{
return message.Content;
}
set
{
// Set value
message.Content = value;
// Trigger INotifyPropertyChanged
RaisePropertyChanged();
}
}
...
}
View 1
View1.xaml
The datacontext is View1ViewModel
View1ViewModel.cs
private List<MessageViewModel> _messages;
public List<MessageViewModel> Messages
{
get
{
return _messages;
}
set
{
// Trigger INotifyPropertyChanged
Set("Messages", ref _messages, value);
}
}
...
private async void loadMessages()
{
// Get the messages from SQLite database
var messages = await newMessages();
Messages = new MessageViewModelCollection(messages);
}
View 2
View2ViewModel.cs
private MessageViewModel _message;
public MessageViewModel Message
{
get
{
return _message;
}
set
{
// Trigger INotifyPropertyChanged
Set("Message", ref _message, value);
}
}
...
private async void loadMessage()
{
// Get the message from SQLite database by Id
var message = await newMessage(messageId);
Message = new MessageViewModel(message);
}
The functions newMessages and newMessage(uint messageId) return new Message objects from the database.
I normally use the INotifyPropertyChanged implementation, but this doesn't work. I query the database 2 times, once for the list (View 1), and once for the edit page (View 2). The SQLite returns two different copies of the Message object, therefore the INotifyPropertyChanged meganism will not work (Only in the current page, not the pages in the backstack).
I could fix the problem by re-using the Message item from the list, but I cannot always do this in all views.
Is there a way to make the INotifyPropertyChanged work in this scenario? Or do I need a different approach to update the values?

You need a different approach. IMHO, the best thing would be to separate the data model from the view model. So have a MessageVm that binds to the WPF view, and put the logic into it to update itself appropriately (along with firing any necessary INotifyPropertyChanged's) from the Message objects that get passed into it.
There are some automatic mapping tools like Automapper which might, to some degree, alleviate the pain that comes along with this approach.
But you really should separate the view model from the data model, the decoupling of these layers is one of the principle tenets of WPF programming.

Related

Extending dynamic dispatch to call functions in the view model?

I'm using MVVM in a Xamarin application, I have an interface to navigate between pages:
public interface INavigate
{
INavigate Next();
INavigate Previous();
string ViewTitle { get; }
}
In the implementing views:
public partial class V2Upload : ContentView, INavigate
{
public string ViewTitle => "Upload photos";
public INavigate Next()
=> new V3AdDetail();
public INavigate Previous()
=> new V1Agreement();
}
and in the view model
I have a property of type INavigate:
public INavigate CurrentAddItemStep
{
get { return _currentAddItemStep; }
set { Set(ref _currentAddItemStep, value); }
}
and the Content property of the parent view is bound to this property:
when next button is clicked I execute this code:
CurrentAddItemStep = CurrentAddItemStep.Next();
ViewTitle = CurrentAddItemStep.ViewTitle;
now a validation method is required before navigating to the next page for all the Content views..
I want to keep the MVVM pattern as clean as possible by not writing business code in the view, for example in the V2Upload view the File1 and File2 properties of the view model shouldn't be null:
private bool ValidateFiles(){
return (File1 ?? File2) != null;
}
but since the navigating is done dynamically in run-time, I can't know which view is the current view.
I'm thinking to use reflection , to know what is the name of the view (but this will break the whole design)
Another option is to provide a function parameter to the Next method, but also how to provide it in the design time from the view model?
This is what I'm doing now:
public INavigate Next()
{
if (((ViewModel.AddItemViewModel)BindingContext).ValidateFiles())
return new V3AdDetail();
else
return this;
}
but again, I'm accessing the view model from the view (and had to change the ValidateFiles method from private to public), which I want to avoid

How to use C# WPF?

For my school assignment I have to make a reservationsystem for a hotel.
The thing is that I have to make the code without the UI (never done this before).
I have to add the UI later. Each UI should be able to be used with my code.
Now I have a class called Secretary
The Secretary is able to make a Reservation.
I have this method in the class Secretary :
public void CheckIn()
{
Reservation reservation = new Reservation();
reservation.ReservationDate1 = //info from a textbox
}
Now I know that I should connect everything when my UI is ready, but what is the best way to tell my code that he should get the information from the textbox when the textbox isn't there yet???
i would suggest you start by reading this
now as for what you have to do then
first you will need to model your data
public class Reservation
{
public DateTime Date{get;set;}
public string Name{get;set;}
public void Save(){/*Copy entry to DB, webservice, file, etc*/}
public void Delete(){/*delete entry from DB, webservice, file, etc*/}
//ect
}
as you can see you now have a list of what is required for a reservation, and functionality that will persist your data
next you need a ViewModel
public class ReservationViewModel:INotifyPropertyCHanged
{
public Reservation Reservation{get;set;} //Link to model
private DateTime _Date;
public DateTime Date
{
get { return _Date; }
set { SetProperty(ref _Date, value); }
}
private string _Name;
public string Name
{
get { return _Name; }
set { SetProperty(ref _Name, value); }
}
public void SetProperty<T>(ref T store, T value,[CallerMemberName] string name = null)
{
store = value;
if(PropertyChanged!=null)PropertyChanged(this,new PropertyChangedArgs(name);
}
public void Save(){/*validate, copy over model values call models save*/}
public void Cancel(){/*change VM values back to Model values*/}
public void Delete(){/*validate, call models delete*/}
//ect
}
at this point you can stop as you have defined the data and behaviour of the system, though i would suggest adding a testing project to run your code and check it works
when you get to your View
you would just bind to your ViewModel and the rest is done for you
<TextBox Text={Binding Name}/>
You can use MVVM with a ViewModel, but if you just want a method ready to accept input when you design the UI, you can make Checkin() take a string parameter so it's Checkin(string value) and assign value to ReservationDate1.
public void CheckIn(string val)
{
Reservation reservation = new Reservation();
reservation.ReservationDate1 = val;
}
This is an exercise in keeping your logic and your UI nice and separate. A little more tightly coupled, but doable, would be this:
public void CheckIn(TextBox tb)
{
Reservation reservation = new Reservation();
reservation.ReservationDate1 = tb.Text;
}

How to update cuurent view in prism region

I wish to know that is there a way to update my current view after loading in prism region.
My view is updated automatically when loaded and i use lifetime interface to load every time when called.
is there a way where we can update the current view like update folder??
First of all, the view model (and with it the view) should update itself automatically when the model changes, either through INotifyPropertyChanged, dedicated events, usage of the EventAggregator or any other message passing system.
That being said, if you want the view model to update only at a certain point in time (e.g. when the user clicks an update button), you should move the update code out of the NavigatedTo method and call that method from NavigatedTo and the UpdateCommand.
internal class MyViewModel : BindableBase, INavigationAware
{
public MyViewModel( IDataSource theSourceOfData )
{
_theSourceOfData = theSourceOfData;
UpdateCommand = new DelegateCommand( UpdateData );
}
public string MyProperty
{
get
{
return _myProperty;
}
set
{
SetProperty( ref _myProperty, value );
}
}
public DelegateCommand UpdateCommand { get; }
#region INavigationAware
public void OnNavigatedTo( NavigationContext navigationContext )
{
UpdateData();
}
#endregion
#region private
private readonly IDataSource _theSourceOfData;
private string _myProperty;
private void UpdateData()
{
_myProperty = _theSourceOfData.FetchTheData();
}
#endregion
}
Now, if we click the update button, MyViewModel.MyProperty is updated and the change notification pushed out to the view. Same happens if we navigate to the view model.

How to use SimpleIoC and Messenger for Lifetime Object(s) / ViewModels?

I have an application with user-login which representates a "lifetime" object till the user logs out. I'm using MVVM Light but don't know how to use the SimpleIoC (with the Messenger) correctly.
Currently I save the object in the MainViewModel as CurrentUser Property of my custom User object and pass it to other ViewModels via constructor and save it there as a Property too. But with that I have the issue, that at some places the object gets overwritten / created new.
This is probably the wrong way to do it as i figured out that the SimpleIoC can simply return the registered instances with GetInstance<>() and if needed a Key.
So the right way is it to create a User object with Key to get it in needed classes, modify and save it so the other classes can get the updated value when calling (or PropertyChanged possible?), right?
I tried to create it like that, but the object is obviously not updated:
public MainViewModel()
{
RegisterMessenger();
CurrentUser = SimpleIoc.Default.GetInstance<User>("123");
_dataService = SimpleIoc.Default.GetInstance<IDataService>();
Messenger.Default.Send(new NotificationMessage("GoToLoginPage"));
}
public User CurrentUser
{
get { return _currentUser; }
set { Set(ref _currentUser, value); }
}
public User CurrentPageViewModel
{
get { return _currenPageViewModel; }
set { Set(ref _currentPageViewModel, value); }
}
public void RegisterMessenger()
{
Messenger.Default.Register<NotificationMessage>(this, message =>
{
switch (message.Notification)
{
case "GoToLoginPage":
if (_loginViewModel == null)
_loginViewModel = new LoginViewModel(_dataService);
CurrentPageViewModel = _loginViewModel;
break;
// Other cases...
});
}
}
// -------------------------------------------------------------------------
public LoginViewModel(IDataService dataService)
{
UserObj = SimpleIoc.Default.GetInstance<User>("123");
_dataService = dataService;
LoginCommand = new RelayCommand(LoginExecute, LoginCanExecute);
}
Would it also be better, if I call the ViewModels with GetInstance<SomeViewModel>().. but then, how I pass paramters to them and let them stay alive / recreate / destroy?
Would be nice if someone can point me out the right way, thanks!

PropertyChangedEvent and CanExecute issue

I am using MVVM (prism) to develop wpf application.
One of my model class "StandardContact" has its properties directly bound to the view. I use IDataErrorInfo to track and notify whether the model has any error. If there are any errors in Model, I disable the "Save" Command.
As the user enters some data, I use the StandardContact.PropertyChanged handler to see if "Save" command can execute (i.e if the model data entered by user is valid). The problem is that the StandardContact.PropertyChanged handler is called before the IDataErrorInfo's validation code, so CanExecute for "Save" command does not correctly reflect whether the command can be executed or not. What I am looking for is that, before the CanExecute executes, the IDataErrorInfo validation should run so that the CanExecute will query on the latest data in model and decide whether it is enabled or not. Here is the sample code that I am using
Model:
public class StandardContact :EntityBase, IDataErrorInfo
{
public virtual string Name
{
get { return _name; }
set { SetField(ref _name, value, () => Name); }
}
//...
//Validators
public string this[string propertyName]
{
get
{
string error = null;
//....
}
ViewModel
public class SContactEditViewModel : NotificationObject, INavigationAware
{
//....
StandardContact.PropertyChanged +=
new PropertyChangedEventHandler(StandardContact_PropertyChanged);
void StandardContact_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//Requery if command can execute
SaveNewCommand.RaiseCanExecuteChanged();
}
}
I just inspected our priprietary MVVM library. Inside the ViewModels indexer (in your case this is the Models indexer) the requested Property is validated:
public string this[string propertyName]
{
get
{
string result = null;
if (CanDataErrorValidated(propertyName))
{
int errorCount = CurrentValidationAdapter.ErrorCount();
result = ValidateProperty(propertyName, GetValidateValue(propertyName));
// if the error flag has been changed after validation
if (errorCount != CurrentValidationAdapter.ErrorCount())
{
RaisePropertyChanged(PropHasError);
RaisePropertyChanged(PropError);
}
}
else
{
RaisePropertyChanged(PropHasError);
RaisePropertyChanged(PropError);
}
return result;
}
}
So the solution of your problem seems to validate the requested property on the fly.
I don't use prism, but if it exposes some sort of IsValid method or property you can use that to trigger your error checking. And if it doesn't you can write your own.
The basic idea without prism is to have to leverage IDataErrorInfo.Error by doing
bool IsValid{ get{return string.IsNullOrEmpty(Error) } // trigger validation
Then inside your Save.CanExecute method
return IsValid; // trigger validation on demand
HTH,
Berryl

Categories