如何在代码中将ViewModel集合绑定到动态创建的ItemsControl?
问题:Avalonia动态创建ItemsControl绑定后显示类型名称而非数据
我在Avalonia应用里有个用户控件(UC),包含数据绑定控件和可动态添加控件的StackPanel,这个UC会被动态加到外部用户控件中。我想在代码里创建该UC实例,添加ItemsControl并把它的ItemsSource绑定到UC的DataContext(ViewModel类)的ObservableCollection属性。
为排查问题做了示例:书架UC显示ViewModel里的4本书籍,UC已有绑定VM的Category属性的TextBlock,还有StackPanel用来放书籍的ItemsControl和书架的TextBlock。UC会和下方的安装支架TextBlock一起动态加到主窗口,预期像XAML定义的ItemsControl一样显示,但实际每个书籍控件组都显示类型名称,不是书籍信息。
相关代码
BookVM.cs
public partial class BookVM : ViewModelBase { [ObservableProperty] private string _title = "Read This"; [ObservableProperty] private string _author = "A. Writer"; [ObservableProperty] private int _yearPublished = 2025; }
BookshelfVM.cs
public partial class BookshelfVM : ObservableObject { [ObservableProperty] private string _category; public ObservableCollection<BookVM> Books { get; set; } = []; public BookshelfVM() { Category = "Science Fiction"; Books.Add(new BookVM { Title = "Fahrenheit 451", Author = "Ray Bradbury", YearPublished = 1953 }); Books.Add(new BookVM { Title = "Dune", Author = "Frank Herbert", YearPublished = 1965 }); Books.Add(new BookVM { Title = "Neuromancer", Author = "William Gibson", YearPublished = 1984 }); Books.Add(new BookVM { Title = "Ender’s Game", Author = "Orson Scott Card", YearPublished = 1985 }); } }
BookshelfUC.axaml
<Grid> <TextBlock Text="{Binding Category}" FontSize="18" FontWeight="Bold" /> <StackPanel x:Name="stpBooksPlusShelf" Margin="0,25,0,0" /> </Grid>
MainView.axaml
<DockPanel HorizontalAlignment="Left" VerticalAlignment="Stretch"> <TextBlock DockPanel.Dock="Top" Text="This should show a stack of books." Margin="0,0,0,10" /> <StackPanel x:Name="stpDynamicControls" /> </DockPanel>
MainView.axaml.cs
public partial class MainView : UserControl { public MainView() { InitializeComponent(); var dynamicBookshelf = new BookshelfUC();// { DataContext = new BookshelfVM() }; var itemsCtlToAdd = new ItemsControl { [!ItemsControl.ItemsSourceProperty] = new Binding("Books") }; var dataTmplt = new StackPanel(); var borderedStack = new StackPanel(); var bookBorder = new Border { BorderBrush = new SolidColorBrush(Colors.Brown), BorderThickness = new Avalonia.Thickness(2) }; var title = new TextBlock { Background = new SolidColorBrush(Color.Parse("#BC8F6F")), Foreground = new SolidColorBrush(Colors.White), [!TextBlock.TextProperty] = new Binding("Title") }; var authorAndYear = new TextBlock { [!TextBlock.TextProperty] = new Binding("YearPublished") }; var shelfAfterBooks = new TextBlock { Background = new SolidColorBrush(Colors.Black), Foreground = new SolidColorBrush(Colors.White), Text = "Shelf on which the stack of book sits" }; var bracket = new TextBlock { Background = new SolidColorBrush(Colors.LightGray), Text = "Mounting brackets" }; borderedStack.Children.Add(title); borderedStack.Children.Add(authorAndYear); bookBorder.Child = borderedStack; dataTmplt.Children.Add(bookBorder); itemsCtlToAdd.ItemTemplate = new FuncDataTemplate<string>((item, _) => { return dataTmplt; }); dynamicBookshelf.stpBooksPlusShelf.Children.Add(itemsCtlToAdd); dynamicBookshelf.stpBooksPlusShelf.Children.Add(shelfAfterBooks); dynamicBookshelf.DataContext = new BookshelfVM(); stpDynamicControls.Children.Add(dynamicBookshelf); stpDynamicControls.Children.Add(bracket); } }
错误原因分析
- FuncDataTemplate类型不匹配:你使用了
FuncDataTemplate<string>,但ItemsSource的实际项类型是BookVM,而非string。当模板类型与项类型不匹配时,Avalonia会回退到默认模板,显示项的ToString()结果(即类的完整名称)。 - 复用同一个UI元素实例:你提前创建了
dataTmplt并在模板委托中返回它,导致所有列表项共享同一个UI元素实例,这会引发运行时异常或显示异常(因为一个UI元素不能同时属于多个父容器)。
修复后的代码(MainView.axaml.cs)
public partial class MainView : UserControl { public MainView() { InitializeComponent(); var dynamicBookshelf = new BookshelfUC(); var itemsCtlToAdd = new ItemsControl { [!ItemsControl.ItemsSourceProperty] = new Binding("Books") }; // 修正:将模板类型改为BookVM,且每次创建新的UI元素实例 itemsCtlToAdd.ItemTemplate = new FuncDataTemplate<BookVM>((item, _) => { var borderedStack = new StackPanel(); var bookBorder = new Border { BorderBrush = new SolidColorBrush(Colors.Brown), BorderThickness = new Avalonia.Thickness(2) }; var title = new TextBlock { Background = new SolidColorBrush(Color.Parse("#BC8F6F")), Foreground = new SolidColorBrush(Colors.White), [!TextBlock.TextProperty] = new Binding("Title") }; var authorAndYear = new TextBlock { // 可选优化:同时显示作者和年份(需自定义StringFormatConverter) // [!TextBlock.TextProperty] = new MultiBinding { // Bindings = { new Binding("Author"), new Binding("YearPublished") }, // Converter = new StringFormatConverter("{0} ({1})") // } [!TextBlock.TextProperty] = new Binding("YearPublished") }; borderedStack.Children.Add(title); borderedStack.Children.Add(authorAndYear); bookBorder.Child = borderedStack; var dataTmplt = new StackPanel(); dataTmplt.Children.Add(bookBorder); return dataTmplt; }); var shelfAfterBooks = new TextBlock { Background = new SolidColorBrush(Colors.Black), Foreground = new SolidColorBrush(Colors.White), Text = "Shelf on which the stack of book sits" }; var bracket = new TextBlock { Background = new SolidColorBrush(Colors.LightGray), Text = "Mounting brackets" }; dynamicBookshelf.stpBooksPlusShelf.Children.Add(itemsCtlToAdd); dynamicBookshelf.stpBooksPlusShelf.Children.Add(shelfAfterBooks); dynamicBookshelf.DataContext = new BookshelfVM(); stpDynamicControls.Children.Add(dynamicBookshelf); stpDynamicControls.Children.Add(bracket); } }
内容的提问来源于stack exchange,提问作者Eric Eggers
相关产品推荐
相关产品推荐

