I have a page where I'm adding ImageButtons dynamically. I first set the OnClientClick of the buttons to simply show a popup of the image enlarged and return false for no postback.
I have a button on the page to set the "primary image" so when this button is clicked I set a property called _IsSettingPrimaryPhotoMode = true, call the function to recreate the ImageButtons, and when creating the ImageButtons if this property is true instead of adding an OnClientClick, I hook up a CommandEventHandler so I can tell which button was clicked by reading the CommandArgument.
The problem is the event handler won't fire on the first click of the image but only on the second click and thereafter. I've also moved the code from Page_Load to OnInit and I load the ImageButtons on every postback.
I save the _IsSettingPrimaryPhotoMode to the Session.
private bool _IsSettingPrimaryPhotoMode {
get {
bool result = false;
if(Session[ConstantsWeb.Session.IS_DELETE_IMAGE_MODE] != null) {
result = Convert.ToBoolean(Session[ConstantsWeb.Session.IS_SETTING_PRIMARY_IMAGE_MODE]);
}
return result;
}
set {
Session[ConstantsWeb.Session.IS_SETTING_PRIMARY_IMAGE_MODE] = value;
}
}
The page OnInit
protected override void OnInit(EventArgs e) {
base.OnInit(e);
if(!IsPostBack) {
_IsSettingPrimaryPhotoMode = false;
}
_LoadGalleryImages();
}
}
The _LoadGalleryImages method
private void _LoadGalleryImages() {
PhotoGalleryImageCollection images = PhotoGalleryImages.GetPhotoGalleryImages();
foreach(PhotoGalleryImage image in images) {
ImageButton displayImage = new ImageButton();
Panel panel = new Panel();
panelPhotoContainer.Controls.Add(panel);
displayImage.ImageUrl = "some URL";
if(!_IsSettingPrimaryPhotoMode) {
displayImage.OnClientClick = "showPopup(); return false;";
}
else {
displayImage.Command += new CommandEventHandler(displayImage_Command);
displayImage.CommandName = "ImageButton" + image.PhotoGalleryImageId.ToString();
displayImage.CommandArgument = image.PhotoGalleryImageId.ToString();
}
panel.Controls.Add(displayImage);
}
}
btnSetPrimaryPhoto_Click
protected void btnSetPrimaryPhoto_Click(object sender, EventArgs e) {
// if I don't call this, duplicate controls will be added since they were added
// from OnInit calling _LoadGalleryImages();
panelPhotoContainer.Controls.Clear();
_IsSettingPrimaryPhotoMode = true;
// reload since _IsSettingPrimaryPhotoMode has now changed
_LoadGalleryImages();
}
What am I doing wrong?
I suppose the issue can be caused by the fact you haven't set IDs of the dynamically created controls. It's quite important, as it's used in the process of firing the post back events. Value of the ID for each of the controls should be constant and not change between postbacks.
You can try to modify your _LoadGalleryImages() method like this:
private void _LoadGalleryImages() {
PhotoGalleryImageCollection images = PhotoGalleryImages.GetPhotoGalleryImages();
int imageCtrlCounter = 0;
foreach(PhotoGalleryImage image in images) {
ImageButton displayImage = new ImageButton() { ID = String.Format("myDisplayImage{0}", imageCtrlCounter) };
Panel panel = new Panel();
panelPhotoContainer.Controls.Add(panel);
displayImage.ImageUrl = "some URL";
if(!_IsSettingPrimaryPhotoMode) {
displayImage.OnClientClick = "showPopup(); return false;";
}
else {
displayImage.Command += new CommandEventHandler(displayImage_Command);
displayImage.CommandName = "ImageButton" + image.PhotoGalleryImageId.ToString();
displayImage.CommandArgument = image.PhotoGalleryImageId.ToString();
}
panel.Controls.Add(displayImage);
imageCtrlCounter++;
}
}
It has to be something related to the initial event wireup because the problem only happens the first time. What's different about that time is that _LoadGalleryImages is called twice (once in the init and once in the button click event handler) so I think that something is not getting cleaned up there when you clear the container panel and call _LoadGalleryImages again in the button's click handler.
Why not try this alternative:
Only call LoadImageGalleries once per page cycle (in the init).
Instead of clearing the controls and calling LoadGalleryImages again in the same postback (in the button click event), on the button click call a method that iterates through the image controls you already created when LoadGalleryImages was called and adjust them:
1) Remove the onclientclick (clear it out).
2) Attach the event.
#swannee
Actually, your method did work after I thought more about it. I do now call _LoadGalleryImages on every OnInit. I realize this is a lot of duplicate code that could be consolidated.
New _LoadGalleryImages
private void _LoadGalleryImages() {
PhotoGalleryImageCollection images = PhotoGalleryImages.GetPhotoGalleryImages();
foreach(PhotoGalleryImage image in images) {
Panel panel = new Panel();
panelPhotoContainer.Controls.Add(panel);
ImageButton displayImage = new ImageButton();
panel.Controls.Add(displayImage);
displayImage.ID = string.Format("ImageButton{0}", image.PhotoGalleryImageId);
displayImage.ImageUrl = "Some URL";
displayImage.AlternateText = displayImage.ToolTip = image.ImageName;
if(!_IsSettingPrimaryPhotoMode) {
displayImage.OnClientClick = "showPopup(); return false;";
}
else {
// handles the image button command wireup
displayImage.Command += new CommandEventHandler(displayImage_Command);
displayImage.CommandArgument = image.PhotoGalleryImageId.ToString();
}
}
}
I added a new method as you suggested to find the controls since they've already been created in OnInit and I just need to find them after the button click and clear the OnClientClick.
private void _LoadSelectPrimaryImages() {
PhotoGalleryImageCollection images = PhotoGalleryImages.GetPhotoGalleryImages();
foreach(PhotoGalleryImage image in images) {
Control control = panelPhotoContainer.FindControl(string.Format("ImageButton{0}", image.PhotoGalleryImageId));
if(control != null) {
ImageButton displayImage = (ImageButton)control;
displayImage.OnClientClick = "";
}
}
}
I also have a cancel button to return the image buttons to the way they were before to show the popup.
private void _ResetGalleryImages() {
PhotoGalleryImageCollection images = PhotoGalleryImages.GetPhotoGalleryImages(_photoGalleryId, false, true);
foreach(PhotoGalleryImage image in images) {
Control control = panelPhotoContainer.FindControl(string.Format("ImageButton{0}", image.PhotoGalleryImageId));
if(control != null) {
ImageButton displayImage = (ImageButton)control;
displayImage.ImageUrl = "Original URL";
displayImage.OnClientClick = "showPopup(); return false;";
}
}
}
and two page button clicks
protected void btnSetPrimaryPhoto_Click(object sender, EventArgs e) {
_IsSettingPrimaryPhotoMode = true;
_LoadSelectPrimaryImages();
}
protected void btnCancelSetPrimaryPhoto_Click(object sender, EventArgs e) {
_IsSettingPrimaryPhotoMode = false;
_ResetGalleryImages();
}
Someone said in a response earlier...looks like the response was removed...to clear the controls in _LoadGalleryImages such as:
private void _LoadGalleryImages() {
panelPhotoContainer.Controls.Clear();
PhotoGalleryImageCollection images = PhotoGalleryImages.GetPhotoGalleryImages();
foreach(PhotoGalleryImage image in images) {
ImageButton displayImage = new ImageButton();
Panel panel = new Panel();
panelPhotoContainer.Controls.Add(panel);
displayImage.ImageUrl = "some URL";
if(!_IsSettingPrimaryPhotoMode) {
displayImage.OnClientClick = "showPopup(); return false;";
}
else {
displayImage.Command += new CommandEventHandler(displayImage_Command);
displayImage.CommandName = "ImageButton" + image.PhotoGalleryImageId.ToString();
displayImage.CommandArgument = image.PhotoGalleryImageId.ToString();
}
panel.Controls.Add(displayImage);
}
}
which also works but I think that may be more inefficient than your method, #swannee. Thanks!
Related
I have a problem with asp.net event system.
As the page is being loaded for the first time, a DropDownList is being populated and that's it. On the list SelectedIndexChange the page does a page postback and loads some more info, dynamically creating buttons for it based on some data taken out from the DB.
And at this point all works as expected, the CustomDivs are created and displayed correctly. The problems happens when i click on one of the dynamically created buttons, the page does the postback, goes trouhg the page load but never enters the method of the button that was clicked.
Looking around in the internet i read something about event handlers must be created in the Page_Load method to registered. So i even modified the code behind so that the CaricaPostazioni() method would be fired in the Page_Load, but that did not work. I also tried some other stuff like AutoPostBack property or UseSubmitBheaviour or CausesValidation but nothing seems to work as expected.
I should say that this page is a content page of master page that does absolutely nothing in it's Page_Load()
Any one can help me in this matter? Thank you very much!
Here is the code behind of assegnapostazione.aspx
using Industry4_camerana_gruppo1.App_Code;
using Industry4_camerana_gruppo1.App_Code.Dao;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace Industry4_camerana_gruppo1 {
public partial class assegnapostazione : System.Web.UI.Page {
static List Macchinisti = null;
protected void Page_Load(object sender, EventArgs e) {
if (Macchinisti == null) {
Macchinisti = new daoUtente().GetByRuolo("macchinista");
drp_Macchinisti.Items.Add(new ListItem("Seleziona...", "-1"));
foreach (Utente U in Macchinisti) {
drp_Macchinisti.Items.Add(new ListItem(U.Username, U.ID.ToString()));
}
}
}
public void CaricaPostazioni() {
//if (drp_Macchinisti.SelectedValue == "-1") return;
int IDUtente = Convert.ToInt32(drp_Macchinisti.SelectedItem.Value);
List Postazioni = new daoPostazioni().GetAll();
Dictionary Relazioni = new daoPostazioni().GetUtentePostazioni(IDUtente);
if (Postazioni != null) {
Panel row = new Panel();
row.CssClass = "row";
int i = 0;
foreach (Postazione p in Postazioni) {
if (i % 4 == 0) {
pnl_Postazioni.Controls.Add(row);
row = new Panel();
row.CssClass = "row";
}
if (Relazioni.ContainsKey(p.ID)) {
row.Controls.Add(CustomDiv(p, IDUtente, true));
} else {
row.Controls.Add(CustomDiv(p, IDUtente, false));
}
i++;
}
pnl_Postazioni.Controls.Add(row);
}
}
public Panel CustomDiv(Postazione P, int IDUtente, bool Assegnato) {
//
//
//
//
// Foratura
//
//
//
Panel wrapper = new Panel();
wrapper.CssClass = "col";
Panel card = new Panel();
card.CssClass = "card text-center postazione form-group";
Image img = new Image();
img.CssClass = "mx-auto d-block width-70";
img.ID = "btn_" + P.ID;
img.ImageUrl = "~/imgs/ic" + P.Tipo + ".png";
Panel cardTitle = new Panel();
cardTitle.CssClass = "card-title";
Label title = new Label();
title.Text = P.Tipo.ToUpper() + " - " + P.Tag;
Button btn = new Button();
//btn.ID = "btn_" + P.ID + IDUtente;
btn.Attributes.Add("PID", P.ID.ToString());
btn.Attributes.Add("UID", IDUtente.ToString());
//btn.UseSubmitBehavior = true;
if (Assegnato) {
btn.Click += new EventHandler(btn_Rimuovi_Click);
//btn.Click += delegate {
// btn_Rimuovi_Click(btn, null);
//};
btn.CssClass = "btn btn-warning mx-auto form-control";
btn.Text = "Rimuovi";
} else {
btn.Click += new EventHandler(btn_Assegna_Click);
//btn.Click += delegate {
// btn_Assegna_Click(btn, null);
//};
btn.CssClass = "btn btn-success mx-auto form-control";
btn.Text = "Assegna";
}
card.Controls.Add(img);
cardTitle.Controls.Add(title);
card.Controls.Add(cardTitle);
card.Controls.Add(btn);
wrapper.Controls.Add(card);
return wrapper;
}
protected void drp_Macchinisti_SelectedIndexChanged(object sender, EventArgs e) {
CaricaPostazioni();
}
protected void btn_Rimuovi_Click(object sender, EventArgs e) {
Button btn = (Button)sender;
new daoPostazioni().AddRelazione(Convert.ToInt32(btn.Attributes["UID"]), Convert.ToInt32(btn.Attributes["PID"]));
}
protected void btn_Assegna_Click(object sender, EventArgs e) {
Button btn = (Button)sender;
new daoPostazioni().DeleteRelazione(Convert.ToInt32(btn.Attributes["UID"]), Convert.ToInt32(btn.Attributes["PID"]));
}
}
}
Well, I have an answer for you but I don't think you are going to like it.
Due to the stateless nature of webforms, any controls that you add dynamically to a form on a postback will also need to be added back to the page and have it's server events rebound during the OnInit event.
I've created a minimal example that can demonstrate what's going on with this behavior so you can better understand what's happened.
ButtonTest.aspx
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="ButtonTest.aspx.cs" Inherits="ButtonTest.ButtonTest" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form runat="server">
<asp:Panel runat="server" ID="ButtonPanel">
<asp:Button runat="server" ID="ButtonAdd" OnClick="ButtonAddClick" Text="Click Me"></asp:Button>
</asp:Panel>
</form>
</body>
</html>
And the code behind:
ButtonTest.aspx.cs
using System;
using System.Collections.Generic;
using System.Web.UI.WebControls;
namespace ButtonTest
{
public partial class ButtonTest : System.Web.UI.Page
{
static List<Button> buttons = new List<Button>();
static bool allowItToWork = true;
protected override void OnInit(EventArgs e)
{
if(Page.IsPostBack && allowItToWork)
{
foreach (var button in buttons)
{
button.Click += ButtonAddClick; // Comment me out and added button's will do nothing when clicked.
ButtonPanel.Controls.Add(button);
}
}
}
protected void ButtonAddClick(object sender, EventArgs e)
{
var button = new Button();
button.Click += ButtonAddClick;
button.Text = "Click Me Too";
buttons.Add(button);
ButtonPanel.Controls.Add(button);
}
}
}
You can toggle between desirable and undesirable behavior by changing the value of the allowItToWork boolean.
When it's set to true, the OnInit overload is adding all buttons back onto the page, as well as applying their event handlers again, because those handlers seem to be lost during the PostBack (unsure on why this happens.) You can click away and new buttons will be added onto the form forever and ever.
When it's set to false, the added button will appear whenever you click the initial button, but it won't fire an event handler as it's no longer bound, and clicking the original button repeatably won't continue to add additional buttons, you'll only ever see a single one, and that one will disappear on PostBack if clicked.
So, the end result of this is essentially that you're stuck reconstructing the whole shebang in the OnInit event.
My application gets a list of files creates a panel for each file with a delete button an puts all of the panels in a flowPanel Layout. While
it is creating the panels it shows a loading panel. See the link below. All that works great. My issue is once a file is deleted I need to reload the flowpanel from the updated directory where we just deleted a file.
Since I have to make my onclick static to keep the RunAsyncProcess() method happy. I cant get back to my UI to reload the control. I hope this make sense. I'm struggling to even find the right search terms for google on this.
Reference to the loading panel methods such as RunAsyncOperation(MyDelegate1); found here.
http://www.codeproject.com/Articles/24044/AJAX-style-Asynchronous-Progress-Dialog-for-WinFor
Here is how my control is loaded.
private void button1_Click(object sender, EventArgs e)
{
SuspendLayout();
Control ctrl = Parent.Parent.Controls.Find("MainControlPanel", false).First();
MyControl myControl = new MyControl();
myControl .Dock = DockStyle.Fill;
Parent.Controls.Remove(this);//removes a menu control
ctrl.Controls.Add(myControl);
ResumeLayout();
}
Here is the control
bool isLoaded {get; set;}
List<Panel> panels {get; set;}
protected override void OnLoad(EventArgs e)
{
isLoaded = false;
panels = new List<Panel>();
RunAsyncOperation(MyDelegate1);
RunAsyncOperation(MyDelegate2);
while (!isLoaded) { //just wait }
foreach (Panel panel in panels)
{
flowLayoutPanel1.Controls.Add(panel);
}
}
AsyncProcessDelegate MyDelagate = delegate ()
{
//setting up the ui
string[] list = ...
foreach(in list)
{
Panel p1 = new Panel();
Button btn = new Button()
btn.Click += new EventHandler(MyEvent); // my event must be static
p1.Controls.Add(btn)
panels.Add(p1);
}
isloaded = true;
}
AsyncProcessDelegate MyDelagate2 = delegate ()
{
while (!isLoaded)
{
//showing the loading ui
Thread.Sleep(5000);
}
};
public static void MyEvent(object sender, EventArgs e)
{
Modifies string[] list and needs to reload
is there a way to call onload again?
How can I make it reload
//this does not work
flowLayoutPanel1.Controls.Clear();
RunAsyncOperation(MyDelegate1);
RunAsyncOperation(MyDelegate2);
while (!isLoaded) { //just wait }
foreach (Panel panel in panels)
{
flowLayoutPanel1.Controls.Add(panel);
}
}
Added flag to show changed data
static bool reload { get; set; }
Added to onload
stopwatch = new Stopwatch();
reload = false;
stopwatch.Start();
PollUpdate();
Added poll method to check if we should reload
public void PollUpdate()
{
while (true)
{
if (reload)
{
stopwatch.Stop();
reload = false;
SuspendLayout();
Control ctrl = Parent.Parent.Controls.Find("MainControlPanel", false).First();
AudioLibraryControl cr = new AudioLibraryControl();
cr.Dock = DockStyle.Fill;
Parent.Controls.Remove(this);
ctrl.Controls.Add(cr);
ResumeLayout();
}
Application.DoEvents();
}
}
It reloads the page empty when I click the button. How do I fire click event on button click? I think Page.IsPostBack is the reason it reloads the page empty instead of showing the label.
protected void Page_Load(object sender, EventArgs e)
{
if (Page.IsPostBack == false)
{
account account = new account();
accountManager accountManager = new accountManager();
group group = new group();
groupManager groupManager = new groupManager();
string emailAddress;
emailAddress = HttpContext.Current.User.Identity.Name;
account = accountManager.getAccInfoByEmailAddress(emailAddress);
group = groupManager.getGroupLeader(account.groupNo);
if (account.groupNo == 0)
{
divMessage.InnerHtml = "You are not in any group.";
}
else
{
try
{
Button btn = new Button();
btn.Text = "Click";
btn.Click += new EventHandler(button_Click);
form1.Controls.Add(btn);
}
catch (Exception)
{
divMessage.InnerHtml = "Unable to retrieve data. Please contact administrator if the problem persists.";
}
}
}
}
.
private void button_Click(object sender, EventArgs e)
{
Label Label1 = new Label();
Label1.Text = "rthfg";
form1.Controls.Add(Label1);
}
When you click the button, or somehow else generate a postback, ASP.NET creates the page (as it always does) and tries to find the source of the request, that is the button you clicked. In your case this button is no longer on the page, so ASP.NET cannot find anything, end does not fire the event.
Resolution seems easy enough in your case - just always create the button and put it on the page, regardless of the postback:
if (!Page.IsPostBack)
{
...
}
Button btn = new Button();
btn.Text = "Click";
btn.Click += new EventHandler(button_Click);
form1.Controls.Add(btn);
Btw, why make the button dynamic? Dynamic controls are always harder to manage.
Trying to add buttons programmatically to a webform.
Some work - others don't.
In the code below I add btnY and btnX in the Page_Load.
These both work - they show on the page and fire the event
and the code in the event handler works....
In the page load I also run bindData which gets a DataTable
and uses the data to create controls.
in the example I am only creating Button.
These buttons will appear on the page correctly but when clicked
they only do a postback ..
the code in the event handler doesn't work - does it get called?
The event handler is the same for all the buttons.
Any ideas why or how I can make it work?
protected void Page_Load(object sender, EventArgs e)
{
PlaceHolder1.Controls.Add(btn("btnY", "Y"));
Pages P = new Pages();
bindData(P.DT);
PlaceHolder1.Controls.Add(btn("btnX", "X"));
}
Button btn(string id, string text)
{
Button btn1 = new Button();
btn1.ID = id;
btn1.Text = text;
btn1.Click += new System.EventHandler(this.btn_click);
return btn1;
}
protected void bindData(DataTable dt)
{
foreach (DataRow row in dt.Rows)
{
render(Convert.ToInt32(row["PageKey"]));
}
}
protected void render(int pageKey)
{
PlaceHolder1.Controls.Add(btn("btn_" + pageKey.ToString(), "Edit"));
}
protected void btn_click(object sender, EventArgs e)
{
Button btn = (Button)sender;
string id = btn.ID;
Pages P = new Pages();
bindData(P.DT);
lt.Text = "ID=" + id;
}
Never mind .. figured it out .. example above should work, my actual code had a if (!Page.PostBack) that caused the problem
I'm setting up a series of buttons, one for each item in a table. What I need to do is set the Navigate parameter for each button, is there anyway I can set the following from the .cs code?:
<ec:NavigateToPageAction TargetPage="/MissionPage.xaml"/>
Here's the code I'm using to make the buttons:
foreach (string i in missionQ)
{
Button btn = new Button() { Content = "Run", Width=120, Height=90 };
btn.HorizontalAlignment = System.Windows.HorizontalAlignment.Right;
btn.VerticalAlignment = System.Windows.VerticalAlignment.Top;
btn.Margin = new Thickness(0, (100*x), 20, 0); }
You could try this code in your Click event of the button
using System.Windows.Navigation;
private void Button_Click(object sender, RoutedEventArgs e)
{
NavigationService.Navigate(new Uri("/MissionPage.xaml.xaml", UriKind.Relative));
}
Ref: NavigationService Class
The way I would handle this is by, creating a new eventhandler for the tap event on each button. Pass i as reference to the function.
btn.Tap += functionToHandleTap(i);
In the function it self I would create a switch or if statement and then navigate based on the i parameter passed to funtionToHandleTap.
private void functionToHandleTap(string i)
{
string naviString = string.Empty;
if (i == "something")
{
naviString = "some xaml here";
}
else
{
naviString = "another xaml here";
}
_rootFrame.Navigate(new Uri(naviString, UriKind.Relative));
}