I'm navigating to a view when its module gets loaded:
public void OnImportsSatisfied()
{
this.ModuleManager.LoadModuleCompleted +=
(s, e) =>
{
if (e.ModuleInfo.ModuleName == EmailModuleName)
{
this.RegionManager.RequestNavigate(
RegionNames.MainContentRegion,
InboxViewUri);
}
};
}
when I navigate to this view I want to raise a NotificationRequest (and his PopupWindow); this method is defined in the ViewModel:
void INavigationAware.OnNavigatedTo(NavigationContext navigationContext)
{
MyInteractionRequest.Raise(...);
}
Through debugging I discovered that the InteractionRequest has no subscribers to its Raised event for the first time I navigate to the view, and that's why no interaction window is shown. In order to make the InteractionRequest work I need to navigate to another view and then go back.
Why does this happend? Does the view get created too late after the viewmodel and so it cant subscribe the InteractionTrigger to the Interaction Request?
My guess is that the view has not been initialized by the time you raise the request. This means no bindings have been created and no handlers have been added. You approach in general is bad practice. If you must show a popup when the page is first show, add an event to command behavior for the view's loaded event and show your popup there. This ensures that the pages has been fully loaded and all bindings have been made.
Related
I have a Page in which I have registered CharacterReceived event as below:
Window.Current.CoreWindow.CharacterReceived += CoreWindow_CharacterReceived;
Also on the same page, there's a usercontrol which acts as a popup windows. In that user control I have another key handling event as below:
Window.Current.CoreWindow.Dispatcher.AcceleratorKeyActivated += PageCoreDispatcher_AcceleratorKeyActivated;
Now when this flyout is open and any key is pressed both the event's is getting fired. Is there any way to disable parent's CharacterReceived event and than enable it again.
I know it can be achieved by unregistering CharacterReceived event when flyout is open and register again when flyout is closed, but to do that I have to do some workaround for complex logic. I want to unregister CharacterReceived from the UserControl itself.
The events themselves are different. If you used AcceleratorKeyActivated in both places for example, you could use the AcceleratorKeyEventArgs.Handled property to stop the event propagation once it was handled in the UserControl.
If that is not an option for you, you could solve the problem by exposing some method on your page, that will start and stop observing the events:
public void StartObserving() =>
Window.Current.CoreWindow.CharacterReceived += CoreWindow_CharacterReceived;
public void StopObserving() =>
Window.Current.CoreWindow.CharacterReceived -= CoreWindow_CharacterReceived;
Now you can call these methods from your user control when appropriate. To get the instance of currently displayed page, you could use the Window.Current property:
var rootFrame = Window.Current.Content as Frame; //if your window contains a frame on top
var page = rootFrame.Content as MyPageType; //where MyPageType is the type of your page
page.StopObserving();
Background
The question title might be a little misleading, but I'm not sure how to quickly ask the question. I'm building a winforms app and following the MVP design pattern, with passive views. In the main view (form) for my application, there is a navigation panel that hosts buttons that when clicked will open another view (form). I'm attempting to create generics buttons:
// The type T represents the view (form) that should be opened when the button is clicked
NavigationButton<T>
The presenter for the main view creates each button individually at run time:
// Code in Main Presenter - register each button
View.RegisterNavigationButton(new NavigationButton<IViewExample1>("Example 1")); // Pass in text to show on button
View.RegisterNavigationButton(new NavigationButton<IViewExample1>("Example 2"));
// Code in View
public void RegisterNavigationButton<T>(NavigationButton<T> button) where T : class, IView
{
// Add button to flow layout panel
_flpNavigation.Controls.Add(button);
// Subscribe to click event
button.Clicked += ButtonClicked<T>;
}
Now, if this wasn't a passive view, when a navigation button was clicked, you could potentially go ahead and create the new view directly from the main view:
// This is the method subscribed to the click event as shown in the above code
private void ButtonClicked<T>(object sender, EventArgs e) where T : class, IView
{
// The ApplicationController creates the new view (form) using an IoC container (Simple Injector)
ApplicationController.ShowModelessForm<T>();
}
But, this is a passive view, so it shouldn't be creating new views (forms)...
Each of my views implements an interface, and the presenters hold a reference to its accompanying view via that interface. The view interfaces define events that the presenter can subscribe to. In other words, the presenter can call methods directly from the view as long as the method is defined in the interface, but the view must raise events to communicate to the presenter.
Question
Considering this setup, how would I go about communicating to the presenter that it should be creating a new view? Each of my views implements an interface, and the presenters hold a reference to its accompanying view via that interface. The view interfaces define events that the presenter can subscribe to. Is it possible to set up some kind of event(s) to communicate the presenter to create a view given my generic setup?
** EDIT **
My road block is that I don't know how to define the events to raise, and how to raise them. Say I have two secondary views that I want to open, defined by the following views: IView1 and IView2. Do I have to have two separate eventhandlers defined in my main view, one for each secondary view? And then, once the button is clicked, how do I raise the appropriate event?
You decide what you need for event handlers. Look at the purpose of the button and raise the appropriate event. If you have multiple buttons with the same purpose, have them raise the same event. If you have another action that has the same purpose as the button, again have it raise the same event.
The event you raise is linked to the button's purpose and is basically ignorant of any things that may happen because the event was raised.
The event could be more literal "Details Button Clicked" or a bit more abstract "Detailed Data Requested".
On the subject of how to raise the event, https://msdn.microsoft.com/en-us/library/edzehd2t(v=vs.110).aspx has an example of a fairly standard technique to raise an event. (Paraphrased below)
class Counter
{
public event EventHandler DetailsButtonClicked;
protected virtual void OnDetailsButtonClicked(EventArgs e)
{
if (DetailsButtonClicked != null)
{
DetailsButtonClicked(this, e);
}
}
// provide remaining implementation for the class
}
In the presenter, subscribe to the event and perform the action, such as opening a new view.
More info on passing messages when raising events
You can use the generic EventHandler to pass messages when you call events. I would just encourage you to evaluate the code you come up with for readability and ability to easily refactor.
For example, avoid sending a string message that means something programmatically. Instead send an enum or constant value.
public void TryIt()
{
var z = new Counter();
z.DetailsButtonClicked += Z_DetailsButtonClicked;
z.OnDetailsButtonClicked("Greetings Earthlings");
}
private void Z_DetailsButtonClicked(object sender, CustomEventArgs e)
{
Debug.Print(e.Message);
}
public class CustomEventArgs : EventArgs
{
public CustomEventArgs(string message) { this.Message = message; }
public string Message { get; set; }
}
class Counter
{
public event EventHandler<CustomEventArgs> DetailsButtonClicked;
public virtual void OnDetailsButtonClicked(string message)
{
if (DetailsButtonClicked != null)
{
DetailsButtonClicked(this, new CustomEventArgs(message));
}
}
// provide remaining implementation for the class
}
I am working with an ASP.NET project that was created by a team elsewhere in the company. One of the pages that I am working with features a userControl that queries the database to populate a generic field. The information from this query is then used by the parent page (that is, the page that contains the userControl) to set the selectedIndex of a dropdown.
The issue that I am having is that the parent page loads before the userControl, which means that the parent page loads before the results of the query are available. As a result, the selectedIndex of the dropdown is only ever set to a default index.
Based on jrista's answer to ASP .NET: when UserControls loads? , I understand that the page.OnLoad event fires before the userControl.OnLoad event. I also understand that there is no OnLoadComplete event for user controls, which I was hoping I could use to make an event that fires on the parent page when the user control finishes loading.
I've heard that the Pre-Render event can be used to the same effect (based on this question over on the asp forums), but is this the only approach? Is there a better way to fire an event from a userControl to the parent page when the user control finishes loading? Or is this simply not a good practice and would I be better to somehow change the code to avoid this situation?
Start by creating an interface that your page will implement like so:
public interface INotifyChildLoaded
{
void OnUserControlLoaded(Control control);
}
Then, implement that interface on your page
public class MyPage : Page, INotifyChildLoaded
{
public void OnUserControlLoaded(Control control) { ... }
....
}
Finally, during your user control load event, call the method:
public void MyControl_OnLoad(object sender, EventArgs e)
{
....
(Page as INotifyChildLoaded).OnUserControlLoaded(this);
}
I'm creating a basic database application in WPF, and I have started using the MVVM pattern.
I have a dialog that asks the user to select an item from a ListBox and click "OK." After that, I take the item the user clicked from a property in the view model and pass it into another dialog. But if the user clicks "Cancel," I set that value to null, and the action is canceled: I don't open the next dialog and return to the main screen. Example:
public class SelectEquipmentViewModel : WorkspaceViewModel
{
private bool _selected;
public Equipment SelectedEquipment
{
// Item selected by the user
}
// Action for "SelectCommand," which is attached to
// the "Select" button in the view
public void ExecuteSelect()
{
_selected = true;
// Fires a RequestClose event in WorkspaceViewModel,
// which is attached to the view's Close method
RequestClose();
}
public override void RequestClose()
{
if (!_selected)
{
// The user clicked "Cancel"
SelectedEquipment = null;
}
base.RequestClose();
}
}
This has been working great, but the problem comes if the user clicks the red "X" close button in the window's control box. The RequestClose method never gets invoked, and the selected item isn't set to null, which is bad.
I've considered attaching the view model to the Closing event of the view, but I feel this could get messy if I start creating handlers for all these events.
What would be the "preferred" way of handling this situation?
Thanks.
I think that using the EventToCommand behavior to wire up the Window object's Closing event to a new ExecuteCancel command is pretty clean.
public void ExecuteCancel()
{
_selected = false;
// Fires a RequestClose event in WorkspaceViewModel,
// which is attached to the view's Close method
RequestClose();
}
Where do you think this will get messy? If you add a Cancel button, it could use the same ExecuteCancel bits...
Behaviors are what you want to use to execute a command when the user presses the "X" button on window using MVVM. Check out Reed Copsey's blog here: http://reedcopsey.com/2009/10/09/using-behaviors-to-allow-the-viewmodel-to-manage-view-lifetime-in-m-v-vm/
You can download a sample application here...
I use this method all the time to allow the ViewModel manage the life of the view.
Approach without additional dependencies is described in article Handling a Window's Closed and Closing events in the View-Model and code with example provided. This does not add code behind xaml.
(Thanks to Reed Copsey's link)
I have a page and a user control — we'll call them Detail.aspx and Selector.ascx.
Let's say the page shows the details of individual records in a database. The user control basically consists of a DropDownList control and some associated HTML. The DropDownList displays a list of other records to switch to at any time.
When the DropDownList fires its SelectedIndexChanged event, I'd like the parent page, Detail.aspx in this case, to handle it. After all, he'll need to know what was selected so that he can appropriately change the URL and the details shown, etc.
To do that, I've done what I usually do, which is also what the top answer says to do in this StackOverflow question:
public event EventHandler DropDownSelectedIndexChanged
{
add
{
MyDropDownList.SelectedIndexChanged += value;
}
remove
{
MyDropDownList.SelectedIndexChanged -= value;
}
}
The above code appears in the Selector.ascx.cs codebehind file.
As a result, on Detail.aspx, I can use it like so:
<cc1:RecordSelector ID="RecordSelector1" runat="server"
OnDropDownSelectedIndexChanged="RecordSelector1_DropDownSelectedIndexChanged" />
So far nothing fancy or surprising.
Here is my problem:
This causes a NullReferenceException when the browser hits Detail.aspx.
Debugging the problem shows that when the page is first hit, the public event I've shown above tries to add the event, but MyDropDownList is null, thus throwing the exception. From what I can tell, the events are added (or attempted to be added) before the Selector user control's Load event fires and thus also before the DropDownList's Load event fires.
Curiously, if I omit the OnDropDownSelectedIndexChanged attribute from Detail.aspx and instead put the following in the Page_Load event in Detail.aspx.cs:
protected void Page_Load(object sender, EventArgs e)
{
RecordSelector1.DropDownSelectedIndexChanged += new EventHandler(RecordSelector1_DropDownSelectedIndexChanged);
}
It works exactly as expected. The events are attached and handled just fine. No problems.
But this means several bad things:
I have to remember not to use the designer to add said event onto my user control
I have to remember not to add the event via attributes when working in source view
Worst of all, as the control's author I need to make sure everybody else using my control knows 1 and 2
So what am I doing wrong? Every example I've seen thus far shows similar usage of exposing child controls' events through a user control.
The reason this works:
protected void Page_Load(object sender, EventArgs e)
{
RecordSelector1.DropDownSelectedIndexChanged
+= new EventHandler(RecordSelector1_DropDownSelectedIndexChanged);
}
and this does not:
<cc1:RecordSelector ID="RecordSelector1" runat="server"
OnDropDownSelectedIndexChanged="RecordSelector1_DropDownSelectedIndexChanged" />
is because the first one adds the handler after the control has been initialized (via the page's Init). The second example gets parsed much earlier and as such the page is attempting to add the handler before the control has initialized.
Due to the nature of the page's life cycle I think you may have to live with adding the event handler in the code-behind. There will be no way to add the handler before the control is initialized because that control will always be null prior to initialization.