How to raise a generic event? - c#

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
}

Related

How to call a UserControl's methods from a Windows.Form

The form has a button and a panel with a UserControl which has a ListBox and a TextBox.
When I click the Windows.Form button it calls UserControl's Add()
listBoxTitles.Items.Add(metroTextBoxTitles.Text);
metroTextBoxTitles.Clear();
Which simply adds whatever the UserControl's TextBox.Text has to the UserControl's ListBox.
For some reason nothing happens when i click the button.
Why. Nothing on the UserControl can be changed or used? Or does it change but doesn't update/show what's going on?
The best way to deal with communication between containers is to implement an observer class
The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.
(wikipedia)
the way i do this is creating an Observer class:
1 public delegate void dlFuncToBeImplemented(int signal);
2 public static event dlFuncToBeImplemented OnFuncToBeImplemented;
3 public static void FuncToBeImplemented(int signal)
4 {
5 OnFuncToBeImplemented(signal);
6 }
so basically: first line says that there would be a function that somebody else will implement
second line is creating an event that occur when the delegated function will call
and the third line is the creation of the function that calls the event
so in your UserControl you should add a function like this:
private void ObserverRegister()//will contain all observer function registration
{
Observer.OnFuncToBeImplemented += Observer_OnFuncToBeImplemented;
/*and more observer function registration............*/
}
void Observer_OnFuncToBeImplemented(int signal)//the function that will occur when FuncToBeImplemented(signal) will call
{
MessageBox.Show("Signal received!", "Atention!", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
and in your Form you should do something like:
public static int signal = 0;
public void button1_Click(object sender, EventArgs e)
{
Observer.FuncToBeImplemented(signal);//will call the event in the user control
}
and now, you can register this function to a whole bunch of other controls and containers and they will all get the signal
I hope this would help :)
So, what was happening was, when you create a UserControl and add it to the Window.Form the Form's designer already initiates that UserControl:
private UserControl userControl1;
So in order to solve the issue i just needed to use the UserControl which the designer code created:
usercontrol1.add();
And everything is working perfectly.

Cannot raise InteractionRequest in OnNavigatedTo Method?

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.

Windows Forms Binding: is there an event similar to DataBindingComplete, but fired when ALL bindings are completed?

I need to change a certain DataGridView's property (a DataSourceUpdateMode for one of its binding) only when ALL of its initial data bindings are completed.
I tried subscribing to the "DataBindingComplete" event, but it's fired too many times (one or more time for each binding associated to the control); what I need is a more global "AllDataBindingsComplete" event, fired when the control is ready to be displayed to the user.
As a temporary workaround, I'm using the MouseDown event (I've assumed that when the user is able to click the control, it means that the control is displayed... :) and the events I'm playing with - SelectionChanged - are fired after the MouseDown):
protected override void OnMouseDown(MouseEventArgs e)
{
Binding selectedItemsBinding = this.DataBindings["SelectedItems"];
if (selectedItemsBinding != null)
{
selectedItemsBinding.DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged;
}
base.OnMouseDown(e);
}
It works, but it smells like an ugly hack A LOT (and it's called too many times, only one time is enough for my needs).
Is there a better way?
(yes, I'm trying to adopt MVVM in a Windows Forms project, and I've added a bindable "SelectedItems" property to the DataGridView...)
What I've done at the Windows Forms form level, and may be improvised down to just the control(s) you want, is to subclass the Windows Forms baseclass into my own. Then, in its constructor, attach an extra event call to the Load() event.
So when everything else is completely loaded, only THEN will it hit my custom method (of the subclass). Since it is the bottom of the call-stack chain being attached to the event queue, I know it's last and everything else is done... Here's a snippet of the concept.
public class MyForm : Form
{
public MyForm()
{
this.Load += AfterEverythingElseLoaded;
}
private void AfterEverythingElseLoaded(object sender, EventArgs e)
{
// Do my own things here...
}
}
This concept can be applied to the Init() function too if that's more appropriate for your control... Let everything else within it get initialized(), then do you the "AfterInitialized()" function.

giving classes click events C#

Hi i was just wondering if theres a way to give classes there own click event. For instance i have a card class, and is there a way of knowing when the user clicks on the rectangle (that displays the picture of the card) from that class?
or better yet, how do i go about knowing when the cards rectangle is clicked?
To get "mouse was clicked here" messages from Windows, you need to have a window handle; in WinForms, anything deriving from Windows.Forms.Control will have a window handle, and it will get mouse messages. Those messages will be translated automatically into invocations of the .NET MouseDown, MouseUp, MouseClick etc. events.
So probably your card should be a control. If it's not (e.g. if you have a "Hand" control that is responsible for managing and drawing lots of cards) then that control needs to be the one that gets mouse events (e.g. MouseClick) and figures out which card actually got clicked based on the context and the coordinates of the mouse event.
There are actually two parts of your problem: first is creating the event, and second is hooking it up to something.
Creating the event is the easy part:
public class Card {
public event EventHandler<MouseEventArgs> Click;
protected void OnClick(MouseEventArgs e) {
EventHandler<MouseEventArgs> handler = Click;
if(handler != null) {
handler(this, e);
}
}
internal void CheckIfClicked(MouseEventArgs e) {
if(/* this mouse event intersects this Card */) {
OnClick(e);
}
}
}
Then elsewhere you can set up subscribers for that event, like usual:
public class CardWatcher {
private Card card;
public CardWatcher(Card card) {
this.card = card;
this.card.Click += card_Clicked;
}
private void card_Clicked(object sender, MouseEventArgs e) {
// Our Card has been clicked; do something about it
}
}
(Note that we're attaching an event handler manually, but this is pretty much exactly the same code the Visual Studio designer would generate if you used the GUI to attach an event handler to a control.)
And then you need to somewhere capture those click events (actually, this seems like the crux of your issue):
public partial class CardContainer : UserControl {
private List<Card> cards = new List<Card>();
public CardContainer() {
InitializeComponent();
// Initialize cards here...
}
protected override void OnMouseUp(MouseEventArgs e) {
foreach(Card card in cards) {
card.CheckIfClicked(e);
}
base.OnMouseUp(e);
}
}
Notice that there are normally two ways of responding to an event:
Create a subclass class, and override the behavior of the OnEvent method
Attach an event handler delegate
As other answers have pointed out, the Windows operating system takes care of delivering events to objects that are windows (that is, objects that have a window handle). Within .NET, this means objects that are subclasses of Control.
It's easy enough to create such classes yourself, either by subclassing Control directly or by subclassing something like UserControl, which then allows you to create controls that can be containers for other controls that you create in the design view (the same way you create forms).
So, your choices are the following:
Make Card a control itself; then it can be positioned on forms and receive click events (and other events) directly. The drawback is that this can use a lot of resources if you have a lot of these elements active at one time, since each allocates a window handle from Windows.
Make a container for Cards that is a control. This container control can be positioned on forms and receive click events (and other events) directly. The drawback is that you have to figure out manually how to delegate those click events on to the individual cards. Since the actual Click event doesn't carry coordinates with it, you'll probably have to handle the MouseUp event instead.
Put your Cards into some other container (like a Form) and attach a handler to that container's Click (or MouseUp) event. In that handler, figure out if any of your Cards have been clicked on, and delegate the click events accordingly.
Note that all of this is independent of whether or not the Card itself has a Click event that users of the Card can subscribe to. If you look at the first code sample in this answer, there's no reason why the CheckIfClicked method on Card has to actually fire a real event, unless you have classes like CardWatcher which are interested in knowing about click events on Cards. If only the Card itself ever cares about the click event, then you can simply create a method like Card.HasBeenClicked(MouseEventArgs e), put your click-handling code there, and let CheckIfClicked call it directly.
You can easily add events to your class, but the only way the class can know when something was clicked is when it is, or has a component that is, based on a window.
That's what controls are for. If you need to write a class that can detect such events, I would recommend creating a control.

Handling the "X" close button in WPF under MVVM

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)

Categories