How to fire Page Constructor every time when navigating to it - xamarin.forms

I have a page that is registered in the AppShell like this:
public AppShell()
{
InitializeComponent();
Routing.RegisterRoute(nameof(LesezeichenPage), typeof(LesezeichenPage));
}
...and the Flyout:
<FlyoutItem FlyoutDisplayOptions="AsMultipleItems" >
<ShellContent Title="Lesezeichen" Route="LesezeichenPage" ContentTemplate="{DataTemplate v:LesezeichenPage}">
</ShellContent>
</FlyoutItem>
Additionally the page can be opened from other pages in the App with an ImageButton:
Shell.Current.GoToAsync("LesezeichenPage");
The problem is with the following approach: I save an Observablecollection to my App's Properties on this page with the following method:
private void LesezeichenSpeichern(ContentPage contentPage)
{
var parentPage = (TabbedPage)contentPage.Parent;
parentPage.CurrentPage = parentPage.Children[1];
Bookmarks.Add(new Bookmark { Novene = PickedNovene, Tag = PickedTag, Date = DateTime.Today.ToString("d/M/yy"), ID = Bookmarks.Count }) ;
string LesezeichenJson = JsonConvert.SerializeObject(Bookmarks);
SetProperties("JsonLesezeichen", LesezeichenJson);
}
public async static void SetProperties(string property, object value)
{
var app = (App)Application.Current;
app.Properties[property] = value;
await app.SavePropertiesAsync();
}
When I open the page the Json will then be deserialised and added to an ObservableCollection in the constructor:
public LesezeichenViewModel()
{
Bookmarks = new ObservableCollection<Bookmark>();
////Lade Properties wenn in einer vorherigen Session gespeichert
if (Application.Current.Properties.ContainsKey("JsonLesezeichen"))
{
string savedLesezeichen = Application.Current.Properties["JsonLesezeichen"].ToString();
var InterimBookmarks = JsonConvert.DeserializeObject<ObservableCollection<Bookmark>>(savedLesezeichen);
foreach (var bookmark in InterimBookmarks)
{
Bookmarks.Add(new Bookmark { Novene = bookmark.Novene, Tag = bookmark.Tag, Date = bookmark.Date, IsChecked = false, ID = bookmark.ID });
}
}
Unfortunately when I make changes to the ObservableCollection after navigating to the page with Shell.Current.GoToAsync("LesezeichenPage") and then open the page about the FlyoutItem, the constructor won't fire for a second time. Hence the Json cannot be deserialised and the changes won't be visible in the ListView of the page.
I have to make the constructor fire somehow every time when opening the page, if that's possible. Seen a post with UWP about a NavigationCacheMode that can be disabled though ContentPage/TabbedPage don't have these options.
Maybe someone has another idea/approach to solve this?

Related

Xamarin Forms 5.0.0.2515: Relative routing to shell elements is currently not supported

I am receiving the below error :
Relative routing to shell elements is currently not supported. I am running Xamarin forms 5.0.0.2515.
The project navigates to the Items Journal Template View Model where I select an item. The item's name is then passed to the Finished Goods View Model where I will do a search based on the value passed. However I am receiving the above error even though I have already done this on other pages without an error.
I have my routing registered:
public AppShell()
{
InitializeComponent();
Routing.RegisterRoute(nameof(LocatePage), typeof(LocatePage));
Routing.RegisterRoute(nameof(FinishedGoodsPage), typeof(FinishedGoodsPage));
Routing.RegisterRoute(nameof(ItemJournalTemplatePage), typeof(ItemJournalTemplatePage));
}
ItemJournalTemplateViewModel: I call the Finished Goods page and pass it the item's name...
async void SelectedItemJournalTemplate(ItemJournalTemplate item)
{
if (item == null)
return;
await Shell.Current.GoToAsync($"{nameof(FinishedGoodsPage)}?{nameof(FinishedGoodsViewModel.PassedJournalBatchName)}={item.Name}");
}
and the page receiving the call FinishedGoodsViewModel:
[QueryProperty(nameof(PassedJournalBatchName), nameof(PassedJournalBatchName))]
public class FinishedGoodsViewModel:BaseViewModel
{
private string passedJournalBatchName;
public string PassedJournalBatchName
{
get => passedJournalBatchName;
set
{
passedJournalBatchName = value;
OnPropertyChanged(nameof(PassedJournalBatchName));
}
} .....
}
What makes this odd is that I use this on the FinishedGoodsViewModel to call the LocateViewModel and it works fine:
public async void UpdateLocation(object value)
{
SelectedValue = (FinishedGood)value;
await Shell.Current.GoToAsync($"{nameof(LocatePage)}" +
$"?{nameof(LocateViewModel.ItemNo)}={SelectedValue.Item_No}" +
$"&{nameof(LocateViewModel.BatchName)}={SelectedValue.Journal_Batch_Name}" +
$"&{nameof(LocateViewModel.TemplateName)}={SelectedValue.Journal_Template_Name}" +
$"&{nameof(LocateViewModel.LineNo)}={SelectedValue.Line_No}");
}
LocateViewModel:
[QueryProperty(nameof(ItemNo), nameof(ItemNo))]
[QueryProperty(nameof(BatchName), nameof(BatchName))]
[QueryProperty(nameof(TemplateName), nameof(TemplateName))]
[QueryProperty(nameof(LineNo), nameof(LineNo))]
public class LocateViewModel : BaseViewModel
{
private string batchName;
public string BatchName
{ get => batchName;
set
{
batchName = value;
OnPropertyChanged(nameof(BatchName));
}
}
private string templateName;
public string TemplateName
{
get => templateName;
set
{
templateName = value;
OnPropertyChanged(nameof(TemplateName));
}
}
private string lineNo;
public string LineNo
{
get => lineNo;
set
{
lineNo = value;
OnPropertyChanged(nameof(LineNo));
}
}
private string itemNo;
public string ItemNo
{
get => itemNo;
set
{
itemNo = value;
OnPropertyChanged(nameof(ItemNo));
}
}...
}
Can someone please tell me where I am going wrong here?
It looks like the answer was in the AppShell.xaml file. The original Flyout pointed to the FinsishedGoodsPage but I changed it to ItemJournalTemplatePage, allowing the user to first make a selection first.
<FlyoutItem Title="Finished Goods" Icon="placeholder.png">
<ShellContent Route="FinishedGoodsPage" ContentTemplate="{DataTemplate local:ItemJournalTemplatePage}" />
</FlyoutItem>
I forgot I also needed to change the Route. It probably would not go to the FinishedGoodsPage because it thought it was already on that page.
<FlyoutItem Title="Finished Goods" Icon="placeholder.png">
<ShellContent Route="ItemJournalTemplatePage" ContentTemplate="{DataTemplate local:ItemJournalTemplatePage}" />
</FlyoutItem>

Can I use ClassId in tabbedpage to differentiate their content

I'm trying to use the same page for 3 tabs in TabbedPage. But each tab has to display different data in the listview. Is there a way to set a parameter for each tab?
Example
<local:Sales Title="Pending"
Icon="ic_shortcut_home.png"
ClassId="pending"/>
<local:Sales Title="Posted"
Icon="ic_shortcut_home.png"
ClassId="posted"/>
<local:Sales Title="Uploaded"
Icon="ic_shortcut_home.png"
ClassId="uploaded"/>
I tried using ClassId, and Title to get their difference but I'm having trouble retrieving the ClassId in the Sales class constructor, are there any other ways to get the output I want?
public Sales()
{
InitializeComponent();
BindingContext = this;
salesCollection = new ObservableCollection<Head>();
initLvw();
Console.WriteLine(Title); //returns null
Console.WriteLine(ClassId); // returns null
}
You can load data in OnAppearing method:
protected override void OnAppearing()
{
base.OnAppearing();
Console.WriteLine(ClassId + "OnAppearing");
BindingContext = this;
salesCollection = new ObservableCollection<Head>();
initLvw();
}
Or load data with a little delay in constructor:
public AboutPage()
{
InitializeComponent();
Task.Run(async () =>
{
await Task.Delay(200);
Console.WriteLine(ClassId + "AboutPage");
BindingContext = this;
salesCollection = new ObservableCollection<Head>();
initLvw();
});
}

Xamarin.Forms page is not appearing immediately after another page has been popped

I have three pages at the moment in my app. I will call them EventsPage, NewEventPage, and ListPage. EventsPage is the first page of the app and you can open a NewEventPage from there. On this NewEventPage is a button that pops the NewEventPage from the stack and is supposed to create a ListPage immediately afterward, but the ListPage is not appearing, although I found out that its constructor is running.
Here's the code for the NewEventPage:
using Partylist.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Partylist.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class NewEventPage : ContentPage
{
// Constructor.
public NewEventPage()
{
InitializeComponent();
}
// Event handlr for when the "Cancel" button is clicked.
async void OnCancelClicked(object sender, EventArgs e)
{
// Goes back to the previous page.
await Navigation.PopAsync();
}
// Event handler for when the "Create" button gets clicked.
async void OnCreateClicked(object sender, EventArgs e)
{
// Make sure there is something in the text entry.
if (string.IsNullOrWhiteSpace(EventNameEntry.Text))
{
// If there is nothing there, print an error message.
ErrorLabel.Text = "Your event needs a name!";
}
// If there is something in the text entry, try to create
// a new event with the text as its name.
else
{
// Variable to store the created event.
Event newEvent = new Event();
// Variable to store its folder.
DirectoryInfo newEventFolder;
// Flag to see if the event was created sccessfully.
bool eventCreated;
try
{
// If there's already an event with that name, let the
// user know.
if
(Directory.Exists(Path.Combine(Environment.GetFolderPath
(Environment.SpecialFolder.LocalApplicationData),
EventNameEntry.Text)))
{
ErrorLabel.Text = "You already have an event with that name.";
eventCreated = false;
}
// Otherwise, try to creaate the folder.
else
{
newEventFolder = Directory.CreateDirectory(
Path.Combine(Environment.GetFolderPath
(Environment.SpecialFolder.LocalApplicationData),
EventNameEntry.Text));
// Then, create the event based on that folder.
newEvent = new Event
{
FolderName = EventNameEntry.Text,
DateCreated = newEventFolder.CreationTime,
DateEdited = newEventFolder.LastWriteTime
};
// Get rid of any error messages that might be on screen.
ErrorLabel.Text = "";
eventCreated = true;
}
}
// Should throw an ArgumentException in most cases where there is
// an invalid character.
catch (ArgumentException)
{
// Get all the invalid characters and turn them into a string.
char[] invalidChars = Path.GetInvalidPathChars();
string invalid = "";
foreach(char currentChar in invalidChars)
{
invalid += currentChar;
}
// Change the text of the error label.
ErrorLabel.Text = "Your event name can't have these characters: \""
+ invalid + "\".";
eventCreated = false;
}
// If the event was created successfully, select it, pop the "New Event"
// page, and open a "List" page for the event.
if (eventCreated)
{
App.selectedEvent = newEvent;
await Navigation.PopAsync();
await Navigation.PushAsync(new ListsPage());
}
}
}
}
}
And here's the code for the ListPage:
using Partylist.Models;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Partylist.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ListsPage : ContentPage
{
// List of lists, used to populate
// the page's ListView (see the XAML).
public ObservableCollection<PartylistList> ListList { get; set; }
// Constructor.
public ListsPage()
{
// Does all the stuff to make the page
// exist that doesn't involve anything
// specific to this particular page in
// this particular app.
InitializeComponent();
}
// Override for OnAppearing().
protected override void OnAppearing()
{
// Regular OnAppearing() method.
base.OnAppearing();
// Set the title to be the name of the selected event.
Title = App.selectedEvent.FolderName;
// Set the BindingContext of the page to itself.
this.BindingContext = this;
// Set the ItemsSource of the ListView in the
// XAML to the ObservableCollection.
ListList = new ObservableCollection<PartylistList>();
ListListView.ItemsSource = ListList;
// Loop to populate the ObservableCollection.
for (int i = 0; i < Directory.GetFiles(
Path.Combine(Environment.GetFolderPath
(Environment.SpecialFolder.LocalApplicationData),
App.selectedEvent.FolderName))
.Length; i++)
{
// Add a new list.
ListList.Add(new ContactList());
// Set the filename to the name of the file
// that the list corresponds to.
ListList.ElementAt(i).Filename =
Directory.GetFiles(
Path.Combine(Environment.GetFolderPath
(Environment.SpecialFolder.LocalApplicationData),
App.selectedEvent.FolderName))[i];
// Sets the date/time created to the file's
// creation date.
ListList.ElementAt(i).DateCreated = Directory
.GetCreationTime(Directory.GetFiles(
Path.Combine(Environment.GetFolderPath
(Environment.SpecialFolder.LocalApplicationData),
App.selectedEvent.FolderName))[i]);
// Sets the date/time last edited to the
// folder's write date.
ListList.ElementAt(i).DateEdited = Directory
.GetLastWriteTime(Directory.GetFiles(
Path.Combine(Environment.GetFolderPath
(Environment.SpecialFolder.LocalApplicationData),
App.selectedEvent.FolderName))[i]);
}
}
}
}

