hcaltenco
hcaltenco

Reputation: 41

mvvmcross navigate to viewmodel from tabbed

I have a Xamarin Forms application using mvvmcross. There I have navigation via TabbedPages. Each page has a xaml + code behind and viewmodel.

Relevant code for first page:

<?xml version="1.0" encoding="utf-8" ?>
<pages:BaseTabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         xmlns:pages="clr-namespace:ABBI_XPlat_3.Pages;assembly=ABBI_XPlat_3"
         x:Class="ABBI_XPlat_3.Pages.DeviceListPage"
         Title="Discover devices"
         x:Name="DevicePage">
<pages:BaseTabbedPage.Resources>
  <ResourceDictionary>
     <DataTemplate x:Key="DeviceItemTemplate"> ... </DataTemplate>
  </ResourceDictionary>
</pages:BaseTabbedPage.Resources>
<pages:BaseTabbedPage.Children>
  <pages:BasePage Title="Scan for devices">
    <Grid>
      <Grid.RowDefinitions>
        <RowDefinition Height="Auto"></RowDefinition>
        <RowDefinition Height="*"></RowDefinition>
        <RowDefinition Height="Auto"></RowDefinition>
      </Grid.RowDefinitions>
      <StackLayout BackgroundColor="#FF6969" Padding="10" IsVisible="{Binding IsStateOn, Converter={StaticResource InverseBoolean}}">
        <Label Text="{Binding StateText}" FontSize="18" HorizontalTextAlignment="Center"></Label>
        </StackLayout>

      <ListView Grid.Row="1" ItemsSource="{Binding Devices}" SelectedItem="{Binding SelectedDevice, Mode=TwoWay}"
             IsPullToRefreshEnabled="True"
             RefreshCommand="{Binding RefreshCommand}"
             IsRefreshing="{Binding IsRefreshing, Mode=OneWay}"
             RowHeight="80"
             ItemTemplate="{StaticResource DeviceItemTemplate}">
      </ListView>

      <StackLayout Grid.Row="2" Orientation="Horizontal">
        <Button Text="Connect" Command="{Binding ConnectToSelectedCommand}" HorizontalOptions="FillAndExpand"/>
        <Button Text="Stop Scan" Command="{Binding StopScanCommand}" HorizontalOptions="End"/>
        <ActivityIndicator IsRunning="{Binding IsRefreshing}"
                         HeightRequest="24"
                         WidthRequest="24"
                         VerticalOptions="Center"
                         HorizontalOptions="End"/>
      </StackLayout>

    </Grid>
  </pages:BasePage>
  <pages:ServiceListPage Title="Services"/>
  <pages:OtherTabbedPage Title="Services"/>
</pages:BaseTabbedPage.Children>
</pages:BaseTabbedPage>

I am able to call different viewmodels from buttons in my main view model using:

public MvxCommand ConnectToSelectedCommand => new MvxCommand(ConnectToSelectedDeviceAsync, CanDisplayServices);

    private async void ConnectToSelectedDeviceAsync()
    {
        ShowViewModel<ServiceListViewModel>(new MvxBundle(new Dictionary<string, string> { { DeviceIdKey, SystemDevices.FirstOrDefault().Id.ToString() } }));
    }

within my main ViewModel. But I want to be able to use the tabs to navigate between ViewModels. At the moment if I click on a tab, then it brings up the page, but without the associated ViewModel.

Help please!

Upvotes: 1

Views: 1174

Answers (2)

James
James

Reputation: 381

So what you have to do to get MvvmCross to bind the Pages to the VMs is have a MvxTabbedPage as the Root TabbedPosition & have your pages that go in the tabs as the Detail TabbedPosition. Then in the MvxTabbedPage's ViewModel, you have to Navigate to all the Detail Tab's ViewModels. Here is an example.

namespace NameSpace
{
    // Tabbed Detail Page
    [MvxTabbedPagePresentation(Title = "Home", Icon = "ic_home_black.png")]
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class HomePage : MvxContentPage<HomeViewModel>
    {
        public HomePage()
        {
            InitializeComponent();
        }
    }
    // Tabbed Root Page
    [MvxTabbedPagePresentation(TabbedPosition.Root, WrapInNavigationPage = true)]
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class TabbedPage : MvxTabbedPage<TabbedViewModel>
    {
        public TabbedProjectDetailsPage()
        {
            InitializeComponent();
        }
    }
    // Tabbed Detail ViewModel
    public class HomeViewModel : MvxViewModel
    {
        IMvxNavigationService _navigation;
        public HomeViewModel(IMvxNavigationService navigation)
        {
            _navigation = navigation;
        }
    }
    // Tabbed Root ViewModel
    public class TabbedViewModel : MvxNavigationViewModel
    {
        public TabbedProjectDetailsViewModel(IMvxLogProvider log, IMvxNavigationService navigation) : base(log, navigation)
        {
        }
        MvxCommand _navHome;
        public ICommand NavigateHome
        {
            get
            {
                _navHome = _navHome ?? new MvxCommand(async () =>
                {
                    await NavigationService.Navigate<HomeViewModel>();
                });
                return _navHome;
            }
        }

        public void ShowViewModels()
        {
            this.NavigateHome.Execute(null);
        }
        bool appeared = false;
        public override void ViewAppearing()
        {
            base.ViewAppearing();
            if (!appeared)
            {
                ShowViewModels();
            }
            appeared = true;
        }
    }
}

Upvotes: 1

hcaltenco
hcaltenco

Reputation: 41

Finally managed to solve the problem. It was so simple that I could not find an answer anywhere. I just had to add a bindingcontext to the codebehind of the page.

public ServiceListPage()
    {
        InitializeComponent();
        this.BindingContext = new ViewModels.ServiceListViewModel(Plugin.BLE.CrossBluetoothLE.Current.Adapter, UserDialogs.Instance);
    }

Upvotes: 0

Related Questions