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.
Related
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); }
}
I'm fairly new to Xamarin and stumbled across MVVM and really like it as an architectural pattern. However, I found that most HowTo's and tutorials out there only address the VVM (i.e. View-ViewModel) side of things, probably for simplicity sake!?
I would like to know how the communication between a ModelView and its associated models takes place using the INotifyPropertyChanged paradigm and other things.
If I understand correctly, I personally would put stuff like data handling, data storage (collections), db connections and stuff like that into a model. At least this is how I would've been doing it in the good old MVC days. Following questions arouse in my mind:
Where do I create the model(s) and how do I assign them to ViewModels?
How do I properly connect Model and ViewModel such that property updates are propagated and can be handled correctly?
Would you set the model as a member of the ViewModel?
In my current example, I would like to implement a SensorModel which provides several sensory data which layers above can subscribe to. I would like to send updates whenever new sensor data is available to the layers above; i.e. a ViewModel, for instance.
I'd basically had something like this in mind:
class Sensor
{
int _id { get; set; }
string _name { get; set; }
}
class SensorModel
{
private List<Sensor> _sensors { get; set; }
public void addSensor(Sensor s) ...
public void removeSensor(Sensor s) ...
}
Does anybody have links to actual/complete MVVM examples, including the connection between Model and ViewModel?
Any help appreciated.
Use Lastest stable Xamarin Forms
MODELS
In the Project, create a Models folder
To store data, i usually use SQLite or a temp store:
class DataStore
{
public static List<SensorModel> SensorStore { get; set; }
}
Create the SensorModel model
class SensorModel
{
internal int Id { get; set; }
internal string Sensor { get; set; }
}
VIEWMODELS
In the Project, create a ViewModels folder
Create a SensorVM viewmodel
class SensorVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public System.Windows.Input.ICommand StartCommand { get; set; }
public string SensorName { get; set; }
public SensorVM()
{
DataStore.SensorStore = new List<SensorModel>();
StartCommand = new Xamarin.Forms.Command(StartSubmit);
}
private void StartSubmit(object paramter)
{
var sensor = new SensorModel()
{
Id = 1,
Sensor = SensorName
};
AddSensor(sensor);
}
public void AddSensor(SensorModel sensor)
{
//do something
DataStore.SensorStore.Add(sensor);
}
}
VIEWS
In the Project, create a Views folder
Create a Sensor.xaml view
<ContentPage.Content>
<StackLayout Spacing="10" Orientation="Vertical">
<Entry Text="{Binding SensorName}" />
<Button Command="{Binding StartCommand}" Text="Start" />
</StackLayout>
</ContentPage.Content>
In the code behind:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Sensor : ContentPage
{
SensorVM vm;
public Sensor()
{
InitializeComponent();
BindingContext = vm = new SensorVM();
}
}
Hope that helps.
I would like to know how the communication between a ModelView and its
associated models takes place using the INotifyPropertyChanged
paradigm and other things.
I think the best way to create a communication in MVVM is Messaging Center.
https://learn.microsoft.com/pt-br/xamarin/xamarin-forms/app-fundamentals/messaging-center
It's not coupled from device (sensor) code to view models ...
Your messages, in this model, active events that could acess your viewmodels as well as other structures.
A sample of this
In your view use :
public void MessegingCenterInit()
{
#region Bluetooth
MessagingCenter.Subscribe<string, string>("App", "Status_name", (sender, arg) =>
{
App.PVM.Name = $"{arg}";//using INotifyPropertyChanged and view model
viewmodelMethod();//using only a viewmodel
});
#endregion
}
in your model use:
public string Name
{
get { return name; }
set
{
name = value;
App.PVM.Add_patient.AddCanExecuteChanged();//PVM is a viewmodel
//The view model need to have INotifyPropertyChanged as a interface
}
}
In specific code you have (into a generic method or event):
string new_name = John;
MessagingCenter.Send<string,string>("App","Status_name",new_name);
There are several ways to do it, its a simple one, you can try use objects as sender with less information.
Regards
Xamarin itself gives a really good example with their default Master-Detail Solution.
Just create a new Xamarin.Forms App and select the Master-Detail Layout.
It includes several Views, ViewModels (with the BaseVIewModel) and some MockUp Data Classes.
For a start just have a look around there :)
In almost all cases there is no communication between the Model and ViewModel, and very rarely there is communication between the Model and View. If you need to communicate between Model and ViewModel it is extremely likely that you are doing something wrong.
To explain, your model usually describes some entity, like that you have the class Cat:
public class Cat
{
public string Color {get; set;}
}
It is generally used in ViewModel either as the field or as a Collection like:
public class CatsViewModel
{
public List<Cat> Cats {get; set;}
}
The cat shouldn't be able to update by itself, if it is updated it is done either by bindings with the view or somewhere from ViewModel.
So you have some architectural problems in your app, I think.
I'm familiar with MVVM and differences between models, viewmodels and views. The only thing that I'm not able to find answer to is how to update models at runtime. Simple example to show you what I mean:
Let's say I have application which can display graphs and store them in a database.
I have models
public class Session {
public Document Doc { get; set; }
}
public class Document {
public string Name { get; set; }
public Point[] GraphPoints { get; set; }
}
I can connect those to their viewmodels by passing them as parameters, so:
public class SessionViewModel{
private readonly Session _session;
public SessionViewModel(Session session)
{
this._session = session;
}
}
public class DocumentViewModel{
private readonly Document_document;
public SessionViewModel(Document document)
{
this._document = document;
}
}
public class ShellViewModel {
public SessionViewModel SessionVm { get; set; } // <-- Bind in view
public DocumentViewModel DocumentVm { get; set; } // <-- Bind in view
private Session _session;
public ShellViewModel()
{
_session = new Session();
session.Doc = new Document();
SessionVm = new SessionViewModel(session);
DocumentVm = new DocumentViewModel(session.Doc);
}
}
Problem appears when in the middle of my application's life cycle I decide to change value of document. For example:
public void OnNewDocumentLoaded(Document newDoc)
{
_session.Doc = newDoc;
}
_session.Doc was changed but every DocumentViewModel has its own instance of document which is passed in a constructor, so even though I changed model, my viewmodel stays the same.
Also I don't want to use INotifyPropertyChanged inside my model, because models should not know about framework and from my understanding this is a bad approach. Also I keep my models in PCL project so I'm not even able to implement INotifyPropertyChanged in my models.
From my understanding of a MVVM approach, models should not have a viewmodel associated with them. Instead, your views should have a viewmodel associated to them. Inside your viewmodel you can have objects from models in your application. Inside your viewmodel is where you should implement INotifyPropertyChanged. Those methods control the objects you have changed and then binding can occur between your view and viewmodel.
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).
Desired Result
A status bar at the top of most xaml pages, of which the colour and text change depending on the value of a boolean variable.
The page background is also set to a colour based on this boolean.
Essentially these signify whether the app is connected to the server; we don't need to discuss connectivity logic here, apart from a method which inverts the "online" boolean, so as to simulate changing connectivity status and to test the bindings.
I want these to update without repeated logic in every view model.
Current Implementation
To try and minimise the amount of code, I will try to reduce the project into four files, and also only concern ourselves with the text property (if we can get text working, we can get colour working too). Hopefully we can assume my ViewModelLocator.cs, App.xaml, Generic.xaml and the supporting ConnectivityStatusBar.cs have been correctly implemented (I can share these too if necessary).
HomePage.xaml: An example view which utilises the connectivity status bar.
<Page
x:Class="Client.View.HomePage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:customElements="using:Client.CustomElements"
mc:Ignorable="d"
DataContext="{Binding Source={StaticResource ViewModelLocator}}">
<Grid>
<StackPanel>
<customElements:ConnectivityStatusBar
Text="{Binding ConnectivityStatusBar.Text,
Source={StaticResource ViewModelLocator}}" />
<Button Content="Invert"
Command="{Binding HomePage.InvertConnectivityCommand}" />
</StackPanel>
</Grid>
</Page>
SessionData.cs: Where the connectivity boolean is found, I have tried setting the class up like a singleton.
using Client.ViewModel;
using GalaSoft.MvvmLight;
namespace Client
{
public sealed class SessionData : ViewModelBase
{
private static readonly SessionData Instance = new SessionData();
private bool _online;
public static SessionData getInstance
{
get { return Instance; }
}
public bool Online
{
get { return _online; }
set
{
_online = value;
RaisePropertyChanged(() => Online);
RaisePropertyChanged(() => ConnectivityStatusBarViewModel.getInstance.Text);
}
}
}
}
ConnectivityStatusBarViewModel.cs: Logic deciding what the colour and text for the status bar should be. I have attempted similar singleton logic here.
using System;
using GalaSoft.MvvmLight;
namespace Client.ViewModel
{
public sealed class ConnectivityStatusBarViewModel : ViewModelBase
{
private static readonly ConnectivityStatusBarViewModel Instance = new ConnectivityStatusBarViewModel();
public static ConnectivityStatusBarViewModel getInstance
{
get { return Instance; }
}
public String Text
{
get { return SessionData.getInstance.Online ? "Online" : "Offline"; }
}
}
}
HomePageViewModel.cs: Where the boolean inversion method is found.
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
namespace Client.ViewModel
{
public class HomePageViewModel : ViewModelBase
{
public HomePageViewModel()
{
InvertConnectivityCommand = new RelayCommand(InvertConnectivity);
}
public RelayCommand InvertConnectivityCommand { get; set; }
private void InvertConnectivity()
{
SessionData.getInstance.Online = !SessionData.getInstance.Online;
}
}
}
This code doesn't give me the results I want; when the button is pressed on HomePage.xaml, the text does not change. It does work if I put the inversion method into ConnectivityStatusBarViewModel.cs.
Is it possible to make this work, or am I wasting my time? If it is possible, how can I change my approach to make it work?
Edit: ViewModelLocator.cs looks like:
using GalaSoft.MvvmLight.Ioc;
using Microsoft.Practices.ServiceLocation;
namespace Client.ViewModel
{
public class ViewModelLocator
{
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<ConnectivityStatusBarViewModel>();
SimpleIoc.Default.Register<HomePageViewModel>();
}
public ConnectivityStatusBarViewModel ConnectivityStatusBar
{
get { return ServiceLocator.Current.GetInstance<ConnectivityStatusBarViewModel>(); }
}
public HomePageViewModel HomePage
{
get { return ServiceLocator.Current.GetInstance<HomePageViewModel>(); }
}
}
}
Instead of registering new ConnectivityStatusBarViewModel instance in ViewModelLocator, can you try providing the exact instance to it.
SimpleIoc.Default.Register(() => ConnectivityStatusBarViewModel.getInstance);
This way when the call to get the instance gets invoked:
return ServiceLocator.Current.GetInstance<ConnectivityStatusBarViewModel>();
you will actually be returning that instance.
So when you do binding to VMlocator and ConnectivityStatusBar.Text, it should work.
Text="{Binding ConnectivityStatusBar.Text,
Source={StaticResource ViewModelLocator}}"
You were essentially binding to another instance, which is why the data wasn't refreshed in the UI.
BTW there are some design issues/redundancies with this/your code, but it's out of the scope of the question - I am trying to make minimal changes to it to get it working.