I create a very simple project to test navigation. Below are the steps.
Create a Blank App (XAML/C#) project.
Add a Basic Page "PageTwo" to the project.
Add a HyperlinkButton and a TextBox to the MainPage.
In the code-behind of the MainPage, using Frame.Navigate method to navigate to PageTow and pass the TextBox's Text as the parameter.
Override OnNavigatedTo method of the PageTwo to get the passed parameter.
Run the project, input some text to the TextBox and click button to PageTwo, it works well, but if I click the built-in Back Button from PageTwo, I get an exception: Value cannot be null. If I comment the override OnNavigatedTo method, the Back button can lead me to the Main Page without exception.
Anyone can help?
MainPage.xaml:
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<StackPanel>
<TextBox Width="200" Name="TB"/>
<HyperlinkButton Content="Go to PageTwo" Click="HyperlinkButton_Click_1"/>
</StackPanel>
</Grid>
MainPage.xaml.cs:
private void HyperlinkButton_Click_1(object sender, RoutedEventArgs e)
{
Frame.Navigate(typeof(PageTwo), TB.Text);
}
PageTwo.xaml:
<TextBlock Name="TB" Grid.Row="1"/>
PageTwo.xaml.cs:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
TB.Text = e.Parameter as string;
}
In general when overriding any of the UI methods, you need to also call the base.
Your code does not cause an exception if I change the PageTwo.xaml.cs override of OnNavigatedTo to the following:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
TB.Text = e.Parameter as string;
// call base method
base.OnNavigatedTo(e);
}
Related
I want to navigate between usercontrols by clicking button (I don't want to reload entire page)
I have grind in which i have blank usercontrol
<Grid x:Name="Content" Grid.Row="1" Background="#FF1A7E1F">
<UserControl x:Name="MainConent"/>
</Grid>
and i want programaticly to set this usercontrol to existing one e.g.
private void Button_Click_1(object sender, RoutedEventArgs e)
{
MainConent.Equals(new MainControl()); // maincontrol is a usercontrol
}
but sadly this doesn't work .... is there any way to navigate without need to reload entire page?
//Have you even checked the purpose of Equals?
First you have to remove the current content of Grid, then set new content.
private void Button_Click_1(object sender, RoutedEventArgs e)
{
Content.Children.Clear();
Content.Children.Add(new MainControl());
}
How can I create Hyperlinks in Xaml to navigate from one page to another page? I don't know actually how to use the hyperlink tags.
You can use RequestNavigate event to add a HyperLink class
Xaml:
<TextBlock>
<Hyperlink NavigateUri="http://www.google.com" RequestNavigate="Hyperlink_RequestNavigate">
Click here
</Hyperlink>
</TextBlock>
Code Behind:
private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
{
Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
e.Handled = true;
}
If you are looking for navigating to another page :
<StackPanel Grid.Row="1"
Margin="120,0,120,60">
<HyperlinkButton Content="Click to go to page 2" Click="HyperlinkButton_Click"/>
</StackPanel>
And handle it like :
private void HyperlinkButton_Click(object sender, RoutedEventArgs e)
{
this.Frame.Navigate(typeof(BasicPage2));
}
And to move to external page : As mentioned by #Brainy
My team has been struggling for the Best Practices approach for handling the response from a Navigation for about 3 weeks now without a definitive answer. We have both a WPF and a Windows Phone 8 solution where we share a common code base.
For Phone 8, we display our company's splash screen and start initializing our data. Due to our complex nature, we have a very long list of steps to initialize before the application is fully operational.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (e.NavigationMode == NavigationMode.New)
{
BeginAppStartup();
return;
}
....
void BeginAppStartup()
{
// Initialization of settings and environment
At this point, we need to optionally display up to 5 different pages requesting additional data. So we check our commands, and if executable, then we navigate and optionally display a Communication Page, a Login Page, or several other possible pages.
if( condition )
DisplayLoginPage();
In WPF, this would be easy since we have modal dialogs and can wait for the user's input before continuing. But in the asynchronous world of WP8, we no longer have this.
To accommodate this platform, we have implemented a wide array of attempts, including saving the next command to execute. The only place that I believe that we are assured that the page is closed is in the OnNavigatedTo of the splash page.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (e.NavigationMode == NavigationMode.Back)
{
// If we are returning to the splash from another set up page, check if there are new actions to perform
if (_startupAction != null)
{
_startupAction();
return;
}
Unfortunately, this is only marginally acceptable since the Login page doesn't close properly since all of our action is in the UI thread. The code continues, but the splash page is hidden behind the still visible Login page.
We have also tried out AutoResetEvents, but since we must Navigate out of the UI thread, we can't block the UI thread. We've also tried Task.Run with similar issues.
// Doesn't work.
void ShowLoginPage()
{
if (condition)
{
_manualResetEvent.Reset();
NavigationService.Navigate(new Uri("/Views/Login.xaml", UriKind.Relative)
_manualResetEvent.WaitOne();
}
}
We've also tried the async/await tasks, but we encounter similar problems. I believe that this is the best solution, but we're not having any better luck than previously.
So back to the question: What is the Best Practice for Navigating from a splash page, optionally to a login page, and then to await for the login page to close completely before continuing?
This sounds like a very common scenario, yet I'm baffled! Thanks for your answers.
It is not difficult to provide a functionality similar to a modal dialog. I'm not sure if it is a great UI design decision, but it certainly can be done. This MSDN blog post describes how to do it with UserControl as a custom adorner. It was written in 2007, by that time there was no async/await nor WP8.
I'm going to show how to do a similar thing using Popup control (which is present in both WPF and WP8) and async/await. Here's the functional part:
private async void OpenExecuted(object sender, ExecutedRoutedEventArgs e)
{
await ShowPopup(this.firstPopup);
await ShowPopup(this.secondPopup);
}
Each popup can and should be data-bound to the ViewModel.
C# (a WPF app):
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
namespace Wpf_22297935
{
public partial class MainWindow : Window
{
// http://stackoverflow.com/q/22297935/1768303
public MainWindow()
{
InitializeComponent();
}
EventHandler ProcessClosePopup = delegate { };
private void CloseExecuted(object sender, ExecutedRoutedEventArgs e)
{
this.ProcessClosePopup(this, EventArgs.Empty);
}
// show two popups with modal-like UI flow
private async void OpenExecuted(object sender, ExecutedRoutedEventArgs e)
{
await ShowPopup(this.firstPopup);
await ShowPopup(this.secondPopup);
}
private void CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
// helpers
async Task ShowPopup(Popup popup)
{
var tcs = new TaskCompletionSource<bool>();
EventHandler handler = (s, e) => tcs.TrySetResult(true);
this.ProcessClosePopup += handler;
try
{
EnableControls(false);
popup.IsEnabled = true;
popup.IsOpen = true;
await tcs.Task;
}
finally
{
EnableControls(true);
popup.IsOpen = false;
popup.IsEnabled = false;
this.ProcessClosePopup -= handler;
}
}
void EnableControls(bool enable)
{
// assume the root is a Panel control
var rootPanel = (Panel)this.Content;
foreach (var item in rootPanel.Children.Cast<UIElement>())
item.IsEnabled = enable;
}
}
}
XAML:
<Window x:Class="Wpf_22297935.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">
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Open" CanExecute="CanExecute" Executed="OpenExecuted" />
<CommandBinding Command="ApplicationCommands.Close" CanExecute="CanExecute" Executed="CloseExecuted"/>
</Window.CommandBindings>
<DockPanel>
<Border Padding="5">
<StackPanel>
<StackPanel>
<TextBlock>Main:</TextBlock>
<TextBox Height="20"></TextBox>
<Button Command="ApplicationCommands.Open" HorizontalAlignment="Left" Width="50">Open</Button>
</StackPanel>
<Popup Name="firstPopup" AllowsTransparency="true" Placement="Center">
<Border Background="DarkCyan" Padding="5">
<StackPanel Background="DarkCyan" Width="200" Height="200" HorizontalAlignment="Left">
<TextBlock>First:</TextBlock>
<TextBox Height="20"></TextBox>
<Button Command="ApplicationCommands.Close" HorizontalAlignment="Left" Width="50">Close</Button>
</StackPanel>
</Border>
</Popup>
<Popup Name="secondPopup" AllowsTransparency="true" Placement="Center">
<Border Background="DarkGray" Padding="5">
<StackPanel Background="DarkGray" Width="200" Height="200" HorizontalAlignment="Left">
<TextBlock>Second:</TextBlock>
<TextBox Height="20"></TextBox>
<Button Command="ApplicationCommands.Close" HorizontalAlignment="Left" Width="50">Close</Button>
</StackPanel>
</Border>
</Popup>
</StackPanel>
</Border>
</DockPanel>
</Window>
When dealing with such complex navigation, you should resort to creating your own navigation service. Instead of using NavigationService.Navigate, use your own wrapper over it.
In case of login page being after splash screen (and optionally), but before some other, you can always remove the page from backstack after navigation. So in this case you always navigate forward to another page and your custom service should remove last page if it is, say, a login page.
I have a second .xaml page set up under the name Page2.xaml and I want to make it so that when my button is clicked, the user is taken to Page2.xaml
I have this for my button inside of my Page1.xaml:
<Grid>
<Button x:Name="localModeBtn"
Style="{StaticResource MainButtonStyle}"
Content="local mode"
Click="localModeBtn_Click" />
</Grid>
And for the button event handler:
private void localModeBtn_Click(object sender, RoutedEventArgs e)
{
Uri uri = new Uri("Page2.xaml", UriKind.Relative);
this.NavigationService.Navigate(uri);
}
Upon clicking the button I receive an error that says "Cannot locate resource page2.xaml"
The thing is that Page2.xaml is in the same folder as Pag1.xaml so I can't see where I've gone wrong?
Solution to my own question:
I feel a bit silly providing a solution to my own question but thanks to Jasti's link I was able to sort my code out. As he had only posted a comment, I can't mark it as an answer, so here is the solution.
I changed the NavigationWindow to a Window and inserted:
<DockPanel>
<Frame x:Name="_NavigationFrame" NavigationUIVisibility="Hidden" />
</DockPanel>
And within the constructor of the MainWindow.xaml.cs I added:
_NavigationFrame.Navigate(new Page1());
Then the last step was to adjust the button event handler to:
this.NavigationService.Navigate(new Uri("Pages/Page2.xaml", UriKind.Relative));
You should use this, this worked for me:
var Page2= new Page2(); //create your new form.
Page2.Show(); //show the new form.
this.Close(); //only if you want to close the current form.
There is a variable type of a page with the page.xaml right name in your solution.
after that, you should use its methods to do it functionally.
Use any container and bind the content to any property in your viewmodel or codebehind.
After that you just have to update the property by setting a new page and call the PropertyChanged-event (see INotifyPropertyChanged interface). This will update the content of your container and you can display anything you want.
In case you want a separate window
NavigationWindow navWIN = new NavigationWindow();
navWIN.Content = new pageWFbchAdmin();
navWIN.Show();
//winBchAdmin.ShowDialog();
You don't need any C# code for this, just do it in XML:
<Button Content="local mode"
Command="NavigationCommands.GoToPage"
CommandParameter="/Page2.xaml"/>
(Reformatted code not tested)
private void Navigate_Click(object sender, RoutedEventArgs e)//By Prince Jain
{
this.NavigationService.Navigate(new Uri("Page3.xaml", UriKind.Relative));
}
My solution was adding a frame in the main window MainWindow.xaml
<Frame Name="Main" Content="" Margin="127,0,0,0" Background="#FFFFEDED" />
For navigation:
1- Navigating from the main windows on button click:
private void Button_Click(object sender, RoutedEventArgs e)
{
// navigate to pages/projects.xaml inside the main frame
Main.Content = new MyProject.Pages.Projects();
}
2- In case of navigation from the page inside a frame ex Projects.xaml
// declare a extension method in a static class (its your choice if you want to reuse)
// name the class PageExtensions (you can choose any name)
namespace MyProject
{
public static class PageExtensions
{
public static void NavigateTo(this Page page, string path)
{
Frame pageFrame = null;
DependencyObject currParent = VisualTreeHelper.GetParent(page);
while (currParent != null && pageFrame == null)
{
pageFrame = currParent as Frame;
currParent = VisualTreeHelper.GetParent(currParent);
}
if (pageFrame != null)
{
pageFrame.Source = new Uri(path, UriKind.Relative);
}
}
}
}
// to navigate from 'pages/projects.xaml' to another page
// heres how to call the extension on button click
this.NavigateTo("NewProject.xaml");
In addition, you can add another extension method that expects another Page object, in case you want to pass parameters to the constructor
// overloading NavigateTo
public static void NavigateTo(this Page page, Page anotherPage)
{
Frame pageFrame = null;
DependencyObject currParent = VisualTreeHelper.GetParent(page);
while (currParent != null && pageFrame == null)
{
pageFrame = currParent as Frame;
currParent = VisualTreeHelper.GetParent(currParent);
}
// Change the page of the frame.
if (pageFrame != null)
{
pageFrame.Navigate(anotherPage);
}
}
// usage
this.NavigateTo(new Pages.EditProject(id));
First of all, you'll need a frame in your window to hold the pages, so in my MainWindow.xaml I'll add a frame like this:
<Frame x:name="mainFrame"/>
Then We'll add an event listener to our navigation button in our MainWindow.xaml like this:
<Button
x:Name="navBtn"
Content="LIVE VIEW"
Click="NavBtn_Click">
</Button>
Now after we've set our window xaml up, we'll go to MainWindow.xaml.cs and write our code:
//this function should be automatically generated
private void NavBtn_Click(object sender, RoutedEventArgs e)
{
//we'll write this line, which opens our page
mainFrame.Content = new YourPage();
}
and that's it your navigation is ready!
In View (.xaml file):
<StackPanel>
<TextBlock>Outside area of frame</TextBlock>
<StackPanel Height="20" Width="400" VerticalAlignment="Top" Orientation="Horizontal">
<Button Content="Page 1" Width="200" Click="Button_Click"/>
<Button Content="Page 2" Width="200" Click="Button_Click_1"/>
</StackPanel>
<Frame Name="Main" Height="300" Width="700" Background="LightGray">
</Frame>
</StackPanel>
In code behind (.xaml.cs file):
private void Button_Click(object sender, RoutedEventArgs e)
{
Main.Content = new Page1();
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
Main.Content = new Page2();
}
Thesse two buttons will now help you to navigate between pages named Page1 and Page2. (Please take care of namespaces if the pages are present in folders or so).
I recently created a Silverlight 3 app in which I created some UI elements in the code behind and added them at run-time dynamically.
I was hoping to just use the built-in MouseButtonEventArgs or the sender object to get a reference to the instance that was clicked, however I noticed once I started that this was not the case. I was not able to access any properties of the object that triggered the event and program against it.
void myFunc(object sender, MouseButtonEventArgs e)
{
//Can't do this :(
sender.someProperty = someValueToUpdate;
//or this
MyClass foo = sender as MyClass;
foo.someProperty = someValueToUpdate;
}
I ended up just writing a CustomEventArgs object to pass an instance, but it surprised me that this wasn't a default behavior.
Can anyone shed some light as to WHY the sender object doesn't contain a reference to the object that triggered the event?
Also, here is what I did to get that instance.
myObject.myEvent += new CustomEvent(myFunc);
...
void myFunc(object sender, CustomEventArgs e)
{
e.MyProperty = someValueToUpdate;
}
...
public class MyClass
{
public MyProperty = 0;
public event CustomEvent myEvent;
protected virtual void MyEventMethod(CustomEventArgs e)
{
if (myEvent != null){myEvent(this, e);}
}
public MyClass ()
{
this.MouseLeftButtonDown += new MouseButtonEventHandler(this_MouseLeftButtonDown);
}
void rect_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
CustomEventArgs e2 = new CustomEventArgs(this);
MyEventMethod(e2);
}
}
public class CustomEventArgs : EventArgs
{
private readonly MyClass myProperty;
public CustomEventArgs(MyClass myProperty) { this.myProperty = myProperty; }
public MyClass MyProperty { get { return myProperty; } }
}
public delegate void CustomEvent(object sender, CustomEventArgs e);
The MouseEventArgs has a OriginalSource property. Its this property which holds a reference to the object that originally triggered it.
The sender argument quite rightly is set to the instance of the object against which you attached the event handler. Perhaps a simple experiment will make how this hangs together clearer. In Visual Studio create a Silverlight Application. Make the content of the MainPage.xaml look like this:-
<UserControl x:Class="SilverlightApplication1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Grid x:Name="LayoutRoot" Background="White" MouseLeftButtonDown="MouseHandler">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel x:Name="OuterPanel" MouseLeftButtonDown="MouseHandler" Margin="5">
<StackPanel x:Name="TopPanel" MouseLeftButtonDown="MouseHandler">
<TextBlock Text="First Top Item" />
<TextBlock Text="Second Top Item" />
</StackPanel>
<StackPanel x:Name="BottomPanel" MouseLeftButtonDown="MouseHandler">
<TextBlock Text="First Bottom Item" />
<TextBlock Text="Second Bottom Item" />
</StackPanel>
</StackPanel>
<ListBox x:Name="lstOutput" Grid.Column="1" Margin="5" />
</Grid>
</UserControl>
And in MainPage.xaml.cs add this code:-
private void MouseHandler(object sender, MouseButtonEventArgs e)
{
FrameworkElement s = sender as FrameworkElement;
TextBlock o = e.OriginalSource as TextBlock;
string text = (o != null) ? o.Text : "Not from a text block";
lstOutput.Items.Add(String.Format("Sender: {0}, Text block: {1}", s.Name, text));
}
Note how this same handler is attached to three different items in the XAML but not to the TextBlocks themselves. Clicking the "First Top Item" gets you this:-
Sender: TopPanel, Text block: First Top Item
Sender: OuterPanel, Text block: First Top Item
Sender: LayoutRoute, Text block: First Top Item
The handler fires 3 times once for each item it is attached to as can be seen by the sender being different for each one. However the OrignalSource it the TextBlock that was actually clicked on despite it not having any handler attached. Also note that the OriginalSource remains the same as it bubbles up the ancestor elements.
Click on the area below the Stack panels. You only get:-
Sender: LayoutRoot, Text block: Not from a text block
Of interest also is that clicking in the Listbox results in no items being added at all, you might expect to the same ase the above line. Clearly ListBox handles the mouse down and therefore sets the event args Handled property to True preventing further bubbling.
From the msdn documentation:
For a bubbling event, the sender
parameter identifies the object where
the event is handled, not necessarily
the object that actually received the
input condition that initiated the
event.
I.e. since it's a bubbling event, maybe you should try something like
void myFunc(object sender, MouseButtonEventArgs e)
{
var theUIElement = sender as TheUIElementOfWhichImInterested;
if (theUIElement != null)
{
// set properties on the element
}
}