如何根据绑定属性动态更改ContentTemplate?

4

我一直遇到一个问题,困扰了我一段时间,但我相信我终于找到了问题所在。症状是当我的一个绑定属性触发DataTrigger并交换ContentTemplate时,我的WPF控件无法正确地呈现。堆栈跟踪:

  System.ArgumentNullException: Value cannot be null.
  Parameter name: d
     at MS.Internal.Data.ElementObjectRef.GetObject(DependencyObject d, ObjectRefArgs args)
     at MS.Internal.Data.ObjectRef.GetDataObject(DependencyObject d, ObjectRefArgs args)
     at System.Windows.Data.BindingExpression.MS.Internal.Data.IDataBindEngineClient.VerifySourceReference(Boolean lastChance)
     at MS.Internal.Data.DataBindEngine.Task.Run(Boolean lastChance)
     at MS.Internal.Data.DataBindEngine.Run(Object arg)
     at MS.Internal.Data.DataBindEngine.OnLayoutUpdated(Object sender, EventArgs e)
     at System.Windows.ContextLayoutManager.fireLayoutUpdateEvent()
     at System.Windows.ContextLayoutManager.UpdateLayout()
     at System.Windows.ContextLayoutManager.UpdateLayoutCallback(Object arg)
     at System.Windows.Media.MediaContext.InvokeOnRenderCallback.DoWork()
     at System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks()
     at System.Windows.Media.MediaContext.RenderMessageHandlerCore(Object resizedCompositionTarget)
     at System.Windows.Media.MediaContext.RenderMessageHandler(Object resizedCompositionTarget)
     at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
     at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)

调试器并没有什么帮助,因为它只会在 application.Run() 处停止。以下是我在实际 XAML 方面所做的事情:

  <CollectionViewSource x:Key="SomeCollectionView"
                        Source="{Binding StatsByUser}"
                        IsLiveSortingRequested="True">
      <CollectionViewSource.SortDescriptions>
          <scm:SortDescription PropertyName="Amount" Direction="Descending"/>
          <scm:SortDescription PropertyName="Name" Direction="Ascending"/>
      </CollectionViewSource.SortDescriptions>
  </CollectionViewSource>

  <ItemsControl Background="Transparent" Width="{StaticResource Width}"
                ItemsSource="{Binding Source={StaticResource SomeCollectionView}}">
      <ItemsControl.Resources>
          <DataTemplate x:Key="FullViewTemplate">
              <Border Style="{StaticResource BorderStyle}">
                  <controls:FullCustomEntityControl CustomEntityObject="{Binding}"
                                                  Style="{StaticResource PanelStyle}"
                                                  MouseDown="Info_OnMouseDown"/>
              </Border>
          </DataTemplate>
          <DataTemplate x:Key="CompactViewTemplate">
              <Border Style="{StaticResource BorderStyle}">
                  <controls:CompactCustomEntityControl CustomEntityObject="{Binding}"
                                                     Style="{StaticResource PanelStyle}"
                                                     MouseDown="Info_OnMouseDown"/>
              </Border>
          </DataTemplate>
      </ItemsControl.Resources>
      <ItemsControl.ItemTemplate>
          <DataTemplate>
              <ContentControl Content="{Binding}">
                  <ContentControl.Style>
                      <Style TargetType="{x:Type ContentControl}">
                          <Setter Property="ContentTemplate" Value="{StaticResource FullViewTemplate}"/>
                          <Style.Triggers>
                              <DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=c:ShellView}, Path=ViewModel.ShowCompactView}" Value="True">
                                  <Setter Property="ContentTemplate" Value="{StaticResource CompactViewTemplate}"/>
                              </DataTrigger>
                          </Style.Triggers>
                      </Style>
                  </ContentControl.Style>
              </ContentControl>
          </DataTemplate>
      </ItemsControl.ItemTemplate>
  </ItemsControl>

每当ViewModel.ShowCompactView引发PropertyChanged事件并启动DataTrigger时,它将切换ContentTemplate,从而引发此错误。有没有修复这个问题或更好的架构ContentTemplate交换的方法,以避免出现这种情况?
编辑:可能相关的支持文章https://support.microsoft.com/en-us/kb/2461678 编辑2:UI发生的情况示例:enter image description here。您可以看到大槽是FullCustomEntityControl,小槽是CompactCustomEntityControl。将它们保留在任一模式下而不更改不会产生任何问题,但是使数据触发器更改它们会导致出现此类问题。此外,所使用的控件应保持一致,而不是像这里一样分割。通过将它们保留在任一模式下,我的意思是删除数据触发器并选择其中一个。
编辑3:关于类似问题的帖子,微软的某位员工回复道:https://social.msdn.microsoft.com/Forums/vstudio/en-US/fb4d0f41-bfea-409f-b8ac-e66558984b7a/argumentnullexception-when-displaying-wpf-window?forum=wpf 相关信息:

