[DevExpress Support Team: CLONED FROM T517263: The NullReference exception occurs when the LayoutChanged method is called]
Hi Support team,
I was going to raise a ticket for the same issue until i found this ticket already opened by Steven (feel free if you want me to raise a ticket instead of commenting this).
We are using the "BeginInvoke" and this issue still happens to us in our product, we are using a common method that takes a Control and a string as parameters, then sets the Control.Text = string, here's the method:
C#internal void SetDisplayText(Control control, string text)
{
try
{
if (control == null)
return;
if (!control.IsHandleCreated)
return;
if (this.InvokeRequired || control.InvokeRequired)
{
BeginInvoke(new UIMarshallingThreadDelegates.ParameterLessTaskDelegate(
delegate
{
control.Text = text;
}));
}
else
{
control.Text = text;
}
}
catch (Exception exception)
{
ServiceLocator.LoggerService.Error("TopOfBook.SetDisplayText => error : " + exception);
}
}
The method above can be called from several threads.
Here's the exception callStack:
Code2020-05-04 14:27:45:832, P:29220, TH:62308, [Error,Error,4] : TopOfBook.SetDisplayText => error : System.NullReferenceException: Object reference not set to an instance of an object.
at DevExpress.Utils.Text.FontsCache.GetFontCacheByFont(Graphics graphics, Font font)
at DevExpress.Utils.Text.TextUtils.GetStringSize(Graphics g, String text, Font font, StringFormat stringFormat, Int32 maxWidth, Int32 maxHeight, IWordBreakProvider wordBreakProvider, Boolean& isCropped)
at DevExpress.Utils.Paint.XPaintMixed.CalcTextSize(Graphics g, String s, Font font, StringFormat strFormat, Int32 maxWidth, Int32 maxHeight, Boolean& isCropped)
at DevExpress.Utils.Paint.XPaintMixed.CalcTextSize(Graphics g, String s, Font font, StringFormat strFormat, Int32 maxWidth)
at DevExpress.Utils.AppearanceObject.CalcTextSize(GraphicsCache cache, StringFormat sf, String s, Int32 width)
at DevExpress.XtraEditors.ViewInfo.LabelControlViewInfo.CalcSimpleTextSize(String Text, Boolean useHotkeyPrefix, LabelAutoSizeMode mode, Int32 predWidth, Int32 predHeight)
at DevExpress.XtraEditors.ViewInfo.LabelControlViewInfo.CalcTextSize(String Text, Boolean useHotkeyPrefix, LabelAutoSizeMode mode, Int32 predWidth, Int32 predHeight)
at DevExpress.XtraEditors.ViewInfo.LabelControlViewInfo.CalcTextSize(String Text, Boolean useHotkeyPrefix, LabelAutoSizeMode mode)
at DevExpress.XtraEditors.ViewInfo.LabelControlViewInfo.CalcTextSize(Boolean useHotkeyPrefix)
at DevExpress.XtraEditors.ViewInfo.LabelControlViewInfo.CalcTextPoints()
at DevExpress.XtraEditors.ViewInfo.LabelControlViewInfo.CalcContentRect(Rectangle bounds)
at DevExpress.XtraEditors.ViewInfo.BaseControlViewInfo.CalcRects()
at DevExpress.XtraEditors.ViewInfo.BaseControlViewInfo.CalcViewInfo(Graphics g)
at DevExpress.XtraEditors.LabelControl.LayoutChanged(Boolean isVisualUpdate)
at DevExpress.XtraEditors.LabelControl.OnTextChanged(EventArgs e)
at System.Windows.Forms.Control.set_Text(String value)
at Com.QuodFinancial.FrontEnd.Gui.Mvp.Views.ForEx.InstrumentSummary.InstrumentSummaryTopOfBook.SetDisplayText(Control control, String text)
The exception is very rare to happen, but in the context we are working at, this should never happen as we can miss big opportunities.
We are using DevExpress 18.2.5.
Thank you,
Chams
Hello,
Thank you for the clarification. However, I need more information to determine the cause of the issue. So, it would help if you share a project where this behavior occurs. You can copy your real application and remove all unnecessary forms from it. It will be enough if you create custom fake data to bind it to controls. If it is necessary to follow specific steps to reproduce the issue, please describe them.
Also, I suggest you update your project to the latest version (20.1.3) and check if the issue persists.
Please note that before you call the BeginInvoke method, it is necessary to check the IsHandleCreated property of the control whose BeginInvoke you call.
Our project is very huge and is related to a complete platform and servers in order to run and display data, providing a simple project will be taking a lot of time from us.
But what is most important that i can tell, is that the main method used is the one described above, and it is used in a multi-threaded context.
Is it not enough to test the "InvokeRequired" before calling BeginInvoke (as you can see in our code above)? as it will be always false if the Handle is not created yet, and we'll not call the BeginInvoke. (that's something from the basic points that we have learned when we started using DevExpress years ago).
Maybe if no clue is given we'll wait for the migration to 20.1.3.
Hi,
Since you check both the this.InvokeRequired and control.InvokeRequired properties, would you please confirm that your controls indicated as control and this are created in the same UI thread?
I investigated our source code and found that such an exception could be thrown only if the LabelControlViewInfo.CalcSimpleTextSize method was called in a background thread. We create and release a Graphics object in this method. If the method is accessed from a background thread, there is a chance that a Graphics object will not be created when the next PaintAppearance.CalcTextSize method is called.
To check this point, please create a custom LabelControl and replace all your existing LabelControls with it using the Find and Replace VS dialog (CTRL+SHIFT+F):
public class LabelControlEx: LabelControl { protected override BaseStyleControlViewInfo CreateViewInfo() { return new LabelControlViewInfoEx(this); } } public class LabelControlViewInfoEx : LabelControlViewInfo { public LabelControlViewInfoEx(BaseStyleControl obj) : base(obj) { } protected override Size CalcTextSize(string Text, bool useHotkeyPrefix, LabelAutoSizeMode mode, int predWidth, int predHeight) { Debug.WriteLine($"Id = {Thread.CurrentThread.ManagedThreadId}, IsBackground = {Thread.CurrentThread.IsBackground}"); return base.CalcTextSize(Text, useHotkeyPrefix, mode, predWidth, predHeight); } }
Once the issue is reproduced, please copy the text from the Output window and send it to us.
Hi DevExpress
We also had this issue in our application and we were sure we did everything right with UI thread. And we did - we are using Invokes and Async/Await so there shouldn't be a thread issue.
But what we didn't expect or didn't know for long time: On every .NET app, there is a global System.Threading.SynchronizationContext. When a .NET app starts, there will be a WindowsFormsSynchronizationContext registered on that property.
In a 100% .NET app, you will most likely never have any issue. But if you have a part of your application in .NET and this part is hosted in another app type (for example C++), there can be the following problem:
As soon as a .NET form is closed (after ShowDialog), the WindowsFormsSynchronizationContext is uninstalled on System.Threading.SynchronizationContext and a simple SynchronizationContext is registered. Note: SynchronizationContext != WindowsFormsSynchronizationContext.
Source:
https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Application.cs,3494
https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/WindowsFormsSynchronizationContext.cs,163
With SynchronizationContext registered on System.Threading.SynchronizationContext, thread issues can become real. In other words: With SynchronizationContext registered on System.Threading.SynchronizationContext, Invokes and Async/Await don't work correctly anymore.
Solution
In our main application at start (before any .NET form is shown), we do the following:
Public Sub EnsureWindowsFormsSynchronizationContext() WindowsFormsSynchronizationContext.AutoInstall = False If SynchronizationContext.Current Is Nothing OrElse TypeOf SynchronizationContext.Current IsNot WindowsFormsSynchronizationContext Then SynchronizationContext.SetSynchronizationContext(New WindowsFormsSynchronizationContext()) End If End Sub
Set AutoInstall to False, so the uninstall of WindowsFormsSynchronizationContext can't happen after a ShowDialog and (if needed) set your own WindowsFormsSynchronizationContext, which will remain as long as the app runs.
Other sources:
https://stackoverflow.com/questions/19535147/await-and-synchronizationcontext-in-a-managed-component-hosted-by-an-unmanaged-a
https://stackoverflow.com/questions/32439669/hosting-net-and-winforms-synchronizationcontexts-is-reset-when-showdialog-of
I hope this also helps in other tickets like: https://www.google.com/search?q=devexpress+exception+GetFontCacheByFont
After this change, we didn't face the issue anymore.
Best Regards,
Silvan