I have a CarouselView. The ItemsSource is binded to an ObservableCollection of type AlarmEvent. AlarmEvent is a base class for different types of alarm events (Generic, Fr, Vfr, Anpr). The ItemTemplate changes depending on the type of CurrentItem. This works.
Xaml:
<CarouselView x:Name="cvAlarmEvents"
ItemsSource="{Binding AlarmEvents}"
CurrentItem="{Binding SelectedAlarmEvent}"
CurrentItemChangedCommand="{Binding CmdSelectedAlarmEventChanged}"
ItemTemplate="{StaticResource AlarmEventDataTemplateSelector}"
Grid.Row="2"
Margin="0, 2, 0, 0"
BackgroundColor="#141d3d"
IsVisible="true"/>
Template Selector:
public class AlarmEventDataTemplateSelector : DataTemplateSelector
{
public DataTemplate Generic { get; set; }
public DataTemplate Fr { get; set; }
public DataTemplate Tfr { get; set; }
public DataTemplate Anpr { get; set; }
public AlarmEventDataTemplateSelector()
{
Generic = new DataTemplate(typeof(GenericView));
Fr = new DataTemplate(typeof(FrView));
Tfr = new DataTemplate(typeof(TfrView));
Anpr = new DataTemplate(typeof(AnprView));
}
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
var alarmEvent = item as AlarmEvent;
switch (alarmEvent.Type)
{
case Enums.AlarmEventType.None:
return null;
case Enums.AlarmEventType.Generic:
return Generic;
case Enums.AlarmEventType.FaceRecognition:
return Fr;
case Enums.AlarmEventType.TemperatureFaceRecognition:
return Tfr;
case Enums.AlarmEventType.ANPR:
return Anpr;
default:
return null;
}
}
}
GenericView, FrView etc inherit from ContentView. The actual command bindings are inside the DataTemplate (ContentView) which is returned from AlarmEventDataTemplateSelector:
<ImageButton Source="CamWhite1"
BackgroundColor="Transparent"
Scale="1"
BorderColor="White"
BorderWidth="3"
Padding="10"
Margin="10, 5"
Command="{Binding CmdAddPic}" />
I have plenty of data bindings on the model which all work, aside from the Command bindings. I've tested the buttons with OnClick events and they fire as they should, which makes me think that the Command data binding is not working.
Here is how the command code looks inside AlarmEvent
public class AlarmEvent : BaseModel
{
public Command CmdAddPic { get; set; }
public AlarmEvent()
{
CmdAddPic = new Command(AddPic);
}
public void AddPic()
{
// I dont fire and this makes me sad ;(
}
}
I cannot find a single thread online about this problem. I can find countless of threads where they are trying to bind a command from a selected item to their viewmodel. THIS IS NOT WHAT I WANT. I simply want the command binding to work on my data model (AlarmEvent), and I am stumped as to why it doesn't work. Perhaps I am missing something obvious?
I am a silly bum for not realising this sooner, the fix was:
Instead of auto property like so -
public Command CmdSubmitUserMedia{ get; set; }
I needed to use a full property like so -
private Command cmdSubmitUserMedia;
[Ignore]
public Command CmdSubmitUserMedia
{
get { return cmdSubmitUserMedia; }
set { SetProperty(ref cmdSubmitUserMedia, value); }
}
Related
This's my first question here, so hi everybody.
I'm working on the mobile app in Xamarin.Forms with Prism. I've created ListView where shown data from the database.
When the user clicks in the selected row app should navigate to a new view and pass the selected item from ListView.
<ListView x:Name="DefectsBase"
RowHeight="65"
ItemsSource="{Binding Defects}"
ItemSelected="ShowDetailsEvent"
IsPullToRefreshEnabled="true"
RefreshCommand="{Binding Refresh}"
IsRefreshing="{Binding IsRefreshing}">
Code backend:
async void ShowDetailsEvent(object sender, EventArgs e)
{
var myListView = (ListView)sender;
var myItem = myListView.SelectedItem;
var p = new NavigationParameters();
p.Add("selectedDefect", myItem);
await _navigationService.NavigateAsync("DefectDetailsView", p);
}
Unfortunately, the app doesn't respond to pressing the selected row in ListView.
As I can see you are already using Prism and you have a List page with Items and you want to navigate to some details page based on the selected/taped/chosen item which the user taps in the ListView.
The idea is to move as much code and logic as we can to the view model and keep our code-behind. This is pretty easy to solve using Prism and EventToCommand behaviour.
In the example and answer below, I will show you how to solve this with few lines of code, with a nice code approach.
First of all, I recommend you use EventToCommand behaviour, you can include it with prism xmlns, like this: xmlns:prism="http://prismlibrary.com", later on, you can use it with ListView.
Remove ItemSelected event from your ListView and move the markup about it to the <ListView.Behaviors> part. Here is my code sample for the ListView which binds to some ObserverableCollection of the Car models:
<ListView ItemsSource="{Binding Cars}">
<ListView.ItemTemplate>
<DataTemplate>
...
</DataTemplate>
</ListView.ItemTemplate>
<ListView.Behaviors>
<prism:EventToCommandBehavior EventName="ItemTapped"
Command="{Binding SelectedCarCommand}"
EventArgsParameterPath="Item" />
</ListView.Behaviors>
The main part here is <ListView.Behaviors>, where you can see that I am binding to the SelectedCarCommand which will be invoked when the user taps on some of the items from the list. I am using the ItemTapped event for this and passing the current "taped" item from the list as a parameter.
In order to follow this XAML part in my view model of this page, I have declared the DelegateCommand and method which will be called when the command is invoked. The view model part looks like this:
This is my CarListPageViewModel, take a look at DelegateCommand and SelectedCar method.
public class CarListPageViewModel
{
private readonly INavigationService _navigationService;
public ObservableCollection<Car> Cars { get; set; }
public DelegateCommand<Car> SelectedCarCommand { get; private set; }
public CarListPageViewModel(INavigationService navigationService, IDataProvider dataProvider)
{
_navigationService = navigationService;
// Insert test data into collection of Cars
Cars = new ObservableCollection<Car>(dataProvider.GetData());
SelectedCarCommand = new DelegateCommand<Car>(SelectedCar);
}
private async void SelectedCar(Car selectedCar)
{
NavigationParameters navigationParameters = new NavigationParameters
{
{ "selectedCar", selectedCar }
};
await _navigationService.NavigateAsync(nameof(CarDetailsPage), navigationParameters);
}
}
As you can see we have DelegateCommand defined with the type of parameter which will be passed, in my case, this is the Car class, the same class as our items in the ListView.
In the constructor, I did my initialization and defined the method which will be called, that method has a parameter of the type Car.
When the user taps on one of the items in the ListView, SelectedCar (method) will be called and we can pass the data to the next view using NavigationParameters and NavigationService.
In order to retrieve the passed data we can use INavigationAware in the details view model and with the OnNavigatedTo method, access the data which is being passed.
This is my CarDetailsPageViewModel, take a look at OnNavigatedTo method.
public class CarDetailsPageViewModel : BindableBase, INavigationAware
{
private string carTitle;
public string CarTitle
{
get { return carTitle; }
set { SetProperty(ref carTitle, value); }
}
private string photoUrl;
public string PhotoUrl
{
get { return photoUrl; }
set { SetProperty(ref photoUrl, value); }
}
public CarDetailsPageViewModel() { }
public void OnNavigatedTo(INavigationParameters parameters)
{
if (parameters.ContainsKey("selectedCar"))
{
Car car = parameters.GetValue<Car>("selectedCar");
if (car != null)
{
CarTitle = $"{car.Make} {car.Model}";
PhotoUrl = car.PhotoUrl;
}
}
}
public void OnNavigatedFrom(INavigationParameters parameters) { }
}
From this answer and example, you can see:
How to, use EventToCommand behaviour with ListView
Define and use DelegateCommand with passing parameter
How to navigate to another view and pass navigation parameter and
... finally how to access the passed data.
Code and this sample you can find on my GitHub profile here.
Hope this answer was helpful for you!
Wishing you lots of luck with coding! š
I'm running into a problem binding to a 'StatusBarItem'. I'll add that I'm using 'PropertyChanged.Fody' to implement my models where I want changes to be raised.
Heres the 'XAML' snippet:
<StatusBar Grid.Row="2"
Background="Transparent"
Padding="5,0">
<StatusBarItem Content="{Binding Path=Application.Status, Source={x:Static m:Locator.Instance}}"/>
</StatusBar>
Base Model:
[AddINotifyPropertyChangedInterface]
public class IModel {
}
Application Model:
public sealed class ApplicationModel : IModel {
static ApplicationModel() {
Instance = new ApplicationModel() {
Status = "Ready"
};
}
public static ApplicationModel Instance {
get;
}
public string Status {
get;
set;
}
}
Locater Model:
public sealed class Locator : IModel {
static Locator() {
Instance = new Locator();
}
public static Locator Instance {
get;
}
public ApplicationModel Application => ApplicationModel.Instance;
}
When I want to change 'Status' from anywhere in code, I do so like:
Locator.Instance.Application.Status = message;
Now my problem isn't that 'Status' wont change, it changes just fine. When the application runs, it updates the XAML once with the default value set in ApplicationModel. It's the XAML not updating to the new changes once I set a new value. I feel I've done everything right as I'm using my 'BaseModel' in other places without problems.
FIXED:
I removed the 'PropertyChanged.Fody' package along with 'FodyWeavers.xml' then re-added them after deleting both '\bin' and '\obj' and finally a solution clean.
Sorry for long and descriptive question, but it really bugs me for a long time. I have a problem with MVVM pattern.
I wrote application which actually works, but I don't think it's in a good style. My view model strucure looks like tree: it has references to all child viewmodels which are used to render proper view in ContentPresenter.
Take a look at sample GUI:
Home tab
--------------------------------------
HOME SETTINGS ADMINPANEL
======---------------------------------
______________________________________
/////////
///////// Home content
/////////
Settings tab
--------------------------------------
HOME SETTINGS ADMINPANEL
------============---------------------
______________________________________
settings1 > settings2 > other...
/////////
///////// Settings1 content
/////////
Notice the submenu which appears only in Settings view. Every switchable view is somehow dependent on model type. settings1 is visible only if model property is Type.One, and settings2 is visible when property is Type.Two.
Take a fast look into my current code: common interface for every view that I can change by click. PageHeader is displayed on button change content view:
public interface IPageVM
{
string PageHeader { get; set; }
}
and view models:
public class WindowVM : ViewModelBase
{
public ObservableCollection<IPageVM> ViewModels { get; set; }
public IPageVM CurrentTab { get; set; }
public ICommand ChangeViewModel { get; set; }
private Model _model;
public WindowVM()
{
_model = new Model();
ViewModels = new ObservableCollection<IPageVM>();
ViewModels.Add(new HomeVM(model));
ViewModels.Add(new SettingsVM(model));
if(_model.Admin)
ViewModels.Add(new AdminVM(model));
}
}
public class HomeVM : ViewModelBase, IPageVM
{
public string PageHeader { get { return "HOME"; } }
string Property { get; set; }
public HomeVM(Model model)
{
this.Property = model.Property;
}
}
public class SettingsVM : ViewModelBase, IPageVM
{
public string PageHeader { get { return "SETTINGS"; } }
public ObservableCollection<IPageVM> Tabs { get; set; }
public IPageVM CurrentTab { get; set; }
public ICommand ChangeViewModel { get; set; }
public SettingsVM(Model model)
{
Tabs = new ObservableCollection<IPageVM>();
if(model.Type = Type.One)
Tabs.Add(new Settings1VM(model));
if(model.Type = Type.Two)
Tabs.Add(new Settings2VM(model));
Tabs.Add(new OtherSettingsVM());
CurrentTab = Tabs[0];
}
}
public class Settings1VM: ViewModelBase, IPageVM
{
public string PageHeader { get { return "settings1"; } }
public Settings1VM(Model model)
{
}
}
public class Settings2VM: ViewModelBase, IPageVM
{
public string PageHeader { get { return "settings2"; } }
public Settings1VM(Model model)
{
}
}
xaml:
display buttons in ItemsControl that will change CurrentViewModel and render appropriate view bounded to viewmodels' type by DataTemplate.
Pros:
it already works
I can easily tell how does my GUI structure looks, because root viewmodel contains its children viewmodels and so on.
it's easy to inject data model to children by constructor.
Cons:
xaml code is all a DataTemplate
UserControls are empty in preview mode, it's hard to edit GUI
terrifying future: how will look my viewmodels structure if my application will grow a bit?
So I decided to change my ViewModels to something that looks more like WPF MVVM:
public class WindowVM : ViewModelBase
{
public bool AdminMode { get; set; }
public WindowVM()
{
_model = new Model();
AdminMode = _model.Admin;
AnotherTabVisibilityDependency = _model.Dependency;
}
}
xaml:
<TabControl>
<TabItem Header="HOME">
<TabItem.DataContext>
<vm:HomeVM/>
</TabItem.DataContext>
</TabItem>
<TabItem Header="SETTINGS">
<TabItem.DataContext>
<vm:SettingsVM/>
</TabItem.DataContext>
</TabItem>
<TabItem Header="ADMINPANEL" Visibility="{Binding AdminMode, Converter BoolToVisibility}">
<TabItem.DataContext>
<vm:AdminVM/>
</TabItem.DataContext>
</TabItem>
<TabItem Header="DEPENDANT" Visibility="{Binding AnotherTabVisibilityDependency, Converter BoolToVisibility}">
</TabItem>
</TabControl>
As you probably see, everything is clean, chaning UI is easy; not too much templates to write. Everything looks fine... But there is a thing I don't understand: Now each ViewModel is a separate instance that doesn't know about its parent. I can't pass model that easy.
Questions:
how can I inject data model into each viewmodel? I can't do it via xaml. Do I need to have a global static class with program state, or IoC? Maybe another way?
any alternative approaches? Maybe my second approach also isn't good?
does it make sense to rewrite my logic which actually works? I really hope it does (I hate my viewmodels' code).
Lets say I have class that looks like this:
public class User
{
public string Name { get; set; }
public int Age { get; set; }
}
User is part of the viewModel I use in my view, i try to update its properties like this:
<TextBox Text="{Binding User.Name}"></TextBox>
<TextBox Text="{Binding User.Age, mode=two-way, updatesourcetrigger = onpropertychanged}"></TextBox>
<Button Content="Save user" Command="{Binding SaveUserCommand}" CommandParameter="{Binding User}" />
Here is the viewmodel:
public RelayCommand<User> SaveUserCommand { get; private set; }
public MainViewModel()
{
SaveUserCommand = new RelayCommand<User>(SaveUser);
}
public void SaveUser(User user)
{
//logic for saving user
}
I thought this would let me change the values in the textboxes and then pass user to the viewmodel. The problem is that nothing gets sent, SaveUser() gets triggred with null.
Can someone see what I am missing/missunderstanding?
Thank you!
EDit:
This is the property that represents my user in the view:
private User _user;
public User User
{
get
{
return _user;
}
set
{
if (_user != value)
{
_user = value;
RaisePropertyChanged("User");
}
}
}
Your Bindings on your TextBox controls need to be Mode=TwoWay. TextBox controls also only update the source value on LostFocus so you may want to change that with UpdateSourceTrigger=PropertyChanged on the Binding as well.
Your ViewModel also seems to be lacking a public property for User.
You need:
public User User {get;set;}
...according to your current Binding definitions.
I also can't see the definition of the ViewModel class, but it should implement INotifyPropertyChanged (or a base class should implement it).
Edit:
OP hadn't instantiated the User property.
IĀ“m new to PRISM and trying some things out. IĀ“m struggeling a little bit with MVVM.
The way you "connect" a view with a viewmodel is clear:
injection through unity, or
set the data context manually (ServiceLocator)
Everything is working fine if I add a view to a region (the viewmodel is created automatically). But thatĀ“s not the use case.
LetĀ“s take a look at the example:
public class MyViewModel : NotificationObject
{
public ObservableCollection<AnotherViewModel> OrderModel { get; private set; }
}
I have to create view models and add them to the collection. This view models have to be displayed (AnotherView) in a region (OrderRegion). My problem is, how can I achieve that now the view is created when I am adding a viewmodel to a region. This region is a TabControl, so it could happened that different views must be displayed.
I have already took a look to the PRISM Quickstarts and the StockTrader example. What I am looking for is quite similar to
virtual protected void StartOrder(string tickerSymbol, TransactionType transactionType)
{
if (String.IsNullOrEmpty(tickerSymbol))
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.StringCannotBeNullOrEmpty, "tickerSymbol"));
}
this.ShowOrdersView();
IRegion ordersRegion = _regionManager.Regions[RegionNames.OrdersRegion];
var orderCompositeViewModel = ServiceLocator.Current.GetInstance<IOrderCompositeViewModel>();
orderCompositeViewModel.TransactionInfo = new TransactionInfo(tickerSymbol, transactionType);
orderCompositeViewModel.CloseViewRequested += delegate
{
OrderModels.Remove(orderCompositeViewModel);
commandProxy.SubmitAllOrdersCommand.UnregisterCommand(orderCompositeViewModel.SubmitCommand);
commandProxy.CancelAllOrdersCommand.UnregisterCommand(orderCompositeViewModel.CancelCommand);
commandProxy.SubmitOrderCommand.UnregisterCommand(orderCompositeViewModel.SubmitCommand);
commandProxy.CancelOrderCommand.UnregisterCommand(orderCompositeViewModel.CancelCommand);
ordersRegion.Remove(orderCompositeViewModel);
if (ordersRegion.Views.Count() == 0)
{
this.RemoveOrdersView();
}
};
ordersRegion.Add(orderCompositeViewModel);
OrderModels.Add(orderCompositeViewModel);
commandProxy.SubmitAllOrdersCommand.RegisterCommand(orderCompositeViewModel.SubmitCommand);
commandProxy.CancelAllOrdersCommand.RegisterCommand(orderCompositeViewModel.CancelCommand);
commandProxy.SubmitOrderCommand.RegisterCommand(orderCompositeViewModel.SubmitCommand);
commandProxy.CancelOrderCommand.RegisterCommand(orderCompositeViewModel.CancelCommand);
ordersRegion.Activate(orderCompositeViewModel);
}
The view model is created inside the code and added to the region. The whole type regestring is happend through the "ViewExportAttribute" so it makes it harder to understand the pattern behind it.
EDIT:
I have found a way to do this manually but itĀ“s not very nice:
var view = (FrameworkElement) ServiceLocator.Current.GetInstance<AnotherView>();
var model = ServiceLocator.Current.GetInstance<AnotherViewModel>();
view.DataContext = model;
regionManager.Regions["OrderRegion"].Add(view, null, true);
regionManager.Regions["OrderRegion"].Activate(view);
Roman
EDIT2:
Hi, IĀ“m sorry, maybe I wasnĀ“t clear enough.
My goal was to create a view model and then to configure it like in the example from the StockTrader above: Subscribe events, commands etc. After that I want to add this view model to the region, so thatĀ“s it could be displayed. This region might be a tabcontrol where different views with different view models are displayed.
The order is:
Create a view model in the controller class
Configure the view model
Add the view model to local collection inside the controller
Add the view model to the region
The missing piece what I was looking for was how to make it happen, that the view is created āautomaticallyā with all stuff like binding etc. I have found an approach in this article (http://www.codeproject.com/Articles/229931/Understand-MVVM-Using-PRISM-by-Hello-World-Silverl). I have to create own interfaces for the view and the view model (IAnotherViewModel, IAnotherView).
Another approach can be found here: http://paulstovell.com/blog/viewmodel-first-prism
Is there any reason not to use implicit DataTemplates for this?
They are DataTemplates that define a DataType property, but not a Key property, and they are used anytime WPF tries to draw an object of the specified DataType
For example,
<TabControl ItemsSource="{Binding MyViewModelCollection}"
SelectedItem="{Binding SelectedViewModel}">
<!-- This could also go elsewhere, like Application.Resources -->
<TabControl.Resources>
<DataTemplate DataType="{x:Type local:ViewModelA}">
<local:ViewA />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModelB}">
<local:ViewB />
</DataTemplate>
</TabControl.Resources>
</TabControl>
If the TabControl is displaying an object of type ViewModelA, it will draw it using ViewA, and if it's displaying ViewModelB, it will draw it using ViewB
yI you are using MEF then you can automate your view registration using Attributes:
/*YOUR VIEW*/
[ExportViewToRegion("MyView", "MyRegion")]
[Export(typeof(MyView))]
public partial class MyView : UserControl
{
....
}
/*IMPLEMENTATION*/
public interface IExportViewToRegionMetadata
{
string ViewName { get; }
string TargetRegion { get; }
}
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = false)]
public class ExportViewToRegionAttribute : ExportAttribute
{
public ExportViewToRegionAttribute(string viewName, string targetRegion)
: base(typeof(UserControl))
{
ViewName = viewName;
TargetRegion = targetRegion;
}
public string ViewName { get; private set; }
public string TargetRegion { get; private set; }
}
[Export(typeof(IFluentRegionManager))]
public class FluentRegionManager : IFluentRegionManager, IPartImportsSatisfiedNotification
{
public IRegionManager RegionManager { get; set; }
[ImportingConstructor]
public FluentRegionManager(IRegionManager regionManager)
{
RegionManager = regionManager;
}
/*This Import will find all views in the assembly with attribute [ExportViewToRegion("ViewName", "RegionName")]*/
[ImportMany(AllowRecomposition = true)]
public Lazy<UserControl, IExportViewToRegionMetadata>[] Views { get; set; }
private readonly List<string> _processedViews = new List<string>();
private Lazy<UserControl, IExportViewToRegionMetadata> _GetViewInfo(string viewName)
{
return (from v in Views where v.Metadata.ViewTypeForRegion.Equals(viewName) select v).FirstOrDefault();
}
public IExportViewToRegionMetadata this[string viewName]
{
get
{
return (from v in Views
where v.Metadata.ViewName.Equals(viewName, StringComparison.InvariantCultureIgnoreCase)
select v.Metadata).FirstOrDefault();
}
}
public void ExportViewToRegion(string viewName)
{
if (viewName==null)
{
throw new ArgumentNullException("viewName");
}
var viewInfo = _GetViewInfo(viewName);
string targetRegion;
UserControl _view;
if (viewInfo != null)
{
targetRegion = viewInfo.Metadata.TargetRegion;
_view = viewInfo.Value;
}
if (string.IsNullOrEmpty(targetRegion) || _processedViews.Contains(viewName)) return;
RegionManager.RegisterViewWithRegion(targetRegion, _view.GetType());
_processedViews.Add(viewName);
}
/*All required views has been discovered and imported */
/*Loop true collection and register view with the region */
public void OnImportsSatisfied()
{
foreach (var viewName in from view in Views where !_processedViews.Contains(view.Metadata.ViewName)
select view.Metadata.ViewName)
{
ExportViewToRegion(viewName);
}
}
}
/* finally call IFluentRegionManager import in the bootstrapper to kick off registration*/