I have a Xamarin.Forms app that uses Alex Rainman's CarouselView (https://github.com/alexrainman/CarouselView) to present a carousel (surprise! :) ). Each page in the carousel consists of a list of items. On Android it looks like this:
It is similar on Windows. However, on iOS I get this:
At first I thought it was a bug in the CarouselView component and submit an issue but the author said it was working fine on his side. I tried to debug it in whatever ways I could think of but couldn't find the problem. The CarouselView is instantiated and added to the view hierarchy. The ViewModel is instantiated and working fine if I replace it with say a ListView displaying the columns. Now I'm out of ideas and would really appreciate some help!
Here is my code:
MainPage.xaml:
<controls:CarouselViewControl Orientation="Horizontal"
InterPageSpacing="0"
Position="0"
ItemsSource="{Binding Columns}"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand"
BackgroundColor="Gray"
ShowIndicators="True"
ShowArrows="True">
<controls:CarouselViewControl.ItemTemplate>
<DataTemplate>
<ContentView HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
Padding="20">
<ListView HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
BackgroundColor="LightGray"
Header="{Binding Name}"
ItemsSource="{Binding Tasks}"
ItemSelected="ListView_ItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ImageCell ImageSource="{Binding Image}"
Text="{Binding Title}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentView>
</DataTemplate>
</controls:CarouselViewControl.ItemTemplate>
</controls:CarouselViewControl>
(The code behind just calls InitializeComponent() in the constructor)
ColumnsViewModel.cs:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace ProofOfConcept
{
public class ColumnsViewModel : BindableObject
{
public static readonly BindableProperty PositionProperty = BindableProperty.Create(nameof(Position), typeof(int), typeof(ColumnsViewModel), 0, BindingMode.TwoWay);
public ObservableCollection<Column> Columns { get; set; }
public int Position
{
get { return (int)this.GetValue(PositionProperty); }
set { this.SetValue(PositionProperty, value); }
}
public ColumnsViewModel()
{
Columns = new ObservableCollection<Column>();
Random random = new Random();
for (int i = 0; i < 10; i++)
{
Column column = new Column
{
Name = String.Format("Column {0}", i),
Background = Color.FromRgb(random.Next(0, 255), random.Next(0, 255), random.Next(0, 255))
};
ObservableCollection<Task> tasks = new ObservableCollection<Task>();
for (int t = 0; t < 25; t++)
{
Task task = new Task
{
Title = String.Format("Column #{0}, Task #{1}", i, t),
Color = Color.Lavender
};
tasks.Add(task);
}
column.Tasks = tasks;
Columns.Add(column);
}
}
}
}
Column.cs:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace ProofOfConcept
{
public class Column
{
public string Name { get; set; }
public ObservableCollection<Task> Tasks { get; set; }
public Color Background { get; set; }
public string Image
{
get
{
return "http://loremflickr.com/100/100";
}
}
}
}
Task.cs:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace ProofOfConcept
{
public class Task : BindableObject
{
public static readonly BindableProperty ColorProperty = BindableProperty.Create(nameof(Color), typeof(Color), typeof(Task), Color.White, BindingMode.TwoWay);
public string Title { get; set; }
public string Assignee
{
get
{
return "Ivan";
}
}
public Color Color
{
get { return (Color)this.GetValue(ColorProperty); }
set { this.SetValue(ColorProperty, value); }
}
public string Image
{
get
{
return "http://loremflickr.com/100/100";
}
}
}
}
Also, here is a zip of the whole solution (without the Windows project since it was very big and not really relevant).
Usage
In your iOS and Android projects call:
Xamarin.Forms.Init();
CarouselViewRenderer.Init();
namespace ProofOfConcept.iOS
{
// The UIApplicationDelegate for the application. This class is responsible for launching the
// User Interface of the application, as well as listening (and optionally responding) to
// application events from iOS.
[Register("AppDelegate")]
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
//
// This method is invoked when the application has loaded and is ready to run. In this
// method you should instantiate the window, load the UI into it and then make the window
// visible.
//
// You have 17 seconds to return from this method, or iOS will terminate your application.
//
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
CarouselViewRenderer.Init();
LoadApplication(new App());
return base.FinishedLaunching(app, options);
}
}
}
In case anyone have this issues in iOS even after calling "init". You can try giving height request to the CarouselViewControl. This is mostly the case if the control is in a grid and you are using Auto for RowDefinitin.Height
Related
My Datagrid does not populate after I click a button in my ViewModel
Model Code:
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Text;
namespace Mist_Management.SubmissionStatusBySite
{
public class Table
{
[JsonProperty("Company")]
public string Company { get; set; }
[JsonProperty("DATE")]
public string Date { get; set; }
[JsonProperty("EODNUMBER")]
public string EODNumber { get; set; }
[JsonProperty("SUBSTATUS")]
public string SubStatus { get; set; }
[JsonProperty("DAYENDSTATUS")]
public string DayEndStatus { get; set; }
}
public class BySite
{
public List<Table> Table { get; set; }
}
}
ViewModel Code:
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Forms;
using Mist_Management.CompanyModels;
using Mist_Management.SubmissionStatusBySite;
using System;
using System.Diagnostics;
namespace Mist_Management.ViewModels
{
class SubmissionStatusBySiteViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
public ObservableCollection<CompanyModels.Table> Company { get; set; }
public ObservableCollection<SubmissionStatusBySite.Table> BySite { get; set; }
public SubmissionStatusBySiteViewModel()
{
Company = new ObservableCollection<CompanyModels.Table>();
GetCompany();
}
private async void GetCompany()
{
string requestUrl = "https://mist.zp.co.za:6502/MIST.svc/CMP/M#H$#203#R";
using (var client = new HttpClient())
{
HttpResponseMessage response = await client.GetAsync(requestUrl);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
string content = await response.Content.ReadAsStringAsync();
Company root = JsonConvert.DeserializeObject<Company>(content);
List<CompanyModels.Table> dates = root.Table;
foreach (var item in dates)
{
Company.Add(item);
}
}
}
}
public CompanyModels.Table selectedCompany;
public CompanyModels.Table SelectedCompany
{
get { return selectedCompany; }
set
{
selectedCompany = value;
OnPropertyChanged("SelectedCompany");
Debug.WriteLine(value.Company);
}
}
public DateTime _selectedFromDate;
public DateTime SelectedFromDate
{
get { return _selectedFromDate; }
set
{
if (_selectedFromDate == value)
return;
_selectedFromDate = value;
Debug.WriteLine(_selectedFromDate);
}
}
public string FromDate
{
get
{
return this.SelectedFromDate.ToString("yyyy-MM-dd");
}
}
public DateTime _selectedEndDate;
public DateTime SelectedEndDate
{
get { return _selectedEndDate; }
set
{
if (_selectedEndDate == value)
return;
_selectedEndDate = value;
Debug.WriteLine(_selectedEndDate);
}
}
public string EndDate
{
get
{
return this.SelectedEndDate.ToString("yyyy-MM-dd");
}
}
private async void GetData()
{
string requestUrl = "https://mist.zp.co.za:6502/MIST.svc/SUB2/M#H$#203#R/"
+ selectedCompany.Company + "/" + FromDate + "/" + EndDate;
using (var client = new HttpClient())
{
HttpResponseMessage response = await client.GetAsync(requestUrl);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
string content = await response.Content.ReadAsStringAsync();
BySite root = JsonConvert.DeserializeObject<BySite>(content);
List<SubmissionStatusBySite.Table> dates = root.Table;
foreach (var item in dates)
{
BySite.Add(item);
}
}
}
Debug.WriteLine(requestUrl);
}
public ICommand LoadButton_Clicked
{
get
{
return new Command(() =>
{
BySite = new ObservableCollection<SubmissionStatusBySite.Table>();
GetData();
});
}
}
}
}
XAML code:
<?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:dg="clr-namespace:Xamarin.Forms.DataGrid;assembly=Xamarin.Forms.DataGrid"
xmlns:system="clr-namespace:System;assembly=mscorlib"
x:Class="Mist_Management.Views.SubmissionStatusBySite"
Title="Submission Status By Site">
<StackLayout>
<Picker Title="Company List" ItemsSource="{Binding Company}"
ItemDisplayBinding="{Binding Company}"
SelectedItem="{Binding SelectedCompany}"/>
<Label Text="From Date"
FontSize="Medium"/>
<DatePicker HorizontalOptions="CenterAndExpand"
Format="yyyy-MM-dd"
MinimumDate="2015-01-01"
Date="{Binding SelectedFromDate}"/>
<Label Text="To Date"
FontSize="Medium"/>
<DatePicker HorizontalOptions="CenterAndExpand"
x:Name="EndDate"
Format="yyyy-MM-dd"
MinimumDate="2015-01-01"
Date="{Binding SelectedEndDate}"/>
<Button Text="Load" Command="{Binding LoadButton_Clicked}"/>
<ContentView>
<ScrollView Orientation="Horizontal">
<dg:DataGrid x:Name="BySiteGrid" ItemsSource="{Binding BySite}" RowHeight="70" HeaderHeight="50"
BorderColor="#CCCCCC" HeaderBackground="#E0E6F8">
<x:Arguments>
<ListViewCachingStrategy>RetainElement</ListViewCachingStrategy>
</x:Arguments>
<dg:DataGrid.HeaderFontSize>
<OnIdiom x:TypeArguments="x:Double">
<OnIdiom.Tablet>15</OnIdiom.Tablet>
<OnIdiom.Phone>12</OnIdiom.Phone>
</OnIdiom>
</dg:DataGrid.HeaderFontSize>
<dg:DataGrid.Columns>
<dg:DataGridColumn Title="Company" PropertyName="Company" Width="200"/>
<dg:DataGridColumn Title="Date" PropertyName="Date" Width="150"/>
<dg:DataGridColumn Title="EOD Number" PropertyName="EODNumber" Width="150"/>
<dg:DataGridColumn Title="Sub Status" PropertyName="SubStatus" Width="150"/>
<dg:DataGridColumn Title="Day End Status" PropertyName="DayEndStatus" Width="150"/>
</dg:DataGrid.Columns>
</dg:DataGrid>
</ScrollView>
</ContentView>
</StackLayout>
</ContentPage>
ContentView Code:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Mist_Management.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class SubmissionStatusBySite : ContentPage
{
public SubmissionStatusBySite()
{
InitializeComponent();
BindingContext = new ViewModels.SubmissionStatusBySiteViewModel();
}
}
}
I have other content viewModels that load on screen create but when I click a button the Datagrid does not want to load.
I have tried everything I can think off. I'm at my end with this problem.
Any help would be appreciated
i have follow grial grid to enable the itemClickCommand.
below is the source code DashboardMultipleTilesPage.xaml:
<grial:GridView
WidthRequest="320"
Margin="0"
Padding="10"
ColumnSpacing="10"
RowSpacing="10"
ItemsSource="{ Binding Items }"
ItemClickCommand="{ Binding ItemCommand }"
ItemTemplate="{ StaticResource Selector }"
ColumnCount="2"/>
and below is from DashboardMultipleTilesViewModel.cs
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Windows.Input;
using UXDivers.Grial;
using Xamarin.Forms;
namespace QlikApps
{
public class DashboardMultipleTilesViewModel :ObservableObject
{
private readonly Command _itemCommand;
public ICommand ItemCommand => _itemCommand;
public DashboardMultipleTilesViewModel(): base(listenCultureChanges: true)
{
_itemCommand = new Command<DashboardMultipleTilesPage>(ItemAction);
LoadData();
}
public ObservableCollection<DashboardMultipleTileItemData> Items { get; } = new ObservableCollection<DashboardMultipleTileItemData>();
protected override void OnCultureChanged(CultureInfo culture)
{
LoadData();
}
private void LoadData()
{
Items.Clear();
JsonHelper.Instance.LoadViewModel(this, source:"NavigationDashboards.json");
}
private void ItemAction(DashboardMultipleTilesPage items)
{
Application.Current.MainPage.DisplayAlert("Hello",
items.Title, "OK");
string id = items.id;
}
}
}
ItemAction(DashboardMultipletilesPage item) not fire at all?
How to access data which currently point to the grid?
please help.
You can improve
public ICommand ItemCommand {get; set;}
And
ItemCommand = new Command<DashboardMultipleTilesPage>(ItemAction);
Update:
Make sure you have set the BindingContext in your ContentPage.
When trying to create a simple Xamarin app, on debugging I will immediately get a dialog box quoting:
Object Reference not set to an instance of an object
Nothing appears in my error list.
Can anyone give me tips on where I am going wrong?
Many thanks,
Gary
MainPage.xaml
<xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamarinApp1"
x:Class="XamarinApp1.MainPage">
<StackLayout>
<!-- Place new controls here -->
<Label Text="Welcome to Xamarin.Forms!"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<Button Text="Click Me" Clicked="Button_Clicked" />
</StackLayout>
</ContentPage>
MainPage.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace XamarinApp1
{
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
int count = 0;
private void Button_Clicked(object sender, EventArgs e)
{
count++;
((Button)sender).Text = $"You clicked {count} times.";
}
}
app.xaml.cs
using System;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace XamarinApp1
{
public partial class App : Application
{
public App()
{
InitializeComponent();
MainPage = new MainPage();
}
protected override void OnStart()
{
// Handle when your app starts
}
protected override void OnSleep()
{
// Handle when your app sleeps
}
protected override void OnResume()
{
// Handle when your app resumes
}
}
}
I want to make a simple login UI in xaml using Xamarin. I create a username and password field with Entry in the MainPage and then I try to bind them to my LoginViewModel where I can access my connexion method.
When I define the Binding context in the Mainpage codebehind the application simply shutdown and I dont understand why, what am I doing wrong ?
MainPage.xaml
<?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:local="clr-namespace:suivAAndroid"
x:Class="suivAAndroid.MainPage">
<StackLayout
VerticalOptions="CenterAndExpand">
<Image></Image>
<Label
Text="Login"
StyleId="lbl_login"></Label>
<Entry
StyleId="ent_login"
Text="{Binding Username}"></Entry>
<Label
Text="Mot de passe"
StyleId="ent_mdp"></Label>
<Entry
StyleId="ent_mdp"
Text="{Binding Password}"></Entry>
<Button
Clicked="connexion_click"
Text="Connexion"></Button>
</StackLayout>
</ContentPage>
MainPage.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace suivAAndroid
{
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
BindingContext = new LoginViewModel(); // Here is where it does not work. If the line is commented out, then the application launch without stopping but because there is no binding context I cant get the user inputs.
}
private void connexion_click(object sender, EventArgs e)
{
LoginViewModel connexionBtn = new LoginViewModel();
Device.BeginInvokeOnMainThread(async () =>
{
await connexionBtn.Connexion();
});
}
}
}
LoginViewModel.cs
using suivAAndroid.Models;
using suivAAndroid.Views;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace suivAAndroid
{
public class LoginViewModel
{
#region propriétés
public string Username
{
get
{
return Username;
}
set
{
Username = value;
}
}
public string Password
{
get
{
return Password;
}
set
{
Password = value;
}
}
#endregion
#region constructor
public LoginViewModel()
{
}
#endregion
#region methodes
public void CreerListeVisiteurDur(List<Visiteur> uneListe)
{
Visiteur unVisiteur = new Visiteur("Clooney", "George", "cgeorge", "azerty", "rue du port", "59", "lille", new DateTime(2015 / 07 / 13));
uneListe.Add(unVisiteur);
}
public async Task Connexion()
{
List<Visiteur> uneListe = new List<Visiteur>();
CreerListeVisiteurDur(uneListe);
if (!string.IsNullOrEmpty(Username) && !string.IsNullOrEmpty(Password))
{
foreach (Visiteur unVisiteur in uneListe)
{
string login = unVisiteur.login;
string pass = unVisiteur.mdp;
if (login == Username && pass == Password)
{
App.Current.MainPage = new CreerVisite();
}
}
}
}
#endregion
}
}
Your ViewModel properties have infinite loops:
public string Username
{
get
{
return Username;
}
set
{
Username = value;
}
}
calling Username = value will call set on Username which in turn calls Username = value again.
Also, in order for your ViewModel to be bindable, you must implement INotifyPropertyChanged.
If you want a framework that is easy to use to help you do this, I would suggest Mvvm Light.
Here's an example of what your ViewModel should look like:
public class MyViewModel : INotifyPropertyChanged
{
public event EventHandler<PropertyChangedEventArgs> OnPropertyChanged;
private string _username;
public string Username
{
get
{
return _username;
}
set
{
_username = value;
PropertyChanged?.Invoke(new PropertyChangedEventArgs("Username");
}
}
....
}
in connexion_click you are creating a new copy of your VM that has no relation to the prior copy you created for your BindingContext.
public partial class MainPage : ContentPage
{
private LoginViewModel vm;
public MainPage()
{
InitializeComponent();
vm = new LoginViewModel();
BindingContext = vm;
}
private void connexion_click(object sender, EventArgs e)
{
Device.BeginInvokeOnMainThread(async () =>
{
await vm.Connexion();
});
}
}
your VM should implement INotifyPropertyChanged
your VM has a recursive getter
I'm currently working on WPF App with Prism 6...I have ShellViewModel, ViewAViewModel and ViewBViewModel.
Inside Shell.xaml, I have "mainRegion" defined. When app is started, I show ViewA in that Region by default.
Now, When I go to from ViewA to ViewB, at this point(Inside ViewBViewModel), I need to have context of ShellViewModel.
Any suggestion to achieve this?
the full source code!
ViewA.xaml
<UserControl x:Class="ModuleA.Views.ViewA"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
<Grid>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" >
<TextBlock Text="{Binding Title}" FontSize="38" />
<Button Command="{Binding UpdateCommand}" Width="100">Update</Button>
</StackPanel>
</Grid>
</UserControl>
ViewA.xaml.cs
using ModuleA.RibbonTabs;
using PrismDemo.Core;
using System.Windows.Controls;
namespace ModuleA.Views
{
[RibbonTab(typeof(ViewATab))]
public partial class ViewA : UserControl, ISupportDataContext
{
public ViewA()
{
InitializeComponent();
}
}
}
ViewB.xaml
<UserControl x:Class="ModuleA.Views.ViewB"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
<Grid>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" >
<TextBlock Text="{Binding Title}" FontSize="38" />
<Button Command="{Binding UpdateCommand}" Width="100">Update</Button>
</StackPanel>
</Grid>
</UserControl>
ViewB.xaml.cs
using ModuleA.RibbonTabs;
using PrismDemo.Core;
using System.Windows.Controls;
namespace ModuleA.Views
{
[RibbonTab(typeof(ViewBTab))]
//the main view can inject any number of tab
//uncomment the following lines and test
//I added the same tab just for demo purposes
//[RibbonTab(typeof(ViewBTab))]
//[RibbonTab(typeof(ViewBTab))]
//[RibbonTab(typeof(ViewBTab))]
public partial class ViewB : UserControl, ISupportDataContext
{
public ViewB()
{
InitializeComponent();
}
}
}
ModuleAModule.cs
using Microsoft.Practices.Unity;
using ModuleA.Views;
using Prism.Modularity;
using Prism.Unity;
namespace ModuleA
{
public class ModuleAModule : IModule
{
IUnityContainer _container;
public ModuleAModule(IUnityContainer container)
{
_container = container;
}
public void Initialize()
{
//register for nav
_container.RegisterTypeForNavigation<ViewA>();
_container.RegisterTypeForNavigation<ViewB>();
}
}
}
RibbonTabAttribute.cs
using System;
namespace PrismDemo.Core
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class RibbonTabAttribute : Attribute
{
public Type Type { get; private set; }
public RibbonTabAttribute(Type ribbonTabType)
{
Type = ribbonTabType;
}
}
}
ISupportDataContext.cs
namespace PrismDemo.Core
{
public interface ISupportDataContext
{
object DataContext { get; set; }
}
}
bootstrapper.cs
using Prism.Unity;
using PrismDemo.Views;
using System.Windows;
using Microsoft.Practices.Unity;
using Prism.Modularity;
using ModuleA;
using Prism.Regions;
using PrismDemo.Prism;
using System.Windows.Controls.Ribbon;
namespace PrismDemo
{
class Bootstrapper : UnityBootstrapper
{
protected override DependencyObject CreateShell()
{
return Container.Resolve<Shell>();
}
protected override void InitializeShell()
{
Application.Current.MainWindow.Show();
}
protected override void ConfigureModuleCatalog()
{
var catalog = (ModuleCatalog)ModuleCatalog;
catalog.AddModule(typeof(ModuleAModule));
}
protected override IRegionBehaviorFactory ConfigureDefaultRegionBehaviors()
{
var behaviors = base.ConfigureDefaultRegionBehaviors();
behaviors.AddIfMissing(RibbonRegionBehavior.BehaviorKey, typeof(RibbonRegionBehavior));
return behaviors;
}
}
}
App.xaml
<Application x:Class="PrismDemo.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PrismDemo">
<Application.Resources>
</Application.Resources>
</Application>
App.xaml.cs
using System.Windows;
namespace PrismDemo
{
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var bs = new Bootstrapper();
bs.Run();
}
}
}
Shell.xaml
<Window x:Class="PrismDemo.Views.Shell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
Title="Shell" Height="720" Width="1280">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Ribbon Grid.Row="0" prism:RegionManager.RegionName="RibbonTabRegion"/>
<DockPanel LastChildFill="True" Grid.Row="1">
<StackPanel>
<Button Content="Navigate ViewA" Command="{Binding NavigateCommand}" CommandParameter="ViewA" />
<Button Content="Navigate ViewB" Command="{Binding NavigateCommand}" CommandParameter="ViewB" />
</StackPanel>
<ContentControl prism:RegionManager.RegionName="ContentRegion" Margin="1,3,3,3" />
</DockPanel>
</Grid>
</Window>
Shell.xaml.cs
namespace PrismDemo.Views
{
public partial class Shell
{
public Shell()
{
InitializeComponent();
}
}
}
ShellViewModel.cs
using Prism.Commands;
using Prism.Mvvm;
using Prism.Regions;
namespace PrismDemo.ViewModels
{
public class ShellViewModel : BindableBase
{
IRegionManager _regionManager;
public DelegateCommand<string> NavigateCommand { get; set; }
public ShellViewModel(IRegionManager regionManager)
{
_regionManager = regionManager;
NavigateCommand = new DelegateCommand<string>(Navigate);
}
void Navigate(string navigationPath)
{
_regionManager.RequestNavigate("ContentRegion", navigationPath);
}
}
}
RibbonRegionBehavior.cs
using Prism.Regions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Specialized;
using PrismDemo.Core;
using System.Windows.Controls.Ribbon;
namespace PrismDemo.Prism
{
public class RibbonRegionBehavior : RegionBehavior
{
public const string BehaviorKey = "RibbonRegionBehavior";
public const string RibbonTabRegionName = "RibbonTabRegion";
protected override void OnAttach()
{
if (Region.Name == "ContentRegion")
Region.ActiveViews.CollectionChanged += ActiveViews_CollectionChanged;
}
private void ActiveViews_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
var tabList = new List<RibbonTab>();
foreach (var newView in e.NewItems)
{
foreach (var atr in GetCustomAttributes<RibbonTabAttribute>(newView.GetType()))
{
var ribbonTabItem = Activator.CreateInstance(atr.Type) as RibbonTab;
if (ribbonTabItem is ISupportDataContext && newView is ISupportDataContext)
((ISupportDataContext)ribbonTabItem).DataContext = ((ISupportDataContext)newView).DataContext;
tabList.Add(ribbonTabItem);
}
tabList.ForEach(x => Region.RegionManager.Regions[RibbonTabRegionName].Add(x));
}
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
var views = Region.RegionManager.Regions[RibbonTabRegionName].Views.ToList();
views.ForEach(x => Region.RegionManager.Regions[RibbonTabRegionName].Remove(x));
}
}
private static IEnumerable<T> GetCustomAttributes<T>(Type type)
{
return type.GetCustomAttributes(typeof(T), true).OfType<T>();
}
}
}
and this is the structure of the demo app:
this solution was provided by Brian Lagunas (prism owner) in his Pluralsight course (Prism Problems & Solutions: Loading Dependent Views), ** **There is another solution for this problem provided (again) by brian, https://www.youtube.com/watch?v=xH6OgCxdXQc, but I think the first solution is the best and the simplest
the view injected in the content region can injects any number of tabs, see comment in ViewB.xaml.cs