I want to create a reference for button click event which happens on another page in UWP project.
Page one (witch button):
private void Button_ItemClick(object sender, ItemClickEventArgs e)
{
// some code to reference to class
}
Class (in external file)
public static void DoSomething()
{
// Do something on Page 2 or run void on page 2
}
If the method is static, you can call it without an instance of the class. Suppose the method is defined in class OtherPage. You could then just do:
private void Button_ItemClick(object sender, ItemClickEventArgs e)
{
OtherPage.DoSomething();
}
In case the method is not static, you cannot easily do that, because there is only one Page instance in memory at a given time, when you have just one navigation Frame. It would make sense to put the method into a service class that would have a singleton instance and you could call it from anywhere. You can use MVVM framework like MvvmLight or MvvmCross to make this easier. These frameworks also maintain navigation stack of pages and their view models so you can potentially access view model instances in the navigation backstack.
Related
I have started learning MVVM for a project I'm writing, and I'm sketching out some of the more complicated parts of the project beforehand to help me get a better handle on how MVVM works. One of the biggest things I'm having trouble with though is dialogs, specifically custom dialogs and message boxes. Right now, I have a list of objects, and to add a new one, a button is pressed. This button calls a command in my ViewModel which invokes a Func that returns the object I want (Pile), then adds that to the list.
Here's that function
private void OnAdd()
{
Pile? pile = GetPileToAdd?.Invoke();
if (pile is null) return;
Piles.Add(pile);
}
This function is set in the view when the data context gets set (I'm implementing a Model-First architecture)
private void PileScreenView_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is PileScreenViewModel psvm)
{
psvm.GetPileToAdd = () =>
{
MessageBox.Show("getting pile");
return new Pile() { Name = "Name", Length = 0 };
};
}
}
The Messagebox.Show call will eventually get replaced with a custom dialog that will provide the data needed. My question then is:
Is this MVVM compliant? It feels a bit gross having to wait until the DataContext is changed to add the method to it, but I'm 99% sure that having the messagebox call in the ViewModel is a big no-no. Also not sure if I'm allowed to interact with the Model like this from the View.
Thanks for the help and for helping me with my MVVM journey :)
Your gut feeling is absolutely right: dialogs are components of the View as they interact with the user as part of the UI. Therefore, dialogs of any kind must be handled in the View.
Your current problem is that your View class PileScreenView depends on a particular View Model instance in order to register the callback.
Note that the callback itself violates MVVM as it delegates dialog handling to the View Model. The View Model must never execute or actively participate in any UI logic.
You can improve your design by making the dependency on the DataContext anonymous and independent (of the particular instance). This will also eliminate the need to observe DataContext changes.
Then move the dialog trigger to the View. Simply let the PileScreenView expose an "Add New Pile" button to the user.
This button will trigger the PileScreenView to show the dialog. Ideally, you would create a dedicated dialog view model class, that will hold the input data of the dialog.
Then use the dialog result (e.g., the dialog view model) and call e.g. CreatePile method on the PileScreenViewModel in order to pass the dialog result to the View Model.
The View Model can then create the actual Pile from the dialog view model class.
It's best if the View only knows View Model types by their interface:
IPileScreenViewModel.cs
interface IPileScreenViewModel : INotifyPropertyChanged
{
// Create the Pile in the View Model.
// View Model should never wait for anything.
// It is invoked by the View after the required data is collected.
void CreatePile(CreatePileViewDialogModel newPileInfo);
}
Then in the View you can show the dialog e.g. on click of a corresponding button. Dialogs an their logic should be generally designed to be triggered by the UI i.e. the user:
PileScreenView.xaml.cs
partial class PileScreenView : UserControl
{
private void OnCreatePileButtonClicked(object sender, RoutedEventArgs e)
{
var dialogViewModel = new CreatePileViewDialogModel();
var createPileDialog = new CreatePileDialog() { DataContext = dialogViewModel };
createPileDialog.ShowDialog();
(this.DataContext as IPileScreenViewModel)?.CreatePile(dialogViewModel);
}
}
The above example is a very simple to demonstrate how the interaction and data flow could look like. There are the usual options to send the data to the View Model, like data binding.
For example, PileScreenView could expose a dependency property e.g., CreatedPileScreenInfo. The PileScreenView would then assign the dialog result to this property, to which the PileScreenViewModel can bind to. This way the DataContext is completely unimportant for the PileScreenView.
Very important note: your current event handler introduces a potential memory leak. Since you are registering a lambda expression as event callback, you will not be able to unregister the handler. There is a chance that the garbage collector won't be able to collect the PileScreenView instance. The old PileScreenViewModel can keep it alive. You must therefore always take care to unregister event handlers. This should be your general practice to help you to guard against accidental leaks of this kind:
private void PileScreenView_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// Unregister handler from old view model instance
if (e.oldValue is PileScreenViewModel oldPsvm)
{
oldPsvm.GetPileToAdd -= OnGetPileToAdd;
}
if (e.NewValue is PileScreenViewModel newPsvm)
{
newPsvm.GetPileToAdd += OnGetPileToAdd;
}
}
private void OnGetPileToAdd(object sender, EventArgs e)
{
// TODO::Handle view model event
}
I suggest implementing a dialog service and injecting it into the view-model.
interface IDialogService
{
Pile? ShowPileDialog(/* any arguments that will be needed to show the dialog */);
}
class MessageBoxDialogService : IDialogService
{
public Pile? ShowPileDialog(/* arguments */)
{
MessageBox.Show("getting pile");
return new Pile() { Name = "Name", Length = 0 };
}
}
You need to register MessageBoxDialogService as the implementation of IDialogService in your DI container.
In the view-model:
class PileScreenViewModel
{
private IDialogService _dialogService;
public PileScriptViewModel(IDialogService dialogService)
{
_dialogService = dialogService;
}
private void OnAdd()
{
Pile? pile = _dialogService.GetPile(/* pass parameters from the view model */);
if (pile is null) return;
Piles.Add(pile);
}
}
It doesn't go against the principles of MVVM. As the view-model is decoupled from the view. You can inject a fake dialog service to test the view-model in isolation. By doing so, your code will also respect the open-closed principle, i.e., if the day comes and you decide to implement your custom dialog you only need to create a new dialog service class without needing to change existing classes.
I am trying to inherite a base global.asax class to create my custom global.asax class. But my custome inherited global class does not work properly. Its Application_Start wont been called.
Anyone knows whey?
public class BaseGlobal : HttpApplication
{
protected void Application_Start(Object sender, EventArgs e)
{
log4net.Config.XmlConfigurator.Configure();
Logger.Warn("Portal Started"); //I can find it log file
}
......
}
public class MyGlobal : BaseGlobal
{
new protected void Application_Start(Object sender, EventArgs e)
{
base.Application_Start(sender,e);
Logger.Warn("Portal Started 2"); // Can not find it in log file
}
}
<%# Application Codebehind="Global.asax.cs" Inherits="Membership_ABC.MyGlobal" Language="C#" %>
In the log file, I could not find "Portal started 2" but just "Portal Started".
Any ideas?
On startup of an application, the runtime takes the HttpApplication descendant that is pointed out by the Global.asax file, and creates an instance of it. The runtime does not know or care how the class got to be descended from HttpApplication, it just cares that it is, in fact a descendant.
After that it start calling method on it, treating it as a regular HttpApplication object. Since the new modifier effectively breaks the inheritance chain (it's just a new method that happens to share the old methods name) it is not called, but instead the method of the parent class is called. Basically you have this situation (pseudocode):
HttpApplication httpApp = new MyGlobal();
httpApp.Application_Start(..)
// ^^^ calls BaseGlobal.Application_Start(..)
//because the is not an unbroken chain from HttpApplication to MyGlobal
This is an example and consequence of the Brittle Base Class problem, a topic about which Eric Lippert has written in depth.
The solution is to declare the function virtual in the base class and then override it in the child class.
But as you can't edit base class to declare the Application_Start method virtual, it won't work :
Is it possible to override a non-virtual method?
The accepted answer gives an example that matches your case.
In Global, as an alternative to the application AutoEventWireups, it seems that events are exposed for most of the underlying Application events (BeginRequest, AuthorizeRequest, Error, etc), as well as a set of asynch methods like AddOnBeginRequestAsync etc. However, I cannot find an equivalent event for ApplicationStart!
So my question is, is there anyway to subscribe to the 'same' event that the AutoEventWireup method Application_(On)Start is hooked into?
public class Global : HttpApplication
{
public Global()
{
// I can do this ...
base.BeginRequest += new EventHandler(Global_BeginRequest);
// Or even AddOnBeginRequestAsync();
// But how can I do this?
base.ApplicationStart += new EventHandler(GlobalApplication_Start);
}
protected void Global_BeginRequest(object sender, EventArgs e)
{
// ...
}
protected void Global_ApplicationStart(object sender, EventArgs e)
{
// ...
}
}
(Out of interest ... is there a way to switch off the AutoEventWireups in Global.asax?. Using the AutoEventWireup = "false" attribute only seems to work on aspx pages)
Edit - it seems that ApplicationStart and ApplicationEnd "are special methods that do not represent HttpApplication events". So I might be barking up the wrong tree entirely.
Edit
Re : Why would I need this? Unfortunately, a corporate customer has a framework in place whereby new apps need to inherit their custom HttpApplication class, and FWR, their HttpApplication had already implemented the autowireup Application_(On)Start, meaning that I needed to find another way to override the Framework wireup, so that I can Bootstrap my IoC container and Automapper maps. As per Lloyd's answer, I could also bootstrap in the ctor or Init() as well, although this isn't quite the same. Ultimately I was able to change the corporate framework to allow multiple subscriptions.
You could override Init:
public class MyApplication : HttpApplication
{
public override void Init()
{
base.Init();
}
}
However your constructor could also work just as well.
Be really careful with the Init method.
Using Init method for code that you want to be executed once in the Application lifecycle is a bad option as the Init method is called once for every instance of the HttpApplication...
As you said ApplicationStart and ApplicationEnd are special methods that do not represent HttpApplication events but they work in a similar fashion to the Page events when the AutoEventWireups is set to true..
After jumping to the .NET source code i found that the HttpApplicationFactory class looks for a method named "Application_OnStart" or "Application_Start" in the Global.asax file and then invokes it using reflection => ReflectOnMethodInfoIfItLooksLikeEventHandler().
Check out my question about a similar subject: HttpApplication.Start event does not exist
I have a code behind file of an aspx file that looks like this:
public partial class Pages_MyPage : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
.....
}
protected int MyMethod()
{
.....
}
[WebMethod]
public static int MyPageMethod()
{
int x = MyMethod();
return x;
}
}
When I'm sending an ajax POST to MyPageMethod, I can't access MyMethod. What's the way around this issue.
Thanks for your suggestions.
MyMethod will also need to be static.
Think about what your trying to do here -
MyMethod belongs to a specific instance of a class.
MyPageMethod belongs to the class itself.
If your running code inside MyPageMethod, how could you possibly know how to call methods on some other instance of the object. The instance methods may as well not exist at that point in code.
If you are trying to mutate some portion of the page's data from javascript, you have a deep misunderstanding of how asp.net pages work.
At the point javascript is running in the browser, your page object is gone. The server finished the page load and discarded it. On the next post back it will create a new instance, and run through the page life cycle once again.
If you need to access page level state, you will have to store it in a place that is acceptable between post backs, the Session object for instance, with System.Web.HttpContext.Current
WebMethods are static methods because they don't get a full Page.
They can only call other static methods
If I override the System.Web.UI.Page constructor, as shown, when does DoSomething() get called in terms of the page lifecycle? I can't seem to find this documented anywhere.
namespace NameSpace1
{
public partial class MyClass : System.Web.UI.Page
{
public MyClass()
{
DoSomething();
}
protected void Page_Load(object sender, EventArgs e)
{
}
}
}
For reference, here is the ASP.NET Page Lifecycle Overview:
http://msdn.microsoft.com/en-us/library/ms178472.aspx
Turns out the best answer was right in the MSDN article. I just had to look carefully at the diagram. Construct is the very first event in the Page life cycle (comes before PreInit, Init, Load, etc).
Diagram http://img156.imageshack.us/img156/9246/lifecyclen.jpg
DoSomething(); will be called before member methods. That's not about Page Lifecycle actually. It's about classes and instances. ASP.NET creates an instance of MyClass. (Contructor is executed). After that any other member methods can be called.
To answer your question, an instance is created at step 10:
http://msdn.microsoft.com/en-us/library/ms178473.aspx
Scroll down to "The request is processed by the HttpApplication pipeline."