Loaded event of a WPF control may be fired repeatedly
In all my Windows Forms applications, I usually start with a common base class for forms and controls that provides a bunch of convenience properties, subscribes to common events, handles proper cleanup etc. I also implement an empty virtual InitControl method which can be overridden by deriving controls to initialize themselves.
As you can see in the Windows Forms snippet below, InitControl is being called right after the control’s Load event has been fired:
/// <summary> /// Inits the base class and registers event listeners. /// </summary> /// <param name="e"></param> protected override void OnLoad(EventArgs e) { base.OnLoad(e); if (!DesignMode) { InitControlInternal(); InitControl(); initControlCompleted = true; } } /// <summary> /// An empty template method which is being invoked after the /// control has been loaded during <see cref="OnLoad"/>. /// </summary> /// <remarks>The base class does not invoke this method if /// the control is in design mode.</remarks> protected virtual void InitControl() { //this method is supposed to be overridden. Initialization of //the base class itself happens in InitControlInternal }
Naturally, I kept this practice in WPF using the Loaded event which is available for all WPF controls. But to my surprise, InitControl method was getting called repeatedly while I expected it to be called just once, which causes some controls to be re-initialized over and over again. The reason in my case was a TabControl that hosted my user controls. As it turned out, TabControl unloads/reloads user controls with every tab switch.
Unfortunately, there seems to be no common solution to this very problem – you will have to decide how to handle Loaded events specifically for your initialization logic. However:
- Using Loaded may not be problem in a lot of scenarios, but keep in mind that you might run in trouble when depending on it, especially with a reusable control.
- You can use the Initialized event of a control rather than Loaded. Check the API documentation for a description of the two events.
- Use a boolean field to track whether your initialization code has alreay been invoked:
protected override void InitControl() { if (initControlCalled) return; initControlCalled = true; //init control ... }
I’ve implemented a short sample that demonstrates the issue. Basically, it’s a tab control that hosts a user control on one of its tab items:
<TabControl> <TabItem Header="Tab 1"> <!-- the user control that counts Loaded events --> <local:LoadedCounter /> </TabItem> <TabItem Header="Tab 2"> <TextBlock>Switch back to trigger event in tab 1.</TextBlock> </TabItem> </TabControl>
Once you switch back and forth, you can see that Loaded is being invoked with every switch: