In my MainPage.xaml I've got a SplitView that loads many pages inside a frame created in it's SlplitView.Content.
I've got data in a MainPage's variable that needs to be sent to every page that loads in my SplitView content's frame according to the ListBoxItem clicked.
Also in the current page I may have to update the MainPage's variable before a new page is loaded.
How can I do this? Is there a way to declare a global variable? Can I transport that information from a page to another updating it's value on the parent page?
I think you can declare a public static variable in App Class in App.xaml.cs, and use it in any pages in the app.
In App.xaml.cs:
sealed partial class App : Application
{
...
public static string MyTestVar { get; set; }
...
}
In MainPage.xaml.cs:
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
App.MyTestVar = "world";
}
}
And for some other cases like implementing a setting page, you can check Store and retrieve settings and other app data.
Complementing the last answer I solved my problem by declaring an internal static variable in my "App.xaml.cs".
internal static string foo = "";
Then to access it I used:
App.foo = "my string";
There is also a more elegant way to preserve and restore page data when leaving it (which is what I needed) as follows: https://msdn.microsoft.com/pt-br/library/windows/apps/ff967548%28v=vs.105%29.aspx
Related
I'm wondering what is the best way to pass a file between pages in a UWP app?
I have a UWP app with two pages. In the first page, I have the user open a file with filepicker and load that file into a media player.
I want to pass that same file onto the second page when the user navigates there. I am passing the file over currently as a string which I then am attempting load as a storagefile using GetFileFromPathAsync.
This currently works as I'm able to load the file on the second page but it requires that the user provide broad file system access.
Code on Page 1 (FileLoaded is file path string):
private async void TranscodeMedia_Click(object sender, RoutedEventArgs e)
{
AppWindow appWindow = await AppWindow.TryCreateAsync();
Frame appWindowContentFrame = new Frame();
appWindowContentFrame.Navigate(typeof(TranscodeMedia), FileLoaded);
Code on Page 2:
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
var fileTransfer = e.Parameter.ToString();
FileName.Text = fileTransfer;
StorageFile PassedFile = await StorageFile.GetFileFromPathAsync(fileTransfer);
I'm wondering if this is the best way to pass the file between pages? I'd rather not require the user to provide broad system access to the app if possible. Any help you can provide is most appreciated!
The best and most standard way in C#/WPF/UWP way is to use a standard pattern that consist of a general ViewModel class (which contains all the common app data that you want to use in the logic layer), put as a field in the static MainPage (or even in the App.xaml.cs class).
I always do it like this:
1) I use the MainPage automatically created as the "shell" of the app, with a property that is the AppViewModel.
The MainPage (and thus the AppViewModel) can be accessed from everywhere in the app, by setting itself as a static field in its own class (the "Current" static field can be called from everywhere in the app... even in a MessageDialog class!).
This is the code for the MainPage (or a shell Page that you wish, but I suggest doing like this, it is a pretty standard way used even by Microsoft), simpler than you think:
public sealed partial class MainPage : Page
{
public AppViewModel ViewModel { get; set; } = new AppViewModel();
public static MainPage Current { get; set; }
public MainPage()
{
this.InitializeComponent();
Current = this;
}
}
THIS is the trick: to make the page static in one field in its
own class, so that that static field will be UNIQUE in the entire app
(this is one of the main features of the "static" word) and, thus, by calling
MainPage.Current.ViewModel you can immediately get any data (in your
specific case, a StorageFile) stored there.
2) The AppViewModel itself is a class that must implement the INotifyPropertyChanged interface, in order to enable bindable properties and functions.
It is common, among Windows developers, to create a base class that implements it and then derive all the classes that needs bindable (i.e. observable) properties from it.
Here it is, exactly how Microsoft itself creates it:
public class BaseBind : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected bool SetProperty<T>(ref T storage, T value,
[CallerMemberName] String propertyName = null)
{
if (object.Equals(storage, value)) return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
}
Then you derive AppViewModel class (and all the other model and viewmodel classes) from it… populating it with all the common properties that you need to share across pages.
I have even added a derived property, in order to show how you can share even multiple data types at once, and a function:
public class AppViewModel : BaseBind
{
public AppViewModel()
{
// Usually we initialize all the starting data here, in the viewmodel constructor...
}
// All common app data
private string sampleCommonString;
public String SampleCommonString
{
get { return sampleCommonString; }
set { SetProperty(ref sampleCommonString, value); OnPropertyChanged(nameof(SampleDerivedProperty1)); OnPropertyChanged(nameof(SampleDerivedProperty2)); }
}
public String SampleDerivedProperty1 => "return something based on SampleCommonString";
public String SampleDerivedProperty2
{
get
{
// evaluate in some way SampleCommonString...
return "Same thing as SampleDerivedProperty1, but it allows to add more than just one istruction";
}
}
// This is a property that you can use for functions and internal logic… but it CAN'T be binded to the UI directly
public String SampleNOTBindableProperty { get; set; }
public void SampleFunction()
{
// Insert code, that needs to interact with all the data contained in the viewmodel itself, here...
// The function has to be with NO parameters, in order to work with simple {x:Bind} markup.
// If your function has to access some specific data, you can create a new bindable (or non) property, just as the ones above, and memorize the data there.
}
}
3) Then, in order to access all this from another Page, just create an AppViewModel field in that page, referencing the viewmodel contained in the static mainpage:
public sealed partial class SecondPage : Page
{
public AppViewModel ViewModel => MainPage.Current.ViewModel;
public SecondPage()
{
this.InitializeComponent();
}
}
...and you can easily bind XAML controls properties to the AppViewModel itself:
<TextBlock Text="{x:Bind ViewModel.SampleCommonString, Mode=OneWay}"/>
<TextBox Text="{x:Bind ViewModel.SampleCommonString, Mode=TwoWay}"/>
<Button Content="Sample content" Click="{x:Bind ViewModel.SampleFunction}"/>
(Mode=OneWay is for real-time binding, in order that the property is immediately updated even in the UI, while Mode=TwoWay is used for those properties that can be edited from the control itself, by the user, in order to interact with app logic).
In this mode you will be able to display data and all its changes in real-time!
So... this is the way to keep all the app data at run-time in a
correct and flexible way... by learning it and practicing, in the
future you will use this pattern even in a smarter way, by creating
viewmodels for every object of your application (for example: if
your app need to store your company's customers data, you will have a
"CustomerViewModel" class derived from the BaseBind class, with all
the data of a customer in it) and creating lists like
ObservableCollection<SampleViewModel> to store all of them (ObservableCollection<t> is a collection type that has built-in mechanism to handle list changes, like adding, removing and reordering list items).
Then you will link every observable collection to the ItemsSource property of a control that inherits from ListBase class (tipically: ListView or GridView), creating a DataTemplate to display each list item, like in this example:
<Page
xmlns:vm="using:SampleApp.ViewModelsPath"
<Grid>
<ListView ItemsSource="{x:Bind ViewModel.SampleListOfObjectViewModel, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="vm:SampleObjectViewModel">
<StackPanel>
<TextBlock Text="{x:Bind SampleObjectProperty1, Mode=OneWay}"/>
<TextBlock Text="{x:Bind SampleObjectProperty2, Mode=OneWay}"/>
<Button Click="{x:Bind SampleObjectFunction}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>
...and all the data displayed will be updated in real-time whenever you change it!
Hope this all will help you boost your knowledge about how preparing a WPF/UWP logic layer, because all of this works pretty in the same way even for the WPF apps (i.e. the old desktop programs).
Best regards
There are some other ways to implement your requirement about accessing the same file on different pages. But for your scenario, you could use Future-access list in your UWP app.
By picking files and folders, your user grants your app permission to access items that might not be accessible otherwise. If you add these items to your future-access list then you'll retain that permission when your app wants to access those items again later.
Here is the sample code I made
In the first page:
FileOpenPicker picker = new FileOpenPicker();
picker.FileTypeFilter.Add("*");
StorageFile file = await picker.PickSingleFileAsync();
if (file != null)
{
// add file to the Future Access list
var storageItemAccessList = Windows.Storage.AccessCache.StorageApplicationPermissions.FutureAccessList;
// this token is the key to get the file.
string FALToken = storageItemAccessList.Add(file, "mediaFile");
// in your real scenario, you need to save the token and pass it when you nee
this.Frame.Navigate(typeof(TestPage), FALToken);
}
In the second page:
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
string token = (string)e.Parameter;
var storageItemAccessList = StorageApplicationPermissions.FutureAccessList;
StorageFile retrievedFile = await storageItemAccessList.GetFileAsync(token);
}
So you don't need the broad file system access if you use Future-access list to keep the permission of files.
For more detailed information, please refer to this document: Track recently used files and folders
I want to make a page template in my UWP project, which can be initialized in main page in C# code.
For example, the page is like an introduction of painting work: a painting picture(could be an url representing the image), text introduction below. And I need to initialize these two elements in main page and dynamically create the new page in C# code. Because for each painting work, I need to use a new page for navigation and tracking.
How to do it? Thanks!
I want to make a page template in my UWP project, which can be initialized in the main page in C# code.
For your requirement, you could create ImagePage that contains ImageModel class like the following. When the page navigated to the image and text label will be initialized with the parameter that comes from the main page.
Xaml
<StackPanel>
<Image Name="MyImage"/>
<TextBlock Name="MyDescription"/>
</StackPanel>
Code Behind
public sealed partial class ImagePage : Page
{
public ImagePage()
{
this.InitializeComponent();
}
public class ImageModel
{
public string ImageUrl { get; set; }
public string Description { get; set; }
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (e.Parameter != null && e.Parameter is ImageModel)
{
var data = e.Parameter as ImageModel;
MyImage.Source = new BitmapImage(new Uri(data.ImageUrl));
MyDescription.Text = data.Description;
}
}
}
Then add Frame that use to navigate On the Main page.
<Grid>
<Frame Name="ImageFrame"/>
</Grid>
Usage
public MainPage()
{
this.InitializeComponent();
var TestData=new ImageModel
{
ImageUrl = "https://via.placeholder.com/150",
Description = "This is a test image"
}
ImageFrame.Navigate( typeof(ImagePage), TestData );
}
Now, when you start the application, your new image page will load in the frame.
This is a short example, the main purpose is to express how to jump to the page and give parameters to the page.
When the page receives a parameter, you can adjust the UI according to the parameter.
So, you can use the GridView to display all the images. When you click on one of them, use this principle to jump to the pre-designed page.
I hope this can help you.
I am using a navigation model that has a MainPage, which contains a hamburger menu and a MyFrame in a splitview. I have two pages, WorkingPage and SavePage, which are displayed in the MyFrame. So MainPage includes this:
<Page>
<!-- Other stuff -->
<SplitView>
<SplitView.Pane>
<!-- Other stuff -->
<ListBox Name="HamburgerMenuMenuItems"
SelectionChanged="HamburgerMenuMenuItems_SelectionChanged">
<ListBoxItem Name="HamburgerMenuItemSave">
<!-- Content -- >
</ListBoxItem>
</ListBox>
<!-- Other stuff -->
</SplitView.Pane>
<SplitView.Content>
<Frame Name="MyFrame"></Frame>
</SplitView.Content>
</SplitView>
<!-- Other stuff -->
</Page>
The user clicks Save, which is one of the items in the Hamburger menu (set up as a listbox), and which raises the selection changed event, on the MainPage, which results in MainPage initiating a navigation from WorkingPage to SavePage in MyFrame.
public sealed partial class MainPage : Page
{
private void HamburgerMenuMenuItems_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Other options similar ...
else if (HamburgerMenuItemSave.IsSelected)
{
MyFrame.Navigate(typeof(Pages.File.SavePage));
}
// Other options similar ...
}
}
WorkingPage contains a data member that I want SavePage to have access to.
This is what I have:
public sealed partial class WorkingPage : Page
{
public MyClass myClass;
// Other stuff ...
}
I want the value of "myClass" to be passed to SavePage, so it ultimately ends up as:
public sealed partial class SavePage : Page
{
public MyClass myClass;
// Other stuff ...
}
I know from research (and extensively using it myself) that the proper way to pass parameters between two pages is as follows:
Frame.Navigate(typeof(PageClass), someParameter);
But the problem is MainPage is where the navigation is initiated, but the needed parameter value to pass (myClass) only exists in the scope of WorkingPage. This thus requires that either:
A) the navigation be initiated by WorkingPage, so that I can use the line of code above, and just put in "SavePage" as the PageClass and "myClass" as the parameter, or
B) MainPage somehow needs to obtain knowledge of the value of "myClass", so that I can use the same line of code as (A), but with "this.MyFrame" instead of "Frame"
How can getting the value of "myClass" from WorkingPage to SavePage be accomplished, with a navigate event initiated from MainPage? This seems like a common need, but everything I have found only talks about the simple case of one page initiating navigation to another, when a parameter must be passed from the initiating page to the other.
I am pretty sure this can be done in different ways.. but personally I like to use following pattern to achieve this :
Firstly, in your MainPage you have to create sort of an utility method for the navigation :
public void navigateWithParameter(Page yourPage,String yourParameter){
MyFrame.Navigate(typeof(yourPage), yourParameter);
}
Next you can call this method from any page you want to (which in your case is the WorkingPage), by getting the current instance of the MainPage and calling the navigateWithParameter function with the appropriate parameters :
var frame = (Frame)Window.Current.Content;
var mainpage = (MainPage)frame.Content;
mainpage.navigateWithParameter(yourPage,"It works!");
Hope this helps!
Through further research, I found a way to address this issue (other ways may exist). There exists a property of a Frame that allows you to get a reference to the page the frame is currently displaying:
MyFrame.Content
Thus, on MainPage, in the event handler, you can obtain a reference to the page being displayed, which then allows you to access MyClass, since it is a public data member. You can then pass the value of MyClass into the page being navigated to... all without WorkingPage having any knowledge of the event firing.
public sealed partial class MainPage : Page
{
private void HamburgerMenuMenuItems_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Other options similar ...
else if (HamburgerMenuItemSave.IsSelected)
{
// 1. Check if the current page is the correct type
if (MyFrame.Content is WorkingPage workingPage)
{
// 2. Grab the data to pass from the instance of the displayed page
MyClass dataToPass = workingPage.myClass;
// 3. Pass the grabbed data to the page being navigated to
MyFrame.Navigate(typeof(Pages.File.SavePage), dataToPass);
}
}
// Other options similar ...
}
}
I had a similar task. I have a UWP page with a lot of settings, grouped buttons with binded NumberBoxes. To move out of MainPage all button handlers needed a "helper Class". There I passed the main page object, which automaticaly gives me access to all elements in my MainPage.
My XAML looked like is:
<Page> <Grid>
<StackPanel >
<muxc:NumberBox x:Name="Offset" PlaceholderText="0,35" />
<Button Content="GrOffset" Click="buttonClickHandler.Offset_Click" />
</StackPanel>
<TextBox x:Name="Messager_Container" Text="Total" />
<Grid/>
<Page/>
C# is
namespace Eval
{
public sealed partial class MainPage : Page
{ /// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
ButtonClickHandler buttonClickHandler = new ButtonClickHandler(); // create a new instance of our "helper class"
//===================================
public MainPage()
{
this.InitializeComponent();
var mainPageVAR = this; // only for check ot, is it valid assigning
buttonClickHandler.mainPage = this; //assign to, we pass the addres to our MainPage class instance
}
public class ButtonClickHandler // our helping class
{
public Eval.MainPage mainPage;
public void Offset_Click(object sender, RoutedEventArgs e)
{
mainPage.Messager_Container.Text=
mainPage.Offset.Value.ToString();
// our value from numberBox "Offset" passed to TextBox "Messager_Container"
}
}
}
So I have this page in an UWP Windows 10 app using C#:
public sealed partial class MainPage : Page
This page contains a TextBlock called tbPageTitle.
I'd like to change the text of tbPageTitle to "bla" from another page, so I use the following code:
MainPage.tbPageTitle.text = "bla";
However, I get the following error:
CS0120 An object reference is required for the non-static field, method, or property 'MainPage.tbPageTitle'
I don't know what to do here. I feel like I've read every single Google result.
I found some results to create a new instance of a class, so that would be for example:
MainPage mp = new MainPage();
mp.tbPageTitle.text = "bla";
But wouldn't that create a completely new MainPage? This also doesn't work by the way...
According to the answer to Sandy's comment you have an inner Frame element in the MainPage where you load other pages. So the easiest way to get the current MainPage instance is the following:
MainPage mainPage = (Window.Current.Content as Frame).Content as MainPage;
Note that this will obviously fail if you every navigate outside of the MainPage and call this line there. Additionally note that objects you are creating in XAML are not public, what means that you can't access your tbPageTitle element here anyway, but will need to create any kind of wrapper property in your MainPage like this:
public string PageTitle {
get { return tbPageTitle.Text; }
set { tbPageTitle.Text = value; }
}
However as mentioned by HeySatan, this is not the most beautiful code design you are creating here. Maybe you could create a method to go to a specific frame, something like that:
public enum TabContent { Home, Replies, Messages, Settings }
public void OpenTab(TabContent content) {
// Set Page title and navigate
switch (content) {
case TabContent.Home:
tbPageTitle.Text = "Home";
InnerFrame.Navigate(typeof(HomePage));
break;
case TabContent.Replies:
tbPageTitle.Text = "Replies";
InnerFrame.Navigate(typeof(RepliesPage));
break;
case TabContent.Messages:
tbPageTitle.Text = "Messages";
InnerFrame.Navigate(typeof(MessagesPage));
break;
case TabContent.Settings:
tbPageTitle.Text = "Settings";
InnerFrame.Navigate(typeof(SettingsPage));
break;
}
}
Main goal of this method is that if you have a button Settings in your HomePage you are only calling the following line and all logic to do the navigation stays in the MainPage and HomePage only has logic related to itself:
// In HomePage:
MainPage mainPage = (Window.Current.Content as Frame).Content as MainPage;
mainPage.OpenTab(TabContent.Settings);
If you don't want to access Window.Current.Content all the time, you could also declare a static method in your MainPage class and make access simpler:
// In MainPage:
public static MainPage Instance {
// This will return null when your current page is not a MainPage instance!
get { return (Window.Current.Content as Frame).Content as MainPage; }
}
// Now in HomePage it's only:
MainPage.Instance.OpenTab(TabContent.Settings);
I don't think the title is the best description for my problem but, the problem is as following:
I have a base class like:
public class BasePage : System.Web.UI.Page
{
public static ProjectDTO Project { get; set; }
// some other code
// Setting the project once per session.
public void SetProject()
{
Project = (ProjectDTO)HttpContext.Current.Session["Project"];
SiteMaster masterPage = Master as SiteMaster;
masterPage.Project = Project;
}
}
And then i have an aspx page like:
public partial class SomePage: BasePage
{
protected void Page_Load(object sender, EventArgs e)
{
//callling the base method to set the project
SetProject();
}
//some db method which requires the Project.Id property.
GetSomeInfo(Project.Id)
{
//irelevant code
}
}
Everything works fine, but, when i have 2 users online at the same time, they will obviously call the SetProject method, and if one of them uses GetSomeInfo method, the Project.Id property will be the latest one set, not the one from the current user session.
Can anyone help me fix this problem?
PS:
A dirty fix would be to read the session variable every time i have to use the Project, won't be a problem, but my page has like 10 methods requiring that property (a lot of redundant code)
A secondary fix would be to declare a public property on SomePage and use that, but then, i would find Project from the BasePage redundant and i don't like that approach because there are quite a few pages requiring the Project property and using the BasePage (filters, searches, etc on objects belonging to that Project)
EDIT After some more testing, 2 different users at the same time, and after Glubus comments, the problem happens only when the page is loading for one of the users (the user which is not loading anything will get wrong results from the database while the other user is loading a page.)
EDIT2 The workflow is as following:
User goes to home page where a list of projects are available (read from db) -> clicks on one project (when the user clicks the project i'm setting a session variable to be read later). Then the user can see/use other data related to this project.
EDIT3
When a user click on a project, they will navigate to a Dashboard page. Here, on the Page_Load even i'm setting the session variable, like:
public partial class Dashboard : BasePage
{
protected void Page_Load(object sender, EventArgs e)
{
int projectId;
int.TryParse(Request.QueryString["projectId"], out projectId);
if (projectId > 0)
{
Session["Project"] = ProjectSvc.GetProjectById(projectId);
SetProject();
}
}
}
ProjectDTO class:
public class ProjectDTO
{
public int idProject { get; set; }
public string Title { get; set; }
public string Users { get; set; }
public string Description { get; set; }
}
I'm setting the Project to the Site Master because i have a label which requires to be seen on the screen with the Project Name and description.
In order to access the Project from the current Session from all places, including WebMethods, declare a static readonly property in BasePage that directly accesses the Session.
public class BasePage : System.Web.UI.Page
{
public static ProjectDTO Project
{
get {return (ProjectDTO)HttpContext.Current.Session["Project"];}
}
// some other code
// Passing the Project to the Master.
public void SetProject()
{
SiteMaster masterPage = Master as SiteMaster;
masterPage.Project = Project;
}
}
You can get rid of the SetProject() call altogether if you also use BasePage.Project to access the current project from the Site Master.
During testing, make sure that you are not using the same browser instance for testing. When session managements happens via cookies and you have two users logged in into the same browser instance, they will actually use the same ASP.net session.
Perhaps a worst case scenario solution - but....
If you are really intent on making the Project variable accessible to all subsequent users, you maybe need to move away from sessions and just declare it as a static?
This way it will be shared application wide, retaining the last assigned value.