Caliburn.Micro Navigation from Usercontrol over ShellViewModel - c#

Description
I use the Caliburn.Micro in version 4 and try to navigatie from a Usercontrol to another page. The ShellView.xaml has a <ContentControl x:Name="ActiveItem" /> element and all navigationmethods like DashboardView(), GcsImportView()... work aslong I am inside the ShellView.xaml. But if I call from a Usercontrol (inside the ContentControl) nothing happens. I don´t even get a error. I can push the Button thousend times without any respond. I hope somebody can help me here.
Update
Even if I try the code from these site it doesn´t work. On debugging the ActiveItem value will be changed. That looks like a bug?
ShellViewModel.cs
namespace GCS.ViewModels
{
public class ShellViewModel : Conductor<object>//, IHandle<GcsImportProgressViewModel>
{
private string _version = "v. " + Assembly.GetExecutingAssembly().GetName().Version.ToString();
public string Version
{
get { return _version; }
}
public ShellViewModel(/*IEventAggregator eventAggregator*/)
{
DashboardView();
//eventAggregator.SubscribeOnUIThread(this);
}
public void DashboardView() => ActivateItemAsync(new DashboardViewModel());
public void GcsImportView() => ActivateItemAsync(IoC.Get<GcsImportViewModel>());
public void GcsExportView() => ActivateItemAsync(new GcsExportViewModel());
public void ChangeView<T>() => ActivateItemAsync(IoC.Get<T>());
//public Task HandleAsync(GcsImportProgressViewModel message, CancellationToken cancellationToken)
//{
// throw new NotImplementedException();
//}
}
}
UserControl Constructor
public GcsImportViewModel(ShellViewModel shell, IGcsRepository gcsRepository/)
{
filePath = "Bitte GCS Excel Datei auswählen";
databases = new ObservableCollection<GcsTable>(gcsRepository.GetAllTables());
selectedDatabase = databases.FirstOrDefault();
this.gcsRepository = gcsRepository;
}
Usercontrol Method to change the view
public void ClickImport()
{
shell.ChangeView<GcsImportProgressViewModel>();
}

It seems like you are calling ChangeView on a different instance of the ShellViewModel.
You should register the view model as a singleton in your bootstrapper:
container.Singleton<ShellViewModel, ShellViewModel>();

Related

Bubbling NotifyOfPropertyChange with embedded classes

