I am new to Xarmain Forms programming and try to follow the MVVM model. However, I am not sure if I am doing it correctly. The program I write works, but
I would like some experts' opinions on whether I am doing it right or not in terms of MVVM.
I see a lot of example having the OnPropertyChanged somewhere in the programs. Do I need it somewhere in my program?
Any way to simplify?
My program read the text file in the system pathe and the text file contains some URLs and my XAML will display what the URL points to.
+++++++++++++++++++++++++++++++++++++++++++++++++
Folder name: Model
Class name: ListViewNewsItem.cs
namespace SCAC.Models
{
public class ListViewNewsItem
{
public string NewsLink { get; internal set; }
}
}
+++++++++++++++++++++++++++++++++++++++++++++++++++
Folder Name: ViewModels
Class Name: ListViewNewsViewModel.cs
namespace SCAC.ViewModels
{
public class ListViewNewsViewModel : ViewModel
{
private ObservableCollection<ListViewNewsItem> newsItem;
private static string documentPath = ReturnDocumentPath(); // Get the system path
public ListViewNewsViewModel()
{
GenerateNews(); // code below
}
public ObservableCollection<ListViewNewsItem> NewsItem
{
get { return newsItem; }
set
{
this.newsItem = value;
}
}
public void GenerateNews()
{
// File
string line;
string toReadFile = Path.Combine(documentPath + "Images.txt");
NewsItem = new ObservableCollection<ListViewNewsItem>();
StreamReader toRead = new StreamReader(toReadFile);
while ((line = toRead.ReadLine()) != null)
{
Console.WriteLine("This is the url: " + line);
var newsDetail = new ListViewNewsItem()
{
NewsLink = line
};
NewsItem.Add(newsDetail);
}
toRead.Close();
}
}
}
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Folder Name: Views
XAML file Name: NewsView.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:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:localvm="clr-namespace:SCAC.ViewModels"
mc:Ignorable="d"
x:Class="SCAC.Views.NewsView" >
<!-- ontentPage.BindingContext>
<localvm:ListViewNewsViewModel />
</ -->
<ContentPage.Content>
<ScrollView>
<StackLayout BindableLayout.ItemsSource="{Binding NewsItem}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Frame Padding="10, 10, 10, 10" HeightRequest="200">
<Image Source="{Binding ImageLink}" Aspect="Fill"></Image>
</Frame>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</ScrollView>
</ContentPage.Content>
</ContentPage>
Code behind: NewsView.xaml.cs
namespace SCAC.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class NewsView : ContentPage
{
public NewsView()
{
InitializeComponent();
BindingContext = new ListViewNewsViewModel();
}
}
}
I think you did a quite good job in MVVM structure. Get the data in the ViewModel, set the binding in the Xaml, set the right bindingContext to contect View and ViewModel. Everything looks well so far.
The Model-View-ViewModel (MVVM) architectural pattern was invented
with XAML in mind. The pattern enforces a separation between three
software layers — the XAML user interface, called the View; the
underlying data, called the Model; and an intermediary between the
View and the Model, called the ViewModel. The View and the ViewModel
are often connected through data bindings defined in the XAML file.
The BindingContext for the View is usually an instance of the
ViewModel.
ViewModels generally implement the INotifyPropertyChanged interface, which means that the class fires a PropertyChanged event whenever one of its properties changes.
You does not need to implement the INotifyPropertyChanged interface because there is only one property in your ViewModel: ObservableCollection<ListViewNewsItem> NewsItem.
ObservableCollection implements the INotifyCollectionChanged interface by default so you don't have to implement the INotifyPropertyChanged interface in ViewModel to notify the data changes.
If you have a simple property like public DateTime DateTime, then you usually need to write it like:
class ClockViewModel : INotifyPropertyChanged
{
DateTime dateTime;
public event PropertyChangedEventHandler PropertyChanged;
public DateTime DateTime
{
set
{
if (dateTime != value)
{
dateTime = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("DateTime"));
}
}
}
get
{
return dateTime;
}
}
}
You can read the document to learn more about MVVM with data-binding.
Related
I have a custom View that I am trying to pass as List<T> to. For some reason when trying to load the page the app throws a System.ArrayTypeMismatchException.
Here is the class:
public class DiaryCalendarCustomView : View
{
MiscFunctions misctools = new MiscFunctions();
private List<DiaryNextContactEventModel> _eventList = new List<DiaryNextContactEventModel>();
public List<DiaryNextContactEventModel> EventList
{
get { return _eventList; }
set { _eventList = value; }
}
public void SetSelectedDate (DateTime selectedDate)
{
SelectedDate = selectedDate;
Settings.Current.NextContactContactDate = selectedDate.ToLocalTime();
}
public DateTime SelectedDate { get; set; }
public DiaryCalendarCustomView()
{
}
}
View Model:
private List<DiaryNextContactEventModel> _eventList = new List<DiaryNextContactEventModel>();
public List<DiaryNextContactEventModel> EventList
{
get { return _eventList; }
set { SetProperty(ref _eventList, value); }
}
When I add static data to the EventList object it works fine and when I remove the Binding from the XAML view it works as well. So the issue appears to be that xamarin is trying to convert my list into another type of enumerable and that's where it is failing.
XAML:
<Grid VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
<Grid.Children>
<partials:DiaryCalendarCustomView EventList="{Binding EventList}"/>
</Grid.Children>
</Grid>
Debugging and searching around hasn't really offered anything useful. Any help would be appreciated.
If you want to use custom property in XAML, you need to declare it in your view. Your code seems fine, just follow some tutorial like this: Creating Custom Controls with Bindable Properties in Xamarin.Forms and add the missing pieces, so the property definiton and propertyChanged method:
public static readonly BindableProperty EventListProperty = BindableProperty.Create(
propertyName: "EventList",
returnType: typeof(List<DiaryNextContactEventModel>),
declaringType: typeof(DiaryCalendarCustomView),
defaultValue: "",
defaultBindingMode: BindingMode.TwoWay,
propertyChanged: EventListPropertyChanged);
and also:
private static void EventListPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var view = (DiaryCalendarCustomView) bindable;
view.EventList = (List<DiaryNextContactEventModel>) newValue;
}
Also make sure that your class implements INotifyPropertyChanged interface, so when you change EventList in EventListPropertyChanged, the view will get reloaded
I have a simple View that displays a label with a Question that is being bound from my ViewModel. now if I set the property in my constructor I see the Label displaying whatever I set it to. if I populated from my command function I do not see the label changed. The funny thing is that if I set the Title property (a simple string that has a get and set), then that changes no matter where I set it. but for some reason this particular property does not want to show the changes to it. I have tried simplifying this as much as I can. I tried to define a public string property in my ViewModel and again if I set it in the Constructor than it binds other wise if it is being set in my Command Function then it does not change.
here is my XAML
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Pre.MyPage"
Title="{Binding Title}"
Icon="about.png">
<StackLayout VerticalOptions="Center" HorizontalOptions="Center" >
<Label Text="{Binding MyClassObj.Question, Mode=TwoWay}"/>
</StackLayout>
</ContentPage>
Here is my Code behind
public partial class MyPage : ContentPage
{
MyViewModel vm;
MyViewModel ViewModel => vm ?? (vm = BindingContext as MyViewModel);
public MyPage()
{
InitializeComponent();
BindingContext = new MyViewModel(Navigation);
}
protected override void OnAppearing()
{
base.OnAppearing();
ViewModel.LoadQuestionCommand.Execute("1");
}
}
Here is my ViewModel
public class MyViewModel : ViewModelBase
{
public MyClass MyClassObj {get;set;}
ICommand loadQuestionCommand;
public ICommand LoadQuestionCommand =>
loadQuestionCommand ?? (loadQuestionCommand = new Command<string>(async (f) => await LoadQuestion(f)));
public MyViewModel(INavigation navigation) : base(navigation)
{
Title = "My Title";
}
async Task<bool> LoadQuestion(string id)
{
if (IsBusy)
return false;
try
{
IsBusy = true;
MyClassObj = await StoreManager.QuestionStore.GetQuestionById(id);
//MyClassObject is populated when I break here
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
finally
{
IsBusy = false;
}
return true;
}
I don't see where you are firing the INofityPropertyChanged event for your MyClassObj property.
Instead of just:
public MyClass MyClassObj {get;set;}
you should have something like:
MyClass myClassObj;
public MyClass MyClassObj
{
get {return myClassObj;}
set
{
//if they are the same you should not fire the event.
//but since it's a custom object you will need to override the Equals
// of course you could remove this validation.
if(myClassObj.Equals(value))
return;
myClassObj = value;
//This method or something has to be in your VieModelBase, similar.
NotifyPropertyChanged(nameof(MyClassObj));
}
}
Where the last method
NotifyPropertyChanged(nameof(MyClassObj));
is who notifies the View about the changes.
I tried to use MVVMLight in our Windows 10 Universal app, but it seems like that it totally can't work. I've seen this blog
Nuget downloaded and added a reference to the MVVM Light assemblies
Nuget also added the ViewModelLocator in the Application.Resources.
Can't see the Locator in Application.Resources
You need to create the ViewModelLocator manually, please follow these steps:
Create a new Windows 10 Universal app, for example: MVVMLightUWPApp1
Add reference to MVVMLight using NuGet Package Manager
Add a folder for your UWP app, for example: ViewModel
Under the ViewModel folder, add two classes: MainViewModel and ViewModelLocator
In MainViewModel.cs:
namespace MVVMLightUWPApp1.ViewModel
{
public class MainViewModel
{
public string MSG { get; set; }
public MainViewModel()
{
MSG = "Test Message";
}
}
}
In ViewModelLocator.cs:
namespace MVVMLightUWPApp1.ViewModel
{
public class ViewModelLocator
{/// <summary>
/// Initializes a new instance of the ViewModelLocator class.
/// </summary>
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
////if (ViewModelBase.IsInDesignModeStatic)
////{
//// // Create design time view services and models
//// SimpleIoc.Default.Register<IDataService, DesignDataService>();
////}
////else
////{
//// // Create run time view services and models
//// SimpleIoc.Default.Register<IDataService, DataService>();
////}
SimpleIoc.Default.Register<MainViewModel>();
}
public MainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
public static void Cleanup()
{
// TODO Clear the ViewModels
}
}
}
In App.xaml:
<Application.Resources>
<vm:ViewModelLocator xmlns:vm="using:MVVMLightUWPApp1.ViewModel"
x:Key="Locator" />
</Application.Resources>
In the View, set DataContext as below:
DataContext="{Binding Main, Source={StaticResource Locator}}"
Now, you can set binding to VM, for example:
<TextBlock Text="{Binding MSG}" FontSize="50" />
Enjoy it:)
I am trying to create an application on the basis of the WAF framework following the MVVM pattern. Currently, my solution consists of two projects (each equipped with MEF and MAF references):
*.Application (holding controllers and viewmodels)
*.Presentation (holding the actual view files)
I am creating the binding between view and viewmodel via the ViewModel interface - see code fragments below. Further, all classes are made available via the MEF framework inside the App.xaml.cs file. Here, the controller is also initialized. In the easiest case, I want to show a string value in a label of the main window.
Here is the problem: If I start the application, the value of the second label only shows the fallback value, but the get method of the property is being called properly (checked via debugging mode). The binding between View and ViewModel seems to be correct - if I change the binding path in the xaml to a non existent property, I get an output that the property can not be found in the ViewModel. My impression is that there could be a problem with the events for view updating? Any suggestions on this strange behaviour?
Here is the expert of the ViewModel:
[Export]
public class MainWindowViewModel : ViewModel<IMainWindowView>
{
private string _labelContent;
public string LabelContent
{
get { return _labelContent; }
set { SetProperty(ref _labelContent, value); }
}
[ImportingConstructor]
public MainWindowViewModel(IMainWindowView view) : base(view)
{
}
}
Here is the exerpt of the controller:
[Export(typeof(IMainWindowController))]
public class MainWindowController : IMainWindowController
{
private MainWindowViewModel _mainWindowViewModel;
public MainWindowViewModel MainWindowViewModel
{
get { return _mainWindowViewModel; }
set { _mainWindowViewModel = value; }
}
[ImportingConstructor]
public MainWindowController(MainWindowViewModel mainWindowViewModel)
{
_mainWindowViewModel = mainWindowViewModel;
}
public void Initialize()
{
_mainWindowViewModel.LabelContent = "stfu";
}
}
The view interface:
public interface IMainWindowView : IView
{
}
And the view itself:
[Export(typeof(IMainWindowView))]
public partial class MainWindow : Window, IMainWindowView
{
public MainWindow()
{
InitializeComponent();
}
}
<Window x:Class="MyCompany.Product.Redesign.Presentation.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Label Content="Test" />
<Label Name="MyLabel" Content="{Binding Path=LabelContent, FallbackValue=Fallback}" />
</StackPanel>
</Window>
Are you sure, that the view that is displayed really is the instance with the ViewModel-Instance you are setting the property on?
First, make sure that you don't have a view set as the Application's StartupUri-Property in the App.xaml. Then make sure, that you call View.Show() through your ViewModel. You are then certain that you really set the property on the instance that is being displayed:
App.xaml
<Application <!-- note: no StartupUri Property -->
x:Name="App" x:Class="YourProject.Presentation.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
ShutdownMode="OnMainWindowClose">
</Application>
MainViewController.cs (with method declaration in IMainViewController.cs)
public void Run()
{
_mainWindowViewModel.Show();
}
App.xaml.cs
_controller = mainExportProvider.GetExportedValue<IMainViewController>();
_controller.Initialize();
_controller.Run();
MainViewModel.cs (with method declaration in IMainViewModel.cs)
public void Show()
{
ViewCore.Show();
}
This should do the trick. Otherwise, you might be seeing a view instance that you don't have a reference to. Thus you are setting a property on a ViewModel whos view isn't being displayed.
In the StockTraderRI sample code the ViewModel is injected by MEF using a property:
[Export(typeof(IOrdersView))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public partial class OrdersView : UserControl, IOrdersView
{
public OrdersView()
{
InitializeComponent();
}
[Import]
[SuppressMessage("Microsoft.Design", "CA1044:PropertiesShouldNotBeWriteOnly", Justification = "Needs to be a property to be composed by MEF")]
public IOrdersViewModel ViewModel
{
set { this.DataContext = value; }
}
}
What I wonder is: why not use an ImportingConstructor like this to inject the ViewModel:
[Export(typeof(IOrdersView))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public partial class OrdersView : UserControl, IOrdersView
{
[ImportingConstructor]
public OrdersView(IOrdersViewModel ViewModel)
{
InitializeComponent();
this.DataContext = ViewModel;
}
}
Is there a special feature, problem or reason I miss why the StockTraderRI sample does use a Property instead of a paramter to the ctor?
Because types partially defined in XAML don't play well with parametrized constructors. XAML is built on the "create a blank object and fill in the properties afterwards" paradigm.