I am trying to create a custom binding to LinearLayout to allow me create a view dynamically and bind this as a child to LinearLayout. For anyone familiar with WPF, this is similar to the functionality provided by the ContentControl or any WPF control deriving from ContentControl. Basically you can create your dynamic content and bind to the Content property of a ContentControl.
Here's what I have for the custom binding:
public class MvxLinearLayoutContentTargetBinding : MvxPropertyInfoTargetBinding<LinearLayout>
{
public MvxLinearLayoutContentTargetBinding(object target, PropertyInfo targetPropertyInfo) : base(target, targetPropertyInfo)
{
}
protected override void SetValueImpl(object target, object value)
{
base.SetValueImpl(target, value);
var view = target as LinearLayout;
if (view == null) return;
view.AddView((View)value);
}
public override Type TargetType
{
get { return typeof(LinearLayout); }
}
}
Here's how I am attempting to use this new binding in 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="fill_parent"
android:layout_height="wrap_content"
android:paddingBottom="3dp"
android:paddingTop="3dp"
android:paddingLeft="5dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingBottom="3dp"
android:paddingTop="3dp"
android:paddingLeft="0dp"
local:MvxBind="Content CustomView">
</LinearLayout>
</LinearLayout>
Any my ViewModel looks like this:
public class CustomViewModel : MvxViewModel
{
public object CustomView { get; set; }
}
The custom binding has also been registered in Setup.cs as follows:
protected override void FillTargetFactories(IMvxTargetBindingFactoryRegistry registry)
{
registry.RegisterPropertyInfoBindingFactory(
typeof(MvxLinearLayoutContentTargetBinding),
typeof(LinearLayout), "Content");
base.FillTargetFactories(registry);
}
Yet with all this in place, I do not see my view.
MvvmCross already supports a primitive version of this. Although without DataTemplateSelector.
You can bind a collection of ViewModels to MvxLinearLayout.ItemsSource. You also need to remember to set a ItemTemplateId:
<MvxLinearLayout
...
local:MvxItemTemplateId="#layout/layout_to_repeat"
local:MvxBind="ItemsSource ViewModelCollection"
/>
This is however super inefficient as it does not recycle views etc. So if you need a variant that supports DataTemplateSelector, then use MvxRecyclerView instead.
Related
I've been using MvxAppCompatActivity throughout my project, but in this specific case I have to make use of a MvxAppCompatDialogFragment.
Unfortunately, in this case I lose the binding context of the ViewModel somehow.
MobileTestView
[MvxDialogFragmentPresentation]
[Register(nameof(MobileScreenTestView))]
public class MobileTestView : MvxAppCompatDialogFragment<MobileTestViewModel>
...
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
return inflater.Inflate(Resource.Layout.mobile_screen, container, false);
}
...
MobileTestViewModel
public class MobileTestViewModel : MvxViewModel<MInput, MResult>
...
public string Instructions { get; set; } = "Instructions";
...
mobile_screen.axml
...
<TextView
android:id="#+id/text_mobile"
android:layout_width="match_parent"
android:layout_height="44dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:gravity="center_vertical|center_horizontal"
tools:text="Scan"
local:MvxBind="Text Instructions" />
...
local:MvxBind="Text Instructions" does not work anymore, but I've checked and it is set in the view model before it gets to OnCreateView().
The above code would work fine for a MvxAppCompatActivity.
If what I'm trying to do is not possible, I can always do it like
view.FindViewById<TextView>(Resource.Id.text_mobile).Text = ViewModel.Instructions;
It's not that I really need to use local:MvxBind, but I would like to know what I'm doing wrong.
Update - For anyone having the same problem:
Change the OnCreateView method to:
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
base.OnCreateView(inflater, container, savedInstanceState);
return this.BindingInflate(Resource.Layout.mobile_screen, container, false);
}
and your BindingContext will work fine.
As you noticed yourself, you had to use this.BindingInflate instead of the LayoutInflater argument in OnCreateView. This is because we have no way to intercept the Fragment lifecycle in MvvmCross to provide our own Layout Inflater.
What BindingInflate does, is to run through the view hierarchy and look for all the custom attributes applied on views, in your case Text Instructions and apply these bindings between the View and the ViewModel.
So whenever working with Fragments, you should use BindingInflate.
I just installed the latest update of Xamarin for Visual Studio.
Ever since, android can't find the views in my activity layouts.
Apparently the auto-generated IDs aren't updated correctly when i just save my resources.
If I clean the project and rebuild, my app runs fine. But after like 20 minutes of coding the problem will occur again.
Simple code: Resources/Layout/Main.axml
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="9dp"
android:paddingBottom="9dp">
<Button
android:text="#string/action_stockcount"
android:layout_width="match_parent"
android:layout_height="90dp"
android:id="#+id/btnStockCount"
android:layout_marginLeft="18dp"
android:layout_marginRight="18dp"
android:layout_marginStart="9dp"
android:layout_marginTop="9dp"
android:textColor="#color/menu_button_text"
android:textSize="30dp"
android:drawableLeft="#drawable/menu_stockcount_big"
android:paddingLeft="18dp"
android:paddingRight="18dp"
android:paddingBottom="9dp"
android:paddingTop="9dp" />
</LinearLayout>
</ScrollView>
MainActivity.designer.cs:
using ...
namespace MyApp.Droid.Activities
{
public partial class MainActivity
{
private Button btnStockCount;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.Main);
SetTitle(Resource.String.ApplicationName);
btnStockCount = FindViewById<Button>(Resource.Id.btnStockCount);
// From time to time, this gives a NullReferenceException
btnStockCount.Click += BtnStockCount_Click;
InitComplete(savedInstanceState);
}
}
}
MainActivity.cs (Not quite important):
using ...
namespace MyApp.Droid.Activities
{
public partial class MainActivity : Activity
{
private void BtnStockCount_Click(object sender, EventArgs e)
{
StartActivity(typeof(IndexStockCountActivity));
}
}
}
I know how android works, I know how Xamarin works. Why would the latest update of Xamarin cause this issue (the IDs that aren't updated correctly when saving a resource)? What can I do to solve this issue?
It's really annoying to have to debug my project, notice that again it's not working well, and having to clean and rebuild, and notice that after that it IS working.
I also already noticed that when it's not working, after I cleaned and rebuilded the Resource Ids are in fact changed.
Cheers
I have a viewmodel that inherits from BindableBase and implements IApplicationLifecycle. However, IApplicationLifecycle.OnResume() and IApplicationLifecycle.OnSleep() is never called on neither iOS nor android.
How do you use IApplicationLifecycle? I cant seem to find any documentation for it. Thanks in advance!
In my App.xaml.cs i have:
NavigationService.NavigateAsync("NavigationPage/MainPage");
and my MainPage.xaml looks like this:
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:PrismApp.Views;assembly=PrismApp"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="PrismApp.Views.MainPage"
Title="MainPage">
<TabbedPage.Children>
<local:APage Icon="tabicon_mapspage.png" />
<local:BPage Icon="tabicon_profilepage.png" />
<local:CPage Icon="tabicon_statuspage.png" />
</TabbedPage.Children>
Finally my MainPageViewModel looks like this:
public class MainPageViewModel : BindableBase, IApplicationLifecycle
{
private string _title;
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
public void OnResume()
{
var debug = 42;
}
public void OnSleep()
{
var debug = 42;
}
}
I have put a minimal project of it here: https://github.com/RandomStuffAndCode/PrismApp
Since you are in a TabbedPage the IApplicationAware methods are only being invoked on the TabbedPage.CurrentPage (SelectedTab), as that is the current active page.
By the way, you can change this behavior by overriding the OnResume and OnSleep methods in your app class to achieve your goal. Use the existing code as a guide: https://github.com/PrismLibrary/Prism/blob/master/Source/Xamarin/Prism.Forms/PrismApplicationBase.cs#L171
I am new to mvvmcross. I do not know how to bind touch event of a relative layout to a view model. Can someone help me on this. Thanks in advance.
This is possible using standard MvvmCross code. For the viewmodel you can use something like:
public class SomeViewModel : MvxViewModel
{
public IMvxCommand OpenSomethingCommand { get; private set; }
public SomeViewModel ()
{
OpenSomethingCommand = new MvxCommand(() => ShowViewModel<SomeOtherNewViewModel>());
}
}
Then for the layout in your android project, you can use:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
local:MvxBind="Click OpenSomethingCommand"
android:id="#+id/someLayout">
//Wrap any other layouts in here
</RelativeLayout>
I have been trying to solve this problem for hours now and have exhausted all the obvious solutions. I have a custom toggle button that works fine as long as I don't try to get it using findViewById
Here is the relevant code:
public class WeekButton extends ToggleButton {
private static int mWidth;
public WeekButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public WeekButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
...
}
Here is the layout..
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:gravity="center"
android:orientation="horizontal"
android:padding="5dp">
<org.myname.projetname.reminders.WeekButton.WeekButton
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mondayToggleButton"
style="#style/RecurrenceDayOfWeekStyle"
android:textOff=" Mon"
android:textOn=" Mon" />
</LinearLayout>
Exception occurs here..
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.edit_reminder_activity);
//This causes null pointer
WeekButton weekButton = (WeekButton)findViewById(R.id.mondayToggleButton));
}
Thanks for any help you can provide!
SOLUTION: Turns out I forgot to instantiate the list I was adding the WeekButtons which made it look like the call to find id was working but it was. Thank you everyone you tried to help.
Didn't it show any error before run? xml name space attribute is given for the parent tag.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:gravity="center"
android:orientation="horizontal"
android:padding="5dp">
<org.myname.projetname.reminders.WeekButton.WeekButton
android:id="#+id/mondayToggleButton"
style="#style/RecurrenceDayOfWeekStyle"
android:textOff=" Mon"
android:textOn=" Mon" />
</LinearLayout>
Also, Cast to your class on findViewById().
Forgot to instantiate a list I was adding the weekbuttons to.