So here's a question that has me running in circles. I am working with an embedded class structure which needs to keep its child objects private but should be able to pass certain NotifyOfPropertyChange events up the ladder from data in those child objects. What is the best way to do this.
My current approach is the code below where my view for SystemViewModel (SystemView) has an element bound to the CommunicationStatus property, and I have a parent class SystemViewModel that has child class CommunicationManager which has child class Communicator as follows.
Things that make it difficult:
1) It MUST be assumed in this case that Communicator has no visibility of SystemViewModel so putting a NotifyOfPropertyChanged(() => CommunicationStatus) in the set method of Communicator's Connected property should not be an option... unless I'm missing something obvious.
2) SystemViewModel should not be able to access Communicator directly so binding from SystemView.xaml to Connected can't be done.
In my mind the NotifyOfPropertyChanged event in Connected should bubble up to the parents due to the implementation of PropertyChangedBase in all classes but that's not happening. Would love any help!
public class SystemViewModel : PropertyChangedBase
{
private CommunicationManager CommunicationManager;
public string CommunicationStatus
{
get
{
if (CommunicationManager.YepConnected)
{
return "Green";
}
else
{
return "Red";
}
}
}
}
public class CommunicationManager : PropertyChangedBase
{
private Communicator Communicator;
public bool YepConnected { get { return Communicator.Connected; } }
}
public class Communicator: PropertyChangedBase
{
private bool _connected;
public bool Connected
{
get { return _connected; }
set
{
_connected = value;
NotifyOfPropertyChange(() => Connected);
}
}
}
EDIT
So it appears that this works correctly and propagates the event as expected from the child class to the parent class. The real issue, which was a bit more insideous, has to do with how the WPF Binding relates to the property. Just for reference, the XAML I'm using looks like this:
<TextBlock Text="Status" Background="{Binding CommunicationStatus}"/>
Also, I used SolidColorBrush instead of string (although they both bind the same and work).
The issue is that when the notification event propagates up from Connected to CommunicationStatus, it stops there and does not propagate to the XAML binding (Nowhere in my code is CommunicationStatus used except in the XAML binding). I know the binding works because by debug I observe that when the program runs initially the color is set to red upon execution of the CommunicationStatus get method, presumably called from the XAML binding. Once the code is running, CommunicationStatus does update whenever Connected does, but the XAML binding no longer observes that change. If I manually implement NotifyOfPropertyChange(() => CommunicationStatus);, the binding element decides to update. However, because I'm not using any sort of set method in CommunicationStatus (and the notify event doesn't propagate up), there doesn't seem to be a straight-forward way of informing the XAML that my value has changed.
Sketchy Solution: Watch for changes to CommunicationStatus and raise the NotifyOfPropertyChange(() => CommunicationStatus); event as follows:
public class SystemViewModel : Conductor<object>
{
private CommunicationManager CommunicationManager;
private SolidColorBrush LastCommunicationStatusValue = new SolidColorBrush();
public SolidColorBrush CommunicationStatus
{
get
{
SolidColorBrush CurCommunicationStatusValue;
if (CommunicationManager.YepConnected)
{
CurCommunicationStatusValue = new SolidColorBrush(Colors.Green);
}
else
{
CurCommunicationStatusValue = new SolidColorBrush(Colors.Red);
}
if (CurCommunicationStatusValue.Color != LastCommunicationStatusValue.Color)
{
LastCommunicationStatusValue = CurCommunicationStatusValue;
NotifyOfPropertyChange(() => CommunicationStatus);
}
return CurCommunicationStatusValue;
}
}
}
And yes, if you don't do it perfectly it's an instant Stack Overflow (pun intended :)
Whenever the value of Connected changes, I observe that CommunicationStatus's get method executes. By doing this, that execution results in another execution of the get method, only this time the XAML updates.
Can anyone explain why this solution works and/or offer a more eloquent solution?
Here is an example how to do that with ReactiveUI
public class SystemViewModel : ReactiveObject
{
private readonly CommunicationManager communicationManager;
private readonly ObservableAsPropertyHelper<string> connectionStatus;
public SystemViewModel( CommunicationManager communicationManager )
{
this.communicationManager = communicationManager ?? throw new ArgumentNullException(nameof(communicationManager));
this.communicationManager
.WhenAnyValue( e => e.YepConnected, state => state ? "Green" : "Red" )
.ToProperty( this, e => e.ConnectionStatus, out connectionStatus );
}
public string ConnectionStatus => connectionStatus.Value;
}
public class CommunicationManager : ReactiveObject
{
private readonly Communicator communicator;
private readonly ObservableAsPropertyHelper<bool> yepConnected;
public CommunicationManager(Communicator communicator)
{
this.communicator = communicator ?? throw new ArgumentNullException(nameof(communicator));
this.communicator
.WhenAnyValue( e => e.Connected )
.ToProperty( this, e => e.YepConnected, out yepConnected );
}
public bool YepConnected => yepConnected.Value;
}
public class Communicator : ReactiveObject
{
private bool _connected;
public bool Connected
{
get { return _connected; }
set { this.RaiseAndSetIfChanged( ref _connected, value); }
}
}
Simple test
var communicator = new Communicator();
var manager = new CommunicationManager(communicator);
var vm = new SystemViewModel( manager );
vm.PropertyChanged += (s,e) => Console.WriteLine( "SystemViewModel.{0} changed", e.PropertyName );
communicator.Connected = true;
communicator.Connected = false;
generated output
SystemViewModel.ConnectionStatus changed
SystemViewModel.ConnectionStatus changed

PreviousPageModel is null

