Avalonia: FindControl returns null for UserControl

Created on 22 May 2019  路  7Comments  路  Source: AvaloniaUI/Avalonia

Hi,

I've updated to the latest CI build that uses XAMLIL.

Now the FindControl if used inside the ctor returns null for UserControl example:

    public ImageColorReplacePage()
    {
        this.InitializeComponent();
        this.Initialized += (s, e) =>
        {
            // FindControl after the Initialization is complete works
            var thisWontBeNull = this.FindControl<KitItemInformationView>("kitView");
        };

        // FindControl within the Constructor won't find anything
        var thisOneWillBeNull = this.FindControl<KitItemInformationView>("kitView");
    }

Repro project: https://github.com/braca/avalonia-findcontrol-bug

bug xaml-compiler

Most helpful comment

For anyone who wants a workaround, here's a method you can use:

        private void Find()
        {
            //var thing = this.FindControl<MyUserControl>("TheName");
            var thing = this.TemporaryFix<MyUserControl>("TheName");
        }

        // Temporary fix for https://github.com/AvaloniaUI/Avalonia/issues/2562
        private T TemporaryFix<T>(string name) where T : UserControl
        {
            T Recursion(System.Collections.Generic.IEnumerable<Avalonia.LogicalTree.ILogical> list)
            {
                foreach (Avalonia.LogicalTree.ILogical i in list)
                {
                    if (i is Avalonia.INamed named && named is T ret && ret.Name == name)
                    {
                        return ret;
                    }
                    else
                    {
                        T r = Recursion(i.LogicalChildren);
                        if (r != null)
                        {
                            return r;
                        }
                    }
                }
                return null;
            }
            return Recursion(this.LogicalChildren);
        }

You would put TemporaryFix() in your class since this.LogicalChildren is actually protected for UserControls. You could put it in some utils class if you pass this.LogicalChildren as another parameter instead.

All 7 comments

@kekekeks This bug could be similar to what @Kermalis got in https://github.com/AvaloniaUI/Avalonia/issues/2632

@grokys Decompiled XAML for https://github.com/braca/avalonia-findcontrol-bug/blob/master/MainWindow.xaml

CompiledAvaloniaXaml.XamlIlContext.Context<MainWindow> context = new CompiledAvaloniaXaml.XamlIlContext.Context<MainWindow>(P_0, new object[1]
{
(object)CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/MainWindow.xaml.Singleton
}, "avares://AvaloniaFindControl/MainWindow.xaml");
context.RootObject = P_1;
((ISupportInitialize)P_1).BeginInit();
P_1.Title = "AvaloniaFindControl";
Grid grid;
Grid grid2 = grid = new Grid();
((ISupportInitialize)grid2).BeginInit();
P_1.Content = grid2;
Grid grid3 = grid;
TextBlock textBlock;
TextBlock obj = textBlock = new TextBlock();
((ISupportInitialize)obj).BeginInit();
IControl control = obj;
Controls children = grid3.Children;
control = control;
((AvaloniaList<IControl>)children).Add(control);
TextBlock obj2 = textBlock;
((StyledElement)obj2).Name = "textBlock";
object obj3 = obj2;
((ILogical)obj3).FindNameScope()?.Register("textBlock", obj3);
obj2.Text = "Hello text block";
((ISupportInitialize)obj2).EndInit();
SampleUserControl sampleUserControl;
SampleUserControl obj4 = sampleUserControl = new SampleUserControl();
((ISupportInitialize)obj4).BeginInit();
control = obj4;
Controls children2 = grid3.Children;
control = control;
((AvaloniaList<IControl>)children2).Add(control);
SampleUserControl obj5 = sampleUserControl;
((StyledElement)obj5).Name = "sampleUserControl";
obj3 = obj5;
((ILogical)obj3).FindNameScope()?.Register("sampleUserControl", obj3);
((ISupportInitialize)obj5).EndInit();
((ISupportInitialize)grid3).EndInit();
((ISupportInitialize)P_1).EndInit();

As you can see both TextBlock and the user control get their name initialized in the same way:

((StyledElement)obj2).Name = "textBlock";
object obj3 = obj2;
((ILogical)obj3).FindNameScope()?.Register("textBlock", obj3);
((ISupportInitialize)obj2).EndInit();
((StyledElement)obj5).Name = "sampleUserControl";
obj3 = obj5;
((ILogical)obj3).FindNameScope()?.Register("sampleUserControl", obj3);
((ISupportInitialize)obj5).EndInit();

Not sure what is wrong here

My constructor is also trying to FindControl() a UserControl. The Avalonia controls (TabControl, Button, TextBox, NumericUpDown) are all working with FindControl() within the same constructor:

        public MainView()
        {
            AvaloniaXamlLoader.Load(this);
            DataContext = this;

            tabs = this.FindControl<TabControl>("Tabs");
            teamBuilder = this.FindControl<TeamBuilderView>("TeamBuilder");
            ip = this.FindControl<TextBox>("IP");
            port = this.FindControl<NumericUpDown>("Port");
            connect = this.FindControl<Button>("Connect");
            connect.Command = ReactiveCommand.Create(Connect);
            teamBuilder.Load(); // Crash here because teamBuilder is null (It inherits UserControl)
        }

This happens in every constructor of mine that tries to FindControl() a UserControl, not just this specific one. Below is an example where I have the view constructed within my code [var bv = new BattleView(client);]

        public BattleView()
        {
            // This constructor only exists so xaml compiles, I did not need it for stable 0.8.0
            AvaloniaXamlLoader.Load(this);
        }
        public BattleView(BattleClient client)
        {
            AvaloniaXamlLoader.Load(this);
            Client = client;
            Client.BattleView = this;
            Field = this.FindControl<FieldView>("Field");
            Field.SetBattleView(this); // Crash here because Field is null (It inherits UserControl)
            Actions = this.FindControl<ActionsView>("Actions");
            Actions.BattleView = this;
            messages = this.FindControl<MessageView>("Messages");
        }

All of these examples were working in 0.8.0.

The Initialized workaround no longer works, as the FindControl() will return null in there now as well. It is now impossible to get the UserControls.

XamlIl might be registering the user control in its own name scope, which might be the issue here.

For anyone who wants a workaround, here's a method you can use:

        private void Find()
        {
            //var thing = this.FindControl<MyUserControl>("TheName");
            var thing = this.TemporaryFix<MyUserControl>("TheName");
        }

        // Temporary fix for https://github.com/AvaloniaUI/Avalonia/issues/2562
        private T TemporaryFix<T>(string name) where T : UserControl
        {
            T Recursion(System.Collections.Generic.IEnumerable<Avalonia.LogicalTree.ILogical> list)
            {
                foreach (Avalonia.LogicalTree.ILogical i in list)
                {
                    if (i is Avalonia.INamed named && named is T ret && ret.Name == name)
                    {
                        return ret;
                    }
                    else
                    {
                        T r = Recursion(i.LogicalChildren);
                        if (r != null)
                        {
                            return r;
                        }
                    }
                }
                return null;
            }
            return Recursion(this.LogicalChildren);
        }

You would put TemporaryFix() in your class since this.LogicalChildren is actually protected for UserControls. You could put it in some utils class if you pass this.LogicalChildren as another parameter instead.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

x2bool picture x2bool  路  4Comments

MarchingCube picture MarchingCube  路  4Comments

khoshroomahdi picture khoshroomahdi  路  4Comments

SeleDreams picture SeleDreams  路  4Comments

kekekeks picture kekekeks  路  4Comments