How to update cuurent view in prism region - c#

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.

Related

Xamarin android with mvvm light : Textview binding only updates when a relaycommand is called

I setup a very simple app to get me started and trying things out. It has a label and two buttons. The buttons are linked to relaycommands in the main view model are used to send a message to a server using Mqtt which work as intended. The label is used to show part of the data received from the server. Everything seems to work fine except the labels won't update as soon as the message is received event though I can see in debugging that the property is set. The label will update as soon as I press one of the two buttons...
I'm new to the whole Xamarin android thing and have used mvvm light once in a WPF application.
Main Activity :
public partial class MainActivity
{
// UI Elements
public TextView ScanInfoLabel { get; private set; }
public Button UnlockButton { get; private set; }
public Button RegisterButton { get; private set; }
// Keep track of bindings to avoid premature garbage collection
private readonly List<Binding> _bindings = new List<Binding>();
// Get view model
private MainViewModel mainViewModel { get { return App.Locator.Main;}}
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
// Set view from the "main" layout resource
SetContentView(Resource.Layout.Main);
// Get the UI elements by ID
ScanInfoLabel = FindViewById<TextView>(Resource.Id.ScanInfoLabel);
UnlockButton = FindViewById<Button>(Resource.Id.UnlockButton);
RegisterButton = FindViewById<Button>(Resource.Id.RegisterButton);
// Set Bindings for textviews
_bindings.Add(
this.SetBinding(
() => mainViewModel.ScanInfoLabel,
() => ScanInfoLabel.Text));
// Set the bindings for commands
UnlockButton.SetCommand("Click", mainViewModel.UnlockCommand);
RegisterButton.SetCommand("Click", mainViewModel.RegisterTagCommand);
}
In Main view model :
// RelayCommands
public RelayCommand UnlockCommand;
public RelayCommand RegisterTagCommand;
public RelayCommand MqttConnectCommand;
// Bindable properties
private string _scanInfoLabel = "Test";
public string ScanInfoLabel
{
get { return _scanInfoLabel; }
set { Set(ref _scanInfoLabel, value); }
}
// New scan message received
private void RFIDScanReceived(RFID.Scan scan)
{
ScanInfoLabel = BitConverter.ToString(scan.UID);
}
I would expect the label to show the data as soon as the mqtt message is received (which is then sent to the mainviewmodel using Messenger.Default.send<>() from mvvm light). But nothing is changed in the UI until I click on one of the buttons and then the correct information in displayed.
I don't really know where to start being new to xamarin android and none of my searches seemed to be of any help.
Any help will be appreciated, thanks!
ViewModels generally implement the INotifyPropertyChanged interface,
which means that the class fires a PropertyChanged event whenever one
of its properties changes. The data binding mechanism in Xamarin.Forms
attaches a handler to this PropertyChanged event so it can be notified
when a property changes and keep the target updated with the new
value.
Solution:
Make you model inherit from INotifyPropertyChanged and add PropertyChanged inside the set part. Then the labels will update as soon as the value of ScanInfoLabel changed.
public class BaseViewModel : INotifyPropertyChanged
{
// Bindable properties
private string _scanInfoLabel = "Test";
public string ScanInfoLabel
{
get { return _scanInfoLabel; }
set
{
_scanInfoLabel = ScanInfoLabel;
PropertyChanged(this, new PropertyChangedEventArgs("ScanInfoLabel"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
You can refer:data-bindings-to-mvvm
Well I found out it was a threading issue since I set the property from a Messenger call. This apparently is not an issue in WPF which is why I was a bit stuck but using the DispatcherHelper did the trick.
// New scan message received
private void RFIDScanReceived(RFID.Scan scan)
{
DispatcherHelper.CheckBeginInvokeOnUI(() =>
{
ScanInfoLabel = BitConverter.ToString(scan.UID);
});
}

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

C# SQLite INotifyPropertyChanged implementation

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.

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

Passing DataContext between windows in MVVM

On the main window onClick I have
AddNoticeAboutWrongCity addNoticeAboutWrongCity = new AddNoticeAboutWrongCity();
addNoticeAboutWrongCity.DataContext = ((VerificationViewModule)this.DataContext).WrongCityNotice;
addNoticeAboutWrongCity.ShowDialog();
At popup window there a lot of textboxes and two buttons
Delete object:
this.DataContext = null;
And second option "Save edited notice" which is not usable , because every change of user affection datacontext on main window,and this is demand from design department :)
I don't know why first option(it's "implementation" doesn't work.
Second explanation:
On the ParentWindow I have list of Notices and I can click EditSelectedNotice.
On the EditNoticeWindow I can edit Notice or delete Notice.
Editinig works(After closing EditNoticeWindow I see changed notice on the ParentWindow), but deleting doesn't (Notice is still in collection - on control and in this.DataContext)
My ViewModel:
class VerificationViewModule
{
public ObservableCollection<ReporterNotice> ReporterNotices { get; set; }
public ReporterNotice OtherNotice
{
get
{
return ReporterNotices.Where(n => n.Type == ReporterNoticeType.Other).FirstOrDefault();
}
}
public ReporterNotice DuplicateNotice
{
get
{
return ReporterNotices.Where(n => n.Type == ReporterNoticeType.Duplicate).FirstOrDefault();
}
}
public ReporterNotice WrongCityNotice
{
get
{
return ReporterNotices.Where(n => n.Type == ReporterNoticeType.WrongCity).FirstOrDefault();
}
set { if(value==null)
{
ReporterNotices.Remove(ReporterNotices.Where(n => n.Type == ReporterNoticeType.WrongCity).First());
}
else
{
if (ReporterNotices.Where(n => n.Type == ReporterNoticeType.WrongCity).FirstOrDefault()==null)//there is always only max one instance of this type of notice
{
ReporterNotices.Add(value);
}
else
{
var c = ReporterNotices.Where(n => n.Type == ReporterNoticeType.WrongCity).First();
c = value;
}
}}
}
public VerificationViewModule()
{
ObservableCollection<ReporterNotice> loadedReporterNotices = new ObservableCollection<ReporterNotice>();
loadedReporterNotices.Add(new ReporterNotice() { Content = "Dublic", Type = ReporterNoticeType.WrongCity });
loadedReporterNotices.Add(new ReporterNotice() { Content = "Hilton", Type = ReporterNoticeType.Duplicate });
loadedReporterNotices.Add(new ReporterNotice() { Content = "Another notice", Type = ReporterNoticeType.Other });
ReporterNotices = loadedReporterNotices;
}
}
You can try the following. Implement the mediator to display windows and make sure that you use view models for the DataContext for both the main and edit windows. It is important to tell the main view model that the object is being deleted. This is done via a callback and routing that through a command on the EditNoticeViewModel
//This viewmodel is on the main windows datacontext
public class ParentViewModel
{
private readonly IWindowMediator _mediator;
public ParentViewModel(IWindowMediator mediator)
{
_mediator = mediator;
}
public ObservableCollection<Notice> Notices { get; private set; } //bound to list in xaml
public void OpenNotice(Notice notice)
{
//open the window using the Mediator pattern rather than a new window directly
_mediator.Open(new EditNoticeViewModel(notice, DeleteNotice));
}
private void DeleteNotice(Notice notice)
{
//This will remove it from the main window list
Notices.Remove(notice);
}
}
//view model for EditNoticeWindow
public class EditNoticeViewModel
{
public EditNoticeViewModel(Action<Notice> deleteCallback, Notice notice)
{
Model = notice;
DeleteCommand = new DelegateCommand((a) => deleteCallback(Model));
}
//Bind in xaml to the Command of a button
DelegateCommand DeleteCommand { get; private set; }
//bound to the controls in the xaml.
public Notice Model { get; private set; }
}
//This is a basic interface, you can elaborate as needed
//but it handles the opening of windows. Attach the view model
//to the data context of the window.
public interface IWindowMediator
{
void Open<T>(T viewModel);
}
Depending on implementation you might want to close the view when the delete button gets pushed. You can do this by implementing something like the as described here with respect to WorkspaceViewModel
Why don't you wrap the WrongCityNotice in a viewModel implementing IReporterNotice and having a reference to the parent viewmodel and a Delete method:
public void Delete() { _parentvm.Delete(_wrongCityNotice); }
You can use this wrapper as DataContext.
You're trying to destroy the DataContext. C# doesn't work that way. Setting an object reference to null doesn't delete the object, it only removes the reference to it. (When nothing references an object anymore it gets garbage collected, but you can't destroy an object directly).
DataContext = null only means that locally your DataContext doesn't point to any object any more. The main view model still has a reference however so nothing changes there. You'll have to ask the main view model to remove the notification from it's collection (probably through a callback method (Action) is best so you don't have to know about the parent view model).

Categories