UWP:添加ListViewItem时切换DataTemplate遇空引用问题
解决ListViewItem获取为null的问题
兄弟,这个坑我之前踩过!你遇到的问题核心原因很明确:当你往ObservableCollection里添加新项后,WPF并不会立刻生成对应的ListViewItem——UI的更新是异步调度的,你紧接着调用ContainerFromIndex的时候,容器还没被创建出来,自然返回null了。
下面给你两种靠谱的解决办法,第二种是WPF推荐的最佳实践,优先考虑:
方法1:用Dispatcher延迟执行,等UI渲染完再操作
通过Dispatcher把切换模板的代码放到UI线程的渲染队列里,给UI一点时间生成新项的容器:
CurrentTicket.Add(new Item { itemID = selectedItem.itemID, price = price, name = selectedItem.name, taxID = selectedItem.taxID, modName = modNames }); if(modNames.Count() != 0) { // 把操作放到渲染优先级的队列,等UI完成新项渲染后再执行 Application.Current.Dispatcher.BeginInvoke( DispatcherPriority.Render, new Action(() => { ListViewItem lvi = ticketListBox.ContainerFromIndex(ticketListBox.Items.Count - 1) as ListViewItem; if(lvi != null) { lvi.ContentTemplate = (DataTemplate)this.Resources["CurrentTicketModDataTemplate"]; } }) ); }
这个方法能快速解决问题,但属于“治标”的方式,毕竟手动操作UI容器不是WPF的设计初衷。
方法2:用DataTemplateSelector(推荐,更优雅)
WPF的核心是数据驱动UI,所以最好的方式是让模板根据数据自动切换,不用手动找容器。你可以自定义一个DataTemplateSelector,根据Item的modName是否有内容来自动选模板:
第一步:写个简单的Selector类
public class TicketItemTemplateSelector : DataTemplateSelector { // 这两个属性在XAML里绑定对应的模板 public DataTemplate DefaultTemplate { get; set; } public DataTemplate ModTemplate { get; set; } public override DataTemplate SelectTemplate(object item, DependencyObject container) { var ticketItem = item as Item; // 如果modName有内容,用带mod的模板,否则用默认模板 if(ticketItem != null && ticketItem.modName?.Count > 0) { return ModTemplate; } return DefaultTemplate; } }
第二步:在XAML里配置Selector和模板
<!-- 先在Window/UserControl的资源里定义模板和Selector --> <Window.Resources> <!-- 默认模板 --> <DataTemplate x:Key="CurrentTicketDefaultDataTemplate"> <!-- 这里放你原来的默认模板内容 --> </DataTemplate> <!-- 带mod的模板 --> <DataTemplate x:Key="CurrentTicketModDataTemplate"> <!-- 这里放你原来的带mod的模板内容 --> </DataTemplate> <!-- 实例化我们的Selector,绑定两个模板 --> <local:TicketItemTemplateSelector x:Key="TicketItemTemplateSelector" DefaultTemplate="{StaticResource CurrentTicketDefaultDataTemplate}" ModTemplate="{StaticResource CurrentTicketModDataTemplate}" /> </Window.Resources> <!-- 给ListView指定这个Selector --> <ListView x:Name="ticketListBox" ItemsSource="{Binding CurrentTicket}" ItemTemplateSelector="{StaticResource TicketItemTemplateSelector}" />
第三步:简化后台代码
现在你只需要安心加数据就行,完全不用管UI模板的事了:
CurrentTicket.Add(new Item { itemID = selectedItem.itemID, price = price, name = selectedItem.name, taxID = selectedItem.taxID, modName = modNames });
这种方式完全符合WPF的MVVM思想,代码更简洁,也不会再出现UI同步的问题,长期维护起来更省心。
内容的提问来源于stack exchange,提问作者user8094304
相关产品推荐
相关产品推荐

