Xamarin Forms Dynamically Load Content in a Page - xaml

My current set up:
Xamarin Forms, consisting of iOS, Android, WP app and shared PCL.
Using MVVM Light to keep a nice separation of concerns.
Brief intro into what I want to achieve. I want to have a Base page that has a Cancel and Next button. On pressing the Next button Content is loaded dynamically within that base page.
Xaml View:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="LogInPresenterView">
<ContentPage.ToolbarItems>
<ToolbarItem Text="Cancel" Priority="0" Order="Primary" Command="{Binding Cancel}"></ToolbarItem>
<ToolbarItem Text="Next" Priority="1" Order="Primary" Command="{Binding Next}"></ToolbarItem>
</ContentPage.ToolbarItems>
</ContentPage>
ViewModel Code:
public class LogInPresenterViewModel : ViewModelBase
{
public LogInPresenterViewModel() {}
private RelayCommand _next;
public RelayCommand Next
{
get
{
return _next ?? (_next = new RelayCommand(async () => await DoNext()));
}
}
private async Task DoNext()
{
// IN HERE I WOULD LIKE TO DYNCAMICALLY LOAD CONTENT / VIEWS
}
}
Usually you would have a StackLayout etc before the element. However, on clicking the Next Toolbar Item I want to dynamically load content (that has a viewmodel).
So maybe my ICommand for my next button checked to see what the current content type was, and depending on that I would load another bit of content.
The scenario would be, the base page would load along with the first bit of content - Enter Email and Password. User enters that then clicks on next, if all ok, the content is replaced with the option to enter a security code, keeping the base Close and Next buttons at the top.
Hopefully this makes sense. I know what I want to do in my head, I just don't know how to translate that into Xamarin Forms...

Ok,
So first job is to create your region service in your PCL. This will look something like this:
using System;
using System.Collections.Generic;
namespace xxx
{
public class RegionService : IRegionService
{
private Dictionary<string, object> _regionDictionary;
public RegionService ()
{
_regionDictionary = new Dictionary<string, object> ();
}
#region IRegionService implementation
public bool RegisterRegion (string regionName, object regionObject)
{
object region = null;
_regionDictionary.TryGetValue (regionName, out region);
if (region != null)
_regionDictionary [regionName] = regionObject;
else
_regionDictionary.Add (regionName, regionObject);
return true;
}
public object ResolveRegion (string regionName)
{
object region = null;
_regionDictionary.TryGetValue (regionName, out region);
if (region == null)
throw new RegionServiceException ("Unable to resolve region with given name");
return region;
}
#endregion
}
}
This when you create your page with the dynamic content register your dynamic contentview in your code behind:
ContentView contentView = this.FindById<ContentView>("myContentView");
regionService.RegisterRegion("DynamicView", contentView);
You'll need to create an interface for your views and pages to use to indicate which region they wish to be presented in:
using System;
namespace xxx
{
public interface IRegionView
{
string GetRegionName ();
}
}
Then in your code behind for your view implement this interface to return the name of the region to display in.
You now need a custom presenter to use this region code. I use MVVMCross, so the details will vary for the MVVM implementation you are using, but essentially something like this is what you need:
public async static Task PresentPage(Page page)
{
if (typeof(IRegionView).GetTypeInfo().IsAssignableFrom(page.GetType().GetTypeInfo()))
{
IRegionService regionService = Mvx.Resolve<IRegionService>();
string regionName = (page as IRegionView).GetRegionName();
Page region = regionService.ResolveRegion(regionName) as Page;
if (typeof(IModalPage).GetTypeInfo().IsAssignableFrom(page.GetType().GetTypeInfo()))
await region.Navigation.PushModalAsync(page);
else if (typeof(IPopupPage).GetTypeInfo().IsAssignableFrom(page.GetType().GetTypeInfo()))
region.PushOverlayPage(page);
else if (typeof(NavigationPage).GetTypeInfo().IsAssignableFrom(region.GetType().GetTypeInfo()))
await (region as NavigationPage).PushAsync(page);
}
}
I hope this is useful for you :)

So if this was me. I would create a region service where the contentview registers a unique region name.
Content would then be marked to use that region, and a custom presenter can be used to show the view model's content in the appropriate region.
I'm on my phone whilst travelling at the moment but I can post some code later on if that helps :)
Tristan

You can dynamically load Xamarin Forms UI with XAML.
Old Answer:
This can be achieved with the use of the LoadFromXaml method. It works in the same was as XamlReader.Load in Silverlight/WPF. It is a hidden method that can be only accessed through reflection. There is an article on how to do it here:
http://www.cazzulino.com/dynamic-forms.html
But, I would like to ask to you go to this feature request at Xamarin and ask that the method be made public so that it becomes a fully supported feature:
https://forums.xamarin.com/discussion/comment/252626

Related

Updating a property in a viewmodel of popup doesn't update the UI

As in the title I have a problem where updating a property in a viewmodel of popup doesn't update the UI. I use popups from xamarin community toolkit. I'm using a command that does this task:
async Task ShowPopup()
{
MessagingCenter.Send(AnimeGroupObservable, "AnimeGroups");
Shell.Current.ShowPopup(new MediaListGroupsPopup());
}
It sends a message with payload and shows popup. This is popup viewmodel:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows.Input;
using OtakuApp.Models;
using Xamarin.Forms;
namespace OtakuApp.ViewModels
{
class MediaListGroupsPopupViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public ObservableCollection<Group> _AnimeGroups = new ObservableCollection<Group>();
public ObservableCollection<Group> AnimeGroups
{
get => _AnimeGroups;
set
{
if (_AnimeGroups == value)
return;
_AnimeGroups = value;
OnPropertyChanged();
}
}
public String _label;
public String label
{
get => _label;
set
{
if (value == _label)
return;
_label = value;
OnPropertyChanged();
}
}
public MediaListGroupsPopupViewModel()
{
MessagingCenter.Subscribe<ObservableCollection<Group>>(this, "AnimeGroups", (AnimeGroupObservable) =>
{
Console.WriteLine(AnimeGroupObservable[0].Name);
label = AnimeGroupObservable[1].Name;
MessagingCenter.Unsubscribe<ObservableCollection<Group>>(this, "AnimeGroups");
});
}
}
}
I'm planning on having a small collection view of labels to select from. But right now I'm struggling to update one label just for testing purposes, so you can imagine that I've tried collection view and it didn't work. Setting _label to something manually in the code shows that binding works. It's just not updating for some reason.
Popup xaml file:
<?xml version="1.0" encoding="utf-8" ?>
<xct:Popup
x:Class="OtakuApp.Popups.MediaListGroupsPopup"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
Size="300,300">
<StackLayout>
<Label Text="{Binding label}" />
</StackLayout>
</xct:Popup>
So right now I have two problems:
Label doesn't update. It's binded to a property that has INotifyPropertyChanged
Weirdly this subscription happens only the second time (and after that too, just not the first time) I open up a popup. Is this because it's in the constructor? If yes, what's the correct way to deal with it?
Also a small question - I have unsubscribe at the end of subscription. When I didn't have it and I printed out AnimeGroupObservable[0].Name, the first time it was printed one time, the second time I open up the popup two times etc. Is the unsubscribe at the end the correct way to fix this?
since you are passing a single parameter to a single page, using the constructor would be much simpler than MessagingCenter (which is great, but overkill for this scenario)
when creating the page, pass the parameter in the constructor
Shell.Current.ShowPopup(new MediaListGroupsPopup(AnimeGroupObservable));
then modify the page constructor to accept the parameter
public MediaListGroupsPopup(ObservableCollection<Group> groups)
{
// you did't show how you create your VM, but I assume it's something like this
this.BindingContext = new MediaListGroupsPopupViewModel(groups);
}
then modify your VM constructor
public MediaListGroupsPopupViewModel(ObservableCollection<Group> groups)
{
label = groups[1].Name;
}
if you really are only using a single string value, you could just pass that instead of the entire ObservableCollection

How to bind entry from page with ViewModel

I'm trying to implement Login system like this:
public Command LoginCommand => new Command(async () =>
{
LoginModel model = new LoginModel("dnetTest", "dnetTest"); // (get value from entry instead of "dnetTest")
if (model.CheckInformation())
{
bool isSuccess = await LoginService.Login(model);
if (isSuccess)
{
await Application.Current.MainPage.DisplayAlert("Пријављивање", "Успешно сте се пријавили", "OK.");
Application.Current.MainPage = new MainPage();
}
}
My LogingPage also have:
<Label Text="Korisničko ime"/>
<Entry x:Name="Entry_Username" Placeholder="Korisničko ime"/>
<Label Text="Lozinka"/>
<Entry x:Name="Entry_Password" Placeholder="Lozinka"/>
<Button Text="Prijavi se" Command="{Binding LoginCommand}"/>
So, my question is how to bind Entry_Username and Entry_Password with LoginModel in LoginViewModel?
And is there any way to bind it without using x:Names?
To be fair; this is a very basic MVVM question that is easy to find, of which the solution is in multiple blogs and pages. But, let me help you get started.
And is there any way to bind it without using x:Names?
The whole point of data binding is that you do not have to make any hard references to these controls. We want to separate the logic from the UI so that we can easily replace one or both without having to touch the other. For instance, say that you want to implement a new design, if you use data binding, you can just show the current properties in the view model (or page model as it is referred to in the Xamarin space as well) if you reference those in your new UI.
If you would have all kind of x:Name references, you would not only have to touch your UI, but also the view model and go through all the references to those fields and replace them as well.
Using data binding promotes reusability and testability mostly.
As for your specific case. I can't see your full code, so there will be some assumptions made here. First, I'm going to assume that your LoginCommand lives in a view model of its own right now. You are already using data binding there, which is good. I don't directly see why you would need a separate model for the view model and the login, possibly your LoginModel is more like a service. Also, I'm assuming you're doing this manually, without the help of an MVVM framework. It's good to know what happens under the hood, but I would recommend looking at using an MVVM framework like FreshMvvm or Prism for example.
The login page that holds your XAML, I will call LoginPage.xaml which should have a LoginPage.xaml.cs code-behind file. In there, go into the constructor and specify this line:
public LoginPage()
{
InitializeComponents();
// This line is relevant here
BindingContext = new LoginViewModel();
}
Seeing that your LoginCommand is already using data binding, this is probably here already.
Now, in your LoginPage.xaml, change your XAML to this:
<Label Text="Korisničko ime"/>
<Entry Text="{Binding Username}" Placeholder="Korisničko ime"/>
<Label Text="Lozinka"/>
<Entry Text="{Binding Password}" Placeholder="Lozinka"/>
<Button Text="Prijavi se" Command="{Binding LoginCommand}"/>
Notice how I removed the x:Name attributes and added the Text attributes on the two Entry controls.
Next, go into your LoginViewModel.cs and add two properties, like this:
public string Username { get; set; }
public string Password { get; set; }
Whenever the text changes in your Entry controls, these properties should contain the value accordingly. Now, you can change the code you posted to something like this:
public Command LoginCommand => new Command(async () =>
{
// Notice how I changed this line
LoginModel model = new LoginModel(Username, Password);
if (model.CheckInformation())
{
bool isSuccess = await LoginService.Login(model);
if (isSuccess)
{
await Application.Current.MainPage.DisplayAlert("Пријављивање", "Успешно сте се пријавили", "OK.");
Application.Current.MainPage = new MainPage();
}
}
This should work for you!
As mentioned, I would recommend looking further into MVVM as a whole and also MVVM frameworks. Here is the official Docs page, a good writeup by Adam Pedley and something I wrote myself a while back.

UWP Telerik RadDataGrid not allowing me to end row edit by hitting enter

I am having trouble ending an edit of a row in Telerik's UWP RadDataGrid. Once the data is populated I click on a cell to start an edit. After I finish editing the row I hit enter to finish editing but it remains in edit mode. Clicking a cell in another row ends the edit and the new data is intact but the bound collection does not get updated. Below is a screen shot of the grid I am using:
Here is the XAML code in my page:
<tg:RadDataGrid ColumnDataOperationsMode="Flyout" x:Name="grid" ItemsSource="{x:Bind ViewModel.Source}" UserEditMode="Inline" Grid.ColumnSpan="4" Grid.Row="1"/>
I would really appreciate some help. Thanks so much in advance!
After I finish editing the row I hit enter to finish editing but it remains in edit mode.
I created a 16299 UWP project to test and installed the Telerik.UI.for.UniversalWindowsPlatform(1.0.0.7) package for it. Then, I can reproduce this issue. But if I change my project's target version to "15063", when I hit Enter key, it will commit an edit operation successfully. So, this telerik control might has some issues when it's running in 16299. You could report this issue to their official site of Telerik.
And since the Telerik controls of UWP is open source, you could also check its source code and fix this issue by yourself, then you could compile your custom version by yourself and use it in your project.
I saw the relevant code about this issue maybe in this line code: https://github.com/telerik/UI-For-UWP/blob/master/Controls/Grid/Grid.UWP/View/RadDataGrid.Manipulation.cs#L392 Maybe, you could check it.
Clicking a cell in another row ends the edit and the new data is intact but the bound collection does not get updated.
I have not saw your code, so I didn't know where the issue is. But it worked well on my side. You could check my simple code sample for reference:
<telerikGrid:RadDataGrid x:Name="DataGrid" ItemsSource="{x:Bind ls}" UserEditMode="Inline"></telerikGrid:RadDataGrid>
public sealed partial class MainPage : Page
{
public ObservableCollection<Data> ls { get; set; }
public MainPage()
{
this.InitializeComponent();
ls = new ObservableCollection<Data>() {new Data { Country = "India", Capital = "New Delhi"},
new Data { Country = "South Africa", Capital = "Cape Town"},
new Data { Country = "Nigeria", Capital = "Abuja" },
new Data { Country = "Singapore", Capital = "Singapore" } };
}
}
public class Data:INotifyPropertyChanged
{
private string _Country;
public string Country
{
get { return _Country; }
set
{
_Country = value;
RaisePropertyChange("Country");
}
}
private string _Capital;
public string Capital
{
get { return _Capital; }
set
{
_Capital = value;
RaisePropertyChange("Capital");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChange(string propertyName)
{
if (PropertyChanged!= null)
{
PropertyChanged(this,new PropertyChangedEventArgs(propertyName));
}
}
}

how can i change menu view by user access role?

I have a web application project with MVC 4 and I use Telerik panel bar and bind it by site map for my menu.but now i want to each user according to user access roles in my program see particular items of menu and hide remind menu items . how can i do this work in MVC any tips or trick would be welcome
this is link of Telerik website that i use it for creating my menu just i use it in partial view and just render its action in my layout razor code
Assume you have this global class :
public class AccessControlList{
public static bool IsAdmin {
get{
//put your code here
return false;
}
}
public static bool HasOpenFileAccess{
get{
//put your code here
return true;
}
}
}
then in your view.cshtml you may have something like this :
#(Html.Telerik().Menu()
.Name("mnuMain")
.Items(itemAdder =>
{
itemAdder.Add()
.Text("Admin Menu")
.Visible(false)
.Url("~/Home")
.Visible(AccessControlList.IsAdmin);
itemAdder.Add()
.Text("Files")
.Items(subItemAdder =>
{
subItemAdder.Add()
.Text("Open File...")
.Url("~/Files/Open")
.Visible(AccessControlList.HasOpenFileAccess)
....
complete your AccessControlList class (AccessControlList.cs file) to check if the authenticated person has your required access or not.

MVVM Light Toolkit design approach (Navigation & view load)

I am building a simple application with 4-5 views in Silverlight. I came across MVVM Light toolkit and I think it suits my need.
Background
Application will have views with typical list and details display
Manufacturer
Product
and so on with left navigation, header and footer (User controls).
I am thinking of having a main page with user controls created at design time.
Problem
On selection of links from left navigation control, the central panel should be updated with a different view (like Manufacturer, product and so on)
I understand that Messenger is an option to communicate between different VMs in light toolkit.
Question
How can I design my app with MVVM light toolkit. Central pane need to be loaded with a different view at runtime.
I am particularly looking at help in implementing the navigation portion of the application.
Thank you.
I had to implement basic nagivigtion in an NON mvvm way. I have a message listener sitting on the constructor of my main view that listens for a page navigation message(custom message learn it, love it,use it)then it sets the content source of the nav frame to the url that is sent in the message. I have the URLs for all my page and subpage navigation setup using string constants.
public MainPage()
{
InitializeComponent();
Loaded += OnLoaded;
WebContext.Current.Authentication.LoggedOut +=
new EventHandler<System.ServiceModel.DomainServices.Client.ApplicationServices.AuthenticationEventArgs>(Authentication_LoggedOut);
Messenger.Default.Register<msgs.NavigationRequest<PageURI>>(this, (uri => ContentFrame.Navigate(uri.Content)));
Messenger.Default.Register<WavelengthIS.Core.Messaging.ExceptionMessage>(this, ex => ShowExceptionMessage(ex));
Messenger.Default.Register<WavelengthIS.Core.Messaging.StringMessage>(this, str => ShowMessageForUser(str));
}
public class PageURI : Uri
{
public PageURI(string uriString, UriKind uriKind)
: base(uriString, uriKind)
{
}
}
public class PageLinks
{
public const string SEARCHBYDAYCOUNTVIEW = "/Views/PatientSearchHeaders/SearchByDayCountView.xaml";
public const string SEARCHBYPATIENTCRITERIAVIEW = "/Views/PatientSearchHeaders/SearchByPatientCriteriaView.xaml";
public const string QUESTIONAIRRESHELL = "/Views/QuestionairreViews/QuestionairreShell.xaml";
public const string HOME = "/Views/PrimarySearchView.xaml";
public const string REPORTS = "/Views/ReportsPage.xaml";
public const string LOGINPAGE = "/Views/LoginPageView.xaml";
}
Actual Calling in VM:
private void OnSurveyCommandExecute()
{
Wait.Begin("Loading Patient List...");
_messenger.Send<ReadmitPatientListViewModel>(this);
_messenger.Send<Messages.NavigationRequest<SubClasses.URI.PageURI>>(GetNavRequest_QUESTIONAIRRESHELL());
}
private static Messages.NavigationRequest<SubClasses.URI.PageURI> GetNavRequest_QUESTIONAIRRESHELL()
{
Messages.NavigationRequest<SubClasses.URI.PageURI> navRequest =
new Messages.NavigationRequest<SubClasses.URI.PageURI>(
new SubClasses.URI.PageURI(Helpers.PageLinks.QUESTIONAIRRESHELL, System.UriKind.Relative));
return navRequest;
}