I have a Xamarin.Forms application with freshmvvm framework. According to the documentation, I can use PreviousPageModel property of FreshBasePageModel base class to access data of the PageModel I navigated from. I navigate like this:
public FirstPageModel()
{
_validator = new CalculatorValidator();
CalculateCommand = new Command(execute: () =>
{
ValidationResult = _validator.Validate(this);
RaisePropertyChanged(nameof(ValidationResult));
if (ValidationResult.IsValid)
{
CoreMethods.PushPageModel<SecondPageModel>();
}
});
}
The navigation happens, but in the SecondPageModel constructor the PreviousPageModel is null:
public SecondPageModel()
{
_previousModel = (FirstPageModel)PreviousPageModel;
}
What am I doing wrong?
Thank you.
EDIT:
I also tried:
public FirstPageModel()
{
_validator = new CalculatorValidator();
CalculateCommand = new Command(Calculate);
}
private void Calculate()
{
ValidationResult = _validator.Validate(this);
RaisePropertyChanged(nameof(ValidationResult));
if(ValidationResult.IsValid)
{
CoreMethods.PushPageModel<SecondPageModel>(this);
}
}
The issue is that until this point in time, In your PageModel lifecycle the PreviousPageModelproperty this not assigned a value.
Now I am sure that this property is available by the time the Init lifecycle method is called i.e.
public override void Init(object initData)
{
_previousModel = (FirstPageModel)PreviousPageModel;
}
But if you do not want to do this and want it to be strictly in your constructor what you can do is call the base implementation and hope that our friend micheal has assigned this property there:
Something like below:
public SecondPageModel() : base()
{
}
I got the answer here:
https://forums.xamarin.com/discussion/comment/365262/#Comment_365262
The PreviousPageModel is null because it hasn't been set in the constructor. Place your code in the ViewIsAppearing lifecycle event, then you will get the correct previous model:
protected override void ViewIsAppearing(object sender, EventArgs e)
{
base.ViewIsAppearing(sender, e);
_previousModel = (FirstPageModel)PreviousPageModel;
}

Navigation and DI

