I am a newbie on MEF and trying to figure out how to structure my Prism 4.0 application to connect views to view models. My use case is that I have one user control nested inside another user control. I would like to connect the nested user control to its view model. I have tried to follow Prism 4.0 examples but not sure if I am using MEF best practices.
Here are some snippets from my application to demonstrate the issue. HomeView has a nested user control called HelloView. I need to connect HelloView to its view model called HelloViewModel. The code in its current state does not work. I think HelloView is not being constructed by MEF and hence HelloViewModel is not being connected.
***** HomeModule *****
[ModuleExport(typeof(HomeModule))]
public class HomeModule : IModule
{
IRegionManager _regionManager;
[ImportingConstructor]
public HomeModule(IRegionManager regionManager)
{
_regionManager = regionManager;
}
public void Initialize()
{
// Create the view
IHomeView homeView = ServiceLocator.Current.GetInstance<IHomeView>();
// Add it to the region
IRegion region = _regionManager.Regions["MainRegion"];
region.Add(homeView, "HomeView");
region.Activate(homeView);
}
}
****** IHomeView *****
public interface IHomeView
{
}
***** HomeView.xaml *****
<UserControl ...>
<Grid x:Name="LayoutRoot">
<view:HelloView x:Name="helloView"/>
</Grid>
</UserControl>
***** HomeView.xaml.cs *****
[Export(typeof(IHomeView))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public partial class HomeView : UserControl, IHomeView
{
public HomeView()
{
InitializeComponent();
}
}
***** IHelloView *****
public interface IHelloView
{
}
***** HelloView.xaml *****
<UserControl ...>
<StackPanel x:Name="LayoutRoot" Margin="10">
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<TextBlock Text="Name" VerticalAlignment="Center" />
<TextBox Width="100" VerticalAlignment="Center" Margin="10 0 0 0"
Text="{Binding Path=Name, Mode=TwoWay}" />
<Button Content="Submit" VerticalAlignment="Center" Margin="10 0 0 0"
Command="{Binding SubmitCommand}"/>
</StackPanel>
<TextBlock Text="{Binding Message}" Margin="0 10 0 0" Foreground="Red" />
</StackPanel>
</UserControl>
***** HelloView.xaml.cs *****
[Export(typeof(IHelloView))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public partial class HelloView : UserControl, IHelloView
{
public HelloView()
{
InitializeComponent();
}
[Import]
public IHelloViewModel ViewModel
{
set { this.DataContext = value; }
}
}
***** IHelloViewModel *****
public interface IHelloViewModel
{
}
***** HelloViewModel *****
[Export(typeof(IHelloViewModel))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class HelloViewModel : NotificationObject, IHelloViewModel
{
public HelloViewModel()
{
this.SubmitCommand = new DelegateCommand<object>(this.OnSubmit);
}
private void OnSubmit(object obj)
{
Message = "Hello " + Name;
}
private string _name;
public string Name
{
get { return _name; }
set
{
if (value != _name)
{
_name = value;
this.RaisePropertyChanged("Name");
}
}
}
private string _message;
public string Message
{
get { return _message; }
set
{
if (value != _message)
{
_message = value;
this.RaisePropertyChanged("Message");
}
}
}
public ICommand SubmitCommand { get; private set; }
}
Your solution is OK, I only have 2 notes:
First: If your catalog contains more than 1 type of IHelloViewModel (that is most likely because you have several views and viewmodels correspondingly) then you get a composition error because import returns more than one result.
[Import]public IHelloViewModel ViewModel
should be something like
[Import(typeof(HelloViewModel))] IHelloViewModel ViewModel
or you just make your property like:
[Import]
public HelloViewModel ViewModel
Second:
Do dont use ServiceLocator for the creation of your HomeView. ServiceLocator is intended to create the singleton instances and EventAggregator is the perfect candidate for that. Views should be not shared (and you correctly marked it as [PartCreationPolicy(CreationPolicy.NonShared)] - otherwise is you want to add your view to another region you get error.)
)
use
[Import]
public HomeView HomeView
Hope this helps.
Related
I am trying to populate listview with database table in xamarin forms app
I am getting null pointer exception
Below is XAML for listview
<ListView x:Name="_listView"
ItemsSource="{Binding itemsInList}"
Grid.Column="0"
Grid.Row="0"
SelectedItem="{Binding SelectedItem}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Label Text="{Binding Name}" Grid.Column="0" Grid.Row="0" />
</Grid>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Below is xaml.cs(code behind)
public List <ServiceProvider> itemlist;
public List <ServiceProvider> itemsInList
{
get {return itemlist;}
}
protected override void OnAppearing()
{
base.OnAppearing();
ExpensesDatabase dbcon = new ExpensesDatabase(completePath);
itemlist = dbcon.GetItems(completePath);
// _listView.ItemsSource = itemlist;
}
Below is db file
public class ExpensesDatabase
{
readonly SQLiteConnection database;
public ExpensesDatabase(string dbPath)
{
database = new SQLiteConnection(dbPath);
database.CreateTable < ServiceProvider > ();
}
public List < ServiceProvider > GetItems(string dbPath)
{
return database.Table < ServiceProvider > ().ToList();
}
}
Data is not displayed in listview
If you want the ListView to automatically update as items are added, removed and changed in the underlying list, you'll need to use an ObservableCollection. ObservableCollection is defined in System.Collections.ObjectModel and is just like List, except that it can notify ListView of any changes:
public ObservableCollection<ServiceProvider> itemsInList { get; set; }
Then make sure you have set the right bindingContext and initialized the ObservableCollection:
public MainPage()
{
InitializeComponent();
itemsInList = new ObservableCollection<ServiceProvider>();
BindingContext = this;
}
I write a sample to test and it works on my side, you can have a look at the full code:
public partial class MainPage : ContentPage
{
public ObservableCollection<ServiceProvider> itemsInList { get; set; }
public MainPage()
{
InitializeComponent();
itemsInList = new ObservableCollection<ServiceProvider>();
BindingContext = this;
}
protected override void OnAppearing()
{
base.OnAppearing();
itemsInList.Add(new ServiceProvider() { Name= "a"});
}
}
public class ServiceProvider : INotifyPropertyChanged
{
string name;
public event PropertyChangedEventHandler PropertyChanged;
public ServiceProvider()
{
}
public String Name
{
set
{
if (name != value)
{
name = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
}
get
{
return name;
}
}
}
Feel free to ask me any question if you still can't solve it.
I am learning MVVM in Xamarin forms using Prism. I have implemented a login functionality which uses a User model class. But the bindings are not working. Please review the code and suggest corrections.
I am not sure how to bind the control's text property to the Model class object's properties.
LoginPage.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:prism="http://prismlibrary.com"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="LeaveTracker.Views.LoginPage"
Title="{Binding Title}">
<StackLayout Orientation="Vertical" BindingContext="{Binding UserObj}">
<Entry Placeholder="User ID" Text="{Binding UserID}"/>
<Entry Placeholder="Password" Text="{Binding Password}" IsPassword="True"/>
</StackLayout>
</ContentPage>
LoginPageViewModel.cs
public class LoginPageViewModel : ViewModelBase
{
private User _user;
private IFirebaseService _firebaseService;
public User UserObj
{
get { return _user; }
set { SetProperty(ref _user, value); }
}
public DelegateCommand LoginCommand { get; set; }
public LoginPageViewModel(IFirebaseService firebaseService, INavigationService navigationService) : base(navigationService)
{
Title = "Log In";
_firebaseService = firebaseService;
LoginCommand = new DelegateCommand(Login, CanLogin);
}
private void Login()
{
var x = _firebaseService.LoginAsync(_user);
}
private bool CanLogin()
{
if (string.IsNullOrEmpty(_user.UserID) && string.IsNullOrEmpty(_user.Password))
{
return true;
}
return false;
}
User.cs
public class User
{
private string _userID;
public string UserID
{
get { return _userID; }
set { _userID = value; }
}
private string _password;
public string Password
{
get { return _password; }
set { _password = value; }
}
}
your BindingContext is LoginPageViewModel, and UserObj is a property of the VM, so your binding path needs to include UserObj
<Entry Placeholder="User ID" Text="{Binding UserObj.UserID}"/>
<Entry Placeholder="Password" Text="{Binding UserObj.Password}" IsPassword="True"/>
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
I tried to use Jet-Image Loader on my windows 8 phone application and it works fine over forms but once i try to apply the Jet-Image caching technique over the aync methods which bind information on async mode it won't work, below is the code which i am using:
XAML:
<ctl:LongListSelector x:Name="ListCards" VerticalAlignment="Center"
LayoutMode="Grid" ItemsSource="{Binding greetingsList}"
SelectionChanged="lstCards_SelectionChanged"
GridCellSize="210,170">enter code here
<ctl:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Width="200" Height="170" VerticalAlignment="Center">
<Image Source="{Binding ImgPath, Converter={StaticResource SampleJetImageLoaderConverter}}"
Width="180" Height="140"
HorizontalAlignment="Center"
Stretch="UniformToFill" />
</StackPanel>
</DataTemplate>
</ctl:LongListSelector.ItemTemplate>
</ctl:LongListSelector>
Code:
public partial class card_List3 : PhoneApplicationPage
{
public class GetGreetingSchema
{
public Uri ImgPath
{
get { return _ImgPath; }
set
{
SetProperty(ref _ImgPath, value);
}
}
}
public ObservableCollection<GetGreetingSchema> greetingsList { get; private set; }
public card_List3()
{
InitializeComponent();
greetingsList = new ObservableCollection<GetGreetingSchema>();
DataContext = this;
}
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
try
{
await LoadDataAsync();
}
catch (Exception listbindException)
{
ReusableMethods.LogStackTrace(listbindException);
}
}
private async Task LoadDataAsync()
{
var dataSource = new Container().Resolve<IfellowsCollection>();
greetingsList = await dataSource.BindGreetingsList(CatId, Contenttype);
ListCards.ItemsSource = greetingsList;
}
}
I found your question here :)
Issue is fixed, now JetImageLoader supports Uri as imageUrl param: https://github.com/artem-zinnatullin/jet-image-loader/issues/8
I want to implement MVVM patter to a registeration page like this:
The Page has text boxes for username, email and password.
I want to bind the Register Button to a command using ICommand and DelegateCommand pattern.
The problem is that I want the button to be disabled if the textboxes are empty and enabled if they have text.
My Model is:
public class User
{
public string UserName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
}
My ViewModel:
public class UserViewModel:INotifyPropertyChanged
{
private User user;
public UserViewModel()
{
user = new User();
}
#region Properties
.
.
.
#endregion
public ICommand RegisterCommand
{
get
{
return new DelegateCommand(Register,CanRegister);
}
}
private void Register(object parameter)
{
//TODO call the web service
}
private bool CanRegister(object parameter)
{
return (!string.IsNullOrEmpty(UserName) && !string.IsNullOrEmpty(Password));
}
}
My DelegateCommand Implementation:
public class DelegateCommand:ICommand
{
//Delegate to the action that the command executes
private Action<object> _executeAction;
//Delegate to the function that check if the command can be executed or not
private Func<object, bool> _canExecute;
public bool canExecuteCache;
public DelegateCommand(Action<object> executeAction):this(executeAction,null)
{
}
public DelegateCommand(Action<object> action, Func<object, bool> canExecute)
{
this._executeAction = action;
this._canExecute = canExecute;
}
//interface method, called when CanExecuteChanged event handler is fired
public bool CanExecute(object parameter)
{
//true by default (in case _canExecute is null)
bool result = true;
Func<object, bool> canExecuteHandler = this._canExecute;
if (canExecuteHandler != null)
{
result = canExecuteHandler(parameter);
}
return result;
}
//Event handler that the controld subscribe to
public event EventHandler CanExecuteChanged;
//interface method
public void Execute(object parameter)
{
_executeAction(parameter);
}
//rause the CanExecuteChanged event handler manually
public void RaiseCanExecuteChanged()
{
EventHandler handler = this.CanExecuteChanged;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
}
and My View:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Username:"/>
<TextBox Grid.Row="1" Name="txtUserName" Text="{Binding UserName, Mode=TwoWay}" HorizontalAlignment="Stretch"/>
<TextBlock Grid.Row="2" Text="Password:" HorizontalAlignment="Stretch" />
<TextBox Grid.Row="3" Name="txtPassword" Text="{Binding Password, Mode=TwoWay}"/>
<Button Grid.Row="4" Content="Register" Command="{Binding RegisterCommand }" />
</Grid>
What I want to achieve is to make the button disabled untill the user enters information in each TextBox
how can this be done ?
Thanks
One thing first: Returning a new DelegateCommand every time the property is accessed will limit your ability to call the RaiseCanExecuteChanged() method as you won't have a reference to the same command as is bound.
So change your ViewModel to be something like this:
public class UserViewModel : INotifyPropertyChanged
{
private User user;
public UserViewModel()
{
user = new User();
RegisterCommand = new DelegateCommand(Register,CanRegister);
}
public DelegateCommand RegisterCommand {get; private set;}
private void Register(object parameter)
{
//TODO call the web service
}
private bool CanRegister(object parameter)
{
return (!string.IsNullOrEmpty(UserName) &&
!string.IsNullOrEmpty(Password));
}
}
The reason you can have the RegisterCommand property as private set with no PropertyChanged call as it will be instantiated before the binding occurs and doesn't need to change.
Assuming the form of the properties UserName and Password trigger the PropertyChanged event, you can just call the RaiseCanExecuteChanged() method on the RegisterCommand when they change.
Eg.
private string _userName;
public string UserName
{
get { return _userName; }
set
{
if(_userName == value)
return;
_userName = value;
RaisePropertyChanged("UserName");
RegisterCommand.RaiseCanExecuteChanged();
}
}
this will force the CanExecute method to re-evaluated.