Event handler firing for all controls instead of individually - c#

I am having a rather odd problem with the Gecko Webbrowser control, I have created my own class which inherits off of the Gecko Webcontrol and within the constructor of this I have set an event:
class fooGeckoClass: Gecko.GeckoWebBrowser
{
public fooGeckoClass()
{
this.DomClick += new EventHandler<Gecko.GeckoDomEventArgs>(fooEventFunction);
}
private static void fooEventFunction(Object sender, Gecko.GeckoDomEventArgs e)
{
((Gecko.GeckoWebBrowser)sender).Navigate("www.foo.com");
}
}
I am using three of these controls in a manually created UserControl, the controls are loaded in dynamically at start up from a config file and added the the UserControl controls collection. When clicking on any of the three controls, all three will navigate to "www.foo.com" away from there original site. I had a look at:
e.StopPropagation();
Which specifies that it stops further propagation of events during an event flow, however it does also specify that it will handle all events in the current flow, I believe the events must have already been given to the controls before this has a chance to stop it as the the three controls will still fire the event. I also tried e.Handled = true to no avail. Has anyone encountered this problem before and have any kind of solution to make it only fire on the control that was clicked on?
EDIT:
It may be worth showing how the controls are added to the form seeing as this must be where the problem is occurring (it does not happen if the controls are just placed in a user control in a small test app).
private void fooUserControl_Load(object sender, EventArgs e)
{
if (!this.DesignMode)
{
for (int iControls = 0; iControls < geckObs.Count(); iControls ++)
{
fooGeckoClass geckControl = new fooGeckoClass();
this.Controls.Add(geckControl );
break;
}
}
}

Odd answer but I seem to have resolved the issue, DomClick was being called at first run, changing to DomMouseClick or DomMouseUp has completely resolved the issue. I assume DomClick must be an event unto itself as it also doesn't use the GeckoDomMouseEventArgs but the regular GeckoEventArgs.
EDIT:
To add to this, the site I was going to was actually calling DomClick when it had finished loading hence the reason it was being called at start up across all three browsers.

Related

WPF Frame.Navigate triggering multiple events

