WPF中TemplateSelector.SelectTemplate未随PropertyChange触发问题
问题
我有可通过不同报表可视化的数据,每种报表具备不同属性。为此我创建了一个对话框,通过ComboBox选择报表类型,ContentControl根据报表类型显示对应的编辑控件。
对话框XAML代码如下:
<StackPanel Margin="9"> <ComboBox Name="cbReport" IsEditable="False" VerticalAlignment="Center" ItemsSource="{Binding ReportTypes}" SelectionChanged="OnReportTypeChanged" SelectedValue="{Binding SelectedReportType, Mode=TwoWay}" SelectedValuePath="Content" DisplayMemberPath="Name"/> <ContentControl ContentTemplateSelector="{StaticResource DialogTemplateSelector}" Content="{Binding CurStatistics}"/> <WrapPanel HorizontalAlignment="Right" Margin="0,9,0,0"> <Button Name="pbDialogOK" Click="OnDialogOK" MinWidth="60" Margin="0,0,9,0" IsDefault="True">Ok</Button> <Button Name="pbDialogCancel" IsCancel="True" MinWidth="60">Cancel</Button> </WrapPanel> </StackPanel>
后台代码将ComboBox的事件回调路由至ViewModel:
public partial class ReportDialog : Window { private void OnReportTypeChanged(object sender, SelectionChangedEventArgs e) { ((DialogViewModel)DataContext).ChangeReportType((CBItem)e.AddedItems[0]); } }
ViewModel代码如下:
public class DialogViewModel : ObservableObject { //Statistics是ObservableObject public Statistics CurStatistics { get; set; } public ObservableCollection<CBItem> ReportTypes { get; } public CBItem SelectedReportType { get => curReportType; set => SetProperty(ref curReportType, value); } CBItem curReportType; public void ChangeReportType(CBItem rt) { SelectedReportType = rt; //触发PropertyChange事件 CurStatistics.ReportType = (ReportType)rt.Id; } }
如代码注释所述,Statistics是ObservableObject,设置ReportType成员时会发送PropertyChange事件。
在ChangeReportType中设置断点可确认ReportType已正确修改,但TemplateSelector.SelectTemplate仅在对话框创建时触发,模板选择功能本身正常。然而切换ComboBox选项时,ChangeReportType会被调用,但SelectTemplate却未触发。请问我忽略了什么?
解决方法
问题核心在于ContentControl的模板选择触发逻辑:ContentTemplateSelector仅在Content属性的引用发生变化时,才会重新调用SelectTemplate方法。你现在只是修改了CurStatistics对象内部的ReportType属性,CurStatistics的引用并未改变,因此ContentControl不会触发模板重新选择。
以下是两种可行的解决方式:
方式一:手动触发CurStatistics的属性变更通知
在修改CurStatistics.ReportType后,手动触发CurStatistics属性的PropertyChanged事件,让ContentControl感知到需要重新选择模板:
public void ChangeReportType(CBItem rt) { SelectedReportType = rt; CurStatistics.ReportType = (ReportType)rt.Id; // 触发CurStatistics属性的变更通知,强制ContentControl重新选择模板 OnPropertyChanged(nameof(CurStatistics)); }
注:你的DialogViewModel继承了ObservableObject,默认应该具备OnPropertyChanged方法。
方式二:替换CurStatistics实例
不要修改现有CurStatistics对象的ReportType,而是根据新报表类型创建全新的CurStatistics实例并赋值,通过引用变更触发模板选择:
public void ChangeReportType(CBItem rt) { SelectedReportType = rt; // 创建新的Statistics实例并赋值,触发Content属性引用变更 CurStatistics = new Statistics { ReportType = (ReportType)rt.Id }; // 若CurStatistics未使用SetProperty封装,需手动调用OnPropertyChanged(nameof(CurStatistics)) }
另外,当前代码同时使用了SelectedValue绑定和SelectionChanged事件,存在逻辑冗余,建议移除事件处理,直接通过SelectedReportType的setter触发报表类型变更,更符合MVVM模式:
public CBItem SelectedReportType { get => curReportType; set { if(SetProperty(ref curReportType, value)) { CurStatistics.ReportType = (ReportType)value.Id; OnPropertyChanged(nameof(CurStatistics)); // 对应方式一的处理逻辑 } } }
此时可删除XAML中ComboBox的SelectionChanged="OnReportTypeChanged"属性,简化代码结构。
内容的提问来源于stack exchange,提问作者unknown