如果您在堆栈上使用VerifySourceReference时收到ArgumentNullException,则绝对是由Connect 561752中描述的问题引起的。即使您的应用程序不直接使用ElementName绑定,它也可能间接使用它们 - 几个内置控件使用ElementName绑定:ComboBox、ContextMenu、MenuItem等。


1
更改ContentTemplate是解决这个问题的可靠方法,本身不应该导致ArgumentNullException。问题可能来自于您的控件CompactCustomEntityControlFullCustomEntityControl启用 CLR 异常抛出,然后在 VS 调试中可以获取异常抛出的实际位置。 - Novitchi S
我还添加了一个可能相关的支持链接,但我怀疑它不是造成问题的原因,因为我的版本是18402,而热补丁版本是395。不幸的是,该DLL已不再可用于下载,因此我没有真正的方法来测试是否存在回归问题。 - Lunyx
@NovitchiS 我不确定我是否误解了什么,但我的堆栈跟踪不就是它抛出的确切位置吗?我需要获取框架的pdb文件以便更深入地调试吗? - Lunyx
尝试将两个自定义控件放入同一个数据模板中(例如“FullViewTemplate”),看看是否为每个列表项呈现了两个模板。我只是很好奇,万一问题出在将两个自定义控件绑定到同一个对象上的情况下。 - Andrew Stephens
@AndrewStephens 都可以没有问题。 - Lunyx
显示剩余4条评论
3个回答

1

据我所知,“ItemTemplateSelector”只执行一次选择。在我的示例中,当绑定的变量更改时,模板会得到更新。 - Lunyx

1
我使用你的设置做了一个快速项目,一切似乎都正常。错误似乎与你在CompactCustomEntityControl/FullCustomEntityControl中所做的与PresentationCore交互有关。
尝试在调试->选项中启用.NET Framework源代码步进以查看发生了什么:

setup screenshot


设置了所有选项,使用了Microsoft符号服务器,但仍然没有可用的源代码。不确定在框架本身内部进行调试会有多少帮助。 - Lunyx
@Lunyx 尝试在选项->调试->符号中关闭默认的“Microsoft符号服务器”,并添加https://referencesource.microsoft.com/symbols作为源。打开.NET框架调试可能有点麻烦,因为mscorlib版本通常与“Microsoft符号服务器”的源版本不匹配,但我认为在你的情况下这可能会有所帮助-你应该看到抛出的确切代码。根据我的经验,这通常有助于解决复杂的WPF问题。 - Shorstok
我按照你说的设置了,重启了VS,清除了符号缓存,然后尝试再次调试。它仍然似乎试图从Microsoft Symbol服务器(MSSS)加载作为源,并且当它到达错误行时,它会说没有加载符号,并提供了MSSS和你提供的新链接之间的选择。我选择了后者,但仍然显示“源不可用”。我还是做错了什么吗? - Lunyx
不幸的是,似乎没有可靠的解决方案来启用框架源代码调试。根本原因始终是符号版本与调试的.dll版本不匹配,但原因可能不同(甚至包括Windows更新)。尝试取消“要求源文件与原始版本完全匹配”的选项。此外,这应该适用于.net >= 4.5.1和vs >=2013(但早期版本也可能适用)。 - Shorstok
这是官方的微软手册,对我很有用 https://referencesource.microsoft.com/setup.html - Shorstok
我已经尝试了几个小时让它工作,但它就是不行。为了尝试调试一个标准的.NET dll,你必须跳过太多的障碍,这真是荒谬。 - Lunyx

1
我不知道是否理解错了问题,但是当我将代码复制到我的解决方案中时,它完美地工作了。以下是我的更改:
我所做的一件事是,将Path=ViewModel.ShowCompactView替换为Path=DataContext.ShowCompactView,并且RelativeSource是我的MainWindow。
<ItemsControl Background="Transparent" 
            ItemsSource="{Binding Source={StaticResource SomeCollectionView}}">
        <ItemsControl.Resources>
            <DataTemplate x:Key="FullViewTemplate">
                <Border >
                    <Label Content="{Binding}"
                                              />
                </Border>
            </DataTemplate>
            <DataTemplate x:Key="CompactViewTemplate">
                <Border >
                    <Button Content="{Binding}"
                                                />
                </Border>
            </DataTemplate>
        </ItemsControl.Resources>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <ContentControl Content="{Binding}">
                    <ContentControl.Style>
                        <Style TargetType="{x:Type ContentControl}">
                            <Setter Property="ContentTemplate" Value="{StaticResource FullViewTemplate}"/>
                            <Style.Triggers>
                                <DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.ShowCompactView}" Value="True">
                                    <Setter Property="ContentTemplate" Value="{StaticResource CompactViewTemplate}"/>
                                </DataTrigger>
                            </Style.Triggers>
                        </Style>
                    </ContentControl.Style>
                </ContentControl>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>

我的现有代码已经可以工作了;然而,在某些情况下,我所提到的问题会发生,控件只有空白处,而没有完全呈现。 - Lunyx

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接