I'm a fairly new to WPF and C#.
I have a frame component on my main window and 4 buttons next to it that navigate to different views in the frame. Within one the the views there is a DataGrid that has a SelectionChanged event which makes an SQL call to a database that fetches records, whose data is then used to populate a list of custom objects (these relate to the selected item on the DataGrid).
Anyways, the problem I have is that from time to time multiple calls (2 or 3) to the SelectionChanged event are being triggered at the same time for a single selection change (mouseclick) on the DataGrid.
The navigation button click events on the main window all look like this:
private void btn_MyDesk_Click(object sender, RoutedEventArgs e)
{
MainFrame.Navigate(new Uri("/Views/MyDeskView.xaml", UriKind.Relative));
}
private void btn_AllOrders_Click(object sender, RoutedEventArgs e)
{
MainFrame.Navigate(new Uri("/Views/AllOrdersView.xaml", UriKind.Relative));
}
After some experiementation, I've found that the bug only happens after changing views away from the view with the DataGrid, and then changing back to it (but not always). When the bug appears the number of calls generally corresponds to the number of times I had switched views. Furthermore, the bug will simply vanish if leave the program alone for a minute or two. This makes me suspect that there multiple instances of the DataGrid view lingering like ghosts in memory and duplicating event calls until they are cleaned up by a garbage collector.
Should I be cleaning something up each time I switch views, or am I looking in the wrong place?
Thank you in advance for any help.
Edit: In answer to #Peter Moore
I subscribe to the event in the DataGrid declaration within the views XAML: SelectionChanged="dtg_MyDeskOrderGrid_SelectionChanged"
Edit: This is the sequence that happens on a selection change in the data grid. It includes several UI changes while the SQL records for the new selection are retrieved and displayed on a second DataGrid (dtg_MyDeskOrderItems). While the SQL call is being made, the relevant controls are disbaled and a semi-transparent panel (bdr_DGLoadingPanel) is moved on screen to cover them and display a loading animation. When the work is done, the work area is re-enabled and the loading panel moved off screen. Focus is also returned to the main "order" Datagrid.
dtg_MyDeskOrderGrid: This is the main DataGrid showing all "Orders"
dtg_MyDeskOrderItems: This is a secondary DataGrid that is updated to show all "Items" in the selected order.
private void dtg_MyDeskOrderGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
CurrSelectedOrder = (Order_class)dtg_MyDeskOrderGrid.SelectedItem;
if (CurrSelectedOrder.ItemList == null)
{
if (NowWorking == false)
{
NowWorking = true;
bdr_DGLoadingPanel.Margin = new Thickness(2);
dtg_MyDeskOrderGrid.IsEnabled = false;
bdr_FilterPanel.IsEnabled = false;
bdr_DGLoadingPanel.Focus();
img_LoadingCircle.RenderTransform = rt;
img_LoadingCircle.RenderTransformOrigin = new Point(0.5, 0.5);
da.RepeatBehavior = RepeatBehavior.Forever;
rt.BeginAnimation(RotateTransform.AngleProperty, da);
bdr_DGLoadingPanel.UpdateLayout();
worker.RunWorkerAsync();
}
}
else
{
dtg_MyDeskOrderItems.ItemsSource = null;
dtg_MyDeskOrderItems.ItemsSource = CurrSelectedOrder.ItemList;
dtg_MyDeskOrderItems.Items.Refresh();
}
}
private void worker_DoWork(object? sender, DoWorkEventArgs e)
{
DatabaseConnection DBConn9 = new DatabaseConnection();
DBConn9.FillOrderItems(CurrSelectedOrder);
}
private void worker_RunWorkerCompleted(object? sender, RunWorkerCompletedEventArgs e)
{
this.Dispatcher.Invoke(() =>
{
dtg_MyDeskOrderItems.ItemsSource = null;
dtg_MyDeskOrderItems.ItemsSource = CurrSelectedOrder.ItemList;
dtg_MyDeskOrderItems.Items.Refresh();
bdr_DGLoadingPanel.Margin = new Thickness(1000, 2, 2, 2);
rt.BeginAnimation(RotateTransform.AngleProperty, null);
dtg_MyDeskOrderGrid.IsEnabled = true;
bdr_FilterPanel.IsEnabled = true;
// The following work-around and accompanying GetDataGridCell function were used to give keyboard focus back to the datagrid to make navigation with arrow keys work again.
// It appears keyboard focus is not returned to the Datagrid cells when using the Datagrid.focus() method.
Keyboard.Focus(GetDataGridCell(dtg_MyDeskOrderGrid.SelectedCells[0]));
NowWorking = false;
});
}
Edit...
Following the advice of the commentors, I was able to fix the bug by unsubscribing from the event in the Unloaded event for the view containing the DataGrid:
private void uct_MyDeskView_Unloaded(object sender, RoutedEventArgs e)
{
dtg_MyDeskOrderGrid.SelectionChanged -= dtg_MyDeskOrderGrid_SelectionChanged;
}
However, I was not able to reproduce the bug using a barebones testing project.
The UI in the original project is quite heavy, so I'm wondering if it's not the old view and events lingering in memory as this seems to fit the behavior of the bug & fix (Only occuring when I navigate away and back causing a new view to be created, multiple event triggers corresponding to the number of times I navigated away, and then finally the bug vanishing of its own accord after a moment or two).
I won't be settling on this as a final solution and instead will learn about ways I can reuse instances of my views (as suggested by Bionic) instead of recreating them. The reason for this is, if the SelectionChanged event is getting multiple triggers from old view instances, then it is likely other events will suffer from the same bug. This would be bad.
#BionicCode If you are still around, could you repost your initial comment as a solution so I can mark it answered?
Thank you to everyone for all the help and education. ^_^
Following the advice of the commentors, I was able to initially fix the bug by unsubscribing from the event in the Unloaded event for the view containing the DataGrid:
private void uct_MyDeskView_Unloaded(object sender, RoutedEventArgs e)
{
dtg_MyDeskOrderGrid.SelectionChanged -= dtg_MyDeskOrderGrid_SelectionChanged;
}
However as this solution only disconnected the one event and didn't solve the root problem of old views lingering in the background and firing events before being cleaned up, I finally went the following code that keeps only one instance of my view in memory and reuses it.
private MyDeskView? currMyDeskView = null;
private void btn_MyDesk_Click(object sender, RoutedEventArgs e)
{
if (currMyDeskView == null)
{
currMyDeskView = new MyDeskView();
}
MainFrame.Navigate(currMyDeskView);
}
Thank you to everyone who helped me get over this bug.

