Performance and UI rendering speed remains at the top of our WinUI priority list. In this blog post, we will describe performance challenges we’ve encountered with WinUI and what we’ve done to address these issues. We’ll also show you how we created the fastest Data Grid for Microsoft’s new WinUI platform.
WinUI Performance Bottlenecks
First a refresher as to how the WinUI platform was constructed and architectural differences from its predecessors (WPF and WinForms). In WPF and WinForms, components and their core logic are implemented within standard .NET libraries. For example, base WPF classes such as DependencyObject are fully implemented in .NET and you can always see what logic is executed when a dependency property changes. Unmanaged code is used only for UI rendering and user interaction (implemented using Windows API calls).
In WinUI, all internal logic is implemented with native WinRT components and Windows App SDK ships only .NET wrappers for these components. The DependencyObject class initializes a WinRT object mapped to the IDependencyObject interface and uses this object when a dependency property setter or getter is invoked:
[WindowsRuntimeType("Microsoft.UI")]
[Guid("9FB92D6F-2CC3-5892-ABB3-45F6461EA7E4")]
[ContractVersion(typeof(WinUIContract), 65536)]
internal interface IDependencyObject
{
object GetValue(DependencyProperty dp);
void SetValue(DependencyProperty dp, object value);
//...
}
public class DependencyObject : ICustomQueryInterface, IWinRTObject, IDynamicInterfaceCastable, IEquatable<DependencyObject>
{
public object GetValue(DependencyProperty dp) => this._default.GetValue(dp);
public void SetValue(DependencyProperty dp, object value) => this._default.SetValue(dp, value);
private IObjectReference _inner;
private IDependencyObject _default => this._defaultLazy.Value;
private readonly Lazy<IDependencyObject> _defaultLazy;
protected DependencyObject()
{
bool flag = this.GetType() != typeof(DependencyObject);
IntPtr innerInterface;
IntPtr instance = DependencyObject._IDependencyObjectFactory.Instance.CreateInstance(flag ? (object)this : (object)(DependencyObject)null, out innerInterface);
try
{
ComWrappersHelper.Init(flag, (object)this, instance, innerInterface, ref this._inner);
this._defaultLazy = new Lazy<IDependencyObject>((Func<IDependencyObject>)(() => (IDependencyObject)new SingleInterfaceOptimizedObject(typeof(IDependencyObject), this._inner)));
this._lazyInterfaces = new Dictionary<Type, object>();
}
finally
{
Marshal.Release(innerInterface);
}
}
//...
}
This architecture allows WinUI components to offer faster rendering, animation support, and higher FPS. Unfortunately, each action within a component requires WinRT interop, which is slow. The following table demonstrates the dramatic (negative) effects of WinRT interop on dependency properties:
Action | WPF, ns | WinUI, ns |
---|---|---|
Get | 19 | 2023 |
Set | 135 | 4272 |
Set with property change handler | 139 | 20431 |
Set to the same value | 89 | 4150 |
Set TextBlock.Text | 253 | 725 |
Set Border.Background | 248 | 2412 |
You can learn more about this issue in the following GitHub discussion: Dependency property is much slower in WinUI 3 than in WPF.
DevExpress Data Grid Performance Optimizations
Though WinUI’s architecture complicates our job as a component vendor, we have taken a series of steps to minimize WinUI’s negative impact on rendering performance. Typically, complex WinUI components consist of multiple visual elements. With v21.2, we minimized the number of internal elements and reduced the number of dependency properties.
These changes produced significant (and beneficial results). As you can see in the chart below, our WinUI Data Grid is now very fast (we think it’s the fasted WinUI Data Grid on the market today)
Startup Performance
Scrolling Performance
With v22.1 the control used to display cells (CellControl) is inherited from the standard Panel class and all parts are created internally. The layout is determined via Measure and Arrange methods. Additionally, the CellControl class doesn’t have custom dependency properties and you can’t customize it. The same optimization was made in the control used to represent rows (RowControl). We also enhanced our virtualization mechanism so that cell DataContexts are not modified during scroll operations.
These changes helped produce a breakthrough in terms of performance. Unfortunately, creating controls internally reduced customization options (to a certain degree). To compensate for this, we added APIs to improve cell/row customization (make it simple and flexible). For example, the RowStyleSettings allows you to customize static row appearance settings:
<dxg:GridControl.RowStyleSettings>
<dxg:RowStyleSettings Background="LightGray"
SelectedBackground="PaleVioletRed"
FocusedBackground="LightBlue"
FocusedSelectedBackground="Orange"
HoverBackground="LightGreen"
FocusedBorderBrush="Red"
FocusedBorderThickness="1"
FocusedCornerRadius="5"/>
</dxg:GridControl.RowStyleSettings>
To change row appearance (based on data), you can use Conditional Formatting. We are also working on creating properties to customize different row sections and offer you maximum development flexibility.
What's Next?
Performance: Our latest release already addresses major performance challenges, but we still see room for improvement. For example, if you add standard controls such as CheckBox or ProgressBar to cells, you may notice a slight decrease in responsiveness. This happens because controls change their state frequently during scrolling and cause WinRT interop. We expect to create optimized grid cells controls (such as CheckBoxes and ProgressBars) this year.
More Controls – Extended Features: We will release additional UI controls and address a broader range of usage scenarios in 2022. We expect to publish a roadmap later this month. Please stay tuned for more information in this regard.
Your Feedback Matters
Should you have any questions about our WinUI product line, or if you’d like to discuss your WinUI development needs with us, please comment below or create a new DevExpress Support Center ticket.