How to bubble an event up multiple levels? - c#

I'm struggling to bubble an event correctly.
I have a master page with a user control, and a page that is a child of the master page.
The user control and the page share common data, so when the user control updates, it updates the apage and vice versa.
The user control exposed an event to the master page. This is the format I use.
outside of class:
public delegate void OfferBookmarkRemoved(int OfferID);
inside class:
public event OfferBookmarkRemoved OfferBookmarkRemoved;
protected void LV_Bookmarks_ItemCommand(object source, ListViewCommandEventArgs e)
{
if (e.CommandName == "RemoveOffer")
{
var offerId = (int)e.CommandArgument;
OnOfferBookmarkRemoved(offerId);
}
}
void OnOfferBookmarkRemoved(int offerId)
{
offerId.ThrowDefault("offerId");
if (OfferBookmarkRemoved != null)
{
OfferBookmarkRemoved(offerId);
}
}
Now this can be used in the master page ok. I don't do anything in the master page and want to expose the event so that the aspx page can use it, like this:
Master.OfferBookmarkRemoved += OnBookmarkRemoved;
void OnBookmarkRemoved(int offerId)
{
offerId.ThrowDefault("offerId");
OfferList1.UpdateBookmark(offerId);
}
So the missing bit is to listen for the event in the master and make it available to the page.
Can anyone help?

You need to define this event in the master page also like that:
public event EventHandler<OfferEventArgs> OfferBookmarkRemoved
{
add
{
userControl.OfferBookmarkRemoved += value;
}
remove
{
userControl.OfferBookmarkRemoved -= value;
}
}
This way any page that registers to the master event will be registered to the usercontrol event.
By the way, you are not following the event pattern. Your event should look like:
public class OfferEventArgs : EventArgs
{
public int OfferID { get; set; }
}
public event EventHandler<OfferEventArgs> OfferBookmarkRemoved;
and when invoked:
OfferBookmarkRemoved(new OfferEventArgs() { OfferID = offerId });

Related

Why is my Presenter not subscribed to my View event in my WinForms project?

I am trying to implement the MVP pattern into my WinForms project. However, the method 'Activate' in my Presenter that is subscribed to my 'ActivatedForm' event from my View, does not seem to fire when i load the form. I have tested it simply by printing someting in the 'Activate' method. Why is this not working properly?
I have posted my code below.
I think it has something to do with the fact that I am creating the Presenter with the concrete View even though the _view attribute is of the interface type 'IHomeScreenView'.
I know that the 'HomeScreenView_Activated' event occurs, because I have put a print in there too and that worked.
The 'ActivatedForm' event just always returns null there which means that nothing is subscribed to the event.
IHomeScreenView.cs
public interface IHomeScreenView
{
List<string> ExistingAssessments { get; set; }
event EventHandler<EventArgs> ActivatedForm;
event EventHandler<EventArgs> CreatingNewAssessment;
event EventHandler<EventArgs> AddingNewStandard;
event EventHandler<EventArgs> OpeningAssessment;
}
HomeScreenView.cs
public partial class HomeScreenView : Form, IHomeScreenView
{
private HomeScreenPresenter homeScreenPresenter;
public List<string> ExistingAssessments
{
get { return recentAssessments.Items.Cast<string>().ToList(); }
set { recentAssessments.DataSource = value; }
}
public event EventHandler<EventArgs> ActivatedForm;
public event EventHandler<EventArgs> CreatingNewAssessment;
public event EventHandler<EventArgs> AddingNewStandard;
public event EventHandler<EventArgs> OpeningAssessment;
// Initialize homescreen.
public HomeScreenView()
{
InitializeComponent();
}
// Fires the activating form event.
private void HomeScreenView_Activated(object sender, EventArgs e)
{
ActivatedForm?.Invoke(this, EventArgs.Empty);
}
HomeScreenPresenter.cs
public class HomeScreenPresenter
{
private IHomeScreenView _view;
private AssessmentsModel _assessmentsModel;
public HomeScreenPresenter(IHomeScreenView view)
{
_assessmentsModel = new AssessmentsModel();
_view = view;
_view.ActivatedForm += Activate;
_view.CreatingNewAssessment += CreateNewAssessment;
_view.AddingNewStandard += AddNewStandard;
_view.OpeningAssessment += OpenAssessment;
}
public void Activate(object sender, EventArgs e)
{
Debug.Print("hi");
HashSet<string> items = new HashSet<string>(_assessmentsModel.GetDataList("Assessments", "assessment_name"));
List<string> assessments = items.ToList();
_view.ExistingAssessments = assessments;
}
I hope someone can help, thank you.
The Form.Activated event is only fired when the form is visible. See the documentation.
When the application is active and has multiple forms, the active form is the form with the input focus. A form that is not visible cannot be the active form. The simplest way to activate a visible form is to click it or use an appropriate keyboard combination.
If your form is already visible when the presenter is being created, then the activated event has already fired. You could call Form.Activate() once the presenter is created and the eventhandler is hooked up.

Call parent from custom control

I have a form, on this form is a flowlayoutpanel with multiple custom made TextBoxes
The form overrides the base methode Refresh(), to do some other things also.
Now I'm digging into the parent to eventueally come on the form and do the refresh
this.Parent.Parent.Parent.Refresh();
I want to re-use the control on other forms, so is there another way to do this?
And I know a While(true) is possible:
Boolean diggToParent = true;
var parent = this.Parent;
while (diggToParent)
{
if (parent.Parent != null)
{
parent = parent.Parent;
}
else
break;
}
parent.Refresh();
But is there a cleaner way to do this?
You can solve this by creating and raising an event that is handled by the parent form:
public class MyUserControl : UserControl
{
// ...
public event EventHandler RequestRefresh;
// Call this method whenever you want the parent to refresh
private void OnRequestRefresh()
{
if (RequestRefresh != null)
RequestRefresh(this, EventArgs.Empty);
}
}
In the parent form (or the container that should be refreshed), you add an event handler, e.g.
public class MyParentForm : Form
{
public MyParentForm()
{
InitializeComponent();
userCtrl.RequestRefresh += userCtrl_RequestRefresh;
}
// Do whatever the parent thinks is necessary to refresh.
public void userCtrl_RequestRefresh(object sender, EventArgs e)
{
Refresh();
}
// ...
}
This way the parent form can decide what to do when the user control requests a refresh. For details on events, see this link.

How can I create a custom event?

I have a custom set of UserControls: NavigationBar and NavigationItem.
I'd like that whenever the user clicks anywhere in the NavigationItem, an event is fired. I don't know how to set this up.
http://i.stack.imgur.com/ocP2D.jpg
I've tried this:
public partial class NavigationBar : UserControl
{
public NavigationBar()
{
InitializeComponent();
SetupEvents();
}
public List<NavigationItem> NavigationItems { private get; set; }
public NavigationItem SelectedItem { get; set; }
private void SetupEvents()
{
navigationItem1.Click += new EventHandler(navigationItemClick);
}
void navigationItemClick(object sender, EventArgs e)
{
MessageBox.Show("Clicked on " + sender.ToString());
}
}
But that event only fires when the user specifically clicks on the NavigationItem control, but not when he clicks on the picture or text. (Those are PictureBox and Label).
What would be the best course of action? I'd like to create something well, not hacky code. Thanks!
Put something like this into your class:
public event EventHandler NavigationItemClick;
This creates a new event in your class named NavigationItemClick. The form designer will even see it.
In your method navigationItemClick you can do this to call the event.
EventHandler handler = this.NavigationItemClick;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
It is important to save the event into the handler variable to avoid race conditions. EventHandler is a delegate, so you call it like a method, hence the line in the if statement. The if itself makes sure that someone has attached to your event.

Custom Paging Repeater Control next and previous events happen after databind

I have a Custom Repeater control that inherits from Repeater and has paging functionality, however when I click the next page button the first time it refreshes the control but does not change the page, if I click it again after that it changes page perfectly.
I know what the issue is, when I click the next button it does a postback, then the data is bound to the repeater, and then after that the NextButton Event is handled.
Is there any way I can change the order of the page load events?? Or force the repeater to reload again after the event is handled??
I've included my Custom Repeater class bellow:
using System.Web.UI.WebControls;
using System.Web.UI;
using System.Data;
using System.Collections;
using System;
namespace ASPresentation.Controls
{
[ToolboxData("<cc:PagedRepeater runat=server></cc:PagedRepeater>")]
public class PagedRepeater : Repeater
{
public int PageSize { get; set; }
public int CurrentPageIndex
{
get
{
return Convert.ToInt16(Page.Session["ProjectIndex"]);
}
set
{
Page.Session.Add("ProjectIndex", value);
}
}
public PagedDataSource pagedData = new PagedDataSource();
LinkButton NextBtn = new LinkButton();
LinkButton PrevBtn = new LinkButton();
public bool IsLastPage
{
get
{
return pagedData.IsLastPage;
}
}
public bool IsFirstPage
{
get
{
return pagedData.IsFirstPage;
}
}
public override object DataSource
{
get
{
return base.DataSource;
}
set
{
pagedData.DataSource = (IEnumerable)value;
}
}
protected void NextButtonClick(object sender, EventArgs e)
{
if (!IsLastPage)
{
CurrentPageIndex++;
}
}
protected void PrevButtonClick(object sender, EventArgs e)
{
if (!IsFirstPage)
{
CurrentPageIndex--;
}
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
NextBtn.Text = "Next";
PrevBtn.Text = "Prev";
NextBtn.Click += new EventHandler(NextButtonClick);
PrevBtn.Click += new EventHandler(PrevButtonClick);
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
base.Controls.Add(PrevBtn);
base.Controls.Add(NextBtn);
}
protected override void Render(HtmlTextWriter writer)
{
base.Render(writer);
}
public override void DataBind()
{
pagedData.AllowPaging = true;
pagedData.PageSize = PageSize;
pagedData.CurrentPageIndex = CurrentPageIndex;
base.DataSource = pagedData;
base.DataBind();
}
}
}
A couple issues here that jump out at me.
One, why are you dynamically creating the prev/next button? Just put them in the ASCX. Show/hide them if you like based on whether your page index is first/last. If you must create them dynamically, do so in Init...
Two, do not store your page index in the session like that. What happens when you have two of these custom repeaters on one page? Use the ViewState. Key the string name to the control ID if necessary, but I think ViewState does this automatically(?).
Finally, what is triggering the DataBind? What event handler? It must be called from the Page that is hosting this control. If that's the case, then you also need to expose the Next/Prev clicks as events so that DataBind can be called in response to these events. This is how Microsoft's controls that handle paging work, such as the GridView. NextBtn.Click or PrevBtn.Click is guaranteed to be the last postback event handled.
You could handle the next/prev internally, but if you're going to do that you need to also call DataBind() in your code, so that it happens at the correct time.
Call "this.DataBind()" in the Page change functions.
This will mean you databind twice when changing pages but will work :-\

How to enable ajax when deriving from System.Web.UI.WebControls.WebControl on controls that are created inside WebControl

I've built a class that derives from System.Web.UI.WebControl. It basically renders pagination links (same as what you see on top of GridView when enabled) for use above a repeater.
I'm creating some anchor tags explicitly inside my nav control obviously, but they don't perform ajax postbacks. My understanding is that ajax requires POSTS to work right? Well, these would be GETs which I think is the problem.
Is there a way to achieve what I'm trying to do?
Thanks!
To take this advantage, you have to inherit the ICallbackEventHandler and implement its methods as follows.
public class CtlTest : WebControl, ICallbackEventHandler
{
private static readonly object EventClick = new object();
public CtlTest() : base(HtmlTextWriterTag.A) { }
public event EventHandler Click
{
add { base.Events.AddHandler(EventClick, value); }
remove { base.Events.RemoveHandler(EventClick, value); }
}
protected override void AddAttributesToRender(HtmlTextWriter writer)
{
base.AddAttributesToRender(writer);
writer.AddAttribute(HtmlTextWriterAttribute.Href, "javascript:" + this.Page.ClientScript.GetCallbackEventReference(this, null, "null", null));
}
protected override void RenderContents(HtmlTextWriter writer)
{
base.RenderContents(writer);
writer.Write("Submit Query");
}
protected virtual void OnClick(EventArgs e)
{
EventHandler handler = this.Events[EventClick] as EventHandler;
if (handler != null)
handler(this, e);
}
#region ICallbackEventHandler Members
string ICallbackEventHandler.GetCallbackResult()
{
return string.Empty;
}
void ICallbackEventHandler.RaiseCallbackEvent(string eventArgument)
{
this.OnClick(EventArgs.Empty);
}
#endregion
}
Whereas you are working on a data pager control and it requires to update some portions of the page, it's better to write a non Ajax enabled control and put it and its relative controls within an UpdatePanel.
Ok, I figured it out. I simply made my class implement the IPostBackEventHandler. This makes your control fire an event when the user takes action on something. In my case, it's clicking a nav pagenumber: [1][2][3][4][5][Next >]
Then, inside my render where I create the Anchor tags, I add this to each href (pageStartRow is different for each):
PostBackOptions options = new PostBackOptions(this, pageStartRow.ToString());
writer.AddAttribute(HtmlTextWriterAttribute.Href, "javascript:" + Page.ClientScript.GetPostBackEventReference(options));
writer.RenderBeginTag(HtmlTextWriterTag.A);
The key is to pass something that uniquely identifies which link they clicked. This is done as the 2nd constructor parameter to the PostBackOptions class.
I then added the following items in my WebControl class:
// Defines the Click event.
public delegate void ClickHandler(object sender, GridPagingNavClickedEventArgs e);
public event ClickHandler Click;
//Invoke delegates registered with the Click event.
protected virtual void OnClick(GridPagingNavClickedEventArgs e)
{
if (Click != null)
{
Click(this, e);
}
}
public void RaisePostBackEvent(string eventArgument)
{
GridPagingNavClickedEventArgs e = new GridPagingNavClickedEventArgs(Convert.ToInt32(eventArgument));
OnClick(e);
}
The GridPagingNavClickedEventArgs contains a single item (pageNumber in my case).
Finally, in my aspx page (where I use the webcontrol), I do this in the Page_OnLoad:
gridNavTop.Click += new GridPagingNavigation.ClickHandler(gridNavTop_Click);
and this is the event code:
private void gridNavTop_Click(object sender, GridPagingNavClickedEventArgs e)
{
StartRow = e.PageStartRow;
}
As long as everything is inside an UpdatePanel, then it works perfectly!

Categories

Resources