Im trying to make a standard code to use in order to implement in my xamarin.forms apps. What I want to do is to have a way to navigate between viewmodels and a wey to correctly implement dependency injection.
What I'm currently doing for navigation:
await Navigation.PushAsync(new SecondPageView());
And for DI:
var test = DependencyService.Get<ITestService>();
WelcomeMessage = test.GetSystemWelcome();
I know that the correct way to implement Di is creating an interface and proceed from that step but the problem is that when I try, I fail trying to have a good navigation system (like register the view and the view model in a file apart).
Does anyone have a sample example that I can have a look? Or maybe some indications in order to proceed?
PD: I trying to avoid frameworks like MvvMcross things like that.
Thanks in advance!
(I will try to simplify all the code examples as much as possible).
1. First of all we need a place where we could register all our objects and optionally define their lifetime. For this matter we can use an IOC container, you can choose one yourself. In this example I will use Autofac(it is one of the fastest available). We can keep a reference to it in the App so it will be available globally (not a good idea, but needed for simplification):
public class DependencyResolver
{
static IContainer container;
public DependencyResolver(params Module[] modules)
{
var builder = new ContainerBuilder();
if (modules != null)
foreach (var module in modules)
builder.RegisterModule(module);
container = builder.Build();
}
public T Resolve<T>() => container.Resolve<T>();
public object Resolve(Type type) => container.Resolve(type);
}
public partial class App : Application
{
public DependencyResolver DependencyResolver { get; }
// Pass here platform specific dependencies
public App(Module platformIocModule)
{
InitializeComponent();
DependencyResolver = new DependencyResolver(platformIocModule, new IocModule());
MainPage = new WelcomeView();
}
/* The rest of the code ... */
}
2.We will need an object responsible for retrieving a Page (View) for a specific ViewModel and vice versa. The second case might be useful in case of setting the root/main page of the app. For that we should agree on a simple convention that all the ViewModels should be in ViewModels directory and Pages(Views) should be in the Views directory. In other words ViewModels should live in [MyApp].ViewModels namespace and Pages(Views) in [MyApp].Views namespace. In addition to that we should agree that WelcomeView(Page) should have a WelcomeViewModel and etc. Here is a code example of a mapper:
public class TypeMapperService
{
public Type MapViewModelToView(Type viewModelType)
{
var viewName = viewModelType.FullName.Replace("Model", string.Empty);
var viewAssemblyName = GetTypeAssemblyName(viewModelType);
var viewTypeName = GenerateTypeName("{0}, {1}", viewName, viewAssemblyName);
return Type.GetType(viewTypeName);
}
public Type MapViewToViewModel(Type viewType)
{
var viewModelName = viewType.FullName.Replace(".Views.", ".ViewModels.");
var viewModelAssemblyName = GetTypeAssemblyName(viewType);
var viewTypeModelName = GenerateTypeName("{0}Model, {1}", viewModelName, viewModelAssemblyName);
return Type.GetType(viewTypeModelName);
}
string GetTypeAssemblyName(Type type) => type.GetTypeInfo().Assembly.FullName;
string GenerateTypeName(string format, string typeName, string assemblyName) =>
string.Format(CultureInfo.InvariantCulture, format, typeName, assemblyName);
}
3.For the case of setting a root page we will need sort of ViewModelLocator that will set the BindingContext automatically:
public static class ViewModelLocator
{
public static readonly BindableProperty AutoWireViewModelProperty =
BindableProperty.CreateAttached("AutoWireViewModel", typeof(bool), typeof(ViewModelLocator), default(bool), propertyChanged: OnAutoWireViewModelChanged);
public static bool GetAutoWireViewModel(BindableObject bindable) =>
(bool)bindable.GetValue(AutoWireViewModelProperty);
public static void SetAutoWireViewModel(BindableObject bindable, bool value) =>
bindable.SetValue(AutoWireViewModelProperty, value);
static ITypeMapperService mapper = (Application.Current as App).DependencyResolver.Resolve<ITypeMapperService>();
static void OnAutoWireViewModelChanged(BindableObject bindable, object oldValue, object newValue)
{
var view = bindable as Element;
var viewType = view.GetType();
var viewModelType = mapper.MapViewToViewModel(viewType);
var viewModel = (Application.Current as App).DependencyResolver.Resolve(viewModelType);
view.BindingContext = viewModel;
}
}
// Usage example
<?xml version="1.0" encoding="utf-8"?>
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewmodels="clr-namespace:MyApp.ViewModel"
viewmodels:ViewModelLocator.AutoWireViewModel="true"
x:Class="MyApp.Views.MyPage">
</ContentPage>
4.Finally we will need a NavigationService that will support ViewModel First Navigation approach:
public class NavigationService
{
TypeMapperService mapperService { get; }
public NavigationService(TypeMapperService mapperService)
{
this.mapperService = mapperService;
}
protected Page CreatePage(Type viewModelType)
{
Type pageType = mapperService.MapViewModelToView(viewModelType);
if (pageType == null)
{
throw new Exception($"Cannot locate page type for {viewModelType}");
}
return Activator.CreateInstance(pageType) as Page;
}
protected Page GetCurrentPage()
{
var mainPage = Application.Current.MainPage;
if (mainPage is MasterDetailPage)
{
return ((MasterDetailPage)mainPage).Detail;
}
// TabbedPage : MultiPage<Page>
// CarouselPage : MultiPage<ContentPage>
if (mainPage is TabbedPage || mainPage is CarouselPage)
{
return ((MultiPage<Page>)mainPage).CurrentPage;
}
return mainPage;
}
public Task PushAsync(Page page, bool animated = true)
{
var navigationPage = Application.Current.MainPage as NavigationPage;
return navigationPage.PushAsync(page, animated);
}
public Task PopAsync(bool animated = true)
{
var mainPage = Application.Current.MainPage as NavigationPage;
return mainPage.Navigation.PopAsync(animated);
}
public Task PushModalAsync<TViewModel>(object parameter = null, bool animated = true) where TViewModel : BaseViewModel =>
InternalPushModalAsync(typeof(TViewModel), animated, parameter);
public Task PopModalAsync(bool animated = true)
{
var mainPage = GetCurrentPage();
if (mainPage != null)
return mainPage.Navigation.PopModalAsync(animated);
throw new Exception("Current page is null.");
}
async Task InternalPushModalAsync(Type viewModelType, bool animated, object parameter)
{
var page = CreatePage(viewModelType);
var currentNavigationPage = GetCurrentPage();
if (currentNavigationPage != null)
{
await currentNavigationPage.Navigation.PushModalAsync(page, animated);
}
else
{
throw new Exception("Current page is null.");
}
await (page.BindingContext as BaseViewModel).InitializeAsync(parameter);
}
}
As you may see there is a BaseViewModel - abstract base class for all the ViewModels where you can define methods like InitializeAsync that will get executed right after the navigation. And here is an example of navigation:
public class WelcomeViewModel : BaseViewModel
{
public ICommand NewGameCmd { get; }
public ICommand TopScoreCmd { get; }
public ICommand AboutCmd { get; }
public WelcomeViewModel(INavigationService navigation) : base(navigation)
{
NewGameCmd = new Command(async () => await Navigation.PushModalAsync<GameViewModel>());
TopScoreCmd = new Command(async () => await navigation.PushModalAsync<TopScoreViewModel>());
AboutCmd = new Command(async () => await navigation.PushModalAsync<AboutViewModel>());
}
}
As you understand this approach is more complicated, harder to debug and might be confusing. However there are many advantages plus you actually don't have to implement it yourself since most of the MVVM frameworks support it out of the box. The code example that is demonstrated here is available on github. There are plenty of good articles about ViewModel First Navigation approach and there is a free Enterprise Application Patterns using Xamarin.Forms eBook which is explaining this and many other interesting topics in detail.

