What is a simple way to show an MvxDialogFragment? - c#

I am trying to use an MvxDialogFragment to show a data bound dialog from an activity. My Dialog ViewModel is as follows:
public class ContainerDialogViewModel : MvxViewModel
{
public string ShipperName;
public void Init(string Name)
{
ShipperName = Name;
LoadData();
}
public void LoadData()
{
Survey = SurveyDataSource.CurrSurvey;
}
private ShipmentSurvey _Survey;
public ShipmentSurvey Survey
{
get
{
return _Survey;
}
set
{
_Survey = value;
RaisePropertyChanged(() => Survey);
RaisePropertyChanged(() => Containers);
}
}
public List<ShipmentSurveyContainer> Containers
{
get
{
if (Survey == null)
return new List<ShipmentSurveyContainer>();
else
return Survey.SurveyContainers.ToList();
}
}
}
The MvxDialogFragment is coded as follows:
public class ContainerDialog : MvxDialogFragment<ContainerDialogViewModel>
{
public override Dialog OnCreateDialog(Bundle savedState)
{
base.EnsureBindingContextSet(savedState);
this.BindingInflate(Resource.Layout.ContainerDialog, null);
return base.OnCreateDialog(savedState);
}
}
In my activity, I am trying to figure out the simplest way to launch the dialog. Here is what I have tried:
public class SurveyView : MvxActivity
{
public void ShowContainerDialog()
{
ContainerDialogViewModel vm = new ViewModels.ContainerDialogViewModel();
vm.Init("Test Name");
var dialogFragment = new ContainerDialog()
{
DataContext = vm
};
dialogFragment.Show(FragmentManager, "Containers");
}
}
I'm pretty sure my method of creating the view model is unorthodox, but I don't know another way to do it. The biggest issue is that FragmentManager is cast to the wrong version. Show is looking for an Android.Support.V4.App.FragmentManager and the FragmentManager that is exposed is an Android.App.FragmentManager. I tried changing the MvxActivity to an MvxFragmentActivity, but this didn't seem to help. Can someone point me in the right direction?

MvvmCross didn't really support this when I was trying to do it, but I came across another instance where I needed this and alas, the functionality is there. Thanks to #Martijn00 for pointing me at the solution. This will be very basic, but I think it might help someone.
My Layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="#style/TableHeaderTextView"
android:text="Work Date"/>
<MvxDatePicker
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20dp"
local:MvxBind="Value WorkDate" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Close"
local:MvxBind="Click CloseCommand" />
</LinearLayout>
My ViewModel:
public class HoursDateDialogViewModel : MvxViewModel<EstimateHours>
{
private readonly IMvxNavigationService _navigationService;
public HoursDateDialogViewModel(IMvxNavigationService navigationService)
{
_navigationService = navigationService;
CloseCommand = new MvxAsyncCommand(async () => await _navigationService.Close(this));
}
public override System.Threading.Tasks.Task Initialize()
{
return base.Initialize();
}
public override void Prepare(EstimateHours parm)
{
base.Prepare();
Hours = parm;
}
public IMvxAsyncCommand CloseCommand { get; private set; }
private EstimateHours _Hours;
public EstimateHours Hours
{
get
{
return _Hours;
}
set
{
_Hours = value;
RaisePropertyChanged(() => Hours);
RaisePropertyChanged(() => WorkDate);
}
}
public DateTime WorkDate
{
get
{
return Hours.StartTime ?? DateTime.Today;
}
set
{
DateTime s = Hours.StartTime ?? DateTime.Today;
DateTime d = new DateTime(value.Year, value.Month, value.Day, s.Hour, s.Minute, s.Second);
Hours.StartTime = d;
DateTime e = Hours.EndTime ?? DateTime.Today;
d = new DateTime(value.Year, value.Month, value.Day, e.Hour, e.Minute, e.Second);
Hours.EndTime = d;
RaisePropertyChanged(() => WorkDate);
}
}
}
My View:
[MvxDialogFragmentPresentation]
[Register(nameof(HoursDateDialogView))]
public class HoursDateDialogView : MvxDialogFragment<HoursDateDialogViewModel>
{
public HoursDateDialogView()
{
}
protected HoursDateDialogView(IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer)
{
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
var ignore = base.OnCreateView(inflater, container, savedInstanceState);
var view = this.BindingInflate(Resource.Layout.HoursDateDialogView, null);
return view;
}
}
That's all there is to it. I am able to pass a parameter object and bind part of the object to an MvxDatePicker. In order to show this dialog, first in your Setup.cs, you need :
protected override IMvxAndroidViewPresenter CreateViewPresenter()
{
return new MvxAppCompatViewPresenter(AndroidViewAssemblies);
}
In your viewmodel from which you will open the dialog, you need a constructor containing:
private readonly IMvxNavigationService _navigationService;
public LocalHourlyViewModel(IMvxNavigationService navigationService)
{
_navigationService = navigationService;
}
This injects the navigation service so you can work with it. And finally, all you have to do to open the dialog is:
async () => await _navigationService.Navigate<HoursDateDialogViewModel, EstimateHours>(Item);
I'm not even sure you have to await the call, but I was following the example. You can see more examples at the link #Martijn00 provided:
https://github.com/MvvmCross/MvvmCross/tree/develop/TestProjects/Playground
Cheers!

I tried changing the MvxActivity to an MvxFragmentActivity
This was the correct first step. Then instead of passing in FragmentManager, pass in SupportFragmentManager.
If you are not familiar with Support Libraries, You can read more about what they are and how to use them with Xamarin here

Related

Pass ItemClicked in MvxRecyclerView object to new View

I'm newbie in Xamarin.
I have MvxRecyclerView to show list of Cars. Clicking in a car let user to display full specification of chosen car. I have problem with displaying full specification of chosen car in new activity (and at the same time pass object between viewmodels)
My MvxRecyclerView .xml looks like:
<mvvmcross.droid.support.v7.recyclerview.MvxRecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/background_light"
local:MvxItemTemplate="#layout/listitem_car"
local:MvxBind="ItemsSource Cars; ItemClick NavigateCommand"
/>
And my CarsViewModel with "empty" navigation to CarItemViewModel:
public class CarsViewModel : MvxViewModel
{
public CarsViewModel(IMvxNavigationService navigationService)
{
Cars = new MvxObservableCollection<Car>();
_navigationService = navigationService;
NavigateCommand = new MvxAsyncCommand(() => _navigationService.Navigate<CarItemViewModel>());
}
private MvxObservableCollection<Car> _cars;
public MvxObservableCollection<Car> Cars
{
get => _cars;
set
{
_cars = value;
RaisePropertyChanged(() => Cars);
}
}
public override async Task Initialize()
{
await base.Initialize();
CarService carService = new CarService();
await Task.Run(async () =>
{
Cars = await carService.GetCars();
});
}
private readonly IMvxNavigationService _navigationService;
public IMvxAsyncCommand NavigateCommand { get; private set; }
Do you know how is it possible to move data of chosen mvxrecyclerview item to new view using MVVMCross? Unfortunately, I don't understand what MVVMCross' documentation says about this (it looks so poor in my opinion).
I would appreciate for any help.
EDIT 1:
I've changed a bit description of my problem for more transparent.
I think the document is clear about passing parameter, here:
When you navigation, you should pass a MyObject of CarItemViewModel:
await _navigationService.Navigate<CarItemViewModel, MyObject>(new MyObject());
And then you can get the MyObject in the CarItemViewModel and use it:
public class CarItemViewModel: MvxViewModel<MyObject>
{
private MyObject _myObject;
public override void Prepare()
{
// first callback. Initialize parameter-agnostic stuff here
}
public override void Prepare(MyObject parameter)
{
// receive and store the parameter here
_myObject = parameter;
}
public override async Task Initialize()
{
await base.Initialize();
// do the heavy work here
}
}

System.NullReferenceException in DisplayRootViewFor in Caliburn Micro

I am trying to integrate the Event Aggregator into my app and following the online documentation, I haven't quite been able to get it to work and also am unable to find a complete example to look at. The app I am trying to build is a simple search engine for our internal knowledge base, so all it has is 3 views - the main search page (the root page), the results page and a details page.
Below I've just added what I think is the relevant code so far for debugging this error I am getting on build. If you need any more snippets, then more than happy to provide!
Admittedly, I really dont know how the bootstrapper works - have just been following tutorials and documentation thus far.
My code is as follows:
Bootstrapper.cs
namespace CleverBot
{
class Bootstrapper : BootstrapperBase
{
private readonly SimpleContainer _container = new SimpleContainer();
public Bootstrapper()
{
Initialize();
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
base.OnStartup(sender, e);
DisplayRootViewFor<ShellViewModel>();
}
protected override void Configure()
{
_container.Singleton<IEventAggregator, EventAggregator>();
_container.RegisterPerRequest(typeof(DetailedDocumentViewModel), null, typeof(DetailedDocumentViewModel));
_container.RegisterPerRequest(typeof(SearchPageViewModel), null, typeof(SearchPageViewModel));
_container.RegisterPerRequest(typeof(SearchResultsViewModel), null, typeof(SearchResultsViewModel));
_container.RegisterPerRequest(typeof(ShellViewModel), null, typeof(ShellViewModel));
}
protected override object GetInstance(Type serviceType, string key)
{
return _container.GetInstance(serviceType, key);
}
protected override IEnumerable<object> GetAllInstances(Type serviceType)
{
return _container.GetAllInstances(serviceType);
}
protected override void BuildUp(object instance)
{
_container.BuildUp(instance);
}
}
}
And my ShellViewModel.cs looks like this:
namespace CleverBot.ViewModels
{
public class ShellViewModel : Conductor<object>
{
private readonly IEventAggregator _eventAggregator;
public ShellViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
Startup.Init<SolrModel>("http://localhost:8983/solr/brain_drive");
ShowSearchPage();
}
public void ShowSearchPage()
{
ActivateItem(new SearchPageViewModel(_eventAggregator));
}
}
}
And finally, my SearchPageViewModel.cs looks like:
namespace CleverBot.ViewModels
{
public class SearchPageViewModel : PropertyChangedBase
{
private string _searchTerm;
private readonly IEventAggregator _eventAggregator;
public SearchPageViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
}
public string SearchTerm
{
get { return _searchTerm; }
set
{
_searchTerm = value;
NotifyOfPropertyChange(() => SearchTerm);
}
}
public void BasicSearchButton()
{
// Build the new query object
var queryResult = new SolrQuery(_searchTerm);
// Execute the query, while also applying all the options
var Result = ExecuteQuery(queryResult);
// Publish the result, to be picked up by SearchResults VM
_eventAggregator.PublishOnUIThread(Result);
}
private SolrQueryResults<SolrModel> ExecuteQuery(SolrQuery query)
{
var solr = CommonServiceLocator.ServiceLocator.Current.GetInstance<ISolrOperations<SolrModel>>();
QueryOptions options = getQueryOptions();
var docs = solr.Query(query, options);
return docs;
}
private QueryOptions getQueryOptions()
{
var facetPivotQuery = new SolrFacetPivotQuery()
{
Fields = new[] { new PivotFields("created_year", "created_month") },
MinCount = 1
};
QueryOptions options = new QueryOptions
{
Facet = new FacetParameters
{
Queries = new ISolrFacetQuery[]
{
new SolrFacetFieldQuery("type"),
new SolrFacetFieldQuery("source"),
facetPivotQuery
}
},
Highlight = new HighlightingParameters
{
Fields = new[] { "text", "id", "author" },
Fragsize = 150,
Snippets = 200,
MaxAnalyzedChars = -1
}
};
return options;
}
}
}
Is anyone able to shed any light on why I am getting the System.NullReferenceException error? I know what it means but I don't see how I am getting it as am just starting out with this framework.
Thanks in advance!
It looks like I had to register the following:
_container.Singleton<IWindowManager, WindowManager>();
in my bootstrapper.cs, so now it looks like:
namespace CleverBot
{
class Bootstrapper : BootstrapperBase
{
private SimpleContainer _container = new SimpleContainer();
public Bootstrapper()
{
Initialize();
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
DisplayRootViewFor<ShellViewModel>();
}
protected override void Configure()
{
_container.Singleton<IEventAggregator, EventAggregator>();
// THE FOLLOWING WAS ADDED
_container.Singleton<IWindowManager, WindowManager>();
GetType().Assembly.GetTypes()
.Where(type => type.IsClass)
.Where(type => type.Name.EndsWith("ViewModel"))
.ToList()
.ForEach(viewModelType => _container.RegisterPerRequest(
viewModelType, viewModelType.ToString(), viewModelType));
}
protected override object GetInstance(Type serviceType, string key)
{
return _container.GetInstance(serviceType, key);
}
protected override IEnumerable<object> GetAllInstances(Type serviceType)
{
return _container.GetAllInstances(serviceType);
}
protected override void BuildUp(object instance)
{
_container.BuildUp(instance);
}
}
}

MvvmCross - Passing a string with IMvxNavigationService

I'm currently working on a Xamarin.iOS project that uses a web-api to gather data. However, I'm running into some problems trying to pass the user input from a textfield to the Tableview that gets the result from the api.
To do this I've followed the example on the MvvmCross documentation.
The problem is that the input from the Textfield never reaches the 'Filter' property in my TableviewController's viewmodel. I think I'm not passing the string object correctly to my IMvxNavigationService when called.
To clarify, in my UserinputViewController I'm binding the textfield's text like so:
[MvxFromStoryboard(StoryboardName = "Main")]
public partial class SearchEventView : MvxViewController
{
public SearchEventView (IntPtr handle) : base (handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
MvxFluentBindingDescriptionSet<SearchEventView, SearchEventViewModel> set = new MvxFluentBindingDescriptionSet<SearchEventView, SearchEventViewModel>(this);
set.Bind(btnSearch).To(vm => vm.SearchEventCommand);
set.Bind(txtSearchFilter).For(s => s.Text).To(vm => vm.SearchFilter);
set.Apply();
}
}
The Viewmodel linked to this ViewController looks like this:
public class SearchEventViewModel : MvxViewModel
{
private readonly IMvxNavigationService _navigationService;
private string _searchFilter;
public string SearchFilter
{
get { return _searchFilter; }
set { _searchFilter = value; RaisePropertyChanged(() => SearchFilter); }
}
public SearchEventViewModel(IMvxNavigationService mvxNavigationService)
{
this._navigationService = mvxNavigationService;
}
public IMvxCommand SearchEventCommand {
get {
return new MvxCommand<string>(SearchEvent);
}
}
private async void SearchEvent(string filter)
{
await _navigationService.Navigate<EventListViewModel, string>(filter);
}
}
And finally, TableviewController's viewmodel looks like this:
public class EventListViewModel : MvxViewModel<string>
{
private readonly ITicketMasterService _ticketMasterService;
private readonly IMvxNavigationService _navigationService;
private List<Event> _events;
public List<Event> Events
{
get { return _events; }
set { _events = value; RaisePropertyChanged(() => Events); }
}
private string _filter;
public string Filter
{
get { return _filter; }
set { _filter = value; RaisePropertyChanged(() => Filter); }
}
public EventListViewModel(ITicketMasterService ticketMasterService, IMvxNavigationService mvxNavigationService)
{
this._ticketMasterService = ticketMasterService;
this._navigationService = mvxNavigationService;
}
public IMvxCommand EventDetailCommand {
get {
return new MvxCommand<Event>(EventDetail);
}
}
private void EventDetail(Event detailEvent)
{
_navigationService.Navigate<EventDetailViewModel, Event>(detailEvent);
}
public override void Prepare(string parameter)
{
this.Filter = parameter;
}
public override async Task Initialize()
{
await base.Initialize();
//Do heavy work and data loading here
this.Events = await _ticketMasterService.GetEvents(Filter);
}
}
Whenever trying to run, the string object 'parameter' in my TableviewController's Prepare function remains 'null' and I have no idea how to fix it. Any help is greatly appreciated!
I believe the issue is with your command setup
new MvxCommand<string>(SearchEvent);
As this command is being bound to a standard UIButton. It will not pass through a parameter value of your filter but null instead. So the string parameter generic can be removed. Additionally, as you want to execute an asynchronous method I would suggest rather using MvxAsyncCommand
new MvxAsyncCommand(SearchEvent);
Then in terms of SearchEvent method you can remove the parameter. The value of filter is bound to your SearchFilter property. It is this property's value that you want to send as the navigation parameter.
private async Task SearchEvent()
{
await _navigationService.Navigate<EventListViewModel, string>(SearchFilter);
}

ItemsSource is empty in constructor of Custom Bound Horizontal ScrollView

I'm having trouble updating Cheesebarons horizontal list view to the new v3 MvvmCross. Everything seems to be working correctly except in the constructor of my "BindableHorizontalListView" control the ItemsSource of the adapter is null. Which is weird because the context shows that the view-model property I am attempting to bind to shows very clearly there are 3 items and the binding seems very straightforward. What am I missing? I hope I've included enough of the code. I've also tried binding it via fluent bindings on the "OnViewModelSet" event with the same result.
Warning presented
[MvxBind] 24.87 Unable to bind: source property source not found Cirrious.MvvmCross.Binding.Parse.PropertyPath.PropertyTokens.MvxPropertyNamePropertyToken on DeviceViewModel
AXML
<BindableHorizontalListView
android:id="#+id/listView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
local:MvxBind="ItemsSource DevicesList; ItemClick ItemSelected"
local:MvxItemTemplate="#layout/devices_horizontal_list_item" />
BindableHorizontalListView control
using System.Collections;
using System.Windows.Input;
using Android.Content;
using Android.Util;
using Cirrious.MvvmCross.Binding.Attributes;
using Cirrious.MvvmCross.Binding.Droid.Views;
namespace Project.Droid.Controls
{
public class BindableHorizontalListView
: HorizontalListView //Which inherits from AdapterView<BaseAdapter>
{
public BindableHorizontalListView(Context context, IAttributeSet attrs)
: this(context, attrs, new MvxAdapter(context))
{
}
public BindableHorizontalListView(Context context, IAttributeSet attrs, MvxAdapter adapter)
: base(context, attrs)
{
InitView ();
var itemTemplateId = MvxAttributeHelpers.ReadListItemTemplateId (context, attrs);
adapter.ItemTemplateId = itemTemplateId;
Adapter = adapter;
SetupItemClickListener();
}
public new MvxAdapter Adapter
{
get { return base.Adapter as MvxAdapter; }
set
{
var existing = Adapter;
if (existing == value)
return;
if (existing != null && value != null)
{
value.ItemsSource = existing.ItemsSource;
value.ItemTemplateId = existing.ItemTemplateId;
}
base.Adapter = value;
}
}
[MvxSetToNullAfterBinding]
public IEnumerable ItemsSource
{
get { return Adapter.ItemsSource; }
set { Adapter.ItemsSource = value; this.Reset (); }
}
public int ItemTemplateId
{
get { return Adapter.ItemTemplateId; }
set { Adapter.ItemTemplateId = value; }
}
public new ICommand ItemClick { get; set; }
private void SetupItemClickListener()
{
base.ItemClick += (sender, args) =>
{
if (null == ItemClick)
return;
var item = Adapter.GetItem(args.Position) as Java.Lang.Object;
if (item == null)
return;
if (item == null)
return;
if (!ItemClick.CanExecute(item))
return;
ItemClick.Execute(item);
};
}
}
}
View
[Activity (Label = "Device", ScreenOrientation = ScreenOrientation.Portrait)]
public class DeviceView : MvxActivity
{
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
SetContentView(Resource.Layout.device);
}
}
Property on ViewModel
private Services.Device[] _devicesList;
public Services.Device[] DevicesList {
get {
return _devicesList;
}
set {
_devicesList = value;
RaisePropertyChanged(() => DevicesList);
}
}
If only there was PCL support in XAM STUDIO I would just step in and see how the other controls are doing it!!!!
ItemsSource will always be empty in the constructor - it's a property which is set by binding, and that property can only be set after the constructor has completed.
The message:
[MvxBind] 24.87 Unable to bind: source property source not found Cirrious.MvvmCross.Binding.Parse.PropertyPath.PropertyTokens.MvxPropertyNamePropertyToken on DeviceViewModel
contains a bug - fixed in a recent commit - so the message should be more readable in the future.
If the bug hadn't been there, I suspect the message would say that the problem is in DevicesList - the binding can't find that property. Is it there? Does it have a get? Is it public?

Caliburn Micro: how to navigate in Windows phone silverlight

i am trying to use Caliburn Micro in my windows phone 7 project.
But i got a nullreferenceexception when navigate the page.
namespace Caliburn.Micro.HelloWP7 {
public class MainPageViewModel {
readonly INavigationService navigationService;
public MainPageViewModel(INavigationService navigationService) {
this.navigationService = navigationService;
}
public void GotoPageTwo() {
/*navigationService.UriFor<PivotPageViewModel>()
.WithParam(x => x.NumberOfTabs, 5)
.Navigate();*/
navigationService.UriFor<Page1ViewModel>().Navigate();
}
}
}
namespace Caliburn.Micro.HelloWP7
{
public class Page1ViewModel
{
readonly INavigationService navigationService;
public Page1ViewModel(INavigationService navigationService)
{
this.navigationService = navigationService;
}
}
}
can anyone tell me what's the problem of my code? thanks in advance.
here is bootstrapper:
public class ScheduleBootstrapper : PhoneBootstrapper
{
PhoneContainer container;
protected override void Configure()
{
container = new PhoneContainer(RootFrame);
container.RegisterPhoneServices();
container.PerRequest<MainPageViewModel>();
container.PerRequest<MainContentViewModel>();
container.PerRequest<Page1ViewModel>();
AddCustomConventions();
}
static void AddCustomConventions()
{
ConventionManager.AddElementConvention<Pivot>(Pivot.ItemsSourceProperty, "SelectedItem", "SelectionChanged").ApplyBinding =
(viewModelType, path, property, element, convention) =>
{
if (ConventionManager
.GetElementConvention(typeof(ItemsControl))
.ApplyBinding(viewModelType, path, property, element, convention))
{
ConventionManager
.ConfigureSelectedItem(element, Pivot.SelectedItemProperty, viewModelType, path);
ConventionManager
.ApplyHeaderTemplate(element, Pivot.HeaderTemplateProperty, viewModelType);
return true;
}
return false;
};
ConventionManager.AddElementConvention<Panorama>(Panorama.ItemsSourceProperty, "SelectedItem", "SelectionChanged").ApplyBinding =
(viewModelType, path, property, element, convention) =>
{
if (ConventionManager
.GetElementConvention(typeof(ItemsControl))
.ApplyBinding(viewModelType, path, property, element, convention))
{
ConventionManager
.ConfigureSelectedItem(element, Panorama.SelectedItemProperty, viewModelType, path);
ConventionManager
.ApplyHeaderTemplate(element, Panorama.HeaderTemplateProperty, viewModelType);
return true;
}
return false;
};
}
protected override object GetInstance(Type service, string key)
{
return container.GetInstance(service, key);
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return container.GetAllInstances(service);
}
protected override void BuildUp(object instance)
{
container.BuildUp(instance);
}
}
I had this too, and tracked it down as follows:
As you know, Caliburn.Micro uses convention-over-configuration to locate Views for ViewModels, and vice-versa, which means we need to follow the conventions. My mistake was to have the namespace's inconsistent for the View and ViewModel
In my case, I had
MyWP7App.DetailsViewModel, and
MyWP7App.Views.DetailsView
--> I renamed the VM's namespace to be MyWP7App.ViewModels.DetailsViewModel, and it worked out fine. I think I could have moved the view into MyWP7App.DetailsView for a good result, too...
Under the covers
the call to Navigate() invokes DeterminePageName() which, in turn, invokes ViewLocator.LocateTypeForModelType
This, like the rest of CM is overridable, but the default implementation looks like this:
public static Func<Type, DependencyObject, object, Type> LocateTypeForModelType = (modelType, displayLocation, context) => {
var viewTypeName = modelType.FullName.Substring(
0,
modelType.FullName.IndexOf("`") < 0
? modelType.FullName.Length
: modelType.FullName.IndexOf("`")
);
Func<string, string> getReplaceString;
if (context == null) {
getReplaceString = r => { return r; };
}
else {
getReplaceString = r => {
return Regex.Replace(r, Regex.IsMatch(r, "Page$") ? "Page$" : "View$", ContextSeparator + context);
};
}
var viewTypeList = NameTransformer.Transform(viewTypeName, getReplaceString);
var viewType = (from assembly in AssemblySource.Instance
from type in assembly.GetExportedTypes()
where viewTypeList.Contains(type.FullName)
select type).FirstOrDefault();
return viewType;
};
If you follow the debugger through, you end up with a collection viewTypeList that contains MyWP7App.DetailsView, and a type whose full name is MyWP7App.Views.DetailsView, and the viewType returned is therefore null... this is the cause of the NullReferenceException.
I'm 99% sure the NameTransformer.Transform call will perform a pattern-match and transform the ViewModels in the namespace of the VM to Views in the namespace of the View it's trying to locate...

Categories

Resources