Assuming that there some event that will call the showEditPage function. Which will update the value of isEditPageVisible.
Index Base class
public abstract class IndexBase : ComponentBase
{
protected bool isEditPageVisble = false;
public void showEditPage(){
isEditPageVisble != isEditPageVisble;
}
}
How would I get it right to update the value of isEditPageVisble in the Index component as well
when someEditPageEvent is called?
Edit component
#page "/Edit/{isEditPageVisble}"
#inherits Project.IndexBase
#using Project.Model;
#if (isEditPageVisble)
{
<EditPage #bind-isVisible="isEditPageVisble"></EditPage>
}
Edit base class
public abstract class EditBase : IndexBase
{
[Parameter]
public bool isEditPageVisble { get; set; }
[Parameter]
public EventCallback<bool> isEditPageVisbleChanged { get; set; }
protected async Task ChangeValueAsync()
{
await isEditPageVisbleChanged .InvokeAsync(isEditPageVisble );
}
public void someEditPageEvent(){
isEditPageVisble = false;
}
}
Edit component
EditPage.razor
<SomeComponent Visible="isEditPageVisble"></SomeComponent>
Use a State/Notification Service. Here's a simple example for your question.
using System;
namespace StackOverflow.Answers.Data
{
public class PageStateService
{
public bool IsEditPageVisible
{
get => _isEditPageVisible;
set
{
if (value != _isEditPageVisible)
{
IsEditPageVisible = value;
EditPageVisibilityChanged?.Invoke(this, EventArgs.Empty);
}
}
}
private bool _isEditPageVisible;
public event EventHandler EditPageVisibilityChanged;
}
}
Register as a scoped service, and inject it into the components that need to use it. If you want to update a component when IsEditPageVisible gets change, wire up an event handler to the EditPageVisibilityChanged.
It should look like this:
private void OnEditPageVisibilityChanged(object sender, EventArgs e)
=> StateHasChanged();
Related
My TabbedPage uses a Binding Property, which is defined in the tabbed page's ViewModel, for showing a Badge text.
I am setting the badge property when initializing the view (actually when it (re)appears). However, sometimes the badge text is changing from outside of my ViewModel(s), this is because I have a SignalR method which is called when a new message is being added by another application.
Though, when this happens the OnAppearing method of my tabbed viewmodel is obviously not called. So the question is, how can I 'notify' the tabbedpage viewmodel that the badge text should be changed.
I think the (best) way to do this is using somekind of Event. Since all of my ViewModels inherit from a 'ViewModelBase' I could implement the event notification / change in the ViewModelBase and override the property in my TabbedPage ViewModel.
Though, sadly my knowledge about using Events / EventArgs is limited and the stuff I found about it is not working.
Is using EventArgs the best way to solve this problem? And if so, could anyone give any pointers how to implement it properly.
*On a side-note, I am also using Prism
My TabbedPage ViewModel:
public class RootTabbedViewModel : ViewModelBase, IPageLifecycleAware
{
private readonly INavigationService _navigationService;
private int _messageCount;
public RootTabbedViewModel(INavigationService navigationService)
: base(navigationService)
{
_navigationService = navigationService;
}
public int MessageCount
{
get { return _messageCount; }
set { SetProperty(ref _messageCount, value); }
}
public void OnDisappearing()
{
}
void IPageLifecycleAware.OnAppearing()
{
// (omitted) Logic for setting the MessageCount property
}
}
ViewModelVase:
public class ViewModelBase : BindableBase, IInitialize, IInitializeAsync, INavigationAware, IDestructible, IActiveAware
{
public event EventHandler MessageAddedEventArgs; // this should be used to trigger the MessageCount change..
protected INavigationService NavigationService { get; private set; }
public ViewModelBase(INavigationService navigationService)
{
NavigationService = navigationService;
Connectivity.ConnectivityChanged += Connectivity_ConnectivityChanged;
IsNotConnected = Connectivity.NetworkAccess != NetworkAccess.Internet;
}
private bool _isNotConnected;
public bool IsNotConnected
{
get { return _isNotConnected; }
set { SetProperty(ref _isNotConnected, value); }
}
~ViewModelBase()
{
Connectivity.ConnectivityChanged -= Connectivity_ConnectivityChanged;
}
async void Connectivity_ConnectivityChanged(object sender, ConnectivityChangedEventArgs e)
{
IsNotConnected = e.NetworkAccess != NetworkAccess.Internet;
if (IsNotConnected == false)
{
await DataHubService.Connect();
}
}
public virtual void Initialize(INavigationParameters parameters)
{
}
public virtual void OnNavigatedFrom(INavigationParameters parameters)
{
}
public virtual void OnNavigatedTo(INavigationParameters parameters)
{
}
public virtual void Destroy()
{
}
public virtual Task InitializeAsync(INavigationParameters parameters)
{
return Task.CompletedTask;
}
}
SignalR Datahub which should trigger the event:
public static class DataHubService2
{
// .. omitted some other SignalR specific code
public static async Task Connect()
{
try
{
GetInstanse();
hubConnection.On<Messages>("ReceiveMessage", async (message) =>
{
if(message != null)
{
// event that message count has changed should be triggered here..
}
});
}
catch (Exception ex)
{
// ...
}
}
}
As pointed out by #Jason, this specific problem is a good use case for using the MessagingCenter.
In the end the implementation looks as following:
public static class DataHubService2
{
// .. omitted some other SignalR specific code
public static async Task Connect()
{
try
{
GetInstanse();
hubConnection.On<Messages>("ReceiveMessage", async (message) =>
{
if(message != null)
{
MessagingCenter.Send("UpdateMessageCount", "Update");
}
});
}
catch (Exception ex)
{
// ...
}
}
}
public class RootTabbedViewModel : ViewModelBase, IPageLifecycleAware
{
private readonly INavigationService _navigationService;
private int _messageCount;
public RootTabbedViewModel(INavigationService navigationService)
: base(navigationService)
{
_navigationService = navigationService;
MessagingCenter.Subscribe<string>("UpdateMessageCount", "Update", async (a) =>
{
await UpdateMessageCount();
});
}
public int MessageCount
{
get { return _messageCount; }
set { SetProperty(ref _messageCount, value); }
}
public void OnDisappearing()
{
}
void IPageLifecycleAware.OnAppearing()
{
UpdateMessageCount();
}
async Task UpdateMessageCount()
{
int messageCount = await App.Database.GetNewMessageCountAsync();
MessageCount = messageCount.ToString();
}
}
My model is mainly made from the 2 classes below (I actually got another class which inherits from the abstract class but it doesnt matter I think):
public abstract class FeedForEvents: BaseObservableObject
{
public abstract void ReadFeed();
public List<Event> Events { get; set; }
public void AddEvent(Event aEvent)
{
Events.Add(aEvent);
OnPropertyChanged("Events");
}
}
public class Event : BaseObservableObject
{
public string MyProp
{
get
{
return _myProp;
}
set
{
_myprop= value;
OnPropertyChanged();
}
}
}
My form contains:
private BindingList<FeedForEvents> ListFeedsForEvents = new BindingList<FeedForEvents>();
private BindingList<Event> ListEvents
=> new BindingList<Event>(ListFeedsForEvents.SelectMany(m =>m.Events).ToList());
private BindingSource pagesBindingSource = new BindingSource();
public void RefreshGrid()
{
pagesBindingSource.DataSource = ListEvents;
this.grdEvents.DataSource = pagesBindingSource;
this.grdEvents.AutoGenerateColumns = true;
}
But even if my 2 objects correctly raised the PropertyChanged notficiation, the interface never show the objects updated (unless I manually refresh them by pressing a button to manually call RefreshGrid() ). Why?
I am trying to pass a value to a view model from another view model before navigating to the page attached to that view model.
I was previously passing it to the view, then passing it to the view model. This seems like a clumsy way of doing things.
I am not using any kind of framework so that is not an option.
At the moment the property is set as static and this works but im not sure if this is good practice.
The code:
View model 1:
This command opens the new page:
public void OpenRouteDetails()
{
RouteStopPopOverViewModel.RouteName = "TestRoute";
App.Page.Navigation.PushAsync(new RouteStopPopOverView());
}
View model 2: (RouteStopPopOverViewModel)
public static string RouteName { get; set; }
This does work but I would prefer not to use static as a way to achieve this.
Is there some way to set the RouteName property without using static or passing it through view-> view model.
I have seen some answers about this but they don't seem to answer to question clearly.
Share a controller class between view models.
The same instance has to be supplied to the constructor in both view models.
So you can set values, and listen for events in both view models.
The controller class becomes the intermediary.
public class SharedController : IControlSomething
{
private string _sharedValue;
public string SharedValue
{
get => _sharedValue;
set
{
if (_sharedValue == value)
return;
_sharedValue = value;
OnSharedValueUpdated();
}
}
public event EventHandler SharedValueUpdated;
protected virtual void OnSharedValueUpdated()
{
SharedValueUpdated?.Invoke(this, EventArgs.Empty);
}
}
public class ViewModel1
{
private readonly IControlSomething _controller;
public ViewModel1(IControlSomething controller)
{
// Save to access controller values in commands
_controller = controller;
_controller.SharedValueUpdated += (sender, args) =>
{
// Handle value update event
};
}
}
public class ViewModel2
{
private readonly IControlSomething _controller;
public ViewModel2(IControlSomething controller)
{
// Save to access controller values in commands
_controller = controller;
_controller.SharedValueUpdated += (sender, args) =>
{
// Handle value update event
};
}
}
here the sample you can achieve your requirement easily with navigation
public class ViewModelFrom : BaseViewModel
{
async Task ExecuteCommand()
{
string routeName="value to trasfer";
Navigation.PushAsync(new View(routeName));
}
}
public partial class View : ContentPage
{
public View(string routeName)
{
InitializeComponent();
BindingContext = new ViewModelTo(routeName);
}
}
public class ViewModelTo : BaseViewModel
{
public string RouteName { get; set; }
public ViewModelTo(string routeName)
{
RouteName=routeName;
}
}
If there is a hierarchy you could express that in a parent to both of them.
public class Route
{
private string Name;
}
public class RouteSelectedArgs : EventArgs
{
public Route Selected { get; set; }
}
public interface IRouteSelection
{
event EventHandler<RouteSelectedArgs> RouteSelected;
}
public interface IRouteDetails { }
public class RouteWizard
{
public UserControl view { get; set; }
private IRouteSelection _selection;
private IRouteDetails _details;
public RouteWizard(IRouteSelection selection, IRouteDetails details)
{
_selection = selection;
_details = details;
_selection.RouteSelected += Selection_RouteSelected;
view = MakeView(_selection);
}
private void Selection_RouteSelected(object sender, RouteSelectedArgs e)
{
_selection.RouteSelected -= Selection_RouteSelected;
view = MakeView(_details, e.Selected);
}
private UserControl MakeView(params object[] args)
{
////magic
throw new NotImplementedException();
}
}
As you are using the MVVM pattern, you can use one of the many MVVM Frameworks to achieve this.
I use FreshMvvm and it allow me to pass parameters between view models like this
await CoreMethods.PushPageModel<SecondPageModel>(myParameter, false);
Then in SecondPageModel I can see access the parameters in the Init method
private MyParamType _myParameter;
public override void Init(object initData)
{
base.Init(initData);
var param = initData as MyParamType;
if (param != null)
{
_myParameter = param;
}
}
You can find more details about FreshMvvm here although most MVVM frameworks have similar functionality.
I would like to know how to send the value of a view model to another viewmodel using mvvcross and uwp
Does anyone know how to do it?
Thanks,
You can use the IMvxNavigationService to pass and return objects. The full documentation is at: https://www.mvvmcross.com/documentation/fundamentals/navigation?scroll=26
In your ViewModel this could look like:
public class MyViewModel : MvxViewModel
{
private readonly IMvxNavigationService _navigationService;
public MyViewModel(IMvxNavigationService navigationService)
{
_navigationService = navigationService;
}
public override void Prepare()
{
//Do anything before navigating to the view
}
public async Task SomeMethod()
{
_navigationService.Navigate<NextViewModel, MyObject>(new MyObject());
}
}
public class NextViewModel : MvxViewModel<MyObject>
{
public override void Prepare(MyObject parameter)
{
//Do anything before navigating to the view
//Save the parameter to a property if you want to use it later
}
public override async Task Initialize()
{
//Do heavy work and data loading here
}
}
Using a IMvxMessenger you can send values without have a connection: https://www.mvvmcross.com/documentation/plugins/messenger?scroll=1446
public class LocationViewModel
: MvxViewModel
{
private readonly MvxSubscriptionToken _token;
public LocationViewModel(IMvxMessenger messenger)
{
_token = messenger.Subscribe<LocationMessage>(OnLocationMessage);
}
private void OnLocationMessage(LocationMessage locationMessage)
{
Lat = locationMessage.Lat;
Lng = locationMessage.Lng;
}
// remainder of ViewModel
}
This one is test project to show my question. (VS2012, WinForms, EntityFramework 5, XtraGrid 12.5)
Model created by EF PowerTools - Reverse Engineer CodeFirst tool.
In the timer1_tick event i'm changing mypoco.value property. I'm expecting that grid.cell shows this changes automatically but not. I also tried with textbox but the same.
if i uncomment BindingSource.ResetCurrentItem() in timer1_tick works expected but this is not my question. If i force to grid (or Textbox) to refresh everything is fine.
I expect that ef created proxy object notifies DbSet.Local (ObservableCollection) -> BindingList -> BindingSource -> Grid etc via interfaces,methots or inherit or i don't know... I'm asking about this notifying system and why not working? Or it is working but my expectation is wrong? (
Why this is not working as expected, Where i'm failing? Please also read notes in the code.
Thank you.
//FORM CODE
public partial class Form1 : Form
{
testContext context = new testContext();
MyPOCO mypoco;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
mypoco = context.MyPOCOes.Create();
// mypoco is created but not proxied currently. state = detached
// After adding it context proxy created and change tacking will be available
context.MyPOCOes.Add(mypoco);
// mypoco is in the memory but not saved to database. This is why using Local
myPOCOBindingSource.DataSource = context.MyPOCOes.Local.ToBindingList();
// Setup timer
timer1.Interval = 15 * 1000;
timer1.Start();
}
private void timer1_Tick(object sender, EventArgs e)
{
// Change the property and then warn user about this event occured
// At this point mypoco is proxied
mypoco.Value = 99;
this.Text = "Timer Tick";
//myPOCOBindingSource.ResetCurrentItem();
}
}
// some code from Form1.Designer file
private System.Windows.Forms.BindingSource myPOCOBindingSource;
private void InitializeComponent()
{
this.myPOCOBindingSource = new System.Windows.Forms.BindingSource();
....
this.myPOCOGridControl.DataSource = this.myPOCOBindingSource;
}
//MYPOCO
public partial class MyPOCO
{
public int ID { get; set; }
public Nullable<int> Value { get; set; }
}
//MAPPING
public class MyPOCOMap : EntityTypeConfiguration<MyPOCO>
{
public MyPOCOMap()
{
// Primary Key
this.HasKey(t => t.ID);
// Table & Column Mappings
this.ToTable("MyPOCO");
this.Property(t => t.ID).HasColumnName("ID");
this.Property(t => t.Value).HasColumnName("Value");
}
}
//CONTEXT
public partial class testContext : DbContext
{
static testContext()
{
Database.SetInitializer<testContext>(null);
}
public testContext()
: base("Name=testContext")
{
}
public DbSet<MyPOCO> MyPOCOes { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new MyPOCOMap());
}
}
The proxy code of MyPoco is here: Nothing related on bindings (of course) ...
public sealed class MyPOCO_F874E881B0FD3EF02199CD96C63396B451E275C5116C5DFBE892C68733857FDE : MyPOCO, IEntityWithChangeTracker, IEntityWithRelationships
{
[NonSerialized]
private IEntityChangeTracker _changeTracker;
private static Func<object, object, bool> _compareByteArrays;
[NonSerialized, IgnoreDataMember, XmlIgnore, ScriptIgnore]
public object _entityWrapper;
private System.Data.Objects.DataClasses.RelationshipManager _relationshipManager;
private static Action<object> _resetFKSetterFlag;
private void EntityMemberChanged(string text1)
{
if (this._changeTracker != null)
{
this._changeTracker.EntityMemberChanged(text1);
}
}
private void EntityMemberChanging(string text1)
{
if (this._changeTracker != null)
{
this._changeTracker.EntityMemberChanging(text1);
}
}
public void SetChangeTracker(IEntityChangeTracker tracker1)
{
this._changeTracker = tracker1;
}
public override int ID
{
get
{
return base.ID;
}
set
{
if (base.ID != value)
{
try
{
this.EntityMemberChanging("ID");
base.ID = value;
this.EntityMemberChanged("ID");
}
finally
{
_resetFKSetterFlag(this);
}
}
}
}
public System.Data.Objects.DataClasses.RelationshipManager RelationshipManager
{
get
{
if (this._relationshipManager == null)
{
this._relationshipManager = System.Data.Objects.DataClasses.RelationshipManager.Create(this);
}
return this._relationshipManager;
}
}
public override int? Value
{
get
{
return base.Value;
}
set
{
try
{
this.EntityMemberChanging("Value");
base.Value = value;
this.EntityMemberChanged("Value");
}
finally
{
_resetFKSetterFlag(this);
}
}
}
}