In MVVMCross, is it possible to close a viewmodel and pass values back to the previous viewmodel in the navigation stack?

Consider the following example. I have three view models, ViewModel_A, ViewModel_B, and ViewModel_Values.
I want to be able to navigate to ViewModel_Values from either ViewModel_A or ViewModel_B, select a value from ViewModel_Values, then return that value to the calling view model.
Is there a way of passing arguments to previous view models in the navigation stack so that I can simply call ViewModel_Values.Close(this), thereby ensuring that the ViewModels_Values is decoupled from any other view models and can be used with arbitrary "parent" view models?
MvvmCross 5 onwards
From MvvmCross 5 you can use the new IMvxNavigationService that allows you to have a much richer navigation. One of the new features is the possibility to await a value from another ViewModel after navigating to it and should be the approach to take after MvvmCross 5 instead of Messenger, e.g.:
public class ViewModel_A : MvxViewModel
{
private readonly IMvxNavigationService _navigationService;
public ViewModel_A(IMvxNavigationService navigation)
{
_navigationService = navigationService;
}
public override async Task Initialize()
{
//Do heavy work and data loading here
}
public async Task SomeMethod()
{
var result = await _navigationService.Navigate<ViewModel_Values, MyObject, MyReturnObject>(new MyObject());
//Do something with the result MyReturnObject that you get back
}
}
public class ViewModel_Values : MvxViewModel<MyObject, MyReturnObject>
{
private readonly IMvxNavigationService _navigationService;
public ViewModel_Values(IMvxNavigationService navigation)
{
_navigationService = navigationService;
}
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
}
public async Task SomeMethodToClose()
{
// here you returned the value
await _navigationService.Close(this, new MyReturnObject());
}
}
More info here
HIH
Use messaging center. Here is the sample code.
//for trigger
MessagingCenter.Send<object> (this, "Hi");
//put this where you want to receive your data
MessagingCenter.Subscribe<object> (this, "Hi", (sender) => {
// do something whenever the "Hi" message is sent
});
Installing & using the MvxMessenger plugin is a great way to decouple view model communication in MvvmCross -
In your case, you could set up a new message -
public class ValuesChangedMessage : MvxMessage
{
public ValuesChangedMessage(object sender, int valuea, string valueb)
: base(sender)
{
Valuea = valuea;
Valueb = valueb;
}
public int Valuea { get; private set; }
public string Valueb { get; private set; }
}
In ViewModel_Values, you would act on / publish your UX changes with -
_mvxMessenger.Publish<ValuesChangedMessage>(new ValuesChangedMessage(this, 1, "boo!"));
And in ViewModel_A, ViewModel_B you would subscribe and act on them (as your ViewModel A / B would be still in the navigation stack when you pushed ViewModel_Values from them, so they could receive the message) -
private MvxSubscriptionToken _messageToken;
_messageToken = _mvxMessenger.Subscribe<ValuesChangedMessage>(async message =>
{
// use message.Valuea etc ..
});
More infos here -
https://www.mvvmcross.com/documentation/plugins/messenger?scroll=644
https://www.youtube.com/watch?feature=player_embedded&v=HQdvrWWzkIk
In my case of trying to navigate in this pattern:
//pseudo code
"ModelA" => "ModelB<List<MyObject>>" => "ModelC<MyObject>"
OR
//pseudo code
"ModelA" => "ModelC<MyObject>"
I used the following work around in my ViewDestroy() override of ModelB<List>:
private bool destroyView = true;
public bool DestroyView
{
get => destroyView;
set
{
destroyView = value;
RaisePropertyChanged(() => DestroyView);
}
}
public override void ViewDestroy(bool viewFinishing)
{
viewFinishing = DestroyView;
base.ViewDestroy(viewFinishing);
}
private async Task ModifySelectedObject()
{
DestroyView = false;
MyObject obj = SelectedObject;
MyObject modifiedObj = await _navigationService.Navigate<ModifySingleViewModel, MyObject, MyObject>(new MyObject());
if (modifiedObj != null)
{
obj = modifiedObj;
}
else
{
await Application.Current.MainPage.DisplayAlert("", "No changes made.", "OK");
}
DestroyView = true;
}
This keeps the original
"await _navigationService.Navigate<ModifyMultipleViewModel,
List, List>(new MyObject);"
from ModelA open when navigating to ModelC from ModelB, but still allows the ViewDestroy Method to close otherwise.