Visual Studio 2010 - C# UserControl event firing

I'm attempting to make a grid based dungeon system at the moment in Visual Studio 2010. I have a main user control which contains 64 other smaller user control objects, which I've called GridSquares, organised into an 8x8 grid. The idea behind the grid squares is to act as potential movement spaces within the 'dungeon'. The problem I have at the moment is that I need to be able to call a click event on the user controls (GridSquares) themselves, which have been placed on screen so I can retrieve their coordinate (name) for comparison. However the event does not work when I call it (through clicking).
I am aware that the events work when I place them within the usercontrol (GridSquare object) but I need the click even to work when the user control itself is clicked.
Given that all 64 objects placed are the same type I can not work within the GridSquare class as I require the name of the user control to be returned through the event.
I hope this makes sense but please ask if I need to explain further.
Many thanks, Liam
EDIT:
I'm not sure how much this will help or what code to display but the GridSpace controls have already been added to the 'dungeon' user control. Then within I add all 64 to a dictionary:
gridSpaces.Add(gs11.Name, gs11);
Where gs11 is the name of the GridSquare.
From here I tried creating event handlers for the individual user controls on the dungeon screen, which failed to call.
I think i get what your saying. Add this code to your user control:
public new event EventHandler Click {
Add {
base.Click += value;
foreach(Control i in Controls) {
i.Click+=value;
}
}
remove {
base.Click -= value;
foreach(Control i in Controls) {
i.Click -= value;
}
}
}
this will add the click event to everything in your user control, i hope i didnt make any errors, and that this helps
You can use the same handler for each GridSquare and use the sender parameter to decide which one was clicked:
protected void Page_Load(object sender, EventArgs e)
{
for (int i = 0; i < 64; i++)
{
GridSquare square = new GridSquare();
square.Click += new EventHandler(gridSquare_Click);
grid.Add(gridSquare);
}
}
void gridSquare_Click(object sender, EventArgs e)
{
GridSquare square = (GridSquare)sender;
// do something cool with the clicked square here
}

Load Server Control from Assembly and hook up events

I have several Server Controls, each in a separate assembly and I'd like to load one of them dynamically into a page depending on some choice. There seems to be a problem where the server side events in the control are not firing however.
e.g. Controls are of the form:
[ToolboxData("<{0}:MyPlugin runat=server></{0}:MyPlugin>")]
public class MyPlugin : WebControl, PluginSystem.Interface.IMyPlugins
{
protected override void RenderContents(HtmlTextWriter output)
{
...
_btn = new Button();
_btn.ID = "btnSave";
this.Controls.Add(_btn);
_btn.Click += new EventHandler(btn_Click);
_btn.RenderControl(output);
}
void btn_Click(object sender, EventArgs e)
{
//do something. This doesn't fire
}
}
The controls are loaded from their assemblies:
public static IMyPlugins GetPlugin(string assembly, string type)
{
var t = Type.GetType(type + ", " + assembly);
IMyPlugins rtn = (IMyPlugins)Activator.CreateInstance(t);
rtn.Initialise();
return rtn;
}
How do I inject the loaded assembly into a page so that the events in the control will fire? Is that possible?
Thanks for any help!
You're adding the controls way too late in the page lifecycle. Add the server side controls in the OnInit Page event is your best bet.
Check out this link for an overview of the lifecycle process. The controls need to be created by the time the postback event handling happens. This series is also really good.
Dynamically changing the page based on user choice can be a pain in the ass because of this. There are a few options to go with. The easiest way is to add all your controls in the OnInit and then remove them when you know the selection the user made.
If it's truly completely dynamic, and you have no idea what control you are going to render until after the postbacks have been handled, it can be easier to step away from ASP.NET's viewstate/postback system and check things yourself. You can always get the full range of post values at any time in the page lifecycle by checking the Form.Request collection.

Dynamic events in a UserControl

I've created a custom UserControl using the GUI, and I can't get it to accept dynamically added events from within a custom class.
Sorry if I get the exact code wrong, going from memory, but you get the gist.
C# .NET 2008, Winform
I have a "container class" that stores all my information.
The main WinForm has an array of these, and they each have a summary panel.
The main form also has a "work zone" that will let you access the container class.
The idea is, interacting with the CustomUserControl will cause stuff to happen within the ContainerClass. The control uses info from there, and I want it to update from within there.
ContainerClass
{
CustomUserControl tempControl;
public ContainerClass()
{
//do stuff
tempControl = new CustomUserControl([send information]);
tempControl.Click += new Event(localClickEvent);
}
public void localClickEvent(object sender, Event e)
{
//do stuff
}
}
.
public class Form1
{
public Form1()
{
//create several container objects
//for each container object, get it's SummaryPanel
//and add it to the FlowLayoutPanel
CustomUserControl tempControl = ContainerObject.GetCustomControl();
flp_summaryPanel.Controls.Add(tempControl);
}
}
When I execute this code, the dynamic event never fires.
I've done this sort of thing before, but it was always with custom classes that inherited the control I needed. I've never inherited UserControl, so I suspect I left something out.
I susepct this is an protection issue, but the compiler isn't catching it.
MORE INFO
This somehow got labeled as an ASP.net question. It's not, I'm using Winforms, though I never specifically said so.
One other thing that may be important: The dynamic event isn't in another control. It's in a custom class that functions as a large container object.
.
.
As always, thanks so much for your help! :)
Try attaching event handler after adding control to Controls collection
public ParentControl()
{
CustomUserControl tempControl = new CustomUserControl();
this.Controls.Add(tempControl);
tempControl.Click += new Event(localClickEvent);
}
It may be related to the fact that unless the control is added to NamingContainer the ClientID cannot be generated properly.
Which version of C# are you using? Try this:
public void ParentControl_Init()
{
CustomUserControl tempControl = new CustomUserControl();
this.Controls.Add(tempControl);
tempControl.Click += localClickEvent;
}
public void localClickEvent(object sender, EventArgs e)
{
//do stuff
}
You don't want to add controls and attach events in the constructor. Look at how ASP.NET does it and you'll see why.

Disabling a control from receiving a event in C#

I have a dialog with loads of control in it. Each and evey control will be populated during the loading sequence and it might take a while for it to get completely filled. Mean while, I don't wanna allow the user to click on any of the controls. In other words, I wanna disable the control from receiving the events and I don't wanna disable the controls (as it might look odd).Also, I don't wanna subscribe and unsubscribe for the events regular intervals. Is there any way to stop the controls from listening to the events for a brief time ??
Sudarsan Srinivasan
The whole point of disabling controls is to communicate to the user that the control cannot be used at a particular time. This is a convention that users have learned and are used to, so I would advice to follow that. Not doing that may confuse the users.
The easiest way is to disable the container in which the controls are located in, rather than disabling each and every control. A better way (or at least the way that I prefer) is to have a method that will control the Visible and Enabled properties of controls based on which state the UI is in.
The easiest way is to move the control population out of the load event (if possible). Then in Load do something like:
private bool _LoadComplete;
void OnFormLoad(Object sender, EventArgs e)
{
_LoadComplete = true;
InitializeControls();
_LoadComplete = false;
}
void InitializeControls()
{
// Populate Controls
}
void OnSomeControlEvent()
{
if (_LoadComplete)
{
// Handle the event
}
}
Edit A Couple other Ideas:
Set the Application.Cursor = WaitCursor (typically will disallow clicking, but not a 100% guarantee)
Create a "Spinner" control to let the user know that the screen is busy. When loading bring it to the front so it sits on top and covers all other controls. Once you're done loading set it to visible = false and show your other controls
Unfortunately the only way i know of is to have a class variable (called something like _loading) and in each control handler do something like:
If (! _loading )
{
...
}
And in your loading code set _loading = true; once you have finished loading.
If you just want to disable user input, then you can set the form's Enabled property to false.
This has the effect of blocking user input to any of the form's controls, without changing the appearance of the controls; it's the technique used internally by the ShowDialog method.

Categories