send data from silverlight to asp.net (or php?) when the user click on a button

If I click on a button of my Silverlight app, another third-party web app must gets some data.
My experience, until now, has been to create web service functions that you can call when you need, but in this case I have to give the possibility to the customer to "handle the click event on the button". In the actual case the third-party app is ASP.Net, but, if it were possible, I would like to do something portable.
Before to start with some crazy idea that will comes in my mind, I would ask: How would you do that?
Pileggi
I Use This Class To Create And Post a Form Dynamically
public class PassData
{
public static PassData Default = new PassData();
public void Send(string strUrl, Dictionary<string, object> Parameters, string ContainerClientID = "divContainer")
{
var obj = HtmlPage.Document.GetElementById(ContainerClientID);
if (obj != null)
{
HtmlElement divContainer = obj as HtmlElement;
ClearContent((HtmlElement)divContainer);
HtmlElement form = HtmlPage.Document.CreateElement("form");
form.SetAttribute("id", "frmPostData");
form.SetAttribute("name", "frmPostData");
form.SetAttribute("target", "_blank");
form.SetAttribute("method", "POST");
form.SetAttribute("action", strUrl);
if (Parameters != null)
foreach (KeyValuePair<string, object> item in Parameters)
{
HtmlElement hidElement = HtmlPage.Document.CreateElement("input");
hidElement.SetAttribute("name", item.Key);
hidElement.SetAttribute("value", item.Value.ToString());
form.AppendChild(hidElement);
}
divContainer.AppendChild(form);
form.Invoke("submit");
ClearContent((HtmlElement)divContainer);
}
}
private void ClearContent(System.Windows.Browser.HtmlElement obj)
{
foreach (HtmlElement item in obj.Children)
{
obj.RemoveChild(item);
}
}
}
divContainer is id of a div in html

