I have 'BasePage' class which is base class for all other pages in my project.
In initialization, I'm adding EventHandler for 'SystemNavigationManager' for event 'BackRequest'. For some reason that line id causing 'AccessViolationException' when XAML designer is trying to render XAML of class that extends 'BasePage'
I'm not familiar with UWP, so I'll be very grateful for tips.
BasePage
public class BasePage: Page {
internal string title = "";
internal HeaderView headerView;
public BasePage() {
this.Loaded += BasePage_Loaded;
// FIXME: For some reason if this line is uncommented then xaml designer fails.
SystemNavigationManager.GetForCurrentView().BackRequested += BasePage_BackRequested;
}
private void BasePage_BackRequested(object sender, BackRequestedEventArgs e) {
bool handled = e.Handled;
this.BackRequested(ref handled);
e.Handled = handled;
}
private void BackRequested(ref bool handled) {
//Get a hold of the current frame so that we can inspect the app back stack.
if (this.Frame == null)
return;
// Check to see if this is the top-most page on the app back stack.
if (this.Frame.CanGoBack && !handled) {
// If not, set the event to handled and go back to the previous page in the app.
handled = true;
this.Frame.GoBack();
}
}
private void setupPageAnimation() {
TransitionCollection collection = new TransitionCollection();
NavigationThemeTransition theme = new NavigationThemeTransition();
var info = new ContinuumNavigationTransitionInfo();
theme.DefaultNavigationTransitionInfo = info;
collection.Add(theme);
this.Transitions = collection;
}
private void BasePage_Loaded(object sender, Windows.UI.Xaml.RoutedEventArgs e) {
setupPageAnimation();
}
}
SOLUTION
Just like Ivan said, final code looks like this. Without of a trace of bug.
BasePage
public BasePage() {
this.Loaded += BasePage_Loaded;
}
protected override void OnNavigatedTo(NavigationEventArgs e) {
base.OnNavigatedTo(e);
SystemNavigationManager.GetForCurrentView().BackRequested += BasePage_BackRequested;
}
protected override void OnNavigatedFrom(NavigationEventArgs e) {
base.OnNavigatedFrom(e);
SystemNavigationManager.GetForCurrentView().BackRequested -= BasePage_BackRequested;
}
You shouldn't subscribe to back events on constructor but OnNavigatedTo and unsubscribe in OnNavigatedFrom. Even if it didn't crash it would cause a lot of problems because your back logic would be activated on all previous pages when you press the back button.
Related
Here is my problem:
In my WPF application I have a MyBaseControl (derives from System.Windows.Controls.ContentControls) and a lot of MyCustomControls which derives from MyBaseControl. I need to do some storing and cleanup operations for all my MyCustomControls befor the application is closed.
Here is some code:
public abstract class MyBaseControl : ContentControl
{
// Do some smart stuff.
}
App.Exit += new System.Windows.ExitEventHandler(App.App_OnExit);
In App_OnExit() I do the really last operations that need to be done.
I tried to do my cleanup operations in the destructor of MyBaseControl but this is called after App_OnExit(). Same problem with AppDomain.CurrentDomain.ProcessExit.
The ContentControl.Closed and ContentControl.Unloaded events don't occour when I exit the application via ALT+F4.
Where can I hook in to do my cleanup operations?
Where can I hook in to do my cleanup operations?
In a Closing event handler for the parent window of the control:
public abstract class MyBaseControl : ContentControl
{
public MyBaseControl()
{
Loaded += MyBaseControl_Loaded;
}
private void MyBaseControl_Loaded(object sender, RoutedEventArgs e)
{
Window parentWindow = Window.GetWindow(this);
parentWindow.Closing += ParentWindow_Closing;
Loaded -= MyBaseControl_Loaded;
}
private void ParentWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
//cleanup...
}
}
You can put it to your application class:
public delegate void TimeToCleanUpEventHandler();
public event TimeToCleanUpEventHandler TimeToCleanUp;
modify Exit event handler:
App.Current.Exit += ((o, e) => { TimeToCleanUp?.Invoke(); App.App_OnExit(o, e); });
and modify your base control:
public abstract class MyBaseControl : ContentControl
{
public MyBaseControl()
{
(App.Current as MyApp).TimeToCleanUp += CleanItUp;
}
public virtual void CleanItUp()
{
(App.Current as MyApp).TimeToCleanUp -= CleanItUp;
//do stuff;
}
}
In my application, I use an event to check network status. In the MainWindow, I instantiate some user controls (for example, I have 3 child user controls), and in one of these child controls, I need to catch the event from the App to this specific user control.
In the App I use this to start:
protected override void OnStartup(StartupEventArgs e)
{
NetworkStatus.AvailabilityChanged +=
new NetworkStatusChangedHandler(DoAvailabilityChanged);
base.OnStartup(e);
}
static void DoAvailabilityChanged(
object sender, NetworkStatusChangedArgs e)
{
//this method will send a notification
//ReportAvailability();
}
When I catch this event, I need to change the brushes in my StackPanel. After I have created the two brushes, how I can change them? I have seen some information about custom triggers. How can I use those in my StackPanel?
I used Tunneling events.
In child viewmodel:
#region Events
public readonly static RoutedEvent NetworkStatusEvent =
EventManager.RegisterRoutedEvent(
"NetworkStatusEvent",
RoutingStrategy.Tunnel,
typeof(RoutedEventHandler),
typeof(NetworkStatusViewModel));
#endregion
public void NetworkStatus_Changed(Object sender, RoutedEventArgs e)
{
Image = "home-scanner";
IsAvailable = NetworkStatus.IsAvailable ? true : false;
TextLegend = "sfsdfhf";
//RaiseEvent(new RoutedEventArgs(NetworkStatusViewModel.GreetEvent, this));
e.Handled = true;
}
In MainViewModel:
private static NetworkStatusViewModel networkStatusViewModel = new NetworkStatusViewModel();
public static NetworkStatusViewModel NetworkStatusViewModel
{
get
{
return networkStatusViewModel;
}
//set {
// networkStatusViewModel = value;
//}
}
I hope this helps.
I have registered a handler with the HardwareButtons.BackPressed event, performed some logic and then set the handled property in the args to true if it applies. The handler runs through without any issue, and the Handled property gets set. The phone still navigates back outside of the app. Am I misunderstanding how to use the event?
Page
public sealed partial class FirstRunPage : VisualStateAwarePage
{
public FirstRunPage()
{
this.InitializeComponent();
#if WINDOWS_PHONE_APP
HardwareButtons.BackPressed += (sender, args) =>
{
bool isHandled = false;
Action handledCallback = () => isHandled = true;
var state = new Dictionary<string, object> { { "Callback", handledCallback } };
((INavigationAware)this.DataContext).OnNavigatedTo("Back", NavigationMode.Back, state);
args.Handled = isHandled;
};
#endif
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
}
}
View model.
public override void OnNavigatedTo(object navigationParameter, NavigationMode navigationMode, Dictionary<string, object> viewModelState)
{
if (navigationParameter == null || !navigationParameter.ToString().Equals("Back"))
{
return;
}
if (!viewModelState.ContainsKey("Callback"))
{
return;
}
var callback = (Action)viewModelState["Callback"];
// If the user is new, then we set it to false and invoke our callback.
if (this.IsNewUser)
{
this.IsNewUser = false;
callback();
}
else
{
return;
}
}
Update
I have modified my FirstRunPage to subscribe and unsubscribe as recommended by #Martin but it still closes the app.
public sealed partial class FirstRunPage : VisualStateAwarePage
{
public FirstRunPage()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
#if WINDOWS_PHONE_APP
HardwareButtons.BackPressed += HardwareBack_OnPressed;
#endif
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
#if WINDOWS_PHONE_APP
HardwareButtons.BackPressed -= HardwareBack_OnPressed;
#endif
}
private void HardwareBack_OnPressed(object sender, BackPressedEventArgs e)
{
Action handledCallback = () => e.Handled = true;
var state = new Dictionary<string, object> { { "Callback", handledCallback } };
((INavigationAware)this.DataContext).OnNavigatedTo("Back", NavigationMode.Back, state);
}
}
With the help of #yasen I was able to get this resolved. The issue stems from the fact that the Prism library has your App.xaml.cs inherit from MvvmAppbase, which intercepts the BackPressed event.
To resolve this, I overrode the MvvmAppBase OnHardwareButtonsBackPressed and added a bit of logic to handle it.
My view model and view both implement a new interface called INavigateBackwards and they're used like this:
View Model
public bool CanNavigateBack()
{
// If the new user is true, then we can't navigate backwards.
// There isn't any navigation stack, so the app will die.
bool canNavigate = !this.IsNewUser;
// Disable the new user mode.
this.IsNewUser = false;
// Return so that the view can return to it's sign-in state.
return canNavigate;
}
View
public sealed partial class FirstRunPage : VisualStateAwarePage, INavigateBackwards
{
private INavigateBackwards ViewModel
{
get
{
return (INavigateBackwards)this.DataContext;
}
}
public FirstRunPage()
{
this.InitializeComponent();
}
public bool CanNavigateBack()
{
return ViewModel.CanNavigateBack();
}
}
Then in the MvvmAppBase subclass, I determine if I need to handle the navigation or not.
MvvmAppBase child
protected override void OnHardwareButtonsBackPressed(object sender, BackPressedEventArgs e)
{
var page = (Page)((Frame)Window.Current.Content).Content;
if (page is INavigateBackwards)
{
var navigatingPage = (INavigateBackwards)page;
if (!navigatingPage.CanNavigateBack())
{
e.Handled = true;
return;
}
}
base.OnHardwareButtonsBackPressed(sender, e);
}
This allows my single view to have multiple states and the user to navigate back from one state to the previous without navigating to an entirely new view.
The reason why your application closes is that the same handler is called more than just once. First handler sets the Handled property to true, but any other subsequent call for the same event fire sets it back to false.
To illustrate it, try this:
public sealed partial class FirstRunPage : VisualStateAwarePage
{
public FirstRunPage()
{
// ...
InitializeComponent();
HardwareButtons.BackPressed += HardwareButtons_BackPressed;
HardwareButtons.BackPressed += HardwareButtons_BackPressed;
}
void HardwareButtons_BackPressed(object sender, BackPressedEventArgs e)
{
bool isHandled = false;
Action handledCallback = () => isHandled = true;
var state = new Dictionary<string, object> { { "Callback", handledCallback } };
((INavigationAware)this.DataContext).OnNavigatedTo("Back", NavigationMode.Back, state);
args.Handled = isHandled;
};
}
And set breakpoint to last line of the handler code.
To avoid it, assign your handler in the OnNavigatedTo method of your FirstRunPage, and unregister the handler in OnNavigatedFrom.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
HardwareButtons.BackPressed += HardwareButtons_BackPressed;
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
HardwareButtons.BackPressed -= HardwareButtons_BackPressed;
}
Well, i'm working in a asp.net 3.5 site.
I have set an Observer like this:
public delegate void ActionNotification();
protected Dictionary<string, List<ActionNotification>> Observers
{
get
{
Dictionary<string, List<ActionNotification>> _observers = Session["Observers"] as Dictionary<string, List<ActionNotification>>;
if (_observers == null)
{
_observers = new Dictionary<string, List<ActionNotification>>();
Observers = _observers;
}
return _observers;
}
set
{
Session["Observers"] = value;
}
}
public void Attach(string actionName, ActionNotification observer)
{
if (!Observers.ContainsKey(actionName))
{
Observers.Add(actionName, new List<ActionNotification>());
}
Observers[actionName].Add(observer);
}
public void Detach(string actionName, ActionNotification observer)
{
if (Observers.ContainsKey(actionName))
{
Observers[actionName].Remove(observer);
}
}
public void DetachAll(string actionName)
{
if (Observers.ContainsKey(actionName))
{
Observers.Remove(actionName);
}
}
public void Notify(string action)
{
if (Observers.ContainsKey(action))
{
foreach (ActionNotification o in Observers[action])
{
o.Invoke();
}
}
}
I use the observer like this:
//Esta es llamada al notify con cierto action
protected void btnNext_Click(object sender, ImageClickEventArgs e)
{
Notify("Next");
}
//Y este es el register del Listener
Attach("Next", new ActionNotification(NextButton_Click));
If before the o.Invoke(); for example i change the page title to "Hello".
And inside the "NextButton_Click" I set it to "Goodbye", after the NextButton_Click finish, the Title goes back to "Hello"...
Any idea why?
I think problem is that the "Page" in your NextButton_Click event is not the same page as the page you set the title to "Hello" on. Because you are passing around events in the session when the event is raised the object is acts on is no longer in scope. You can recreate it with the following code (which is using EventHandlers, but they are basically the same as what you have outlined in your code)
protected void Page_Load(object sender, EventArgs e)
{
this.Page.Title = "test";
//Store it in your session...seems like a weird thing to do given how your page should be stateless, so I would think about what you are
//trying to do a bit more carefully. You don't want to call an event handler such as test below from another page in your asp.net app.
Dictionary<string, EventHandler> myEvents = null;
if (Session["Invokers"] == null)
{
myEvents = new Dictionary<string, EventHandler>();
Session["Invokers"] = myEvents;
}
else
{
myEvents = Session["Invokers"] as Dictionary<string, EventHandler>;
}
//If the event handler key is not in there then add it
if (myEvents.ContainsKey("buttonClickOnPageDefault") == false)
{
//Subscribe to event (i.e. add your method to the invokation list
this.TestEvent += new EventHandler(test);
myEvents.Add("buttonClickOnPageDefault", this.TestEvent);
}
else
{
//if it does contain this key then you may already be subscribed to event, so unsubscribe in case and then resubscribe...you could
//probably do this more elegantly by looking at the vales in the GetInvokationList method on the eventHandler
//Wire up the event
this.TestEvent -= new EventHandler(test);
this.TestEvent += new EventHandler(test);
}
//Resave the dictionary.
Session["Invokers"] = myEvents;
}
void test(object o, EventArgs e)
{
this.Page.Title = "testEvent";
}
public event EventHandler TestEvent;
protected void Button1_Click(object sender, EventArgs e)
{
if (Session["Invokers"] != null)
{
Dictionary<string, EventHandler> myEvents = (Dictionary<string, EventHandler>)Session["Invokers"];
if (myEvents.ContainsKey("buttonClickOnPageDefault"))
{
EventHandler ev = myEvents["buttonClickOnPageDefault"];
ev(null, EventArgs.Empty);
}
}
}
If you put the above code in an asp.net page it will never change page title, but if you put a breakpoint in the Test method you will see it being hit. The reason is that its being hit in a different page (and that page is out of scope and may not be garbage collected as your event still has a reference to it, so this could cause a memory leak...be careful with it!). Really you probably shouldn't be using your events this way (at least not to act on a page...maybe it has some utility for domain objects). Note that the following will work (as its acting on the same page)
protected void Page_Load(object sender, EventArgs e)
{
this.Page.Title = "test";
//Store it in your session...seems like a weird thing to do given how your page should be stateless, so I would think about what you are
//trying to do a bit more carefully. You don't want to call an event handler such as test below from another page in your asp.net app.
this.TestEvent += new EventHandler(test);
Session["Invoker"] = this.TestEvent;
}
void test(object o, EventArgs e)
{
this.Page.Title = "testEvent";
}
public event EventHandler TestEvent;
protected void Button1_Click(object sender, EventArgs e)
{
if (Session["Invoker"] != null)
{
EventHandler ev = (EventHandler)Session["Invoker"];
ev(null, EventArgs.Empty);
}
}
Hope that gives you some pointers to where your problem might be.
Maybe I'm just an idiot, but I can't seem to find an event that will fire for a textbox at the same time as the leave, but only when the contents of the textbox has changed. Kinda like a combination of textchanged and leave. I can't use textchanged cause it fires on each keystroke. Right now I'm storing the current value of the textbox in a variable and comparing it on the leave event, but it seems really hackish.
Thanks
You can create your own (derived) class which overrides OnEnter, OnLeave and OnTextChanged to set flags and trigger "your" event.
Something like this:
public class TextBox: System.Windows.Forms.TextBox {
public event EventHandler LeaveWithChangedText;
private bool textChanged;
protected override void OnEnter(EventArgs e) {
textChanged = false;
base.OnEnter(e);
}
protected override void OnLeave(EventArgs e) {
base.OnLeave(e);
if (textChanged) {
OnLeaveWithChangedText(e);
}
}
protected virtual void OnLeaveWithChangedText(EventArgs e) {
if (LeaveWithChangedText != null) {
LeaveWithChangedText(this, e);
}
}
protected override void OnTextChanged(EventArgs e) {
textChanged = true;
base.OnTextChanged(e);
}
}
The answer of #Lucero does it's job almost perfectly.
However, it does not handle the case when a user edits the text and finally enters the same value as before. Therefore I created a similar solution for my own (in C++/CLI, but you can easily adapt it to C#):
public ref class EventArgsCTextBox1 : EventArgs
{
public:
String^ PreviousText;
};
public ref class CTextBox1 : Windows::Forms::TextBox
{
public:
virtual void OnEnter (EventArgs^ i_oEventArgs) override;
virtual void OnLeave (EventArgs^ i_oEventArgs) override;
delegate void EventHandlerCTextBox1 (Object^ i_oSender, EventArgsCTextBox1^ i_oEventArgs);
event EventHandlerCTextBox1^ LeaveChanged;
private:
String^ m_sValue;
};
void CTextBox1::OnEnter (System::EventArgs^ i_oEventArgs)
{
TextBox::OnEnter (i_oEventArgs);
m_sValue = this->Text;
}
void CTextBox1::OnLeave (System::EventArgs^ i_oEventArgs)
{
TextBox::OnLeave (i_oEventArgs);
if (m_sValue != this->Text)
{
EventArgsCTextBox1^ oEventArgs = gcnew EventArgsCTextBox1;
oEventArgs->PreviousText = m_sValue;
LeaveChanged (this, oEventArgs);
}
}