Repository: AvaloniaUI/Avalonia

Issue:
Datagrid ComboBox SelectedItem Show Error
### Describe the bug

![Image](https://github.com/user-attachments/assets/5ed61cc9-5094-425d-acc5-95aaa51abc5d)

  // DataType ComboBox ItemsSource,this show OK
    public static List<string> DataTypes =>
  Enum.GetNames(typeof(DataTypeEnum)).ToList();

  // DataType2 ComboBox ItemsSource, no static ,this show Error
    public List<string> DataTypeList => Enum.GetNames(typeof(DataTypeEnum)).ToList();


          

### To Reproduce

```
public enum DataTypeEnum
{
    Bit,
    Byte,
    Int16,
    Uint16,
    Int32,
    Uint32,
    Real,
    String,
    Wstring,
    DateTime
}
```

```
public partial class MainWindowViewModel : ViewModelBase
{
    [ObservableProperty]
    private ObservableCollection<TagViewModel>? _tags;

  // DataType ComboBox ItemsSource,this show OK
    public static List<string> DataTypes =>
  Enum.GetNames(typeof(DataTypeEnum)).ToList();

  // DataType2 ComboBox ItemsSource, no static ,this show Error
    public List<string> DataTypeList => Enum.GetNames(typeof(DataTypeEnum)).ToList();

    public MainWindowViewModel()
    {
        Tags = new ObservableCollection<TagViewModel>();
    }

    private static readonly Random _random = new Random();

    private string GenerateRandomAddress()
    {
        const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        return new string(Enumerable.Repeat(chars, 8)
          .Select(s => s[_random.Next(s.Length)]).ToArray());
    }

    [RelayCommand]
    private void Refresh()
    {
        var tags = new List<TagViewModel>
            {
                new TagViewModel { Id = 1, DeviceId = 101, DeviceName = "Device1", Name = "Tag1", Address = GenerateRandomAddress(), Desc = "Description1", DataType = DataTypeEnum.Int16.ToString(),DataType2=DataTypeEnum.Int16.ToString(), ArrayCount = 1 },
                new TagViewModel { Id = 2, DeviceId = 102,DataType2=DataTypeEnum.Int16.ToString(),  DeviceName = "Device2", Name = "Tag2", Address = GenerateRandomAddress(), Desc = "Description2", DataType = DataTypeEnum.Int32.ToString(), ArrayCount = 2 },
                new TagViewModel { Id = 3, DeviceId = 103, DeviceName = "Device3", Name = "Tag3", Address = GenerateRandomAddress(), Desc = "Description3", DataType = DataTypeEnum.Real.ToString(), DataType2=DataTypeEnum.Int16.ToString(), ArrayCount = 3 },
                new TagViewModel { Id = 4, DeviceId = 104, DeviceName = "Device4", Name = "Tag4", Address = GenerateRandomAddress(), Desc = "Description4", DataType = DataTypeEnum.String.ToString(), DataType2 = DataTypeEnum.Int16.ToString(), ArrayCount = 4 }
            };

        Tags?.Clear();
        foreach (var tag in tags)
        {
            Tags?.Add(tag);
        }

    }
}
```

```
 <DataGridTemplateColumn Header="DataType" Width="120">
   <DataGridTemplateColumn.CellTemplate>
     <DataTemplate x:DataType="vm:TagViewModel">
       <ComboBox 
                 ItemsSource="{x:Static vm:MainWindowViewModel.DataTypes}"
                 SelectedItem="{Binding DataType, Mode=TwoWay}"/>
     </DataTemplate>
   </DataGridTemplateColumn.CellTemplate>
 </DataGridTemplateColumn>

 <DataGridTemplateColumn Header="数据类型2" Width="120">
   <DataGridTemplateColumn.CellTemplate>
     <DataTemplate x:DataType="vm:TagViewModel">
       <ComboBox  x:CompileBindings="False"           
                 ItemsSource="{Binding $parent[DataGrid].DataContext.DataTypeList}}"
                 SelectedItem="{Binding DataType2, Mode=TwoWay}"/>
     </DataTemplate>
   </DataGridTemplateColumn.CellTemplate>
 </DataGridTemplateColumn>
```

### Expected behavior

_No response_

### Avalonia version

11.2.6

### OS

Windows

### Additional context

_No response_


Hints:
demo
[DatagridDemo.zip](https://github.com/user-attachments/files/19662338/DatagridDemo.zip)
This is not a bug. The reason is that the binding for `SelectedItem` may complete before the binding for `ItemsSource`, making your initial value setting ineffective. In this case, the `SelectedItem` you set does not exist in the `ItemsSource`. You can resolve this issue through some design changes, such as subscribing to the `ValueChanged` event of `ComboBox.ItemsSource` and binding `ComboBox.SelectedItem` after it is not null.

Patch:
diff --git a/src/Avalonia.Controls/ItemCollection.cs b/src/Avalonia.Controls/ItemCollection.cs
index 03f46551c5f..03fed042bec 100644
--- a/src/Avalonia.Controls/ItemCollection.cs
+++ b/src/Avalonia.Controls/ItemCollection.cs
@@ -11,15 +11,10 @@ namespace Avalonia.Controls
     /// </summary>
     public class ItemCollection : ItemsSourceView, IList
     {
-// Suppress "Avoid zero-length array allocations": This is a sentinel value and must be unique.
-#pragma warning disable CA1825
-        private static readonly object?[] s_uninitialized = new object?[0];
-#pragma warning restore CA1825
-
         private Mode _mode;
 
         internal ItemCollection()
-            : base(s_uninitialized)
+            : base(UninitializedSource)
         {
         }
 
@@ -100,7 +95,7 @@ private IList WritableSource
             {
                 if (IsReadOnly)
                     ThrowIsItemsSource();
-                if (Source == s_uninitialized)
+                if (Source == UninitializedSource)
                     SetSource(CreateDefaultCollection());
                 return Source;
             }
diff --git a/src/Avalonia.Controls/ItemsSourceView.cs b/src/Avalonia.Controls/ItemsSourceView.cs
index 977d712371a..22cddddd058 100644
--- a/src/Avalonia.Controls/ItemsSourceView.cs
+++ b/src/Avalonia.Controls/ItemsSourceView.cs
@@ -27,6 +27,13 @@ public class ItemsSourceView : IReadOnlyList<object?>,
         /// </summary>
         public static ItemsSourceView Empty { get; } = new ItemsSourceView(Array.Empty<object?>());
 
+        /// <summary>
+        /// Gets an instance representing an uninitialized source.
+        /// </summary>
+        [SuppressMessage("Performance", "CA1825:Avoid zero-length array allocations", Justification = "This is a sentinel value and must be unique.")]
+        [SuppressMessage("ReSharper", "UseCollectionExpression", Justification = "This is a sentinel value and must be unique.")]
+        internal static object?[] UninitializedSource { get; } = new object?[0];
+
         private IList _source;
         private NotifyCollectionChangedEventHandler? _collectionChanged;
         private NotifyCollectionChangedEventHandler? _preCollectionChanged;
@@ -49,6 +56,9 @@ public class ItemsSourceView : IReadOnlyList<object?>,
         /// </summary>
         public IList Source => _source;
 
+        internal IList? TryGetInitializedSource()
+            => _source == UninitializedSource ? null : _source;
+
         /// <summary>
         /// Retrieves the item at the specified index.
         /// </summary>
diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
index 0d98d41f080..59289838bbf 100644
--- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
+++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
@@ -592,10 +592,7 @@ protected override void OnInitialized()
         {
             base.OnInitialized();
 
-            if (_selection is object)
-            {
-                _selection.Source = ItemsView.Source;
-            }
+            TryInitializeSelectionSource(_selection, _updateState is null);
         }
 
         /// <inheritdoc />
@@ -896,8 +893,8 @@ private ISelectionModel GetOrCreateSelectionModel()
 
         private void OnItemsViewSourceChanged(object? sender, EventArgs e)
         {
-            if (_selection is not null && _updateState is null)
-                _selection.Source = ItemsView.Source;
+            if (_updateState is null)
+                TryInitializeSelectionSource(_selection, true);
         }
 
         /// <summary>
@@ -1202,7 +1199,7 @@ private void InitializeSelectionModel(ISelectionModel model)
         {
             if (_updateState is null)
             {
-                model.Source = ItemsView.Source;
+                TryInitializeSelectionSource(model, false);
             }
 
             model.PropertyChanged += OnSelectionModelPropertyChanged;
@@ -1237,6 +1234,32 @@ private void InitializeSelectionModel(ISelectionModel model)
             }
         }
 
+        private void TryInitializeSelectionSource(ISelectionModel? selection, bool shouldSelectItemFromSelectedValue)
+        {
+            if (selection is not null && ItemsView.TryGetInitializedSource() is { } source)
+            {
+                // InternalSelectionModel keeps the SelectedIndex and SelectedItem values before the ItemsSource is set.
+                // However, SelectedValue isn't part of that model, so we have to set the SelectedItem from
+                // SelectedValue manually now that we have a source.
+                //
+                // While this works, this is messy: we effectively have "lazy selection initialization" in 3 places:
+                //  - UpdateState (all selection properties, for BeginInit/EndInit)
+                //  - InternalSelectionModel (SelectedIndex/SelectedItem)
+                //  - SelectedItemsControl (SelectedValue)
+                //
+                // There's the opportunity to have a single place responsible for this logic.
+                // TODO12 (or 13): refactor this.
+                if (shouldSelectItemFromSelectedValue && selection.SelectedIndex == -1 && selection.SelectedItem is null)
+                {
+                    var item = FindItemWithValue(SelectedValue);
+                    if (item != AvaloniaProperty.UnsetValue)
+                        selection.SelectedItem = item;
+                }
+
+                selection.Source = source;
+            }
+        }
+
         private void DeinitializeSelectionModel(ISelectionModel? model)
         {
             if (model is object)
@@ -1266,7 +1289,7 @@ private void EndUpdating()
 
                 if (_selection is InternalSelectionModel s)
                 {
-                    s.Update(ItemsView.Source, state.SelectedItems);
+                    s.Update(ItemsView.TryGetInitializedSource(), state.SelectedItems);
                 }
                 else
                 {
@@ -1275,7 +1298,7 @@ private void EndUpdating()
                         SelectedItems = state.SelectedItems.Value;
                     }
 
-                    Selection.Source = ItemsView.Source;
+                    TryInitializeSelectionSource(Selection, false);
                 }
 
                 if (state.SelectedValue.HasValue)


Test patch:
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
index 5c1ece224ad..13c6ee50a5b 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
@@ -441,7 +441,7 @@ public void Setting_SelectedIndex_Should_Set_SelectedItem()
         }
 
         [Fact]
-        public void Setting_SelectedIndex_Out_Of_Bounds_Should_Clear_Selection()
+        public void Setting_SelectedIndex_Out_Of_Bounds_With_ItemsSource_Should_Clear_Selection()
         {
             var items = new[]
             {
@@ -462,11 +462,50 @@ public void Setting_SelectedIndex_Out_Of_Bounds_Should_Clear_Selection()
         }
 
         [Fact]
-        public void Setting_SelectedItem_To_Non_Existent_Item_Should_Clear_Selection()
+        public void Setting_SelectedIndex_Out_Of_Bounds_Without_ItemsSource_Should_Keep_Selection_Until_ItemsSource_Is_Set()
         {
             var target = new SelectingItemsControl
             {
-                Template = Template(),
+                Template = Template()
+            };
+
+            target.ApplyTemplate();
+            target.SelectedIndex = 2;
+
+            Assert.Equal(2, target.SelectedIndex);
+
+            target.ItemsSource = Array.Empty<Item>();
+
+            Assert.Equal(-1, target.SelectedIndex);
+        }
+
+        [Fact]
+        public void Setting_SelectedIndex_Without_ItemsSource_Should_Keep_Selection_If_Index_Exists_When_ItemsSource_IsSet()
+        {
+            var target = new SelectingItemsControl
+            {
+                Template = Template()
+            };
+
+            target.ApplyTemplate();
+            target.SelectedIndex = 2;
+
+            Assert.Equal(2, target.SelectedIndex);
+
+            var items = new Item[] { new(), new(), new(), new() };
+            target.ItemsSource = items;
+
+            Assert.Equal(2, target.SelectedIndex);
+            Assert.Same(items[2], target.SelectedItem);
+        }
+
+        [Fact]
+        public void Setting_SelectedItem_To_Non_Existent_Item_With_ItemsSource_Should_Clear_Selection()
+        {
+            var target = new SelectingItemsControl
+            {
+                ItemsSource = Array.Empty<Item>(),
+                Template = Template()
             };
 
             target.ApplyTemplate();
@@ -476,6 +515,50 @@ public void Setting_SelectedItem_To_Non_Existent_Item_Should_Clear_Selection()
             Assert.Null(target.SelectedItem);
         }
 
+        [Fact]
+        public void Setting_SelectedItem_To_Non_Existent_Item_Without_ItemsSource_Should_Keep_Selection_Until_ItemsSource_Is_Set()
+        {
+            var item = new Item();
+
+            var target = new SelectingItemsControl
+            {
+                Template = Template()
+            };
+
+            target.ApplyTemplate();
+            target.SelectedItem = item;
+
+            Assert.Equal(-1, target.SelectedIndex);
+            Assert.Same(item, target.SelectedItem);
+
+            target.ItemsSource = Array.Empty<Item>();
+
+            Assert.Equal(-1, target.SelectedIndex);
+            Assert.Null(target.SelectedItem);
+        }
+
+        [Fact]
+        public void Setting_SelectedItem_Without_ItemsSource_Should_Keep_Selection_If_Item_Exists_When_ItemsSource_IsSet()
+        {
+            var item = new Item();
+
+            var target = new SelectingItemsControl
+            {
+                Template = Template()
+            };
+
+            target.ApplyTemplate();
+            target.SelectedItem = item;
+
+            Assert.Equal(-1, target.SelectedIndex);
+            Assert.Same(item, target.SelectedItem);
+
+            target.ItemsSource = new[] { new(), new(), item, new() };
+
+            Assert.Equal(2, target.SelectedIndex);
+            Assert.Same(item, target.SelectedItem);
+        }
+
         [Fact]
         public void Adding_Selected_Item_Should_Update_Selection()
         {
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_SelectedValue.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_SelectedValue.cs
index b0e430cbe07..a779e8d0744 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_SelectedValue.cs
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_SelectedValue.cs
@@ -170,6 +170,53 @@ public void Setting_SelectedValue_Before_Initialize_Should_Retain_Selection()
             Assert.Equal(items[2].Name, sic.SelectedValue);
         }
 
+        [Fact]
+        public void Setting_SelectedValue_To_Non_Existent_Item_Without_ItemsSource_Should_Keep_Selection_Until_ItemsSource_Is_Set()
+        {
+            var target = new SelectingItemsControl
+            {
+                Template = Template(),
+                SelectedValueBinding = new Binding("Name")
+            };
+
+            target.ApplyTemplate();
+            target.SelectedValue = "Item2";
+
+            Assert.Equal(-1, target.SelectedIndex);
+            Assert.Null(target.SelectedItem);
+            Assert.Same("Item2", target.SelectedValue);
+
+            target.ItemsSource = Array.Empty<TestClass>();
+
+            Assert.Equal(-1, target.SelectedIndex);
+            Assert.Null(target.SelectedItem);
+            Assert.Null(target.SelectedValue);
+        }
+
+        [Fact]
+        public void Setting_SelectedValue_Without_ItemsSource_Should_Keep_Selection_If_Item_Exists_When_ItemsSource_IsSet()
+        {
+            var target = new SelectingItemsControl
+            {
+                Template = Template(),
+                SelectedValueBinding = new Binding("Name")
+            };
+
+            target.ApplyTemplate();
+            target.SelectedValue = "Item2";
+
+            Assert.Equal(-1, target.SelectedIndex);
+            Assert.Null(target.SelectedItem);
+            Assert.Same("Item2", target.SelectedValue);
+
+            var items = TestClass.GetItems();
+            target.ItemsSource = items;
+
+            Assert.Equal(2, target.SelectedIndex);
+            Assert.Same(items[2], target.SelectedItem);
+            Assert.Equal("Item2", target.SelectedValue);
+        }
+
         [Fact]
         public void Setting_SelectedValue_During_Initialize_Should_Take_Priority_Over_Previous_Value()
         {