SharePoint Web Part Custom Properties Don't Take Effect Until Page Reload

I am developing a sharepoint 2007 web part that uses custom properties. Here is one:
[Personalizable(PersonalizationScope.User), WebDisplayName("Policy Update List Name")]
[WebDescription("The name of the SharePoint List that records all the policy updates.\n Default value is Policy Updates Record.")]
public string PolicyUpdateLogName
{
get { return _PolicyUpdateLogName == null ? "Policy Updates Record" : _PolicyUpdateLogName; }
set { _PolicyUpdateLogName = value; }
}
The properties work fine except that the changes are not reflected in the web part until you leave the page and navigate back (or just click on the home page link). Simply refreshing the page doesn't work, which makes me think it has something to do with PostBacks.
My current theory is that the ViewState is not loading postback data early enough for the changes to take effect. At the very least, the ViewState is involved somehow with the issue.
Thanks,
Michael
Here is more relevant code:
protected override void CreateChildControls()
{
InitGlobalVariables();
FetchPolicyUpdateLog_SPList();
// This function returns true if the settings are formatted correctly
if (CheckWebPartSettingsIntegrity())
{
InitListBoxControls();
InitLayoutTable();
this.Controls.Add(layoutTable);
LoadPoliciesListBox();
}
base.CreateChildControls();
}
...
protected void InitGlobalVariables()
{
this.Title = "Employee Activity Tracker for " + PolicyUpdateLogName;
policyColumnHeader = new Literal();
confirmedColumnHeader = new Literal();
pendingColumnHeader = new Literal();
employeesForPolicy = new List<SPUser>();
confirmedEmployees = new List<SPUser>();
pendingEmployees = new List<SPUser>();
}
...
// uses the PolicyUpdateLogName custom property to load that List from Sharepoint
private void FetchPolicyUpdateLog_SPList()
{
site = new SPSite(siteURL);
policyUpdateLog_SPList = site.OpenWeb().GetList("/Lists/" + PolicyUpdateLogName);
}
...
protected void InitListBoxControls()
{
// Init ListBoxes
policies_ListBox = new ListBox(); // This box stores the policies from the List we loaded from SharePoint
confirmedEmployees_ListBox = new ListBox();
pendingEmployees_ListBox = new ListBox();
// Postback & ViewState
policies_ListBox.AutoPostBack = true;
policies_ListBox.SelectedIndexChanged += new EventHandler(OnSelectedPolicyChanged);
confirmedEmployees_ListBox.EnableViewState = false;
pendingEmployees_ListBox.EnableViewState = false;
}
...
private void LoadPoliciesListBox()
{
foreach (SPListItem policyUpdate in policyUpdateLog_SPList.Items)
{
// Checking for duplicates before adding.
bool itemExists = false;
foreach (ListItem item in policies_ListBox.Items)
if (item.Text.Equals(policyUpdate.Title))
{
itemExists = true;
break;
}
if (!itemExists)
policies_ListBox.Items.Add(new ListItem(policyUpdate.Title));
}
}
Do some reading up on the Sharepoint web part life cycle. Properties are not updated until the OnPreRender event.

Resources