Home > Software design >  WPF: Should I use "loaded" event Instead of Constructor When I create Usercontrol and Init
WPF: Should I use "loaded" event Instead of Constructor When I create Usercontrol and Init

Time:09-18

summary:

  • There are some actions that must be executed when a UserControl is created.
  • Is it a bad to declare this to the constructor?

question:

  • I always declared some actions to the constructor when creating Usercontrol. As below.
public MainWindow()
{
    InitializeComponent();
    InitSomething();
}
  • My boss advised me, "If you write it like this, some problem will occur, so I should write it as follows."
public MainWindow()
{
    InitializeComponent();
    this.Loaded  = OnLoaded;
}

private void OnLoaded(object sender, RoutedEventArgs e)
{
    InitSomething();
    this.Loaded -= OnLoaded;
}
  • I asked him what some problem was with him, but he said he had forgotten (...). But he said this is a famous issue and it will be easy to find through search.
  • I found the following article through the search.

should-i-use-the-constructor-the-initialized-or-the-loaded-event

WPF: What is between the Initialized and Loaded event?

  • Even though I read these articles, I couldn't figure out "why I shouldn't do InitSomething on the constructor", or "what kind of some problem are".
  • I searched for about 2 hours, but I couldn't find related data because I didn't have enough ability to search. I ask for your help.
  • I am not an English user, so I received help from a translator to write this article. Thank you very much for reading the poor article until the end.

CodePudding user response:

It depends on the logic of your method. InitializeComponent() method only initializes fields with references to named elements and Resources in XAML . The visual content of elements (mostly templates), bindings, triggers, and more may not yet be initialized at the time the InitializeComponent() method completes. All this begins to be initialized only when loaded into the visual tree. For a Window, this happens after the Show() or ShowDialog() method call. For UI elements after inclusion in the Window. Loading an element starts with its child elements. And the Loaded event notifies that the element itself and all its child elements are loaded.

Therefore, if your InitSomething() method does any parsing of the visual tree, it should be called on the Loaded event. If in this method some calculations are simply made, the result is stored in fields, properties, then it does not matter where to call it.

Another aspect is throwing an exception. If exceptions are possible in the InitSomething() method, then calling it in a constructor will crash the application at the point in the code where the constructor is called. Very often this is XAML and then it becomes very difficult to diagnose the exception and its cause. When called from Loaded, the program will stop at the line where the exception was thrown. This greatly simplifies debugging.

There are also rare cases where XAML has bindings or x:Static properties set to values ​​that need to be set before the element is rendered (loaded). In such cases, the method must be called from the constructor. Sometimes even before calling the InitializeComponent() method.

CodePudding user response:

Some ideas why you should avoid executing complex initialization code in an instance/class constructor (this also includes threading):

  • Constructors must return as quick as possible. The type construction must be completed as fast as possible. The caller of the constructor must not wait for construction to complete.
  • Constructors are meant to initialize public properties to a reasonable value. If those values require computation or allocation of expensive or unmanaged resources, their initialization should be deferred.

"DO minimal work in the constructor.

Constructors should not do much work other than capture the constructor parameters. The cost of any other processing should be delayed until required." (Microsoft Docs: Constructor Design)

  • Especially when initialization involves UI blocking code, you would want to execute it asynchronously. Since constructors can't be marked async, you must delay such initialization. In context of UI related code like controls, blocking is synonymous with freezing the GUI.
  • If initialization relies on the control being properly loaded, for example to access layout related values, the FrameworkElement.Loaded event is the best place for such initialization. See Object lifetime events (WPF .NET) to learn more about available events that could be considered to use for initialization of controls.
  • When inside the constructor of a base class, derived classes are not fully constructed while executing the constructor. Therefore, invoking virtual members will yield unpredictable results. To access virtual members, the Loaded event is a good place: the subclass is now fully constructed and their overrides ready for invocation.

"AVOID calling virtual members on an object inside its constructor.

Calling a virtual member will cause the most derived override to be called, even if the constructor of the most derived type has not been fully run yet." (Microsoft Docs: Constructor Design)

  • For the above reasons, threading or advanced thread management is not possible/should be avoided in a constructor as you can't or don't want to block/synchronize the execution. Thread management should always take place outside the constructor (e.g., in the Loaded event). Threads are always resource expensive. The caller of a constructor won't ever expect a constructor to be that heavy and expensive. For example spinning inside a constructor is a very bad practice.

I guess I missed some and somebody could complete the list. But those are the most important reasons to avoid complex object initialization in the constructor.

In case of classes that are not extending FrameworkElement (and therefore don't have a Loaded event, you can use different pattern to defer the expensive initialization.
For example such a class could

  • expose a Initialize method (that also could be async) that must be called by the user of the type after invoking the constructor.
  • expose a factory method that the user of the type must call to complete construction (instead of calling a constructor, which could be private in such case)
  • use a factory to create instances of a type that is complex to initialize or to configure
  • use Lazy<T> or AsyncLazy<T> to defer initialization of particular properties or resources
  • define critical members as static fields/properties and use field initializers (because static field initializers are only invoked when the corresponding field/property is actually referenced)
  • Related