How to pass object through pages that swtich via Tabs? - c#

I'm programming an app with MAUI where I have an object called Company that is initialized in the MainPage
public partial class MainPage : ContentPage
{
Company company { get; set; } = new Company();
and I want that object to be shared across two pages that switch between one another through a tabs system run on the AppShell.
AppShell.xaml
<Shell
x:Class="Work_Tasks.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Work_Tasks"
xmlns:views="clr-namespace:Work_Tasks.Pages">
<TabBar>
<ShellContent
ContentTemplate="{DataTemplate local:MainPage}"
Route="MainPage"
Icon="home.png"/>
<ShellContent
ContentTemplate="{DataTemplate views:AddPersonel}"
Route="AddPersonel"
Icon="add_contact.png"/>
</TabBar>
I want to avoid making the object static. Is there any way of passing the object through both or more pages? How should I go about it?

If I had this problem, I would probably go for something like this. Create class:
public class CompanyContainer
{
public Company Company { get; set; } = new Company();
}
Now register it in MauiProgramm.cs as a singleton
builder.Services.AddSingleton<CompanyContainer>();
Now you can inject this instance through constructor to your page:
public partial class MainPage : ContentPage
{
private readonly CompanyContainer _companyContainer;
public MainPage(CompanyContainer container)
{
_companyContainer = container;
}
}
This should solve your issue. You can also make it as a property with public getter in MainPage if you need. And one more thing. In c# by convention we usually write property names with capital letter.

Related

Calling one of child pages of TabbedPage to reach entry text

I know, "It is highly recommended to use MVVM" but I am just trying to understand and learn xamarin.forms structure. So here is the question:
My application is based on TabbedPage which consists of two NavigationPage:
<TabbedPage.Children >
<NavigationPage Title="Search">
<NavigationPage.Icon>
<OnPlatform x:TypeArguments="FileImageSource">
<On Platform="iOS" Value="tab_feed.png"/>
</OnPlatform>
</NavigationPage.Icon>
<x:Arguments>
<views:SearchPage />
</x:Arguments>
</NavigationPage>
<NavigationPage Title="Study">
<NavigationPage.Icon>
<OnPlatform x:TypeArguments="FileImageSource">
<On Platform="iOS" Value="tab_about.png"/>
</OnPlatform>
</NavigationPage.Icon>
<x:Arguments> <!---->
<views:AboutPage />
</x:Arguments>
</NavigationPage>
</TabbedPage.Children>
I am trying to call a child of this tabbedpage from a method created in a different class/view model to reach SearchBar text:
public async void AddToList()
{
var mp = (MainPage)App.Current.MainPage;
var sp = (SearchPage)mp.Children[0]; /// exception related with casting is thrown.
var Word = sp.WordSearchBar.Text;
...
}
SearchPage is defined as below. So I what is the issue creating casting exception. And how I can reach the searcbar text (other than bindings and MVVM)
public partial class SearchPage : ContentPage
{....}
mp.Children[0]; is kind of NavigationPage not SearchPage, so you get the casting exception.
One way to achieve that is create a static property in the App class:
public partial class `App` : Application
{
public static AboutPage aboutPageInstance { get; set; }
public App()
{
InitializeComponent();
DependencyService.Register<MockDataStore>();
MainPage = new MainPage();
}
}
Then assign the value in the AboutPage and let's say you have a label called currentLabel in the AboutPage:
public partial class AboutPage : ContentPage
{
public Label currentLabel { get; set; }
public AboutPage()
{
InitializeComponent();
currentLabel = myLabel;
App.aboutPageInstance = this;
}
}
Then you can access the label in other ContentPage by:
async void AddToList(object sender, EventArgs e)
{
if (App.aboutPageInstance != null)
{
AboutPage about = App.aboutPageInstance;
about.currentLabel.Text = "kkk";
}
}
MessagingCenter is also a good option to communicate between two different ContentPages.
In the AddToList method you are trying to cast the first child of MainPage to SearchPage. Anyway, in your XAML, the first child in <TabbedPage.Children> is a NavigationPage which does - of course - not derive from your SearchPage, hence cannot be casted and a InvalidCastException is thrown.
If you really want to do it this way (learning MVVM right away could be helpful, but if you really want to learn it the hard way, it's fine for me), you would have to access the NavigationPage and then access its CurrentPage
public async void AddToList()
{
var mainPage = (MainPage)App.Current.MainPage;
var navigationPage = (NavigationPage)mainPage.Children[0];
if (navigationPage.CurrentPage is SearchPage searchPage)
{
var Word = searchPage.WordSearchBar.Text;
...
}
}
I've introduced safe casting with pattern matching because the NavigationPage.CurrentPage is subject to change (as opposed to the MainPage and the NavigationPage) and we don't want our app to crash just because the wrong page is active in the navigation page.
Please note that this way, your pages are very tightly coupled, which is usually considered not a very good thing. Even without MVVM you should think about decoupling your pages, e.g. with MessagingCenter, see the Xamarin.Forms tutorial on loosely coupled components. Anyway, you should consider introducing interfaces for your pages in that case, since MessagingCenter requires the type of the sender, which would introduce some kind of tight coupling again. Sending the search text to another component could be achieved by calling
MessagingCenter.Send<ISearchPage, string>(this, MessengerKeys.SearchTextChanged, searchText);
But what exactly would be sensible, strongly depends on your app and there might be a better way.

Xamarin Forms MVVM with an actual model

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.

WPF, Binding not working even with property changed implemented

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.

Hierarchical viewmodels which represent UI structure. What is better approach?

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).

C# sharing form data with a class

This is a kind of a newbie question. I haven't done C# programming in a while especially with creating with custom classes. I want to share some data between my forms, I was thinking of creating a class,
public class User
{
public string id;
public string name;
public User()
{
}
public User(string id, string name)
{
this.id = id;
this.name = name;
}
}
Does the class have to be in a separate file (User.cs)?
If I had the following code:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
User user1 = new User("abc","cde");
}
}
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}
}
How should I create the class in Form1 and how should I access it in Form2?
Typically, you separate classes out into separate files, so I would suggest making a User.cs file that contains the User class.
How you are instantiating the class in Form1 is fine, but to get the instance of the class to Form2 you need to pass it to the constructor of Form2, like this:
Form2 theForm2 = new Form2(user1);
Note: To accomplish the code above requires modifying the constructor of Form2, or in this case creating a non-default constructor, and creating a member variable to hold the User class instance, like this:
public partial class Form2 : Form
{
private User _user;
public User TheUser
{
get
{
return _user;
}
}
public Form2()
{
InitializeComponent();
}
public Form2(User theUser)
{
_user = theUser;
}
}
Now you can use the User class in Form2 by simply referencing the property TheUser.
How you distribute the classes ove files its all up to you. In this example I made you could have UserHandler.cs, User.cs, Form1.cs & Form2.cs. Hope it helps.
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public static class UserHandler
{
private static List<User> Users = new List<User>();
public static void AddNewUser(User user)
{
Users.Add(user);
}
public static void RemoveUser(User user)
{
Users.Remove(user);
}
public static User GetUserById(int id)
{
if(Users.Exists(x => x.userId == id))
{
return Users.Find(user => user.userId == id);
}
return null;
}
}
public class User
{
public int userId { get; set; }
public string userName { get; set; }
public User(int id, string name)
{
this.userId = id;
this.userName = name;
}
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
var _user = new User(1,"user2252502");
UserHandler.AddNewUser(_user);
MessageBox.Show(UserHandler.GetUserById(1).userName);
Form2 form2 = new Form2();
}
}
public partial class Form2 : Form
{
public Form2()
{
MessageBox.Show(UserHandler.GetUserById(1).userName);
}
}
}
There are multiple ways of doing this. one way can be that you pass the class in constructor of Form2. like below.
public partial class Form1 : Form
{
User user1 = new User("abc","cde");
public Form1()
{
InitializeComponent();
new Form2(user1);
}
}
public partial class Form2 : Form
{
User user1;
public Form2(User u)
{
InitializeComponent();
user1 = u;
}
}
Other ways of doing this can be to expose a public property in form1 that returns User. and you access that property in Form2.
They can be in the same file. If you want to access data from Form1 in Form2, add Form1 as a parameter to the constructor of Form2. Or add it as a property in Form2 and allow it to be "set".
How should I create the class in Form1 and How should I access it in Form 2?
This depends on its use. Since your desire is to share it between forms, I am assuming you would like its scope to be class wide. Define it as a class property. Preferably above your constructor for Form1.
Does the class have to be in a separate file, User.cs If I had the following code
It does not have to be, but best practice is that it is.
How do I share it with Form2?
A. Make it a parameter of the constructor of Form2:
public partial class Form2 : Form
{
public User MyUser { get; set; }
public Form2(User user)
{
InitializeComponent();
MyUser = user;
}
}
B. Make it a public property of Form2 and set it from Form1
var frm2 = new Form2();
frm2.User = user;
I'm speaking purely from a "I think I've been down the same road you're going down" point of view.
It might be time to break down your application into some patterns. A User class is usually a special kind of entity within an application. In typical cases, it can define the attributes of a user, or it can be used to define the Identity and Claims for a particular user, or all of the above.
It would be good to make a service or utility class for your user. If any given Form, or class for that matter, in your application needs to have information about a User, your service is there to answer the call. This service could provide a CurrentUser property which would inform other classes that the currently logged in user is (null | Bob | xyz...) It can perform validation, access background utilities to cache attributes about the user. but I digress. While writing my answer, #Iker Ruiz Amauda has given a practical example of how might implement this service.
As to how to make this available across your application: it depends on the magnitude of your project. I've like Dependency Injection as mentioned by Karl Anderson. To go along with Dependency Injection would be Inversion of control (IoC). My current flavor of IoC is MEF (http://mef.codeplex.com/documentation). By defining an IUserService that is implemented by UserService, you can "Inject" this service into any form that relies on the userservice. In my current project this is achieved practically using attributes to export and import items.
For more information on Inversion of Control (I think Dependency Injection is pretty straight forward but Google is your friend on that one,) see This answer on stack overflow:
https://stackoverflow.com/a/3140/93964
or this was a decent blog entry I found while google the subject:
http://joelabrahamsson.com/inversion-of-control-an-introduction-with-examples-in-net/

Categories