Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Crash when Toggling TextEditor.ShowLineNumbers #434

Open
gebodal opened this issue Jun 23, 2024 · 1 comment
Open

Crash when Toggling TextEditor.ShowLineNumbers #434

gebodal opened this issue Jun 23, 2024 · 1 comment

Comments

@gebodal
Copy link

gebodal commented Jun 23, 2024

Unless I'm missing something, it seems that changing the ShowLineNumbers property of a loaded TextEditor from false to true will cause an ArgumentOutOfRangeException that seems to be originating from LineNumberMargin.Render(). Specifically, this error is occurring in a TextEditor hosted inside a Window which has finished loading and is visible, with the ShowLineNumbers property being changed via user interaction.

var text = TextFormatterFactory.CreateFormattedText(
this,
lineNumber.ToString(CultureInfo.CurrentCulture),
Typeface, EmSize, foreground
);

It seems that the EmSize (taken from the TextBlock.FontSizeProperty) is not being set somehow, possibly because it is set in MeasureOverride(), which presumably is not being called before rendering?

EmSize = GetValue(TextBlock.FontSizeProperty);

The "OnChanged" method for ShowLineNumbers in TextEditor creates a brand new LineNumberMargin Control when set to true from false, so even if altered in sequence true->false->true, the error occurs.

Stack trace of error:

System.ArgumentOutOfRangeException: The parameter value must be greater than zero. (Parameter 'emSize')
   at Avalonia.Media.FormattedText.ValidateFontSize(Double emSize)
   at Avalonia.Media.FormattedText..ctor(String textToFormat, CultureInfo culture, FlowDirection flowDirection, Typeface typeface, Double emSize, IBrush foreground)
   at AvaloniaEdit.Utils.TextFormatterFactory.CreateFormattedText(Control element, String text, Typeface typeface, Nullable`1 emSize, IBrush foreground)
   at AvaloniaEdit.Editing.LineNumberMargin.Render(DrawingContext drawingContext)
   at Avalonia.Rendering.Composition.CompositingRenderer.UpdateCore()
   at Avalonia.Rendering.Composition.CompositingRenderer.Update()
   at Avalonia.Rendering.Composition.Compositor.CommitCore()
   at Avalonia.Rendering.Composition.Compositor.Commit()
   at Avalonia.Media.MediaContext.CommitCompositor(Compositor compositor)
   at Avalonia.Media.MediaContext.SyncCommit(Compositor compositor, Boolean waitFullRender)
   at Avalonia.Media.MediaContext.SyncDisposeCompositionTarget(CompositionTarget compositionTarget)
   at Avalonia.Rendering.Composition.CompositingRenderer.Dispose()
   at Avalonia.Controls.TopLevel.HandleClosed()
   at Avalonia.Controls.WindowBase.HandleClosed()
   at Avalonia.Win32.WindowImpl.AppWndProc(IntPtr hWnd, UInt32 msg, IntPtr wParam, IntPtr lParam)
   at Avalonia.Win32.WindowImpl.WndProc(IntPtr hWnd, UInt32 msg, IntPtr wParam, IntPtr lParam)
   at Avalonia.Win32.PopupImpl.WndProc(IntPtr hWnd, UInt32 msg, IntPtr wParam, IntPtr lParam)

If there's a workaround, I'd love to hear about it! So far I have tried: InvalidateMeasure(), InvalidateArrange() and InvalidateVisual() after changing ShowLineNumbers on the TextEditor, the TextArea, and all Controls in textArea.LeftMargins; using Dispatcher.UIThread.Invoke() to change the value of ShowLineNumbers. None of these have solved the issue.

@gebodal
Copy link
Author

gebodal commented Jun 24, 2024

OK, I realised there's a simple enough hack for this, where you just have to replace the TextEditor.ShowLineNumbers setter logic with your own. The only important point is that you have to wait until the control is loaded before disabling, otherwise the EmSize will not have been set correctly.

The below uses a modified version of the TextEditor code here:

for (var i = 0; i < leftMargins.Count; i++)
{
if (leftMargins[i] is LineNumberMargin)
{
leftMargins.RemoveAt(i);
if (i < leftMargins.Count && DottedLineMargin.IsDottedLineMargin(leftMargins[i]))
{
leftMargins.RemoveAt(i);
}
break;
}
}

public MyControl() {
	// Other initialisation here

	//// Line numbers setup
	// Set to true initially so that internal values get initialised properly
	textEditor.ShowLineNumbers = true;

	// Collect list of line controls (following logic in TextEditor.cs#L549-L560)
	List<Control> lineNumberControlsList = new List<Control>();
	var leftMargins = textEditor.TextArea.LeftMargins;
	for (int i = 0; i < leftMargins.Count; i++) {
		if (leftMargins[i] is LineNumberMargin) {
			lineNumberControlsList.Add(leftMargins[i]);
			if (i + 1 < leftMargins.Count && DottedLineMargin.IsDottedLineMargin(leftMargins[i + 1])) {
				lineNumberControlsList.Add(leftMargins[i + 1]);
			}
			break;
		}
	}
	lineNumberControls = lineNumberControlsList.ToArray();

	// When finished loading, set visibility to desired value
	textEditor.Loaded += OnLoadedAdjustShowLineNumbers;
}

private readonly Control[] lineNumberControls;

// Adjust the visibility of the line number controls without actually removing them
public bool ShowLineNumbers {
	get {
		return lineNumberControls.Any(c => c.IsVisible); // Or something neater
	}
	set {
		foreach(Control c in lineNumberControls) {
			c.IsVisible = value;
		}
	}
}

private void OnLoadedAdjustShowLineNumbers(object? sender, RoutedEventArgs e) {
	ShowLineNumbers = MyDataManager.GetShowLineNumbers();
}

Or something like that, anyway. (Seems to work on my machine!)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant