Xaml: Best Practice for event handling in custom controls

Time:2020-2-12

In developing XAML (WPF / uwp) applications, sometimes we need to create custom controls to meet the actual needs. In the custom control, we usually use some native controls (such as button, textbox, etc.) to assist to complete the function of the custom control.

Unlike user control, custom controls use code behind technology. Instead, it decouples the UI from the logic. Therefore, creating a custom control will generate two files, one is generic.xaml, which defines its template and style; the other is < controlname >. CS, which stores its logic, as shown below:

In this case, to get the control defined in the template in the code is not as easy as in code behind, but with the help of onapplytemplate and gettemplatechild. Their meanings are as follows:

Onapplytemplate: in a custom control, this method is usually overridden. When the base class calls the applytemplate() method to construct a visual tree, it will be called;

Gettemplatechild: gets the element with the specified name on the visual tree defined in controltemplate;

So, if we define a button named part? Viewbutton in the template, we can get it and register the response event for it as follows:

public override void OnApplyTemplate()
  {
   base.OnApplyTemplate();
   Button btnView = GetTemplateChild("PART_ViewButton") as Button;
   if (btnView != null)
   {
    btnView.Click += BtnView_Click;
   }
  }
  private void BtnView_Click(object sender, RoutedEventArgs e)
  {
   //Write response logic here
  }

When we (or others) want to use this control, the onapplytemplate method will be executed by setting a template for it (usually the default template). That doesn’t seem to be a problem. However, there may be a serious problem:Memory leak.

What is memory leak

There are many types of memory leaks. Generally speaking, it means that some kind of resources are no longer used, but still occupy memory. In other words, it “leaks” out of the managed memory area. If there are multiple memory leaks in the program, it will occupy a lot of memory and eventually lead to memory exhaustion.

In C, common memory leaks are:

• event monitoring is not removed;

• unmanaged resources (such as databases, file streams, etc.) are not destroyed;

For the above two cases, their solutions are also very simple: to unregister an event (i.e. remove event listening) and to call the dispose method (if not, implement the IDisposable interface and destroy the unmanaged resources in it).

For the second case, it’s easy to understand; for the first case, the question is, why does not remove event monitoring lead to memory leakage? This is because event sources have a longer life cycle than event listeners. Look at the code:


ObjectA objA = new ObjectA(); 

ObjectB objB = new ObjectB(); 

objA.Event += objB.EventHanlder;

The event event is defined in objecta, for which we register an event handler (eventhanlder method in object objb); therefore, the event source obja has a reference to the event listener objb.

If obj B is no longer used, we will destroy it, but because obj a references it, it will not be destroyed or recycled; it can not be destroyed until obj A is destroyed. So the object that needs to be destroyed, but because of the reference of other objects to it, results in a memory leak.

How to solve

Go back to the problem of custom controls, because our custom controls may be overridden by styles or templates, which will make onapplytemplate method executed multiple times in the life cycle of this custom control. Therefore, we need to unregister events for those controls (such as btnview control in the above code) that are obtained through the gettemplatechild method and have event handling added. Because these are the controls (elements) in the previous template, after de registration, there is no reference relationship between the original control and the event listener (the custom control itself), so as to avoid the problem of memory leakage.

According to our solution, the previous code is reconstructed as follows:

private Button btnView = null;
  public override void OnApplyTemplate()
  {
   base.OnApplyTemplate();
   //First unregister event
   if (btnView != null)
   {
    btnView.Click -= BtnView_Click;
   }
   btnView = GetTemplateChild("PART_ViewButton") as Button;
   if (btnView != null)
   {
    btnView.Click += BtnView_Click;
   }
  }
  private void BtnView_Click(object sender, RoutedEventArgs e)
  {
   //Write response logic here
  }

In this way, the problems mentioned at the beginning of this paper are solved. But next, we need to make some adjustments.

Further reconstruction

Imagine that if there are multiple controls like btnview in our custom control, we will copy the above code in onapplytemplate method several times, which will increase the complexity of onapplytemplate method and reduce the readability of the code.

To improve this, we encapsulate each control and its event registration and deregistration.

After refactoring, the code is as follows:

protected const string PART_ViewButton = nameof(PART_ViewButton);
    private Button btnView = null;
    public Button ViewButton
    {
      get
      {
        return btnView;
      }
      set
      {
        //First unregister event
        if (btnView != null)
        {
          btnView.Click -= BtnView_Click;
        }
        btnView = value;
        if (btnView != null)
        {
          btnView.Click += BtnView_Click;
        }
      }
    }
    public override void OnApplyTemplate()
    {
      base.OnApplyTemplate();
      ViewButton = GetTemplateChild(PART_ViewButton) as Button;
    }
    private void BtnView_Click(object sender, RoutedEventArgs e)
    {
      //Write response logic here
    }

For the final code, here are a few more points:

1. In onapplytemplate method, it is recommended to call base. Onapplytemplate() first;

2. Whether the control is empty or not should be judged when unregistering events for the control or registering events, because it is possible that the user does not follow the control name specified in the templatepart property when rewriting the template;

3. Declaring the name of the control as a constant can avoid misspelling the string;

summary

This article discusses the problem of memory leakage when creating custom controls in WPF or uwp, which is mainly caused by the fact that the control events in the template are not unregistered. We not only analyze the reasons, but also give the best practices for this situation.

Although in general, this problem will not cause great impact, if we can pay attention to these details, it can not only improve our code quality and program performance, but also provide us with necessary ideas and experience when designing or dealing with similar problems.

The above XAML: the best practice of event handling in custom controls is all that Xiaobian has shared with you. I hope it can give you a reference, and I hope you can support developepaer more.