使用不同类型的变量在 MenuItem 中作为 ItemSource 时会出现绑定错误。

huangapple go评论72阅读模式
英文:

Binding errors when using CompositeCollection as ItemSource with variable types on a MenuItem

问题

以下是您提供的内容的翻译:

In simple terms, I have a WPF MenuItem with a list of recent files that have been opened, that uses a class named "RecentFilesViewModel" to populate the files and setup the commands for them to open. But the problem comes when I add a Seperator and a final manually added MenuItem that clears the recent files list.

我简单地说,我有一个 WPF 菜单项,其中包含一个最近打开的文件列表,它使用名为 "RecentFilesViewModel" 的类来填充文件并设置打开它们的命令。但问题出现在我添加分隔符和一个最终手动添加的菜单项以清除最近文件列表时。

My problem is, while using a CompositeCollection to set the ItemSource, it works fine with the CollectionContainer of my recent files list provided by a custom class, but as soon as I include the Seperator or clear files MenuItem I get binding issues. Annoyingly it does actually work just as expected with no issues, but I really want to understand why the binding errors are showing, and just get rid of them.

我的问题是,使用 CompositeCollection 设置 ItemSource 时,它在使用自定义类提供的最近文件列表的 CollectionContainer 时运行正常,但一旦包括分隔符或清除文件的菜单项,就会出现绑定问题。令人讨厌的是,它实际上的确像预期的那样工作,没有问题,但我真的想了解为什么会显示绑定错误,并摆脱它们。

Here is my XAML for the MenuItem and it's CompositeCollection:

这是我的菜单项和它的 CompositeCollection 的 XAML 代码:

<MenuItem Header="Recent files">
    <MenuItem.ItemsSource>
        <CompositeCollection>
            <CollectionContainer Collection="{Binding Source={StaticResource recentFilesViewModel}, Path=RecentFiles}" />
            <Separator Name="Seperator" />
            <MenuItem Name="ClearRecentFilesButton" Header="Clear recent files" Command="{x:Static local:ApplicationMenuHandler.File_RecentFiles_Clear}" />
        </CompositeCollection>
    </MenuItem.ItemsSource>
    <MenuItem.ItemContainerStyle>
        <Style TargetType="MenuItem">
            <Style.Triggers>
                <DataTrigger Value="{x:Null}">
                    <DataTrigger.Binding>
                        <PriorityBinding>
                            <Binding Path="Command"/>
                        </PriorityBinding>
                    </DataTrigger.Binding>
                    <Setter Property="Command" Value="{x:Static local:ApplicationMenuHandler.File_RecentFiles_Open}"/>
                    <Setter Property="CommandParameter" Value="{Binding FilePath}"/>
                    <Setter Property="Header" Value="{Binding FilePath}"/>
                    <Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </MenuItem.ItemContainerStyle>
</MenuItem>

After removing the lines:

在移除以下行后:

<Separator Name="Seperator" />
<MenuItem Name="ClearRecentFilesButton" Header="Clear recent files" Command="{x:Static local:ApplicationMenuHandler.File_RecentFiles_Clear}" />

I get no binding errors at all. So what is causing the errors? I would have thought that the CompositeCollection allows for exactly that, a composite collection of variable types?

在移除上述行后,我根本没有绑定错误。那么是什么导致了错误呢?我本以为 CompositeCollection 允许包含不同类型的复合集合。

Some things to note are:

需要注意的一些事项:

  1. When adding just the Seperator to the collection, the binding error only shows AFTER I click on one of the contained menu items. Here is the error:

    1. 当只添加分隔符到集合中时,绑定错误只会在我单击其中一个包含的菜单项后显示。以下是错误消息:

    System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.ItemsControl', AncestorLevel='1''. BindingExpression:Path=HorizontalContentAlignment; DataItem=null; target element is 'MenuItem' (Name=''); target property is 'HorizontalContentAlignment' (type 'HorizontalAlignment')

  2. When adding just the extra MenuItem, the error shows as soon as the application loads. But is basically the same error:

    1. 当只添加额外的菜单项时,错误会在应用程序加载后立即显示。但基本上是相同的错误:

    System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.ItemsControl', AncestorLevel='1''. BindingExpression:Path=HorizontalContentAlignment; DataItem=null; target element is 'MenuItem' (Name='ClearRecentFilesButton'); target property is 'HorizontalContentAlignment' (type 'HorizontalAlignment')

I have gone around in circles trying to solve it, I wondered if it had something to do with the DataTrigger, but after trying many different ways of targeting only MenuItems that have a "Command" attribute, nothing seemed to change anything. Maybe I am misunderstanding how the DataTrigger works, I really wish I could just use the code behind as this seems so unnecessarily complicated to achieve something so simple if it was code and not XAML markup.

我已经绕了很多圈尝试解决它,我想知道它是否与 DataTrigger 有关,但在尝试多种不同的方式来仅针对具有 "Command" 属性的菜单项时,似乎什么都没有改变。也许我误解了 DataTrigger 的工作原理,我真的希望能够使用代码,因为如果使用代码而不是 XAML 标记,似乎这个问题是如此不必要的复杂。

Really would appreciate any help at all, and I'm very grateful for any help! Thank you in advance.

我真的非常感激任何帮助,对任何帮助都非常感激!提前谢谢您。


更新(按照 davmos 的要求)

@davmos 建议我提供有关 ApplicationMenuHandlerRecentFilesViewModel 类的更多信息,以及它在哪里实例化等等。

ApplicationMenuHandler 仅仅是一组静态命令,所有似乎都正常工作,以下是类本身的一部分示例:

public static class ApplicationMenuHandler
{
    // File -> New
    public static ICommand File_New { get; } = new RelayCommand((

<details>
<summary>英文:</summary>

In simple terms, I have a WPF MenuItem with a list of recent files that have been opened, that uses a class named &quot;RecentFilesViewModel&quot; to populate the files and setup the commands for them to open. But the problem comes when I add a Seperator and a final manually added MenuItem that clears the recent files list.

My problem is, while using a CompositeCollection to set the ItemSource, it works fine with the CollectionContainer of my recent files list provided by a custom class, but as soon as I include the Seperator or clear files MenuItem I get binding issues. Annoyingly it does actually work just as expected with no issues, but I really want to understand why the binding errors are showing, and just get rid of them.

Here is my XAML for the MenuItem and it&#39;s CompositeCollection:

<MenuItem Header="_Recent files">
<MenuItem.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource recentFilesViewModel}, Path=RecentFiles}" />
<Separator Name="Seperator" />
<MenuItem Name="ClearRecentFilesButton" Header="Clear recent files" Command="{x:Static local:ApplicationMenuHandler.File_RecentFiles_Clear}" />
</CompositeCollection>
</MenuItem.ItemsSource>
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Style.Triggers>
<DataTrigger Value="{x:Null}">
<DataTrigger.Binding>
<PriorityBinding>
<Binding Path="Command"/>
</PriorityBinding>
</DataTrigger.Binding>
<Setter Property="Command" Value="{x:Static local:ApplicationMenuHandler.File_RecentFiles_Open}"/>
<Setter Property="CommandParameter" Value="{Binding FilePath}"/>
<Setter Property="Header" Value="{Binding FilePath}"/>
<Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>


After removing the lines:

<Separator Name="Seperator" />
<MenuItem Name="ClearRecentFilesButton" Header="Clear recent files" Command="{x:Static local:ApplicationMenuHandler.File_RecentFiles_Clear}" />


I get no binding errors at all. So what is causing the errors? I would have thought that the CompositeCollection allows for exactly that, a composite collection of variable types?

Some things to note are:

1. When adding just the Seperator to the collection, the binding error only shows AFTER I click on one of the contained menu items. Here is the error:

&gt; System.Windows.Data Error: 4 : Cannot find source for binding with
&gt; reference &#39;RelativeSource FindAncestor,
&gt; AncestorType=&#39;System.Windows.Controls.ItemsControl&#39;,
&gt; AncestorLevel=&#39;1&#39;&#39;. BindingExpression:Path=HorizontalContentAlignment;
&gt; DataItem=null; target element is &#39;MenuItem&#39; (Name=&#39;&#39;); target property
&gt; is &#39;HorizontalContentAlignment&#39; (type &#39;HorizontalAlignment&#39;)

2. When adding just the extra MenuItem, the error shows as soon as the application loads. But is basically the same error:

&gt; System.Windows.Data Error: 4 : Cannot find source for binding with
&gt; reference &#39;RelativeSource FindAncestor,
&gt; AncestorType=&#39;System.Windows.Controls.ItemsControl&#39;,
&gt; AncestorLevel=&#39;1&#39;&#39;. BindingExpression:Path=HorizontalContentAlignment;
&gt; DataItem=null; target element is &#39;MenuItem&#39;
&gt; (Name=&#39;ClearRecentFilesButton&#39;); target property is
&gt; &#39;HorizontalContentAlignment&#39; (type &#39;HorizontalAlignment&#39;)

I have have gone around in circles trying to solve it, I wondered if it had something to do with the DataTrigger, but after trying many different ways of targeting only MenuItems that have a &quot;Command&quot; attribute, nothing seemed to change anything. Maybe I am missunderstanding how the DataTrigger works, I really wish I could just use the code behind as this seems so unnecessarily complicated to achieve something so simple if it was code and not XAML markup.

Really would appreciate any help at all, and I&#39;m very grateful for any help! Thank you in advance.

-----
#### Update (as requested by davmos)

@davmos suggested that I added some more info regarding the `ApplicationMenuHandler` and `RecentFilesViewModel` classes, and also where I instantiate it etc.

The `ApplicationMenuHandler` is simply a set of static commands, all seem to be working fine, here is an example of the beginning of the class itself:

public static class ApplicationMenuHandler
{
// File -> New
public static ICommand File_New { get; } = new RelayCommand((parameter) => {
// Create new level editor
LevelEditor levelEditor = new(){ Tag = "New Level.lvl (" + MainWindow.instance?.actionTabsModel.Tabs.Count + ")" };

    // Add a tab with the new level editor
    MainWindow.instance?.actionTabsModel.AddTab(levelEditor);

    // Once level editor is loaded and ready to scroll, scroll to center
    SubRoutines.WaitUntil(() =&gt; levelEditor.levelScrollViewer != null &amp;&amp; levelEditor.levelScrollViewer.IsLoaded, () =&gt; {
        levelEditor.levelScrollViewer.ScrollToCenter();
    });
});

And here is the entire `RecentFileViewModel` class which extends `ViewModelBase` (which simply has a `PropertyChangedEventHandler` and `NotifyPropertyChanged` method). Here is the class:

namespace LevelXEditor.Project.RecentFiles
{
public class RecentFilesViewModel : ViewModelBase
{
public static RecentFilesViewModel? Instance { get; private set; }

    private ObservableCollection&lt;RecentFile&gt; recentFiles = new();
    public ObservableCollection&lt;RecentFile&gt; RecentFiles { get =&gt; recentFiles; set { recentFiles = value; NotifyPropertyChanged(&quot;RecentFiles&quot;); } }

    // Constructor
    public RecentFilesViewModel()
    {
        Instance = this;
        RecentFiles = new ObservableCollection&lt;RecentFile&gt;();
        Refresh();
    }

    public void Refresh()
    {
        // Clear recent files
        RecentFiles.Clear();

        // Add recent files
        foreach (string recentFile in MainWindow.AppDataHandler.Data.recentFiles)
        {
            RecentFiles.Add(new RecentFile() { FilePath = recentFile, IsEnabled = true });
        }

        // If there are no recent files then add a placeholder
        if (RecentFiles.Count == 0)
        {
            RecentFiles.Add(new RecentFile() { FilePath = &quot;No recent files&quot;, IsEnabled = false });
        }
    }
}

public class RecentFile
{
    public string FilePath { get; set; } = &quot;&quot;;
    public bool IsEnabled { get; set; } = true;
}

}


I&#39;m still coming to understand how WPF works on the XAML side of things, but this is how I am &quot;instantiating&quot; the `RecentFilesViewModel` object in my `MainWindow` XAML:

<Window x:Class="LevelXEditor.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:LevelXEditor"
xmlns:recentFiles="clr-namespace:LevelXEditor.Project.RecentFiles"
xmlns:statusBar="clr-namespace:LevelXEditor.Project.StatusBar"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="600" Width="1000">
<Window.Resources>
<statusBar:StatusBarViewModal x:Key="statusBarViewModel"/>
<recentFiles:RecentFilesViewModel x:Key="recentFilesViewModel"/>
<local:EqualConverter x:Key="EqualConverter" />
</Window.Resources>


Hopefully this gives a little more helpful information.

</details>


# 答案1
**得分**: 1

以下是要翻译的代码部分:

```xml
&lt;Application.Resources&gt;
    &lt;Style TargetType=&quot;MenuItem&quot;&gt;
        &lt;Setter Property=&quot;HorizontalContentAlignment&quot; Value=&quot;Left&quot;/&gt;
        &lt;Setter Property=&quot;VerticalContentAlignment&quot; Value=&quot;Center&quot;/&gt;
    &lt;/Style&gt;
&lt;/Application.Resources&gt;

MenuItem added programmatically causes Binding error

英文:

Seems to be known issue that can be worked around by overriding 2 properties in your App.xaml file...

&lt;Application.Resources&gt;
    &lt;Style TargetType=&quot;MenuItem&quot;&gt;
        &lt;Setter Property=&quot;HorizontalContentAlignment&quot; Value=&quot;Left&quot;/&gt;
        &lt;Setter Property=&quot;VerticalContentAlignment&quot; Value=&quot;Center&quot;/&gt;
    &lt;/Style&gt;
&lt;/Application.Resources&gt;

If you want to dig deeper, see answer below & the links to MSDN support forum threads within it...

MenuItem added programmatically causes Binding error

huangapple
  • 本文由 发表于 2023年3月31日 22:34:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/75899749.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定