How do I change the values of variables within one class, from another?

Please excuse my ignorance for I am new to C#.
I am currently working on an MVVM project in which a viewmodel has multiple instantiated public variables that are data-bound to elements in a view (WPF). When these variables are changed they automatically update in my view. Take for instance the code segment below from my view model...
private string _displaybind;
public string DisplayBind
{
get { return _displaybind; }
set
{
SetProperty(ref _displaybind, value);
if (_displaybind.Length > 5000)
{
DisplayBind = _displaybind.Substring(_displaybind.IndexOf('\n') + 1);
}
}
}
By using the command DisplayBind = "Hello"; within my viewmodel I can push out text to a textbox I have located in my XAML view. Unfortunately, I have reached a point where I can not simply edit the value of DisplayBind.
I need to start a state machine within my viewmodel which will access several states (classes) in separate C# files. However, I have no idea how to receive, and more importantly edit the values within my viewmodel from these separate classes.
I start my state machine in my viewmodel using this...
IPMProgram ipmprogram = new IPMProgram();
ipmprogram.StartTheIPMProgram();
This is my IPMProgram class
public class IPMProgram
{
public IPMProgramState currentState = null;
public IPMProgram()
{
currentState = new BootBannerState(this);
}
public void StartTheIPMProgram()
{
while (true)
{
currentState.GetNextState();
}
}
}
This is my IPMProgramState class
public abstract class IPMProgramState
{
private IPMProgram ipmprogram;
public IPMProgram Ipmprogram
{
get { return ipmprogram; }
set { ipmprogram = value; }
}
public abstract void GetNextState();
}
And this is my BootBannerState class (The state I want to edit DisplayBind from)
class BootBannerState : IPMProgramState
{
public BootBannerState(IPMProgramState state)
:this(state.Ipmprogram)
{
}
public BootBannerState(IPMProgram ipmprograminstance)
{
this.Ipmprogram = ipmprograminstance;
}
public override void GetNextState()
{
//DisplayBind = "Hello"!
return;
}
}
Someone suggested that I should look into Dependency Injection, but I don't quite understand how it would work for me. What should I do?
Thank you for all of your help,
Tesnich

Categories