FirstFloor ModernUI 无法通过 TitleLinks.LinkNavigator 导航

FirstFloor ModernUI Unable to Navigate via TitleLinks.LinkNavigator

我试图在按下键盘快捷键时通过以下代码导航到 XAML 控件(特别是 ModernFrame 控件):

LinkNavigator.Navigate(new Uri("/Controls/SettingsManager.xaml", UriKind.Relative), this);

键盘快捷键触发,然后出现异常:

System.ArgumentException: 'Unable to navigate to /Controls/SettingsManager.xaml, could not find a ModernFrame target '''

以下是作为 ModernFrame 的 SettingsManager 的来源——请注意,如果将其更改为 UserControl,它仍然有效。我将其更改为 ModernFrame 因为上述异常正在寻找 ModernFrame.

现在,如果我通过 window 中的 TitleLink 导航到它,SettingsManager.xaml 控件可以正常工作。但是,当我尝试以编程方式导航到它时,我收到了异常。您可以将控件完全留空,但仍会抛出异常。

SettingsManager.xaml.cs:

using System.Collections.Generic;
using System.Linq;
using System.Windows.Controls;
using FirstFloor.ModernUI.Presentation;
using FirstFloor.ModernUI.Windows.Controls;
using KeystoneEstimating.Containers;

namespace KeystoneEstimating.Controls {
    /// <summary>
    /// Interaction logic for SettingsManager.xaml
    /// </summary>
    public partial class SettingsManager : ModernFrame {
        public SettingsManager() {
            InitializeComponent();
        
            /// Load settings into the Link interface of MUI.
            List<AppSettings> settings = AppInfo.SettingsContainers;
            foreach (AppSettings set in settings) {
                Link lnk = new Link();
                lnk.DisplayName = set.SettingsName;
                lnk.Source = set.ControlPath;
                SettingsLinks.Links.Add(lnk);
            }

            // Load up the very first registered settings page.
            if (SettingsLinks.Links.First() != null)
                SettingsLinks.SelectedSource = SettingsLinks.Links.First().Source;
        }
    }
}

SettingsManage.xaml:

<mui:ModernFrame xmlns:mui="http://firstfloorsoftware.com/ModernUI"
             x:Class="KeystoneEstimating.Controls.SettingsManager"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:KeystoneEstimating"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid Style="{StaticResource ContentRoot}">
        <mui:ModernTab Layout="List" Name="SettingsLinks"/>
    </Grid>
</mui:ModernFrame>

ModernUI 包含两个标记为 ModernUI.WPFCore 的 NugetPackage。一个是版本 2.0.0,另一个是版本 3.1.2。问题在两者上都可重现。

似乎 FirstFloor ModernUI 的导航命令存在固有问题,它无法找到 MainWindow.xaml 的 ContentFrame 类型的样式控件 ModernFrame 调用时 Navigate().

我从源代码中搜索了样式,发现通过 XAML 可以通过 WPF 绑定找到 ContentFrame

<Button Content="{Binding DisplayName}"
        Command="navigation:LinkCommands.NavigateLink"
        CommandParameter="{Binding Source}"
        CommandTarget="{Binding ElementName=ContentFrame}"
        Style="{StaticResource SystemButtonLink}" />

当您单击 ModernWindow 中的任何 Link 时,这就是代码的处理方式,当您在 XAML 中对 UI 元素进行预编码时,代码将完美运行因为所有 XAML 元素将自动继承 ModernWindow 作为它们的 parent object。这是 Navigate 命令所必需的,因为它会向上搜索 parent 的元素层次结构以找到要填充到的 ModernFrame。请参阅 DefaultLinkNavigator.Navigate 调用函数 NavigationHelper.FindFrame(parameter, source):

    /// <summary>
    /// Finds the frame identified with given name in the specified context.
    /// </summary>
    /// <param name="name">The frame name.</param>
    /// <param name="context">The framework element providing the context for finding a frame.</param>
    /// <returns>The frame or null if the frame could not be found.</returns>
public static ModernFrame FindFrame(string name, FrameworkElement context)
{
    if (context == null) {
        throw new ArgumentNullException("context");
    }

    // collect all ancestor frames
    var frames = context.AncestorsAndSelf().OfType<ModernFrame>().ToArray();

    if (name == null || name == FrameSelf) {
        // find first ancestor frame
        return frames.FirstOrDefault();
    }
    if (name == FrameParent) {
        // find parent frame
        return frames.Skip(1).FirstOrDefault();
    }
    if (name == FrameTop) {
        // find top-most frame
        return frames.LastOrDefault();
    }

    // find ancestor frame having a name matching the target
    var frame = frames.FirstOrDefault(f => f.Name == name);
        
    if (frame == null) {
        // find frame in context scope
        frame = context.FindName(name) as ModernFrame;

        if (frame == null) {
            // find frame in scope of ancestor frame content
            var parent = frames.FirstOrDefault();
            if (parent != null && parent.Content != null) {
                var content = parent.Content as FrameworkElement;
                if (content != null) {
                    frame = content.FindName(name) as ModernFrame;
                }
            }
        }
    }

    return frame;
}

但是在代码中情况并非如此...在代码中执行链接时,您希望导航到的控件的 XAML 页面尚不存在,因此没有 parent 并且不能用于在导​​航中查找 parent 的 ModernFrame

然而,解决方案是在代码中创建绑定并将绑定作为参数传递给 Navigate 命令——我还不知道该怎么做——或者在 ModernWindow 控件并手动搜索名称为 ContentFrame 的所有 ModernFrame 类型的源元素(这是显示您的 ModernWindow.xaml 中的 window 的显示元素内容。

首先向您的 MainWindow.cs class 添加一个分层搜索功能——它应该扩展 ModernWindow 而不是 WPF 中的 Window:

public static T FindChild<T>(DependencyObject parent, string childName)
    where T : DependencyObject {
    // Confirm parent and childName are valid. 
    if (parent == null) return null;

    T foundChild = null;

    int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
    for (int i = 0; i < childrenCount; i++) {
        var child = VisualTreeHelper.GetChild(parent, i);
        // If the child is not of the request child type child
        T childType = child as T;
        if (childType == null) {
            // recursively drill down the tree
            foundChild = FindChild<T>(child, childName);

            // If the child is found, break so we do not overwrite the found child. 
            if (foundChild != null) break;
        } else if (!string.IsNullOrEmpty(childName)) {
            var frameworkElement = child as FrameworkElement;
            // If the child's name is set for search
            if (frameworkElement != null && frameworkElement.Name == childName) {
                // if the child's name is of the request name
                foundChild = (T)child;
                break;
            }
        } else {
            // child element found.
            foundChild = (T)child;
            break;
        }
    }

在同一个 class 中通过代码导航到控件——在我的例子中,当执行键绑定时——你可以使用三种不同的方法:

var target = FindChild<ModernFrame>(this, "ContentFrame");
// A:
LinkCommands.NavigateLink.Execute(new Uri("/Controls/SettingsManager.xaml", UriKind.RelativeOrAbsolute), target);
            
// B:
NavigationCommands.GoToPage.Execute("/Controls/SettingsManager.xaml", target);
            
// C:
this.LinkNavigator.Navigate(new Uri("/Controls/SettingsManager.xaml", UriKind.Relative), target as FrameworkElement);

层次结构来源 child 搜索: