MonoTouch can't open new window from AccessoryButtonTapped - c#

How do I open an new window from AccessoryButtonTapped button from my BasisViewController. Right now I have this for open, but the problem is the NavigationController which I can't find because I'm inherent the UITableViewSource.
Opskrift ops = new Opskrift(item.ImageName, item.Name, item.optionTxt, item.SubHeading)
this.NavigationController.PushViewController(this.opskrift, true);
If I use ops.NavigationController.PushViewController(this.opskrift, true);
I get an Object reference not set to an instance of an object exception.

Give your UITableViewSource inherited class access to your controller by passing it through its constructor:
public class MyTableSource : UITableViewSource
{
private BasisViewController controller;
public MyTableSource(BasisViewController parentController)
{
this.controller = parentController;
}
//use like this in a method:
//this.controller.NavigationController.PushViewController(opskrift, true);
}
Your Opfskrift controller's NavigationController property returns null because it is not part of a navigation controller's stack when you initialize it (=hasn't been "pushed" in a navigation controller). Of course, BasisViewController must also belong to a navigation controller for its NavigationController property to contain something other than null.

Related

Xamarin iOS Object reference error when calling method in another View Controller

Need some help with a object instance problem when using Xamarin iOS. My App starts with a Tab bar controller holding 2 navigation controllers. Each navigation controller has its own View Controller called A_ViewController and B_ViewController.
A_ViewController has a button which when clicked will navigate to B_ViewController.
B_ViewController has a Text Field which I want to update with a value from the button press in A_ViewController.
In B_ViewController I have a method that does the Text Field update when I call it from A_ViewController.
I get a Null reference error when I run this and I'm not sure why. I've instantiated B_ViewController so I should be able to update the Text field by calling bvc.UpdateTextField().
class A_ViewController : UIViewController {
partial void btnUpdate_TouchUpInside(UIButton sender) {
var bvc = Storyboard.InstantiateViewController("BVC") as B_ViewController;
bvc.UpdateTextField();
NavigationController.PushViewController(bvc, true);
}
}
class B_ViewController : UIViewController {
public void UpdateTextField(){
myTextField.Text = "Hello World"; <-- System.NullReferenceException: Object reference not set to an instance of an object.
}
}

How to access a variable in another page

So I have this page in an UWP Windows 10 app using C#:
public sealed partial class MainPage : Page
This page contains a TextBlock called tbPageTitle.
I'd like to change the text of tbPageTitle to "bla" from another page, so I use the following code:
MainPage.tbPageTitle.text = "bla";
However, I get the following error:
CS0120 An object reference is required for the non-static field, method, or property 'MainPage.tbPageTitle'
I don't know what to do here. I feel like I've read every single Google result.
I found some results to create a new instance of a class, so that would be for example:
MainPage mp = new MainPage();
mp.tbPageTitle.text = "bla";
But wouldn't that create a completely new MainPage? This also doesn't work by the way...
According to the answer to Sandy's comment you have an inner Frame element in the MainPage where you load other pages. So the easiest way to get the current MainPage instance is the following:
MainPage mainPage = (Window.Current.Content as Frame).Content as MainPage;
Note that this will obviously fail if you every navigate outside of the MainPage and call this line there. Additionally note that objects you are creating in XAML are not public, what means that you can't access your tbPageTitle element here anyway, but will need to create any kind of wrapper property in your MainPage like this:
public string PageTitle {
get { return tbPageTitle.Text; }
set { tbPageTitle.Text = value; }
}
However as mentioned by HeySatan, this is not the most beautiful code design you are creating here. Maybe you could create a method to go to a specific frame, something like that:
public enum TabContent { Home, Replies, Messages, Settings }
public void OpenTab(TabContent content) {
// Set Page title and navigate
switch (content) {
case TabContent.Home:
tbPageTitle.Text = "Home";
InnerFrame.Navigate(typeof(HomePage));
break;
case TabContent.Replies:
tbPageTitle.Text = "Replies";
InnerFrame.Navigate(typeof(RepliesPage));
break;
case TabContent.Messages:
tbPageTitle.Text = "Messages";
InnerFrame.Navigate(typeof(MessagesPage));
break;
case TabContent.Settings:
tbPageTitle.Text = "Settings";
InnerFrame.Navigate(typeof(SettingsPage));
break;
}
}
Main goal of this method is that if you have a button Settings in your HomePage you are only calling the following line and all logic to do the navigation stays in the MainPage and HomePage only has logic related to itself:
// In HomePage:
MainPage mainPage = (Window.Current.Content as Frame).Content as MainPage;
mainPage.OpenTab(TabContent.Settings);
If you don't want to access Window.Current.Content all the time, you could also declare a static method in your MainPage class and make access simpler:
// In MainPage:
public static MainPage Instance {
// This will return null when your current page is not a MainPage instance!
get { return (Window.Current.Content as Frame).Content as MainPage; }
}
// Now in HomePage it's only:
MainPage.Instance.OpenTab(TabContent.Settings);

URI to Class Object in ModernUI

Hi i am using Modern UI and Its loading ".xaml" file by ContentSource and link parameter. So i am stuck with the .xaml Class Object. How could we get the Class Object reference by Content Source or say URI.
My Main class file code is as below
public partial class MainWindow : ModernWindow
{
public MainWindow()
{
InitializeComponent();
this.ContentSource = new Uri("Pages/Home.xaml", UriKind.Relative);
}
}
i want Home.xaml object as Home Class Object. i tried to create a new Home Object as
Home homapge = new Home();
But its creating a new Home class Object not that one, which is used in Cotent Source.
So please help me to get this object.

Is there any way to remove a view (by name) from a Prism region when the view was added using the RegionManager.RequestNavigate method?

I am using Prism for navigation in my WPF MVVM application. I register my view as follows.
// MyView is the data type of the view I want to register and "MyView"
// is the name by which I want the data type to be identified within
// the IoC container.
_container.RegisterType<object, MyView>("MyView");
I display this view as follows.
_regionManager.RequestNavigate(
"MyRegion", // This is the name of the Region where the view should be displayed.
"MyView" // This is the registered name of the view in the IoC container.
);
Elsewhere in the application, I need to remove this view in an event handler; however, the following code returns an ArgumentNullException.
_regionManager.Regions["MyRegion"].Remove(
_regionManager.Regions["MyRegion"].GetView("MyView")
);
This indicates that the RequestNavigate method does not add MyView to MyRegion using the name "MyView". I know that if I were to use the _regionManager.Add(MyView, "MyView") method, the GetView method would not return null. Unfortunately, RequestNavigate does not seem to handle the view name in the same way. Is there any way to remove a view (by name) from a region when the view was added using the RequestNavigate method?
It stems from how you add your view, not with your removal. Previously asked answered by adding the view fully, aka including the name.
_regionManager.Regions["MyRegion"].Add(myView, "MyView");
So now you can do your retrieval and removal:
var theView = _regionManager.Regions["MyRegion"].GetView("MyView");
_regionManager.Regions["MyRegion"].Remove(theView);
Without defining name during Regions.Add()
In your View, define a property that is accessible (public if multi-project, internal if all in one project). Use this property in everything, one example would be a public string ViewTitle { get { return "XYZ"; } }. Then retrieve from the Views the item that has the desired ViewTitle. The Views collection is the collection of views in that region, so you can use dynamic in .NET 4.0+ to ignore the type and get the property/function you specify, assuming it is there. Another option is to make your imported ViewModel in the View have a getter rather than just setting the DataContext, then you'd check the property "is" to the ViewModel you're looking for. Removes the string search but exposes the view's datacontext. So probably make an enum like I would do with the region.
I included everything in my View's .cs file so you can see how it works without complicating it or really breaking MVVM.
[ViewSortHint("050")]
[ViewExport(RegionName = RegionNames.WorkspaceTabRegion)]
[PartCreationPolicy(CreationPolicy.Shared)]
public partial class AView : UserControl
{
public AView()
{
InitializeComponent();
}
[Import]
[SuppressMessage("Microsoft.Design", "CA1044:PropertiesShouldNotBeWriteOnly", Justification = "MEF requires property; never retrieved")]
PrintingViewModel ViewModel { set { this.DataContext = value; } }
public string ViewTitle { get { return "AView"; } }
}
Now in the ViewModel at some point:
var viewToRemove = RegionManager.Regions[RegionNames.WorkspaceTabRegion].Views.FirstOrDefault<dynamic>(v => v.ViewTitle == "AView");
RegionManager.Regions[RegionNames.WorkspaceTabRegion].Remove(viewToRemove);
We recently found ourselves with the same problem; thanks #odysseus.section9 for pointing its root in your comment, it really helped.
We considered making all views implement an interface having a Name property but didn't feel quite right. Then we explored #bland solution but felt uncomfortable about using dynamic so we went for a very similar approach using reflection.
Since we are also already using the ViewExportAttribute to export our views and it contains the desired ViewName property, what we do is querying for each view in a region for its attributes, looking for the ViewExportAttribute and checking the value of the ViewName property. Although in our design all views are annotated with it, the query tolerates views that don't - it simply ignores them.
For convenience we created an extension method for IRegion which searches for the views with the desired name within a region. Also, we added two extension methods to IRegionManager for two common scenarios in our application: re-using an existing view or navigating and removing all existing views (matching a name) and navigating. I think the latter solves your need just by getting rid of the call to
public static IEnumerable<object> FindViews(this IRegion region, string viewName)
{
return from view in region.Views
from attr in Attribute.GetCustomAttributes(view.GetType())
where attr is ViewExportAttribute && ((ViewExportAttribute)attr).ViewName == viewName
select view;
}
public static void ActivateOrRequestNavigate(this IRegionManager regionManager, string regionName, string viewName, UriQuery navigationParams)
{
IRegion region = regionManager.Regions[regionName];
object view = region.FindViews(viewName).FirstOrDefault();
if (view != null)
region.Activate(view);
else
regionManager.RequestNavigate(regionName,
new System.Uri(navigationParams != null ? viewName + navigationParams.ToString() : viewName, UriKind.Relative));
}
public static void RemoveAndRequestNavigate(this IRegionManager regionManager, string regionName, string viewName, UriQuery navigationParams)
{
IRegion region = regionManager.Regions[regionName];
foreach (object view in region.FindViews(viewName))
region.Remove(view);
regionManager.RequestNavigate(regionName,
new System.Uri(navigationParams != null ? viewName + navigationParams.ToString() : viewName, UriKind.Relative));
}

View an Already Instantiated ViewModel in MVVMCross

Does anyone know how to view an existing IMvxViewModel?
In my app, I have already created a bunch of ViewModels (PhotoViewModel) inside of another view model. They exist as a property on the parent ViewModel (AlbumViewModel). It would be very nice to just show a particular instance of a PhotoViewModel instead of creating a new instance of that view model when I want to view it.
public class AlbumViewModel : MvxViewModel {
public ObservableCollection<PhotoViewModel> Photos
{
get { return GetValue(() => Photos); }
set { SetValue(value, () => Photos); }
}
}
public class PhotoViewModel : MvxViewModel { }
I was wondering if there was a way, other then creating my own IMvxViewModelLocator, to accomplish this task. I think having a protected method on the MvxNavigationObject called View could be really helpful both for new developers using the framework as well as performance. We'd be able to skip all of the reflection done currently to instantiate a view model.
The default ShowViewModel mechanism in MvvmCross uses page-based navigation - this navigation has to use Uris on WindowsPhone and Intents on Android.
Because of this, MvvmCross does not allow navigation by 'rich' objects - simple serialisable POCOs are Ok, but complicated 'rich' objects are not supported.
This is further essential because of 'tombstoning' - if your app/page/activity is later rehydrated then you cannot be sure of what historic View or ViewModel objects are actually in your history "back" stack.
If you want to navigate by rich object then the best way is to store those rich objects in a lookup service and to then navigate by some key/index into the lookup. However, I would personally call those lookedup objects Models rather than ViewModels (but the boundary does sometimes become blurred!)
Although based on MvvmCross v1 code, this question still gives quite a good background to this - What is the best way to pass objects to "navigated to" viewmodel in MVVMCross?
Some more up-to-date explanations include:
How to pass data across screens using mvvmcross
Custom types in Navigation parameters in v3
https://github.com/slodge/MvvmCross/wiki/ViewModel--to-ViewModel-navigation (under construction)
One final thing....
... the MvvmCross manifesto insists that MvvmCross is very open to customisation ...
Because of this you can override MvvmCross navigation and view model location if you want to. To do this, creating your own IMvxViewModelLocator would probably be a good way to start.
After some testing, below is a proposed solution. I'm not 100% in love with it, but it does work and provide the type developer experience I was looking for. So lets dig in.
To start, all of my ViewModels (VM) inherit from a base VM, AVM. This abstract base class supports looking up of an object as a public static method. It's a little gross, but it works well if you're willing to sip on the Kool-Aid. Below is the portion of the class that's relevant to this problem:
public abstract class AVM : MvxViewModel {
private static readonly Dictionary<Guid, WeakReference> ViewModelCache = new Dictionary<Guid, WeakReference>();
private static readonly string BUNDLE_PARAM_ID = #"AVM_ID";
private Guid AVM_ID = Guid.NewGuid();
private Type MyType;
protected AVM()
{
MyType = this.GetType();
ViewModelCache.Add(AVM_ID, new WeakReference(this));
}
public static bool TryLoadFromBundle(IMvxBundle bundle, out IMvxViewModel viewModel)
{
if (null != bundle && bundle.Data.ContainsKey(BUNDLE_PARAM_ID))
{
var id = Guid.Parse(bundle.Data[BUNDLE_PARAM_ID]);
viewModel = TryLoadFromCache(id);
return true;
}
viewModel = null;
return false;
}
private static IMvxViewModel TryLoadFromCache(Guid Id)
{
if (ViewModelCache.ContainsKey(Id))
{
try
{
var reference = ViewModelCache[Id];
if (reference.IsAlive)
return (IMvxViewModel)reference.Target;
}
catch (Exception exp) { Mvx.Trace(exp.Message); }
}
return null;
}
protected void View()
{
var param = new Dictionary<string, string>();
param.Add(BUNDLE_PARAM_ID, AVM_ID.ToString());
ShowViewModel(MyType, param);
}
In order to get this all wired up, you have to create a custom view model locator. Here's the custom locator:
public class AVMLocator : MvxDefaultViewModelLocator
{
public override bool TryLoad(Type viewModelType, IMvxBundle parameterValues, IMvxBundle savedState, out IMvxViewModel viewModel)
{
if (AVM.TryLoadFromBundle(parameterValues, out viewModel))
return true;
return base.TryLoad(viewModelType, parameterValues, savedState, out viewModel);
}
}
Lastly you have to wire up. To do so, go into your App.cs and override CreateDefaultViewModelLocator like so:
protected override IMvxViewModelLocator CreateDefaultViewModelLocator()
{
return new AVMLocator();
}
You're all set. Now in any of your derived ViewModels that are already alive and well, you can do the following:
myDerivedVM.View();
There's still some more I need to do (like making sure the WeakReferences do their job and I don't have memory leaks and some additional error handling), but at the very least it's the experience I was going for. The last thing I did was add the following command to the AVM base class:
public MvxCommand ViewCommand
{
get { return new MvxCommand(View); }
}
Now you can bind that command to any UI object and when invoked, it'll launch that view with that very instance of the VM.
Stuart, thanks for your help in steering me in the right direction. I'd be interested in hearing your feedback on the solution I provided. Thanks for all of your work with MVVMCross. It really is a very beautiful bit of code.
Cheers.

Categories