0

我正在尝试创建一个支持 MVVM 的自定义控件,该控件扩展 Frame(来自 Navigation),并考虑了两个主要目标。

首先,我希望能够通过触发器更改帧的来源。这样,当 ViewModel 的属性发生更改时,我可以根据某些情况更改使用哪个视图。

其次,我希望视图本身能够更改使用哪个视图,在某些情况下,ViewModel 中没有任何变化。使用框架/页面系统并从页面内部调用 NavigationCommands.GoToPage 命令似乎是最合适的方法,因为每个不同的视图都可以定义为一个页面。

我遇到的问题是通过触发器设置 Frame.Source 工作得很好,直到第一次使用 GoToPage。在那之后,触发器似乎没有效果。GoToPage 似乎随时都可以工作。我整天都在搜索,找不到任何解释这一点的文档。

无论如何,这是我的自定义 Frame 的实现,我所做的只是绑定到 GoToPage 并确保 Pages 继承 DataContext:

public class FrameExtended : Frame
{
    public FrameExtended()
    {
        CommandBindings.Add(new CommandBinding(NavigationCommands.GoToPage, GoToPage_Executed));
        Navigated += new System.Windows.Navigation.NavigatedEventHandler(FrameExtended_Navigated);
    }

    void FrameExtended_Navigated(object sender, NavigationEventArgs e)
    {
        (Content as FrameworkElement).DataContext = this.DataContext;
    }

    void GoToPage_Executed(object sender, ExecutedRoutedEventArgs e)
    {
        if (e.Parameter is Uri) Source = e.Parameter as Uri;
        else if (e.Parameter is string) Source = new Uri(e.Parameter as string, UriKind.Relative);
    }
}

这是我的 ViewModel 的测试用例,它与 ViewModel 一样简单:

public enum MyEnum { MyEnumVal1, MyEnumVal2, MyEnumVal3 }

public class ViewModel : INotifyPropertyChanged
{
    private MyEnum enumVal = MyEnum.MyEnumVal1;

    public MyEnum EnumVal
    {
        get { return enumVal; }
        set
        {
            if (enumVal != value)
            {
                enumVal = value;
                OnPropertyChanged("EnumVal");
            }
        }
    }

    protected void OnPropertyChanged(string PropertyName)
    {
        if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

这是 XAML 的测试片段,我在其中使用了框架并定义了一些触发器:

<Button Content="ChangeViewModel" Click="Button_Click"/>
<Control>
    <Control.Template>
        <ControlTemplate TargetType="Control">
            <local:FrameExtended x:Name="MyFrame"/>
            <ControlTemplate.Triggers>
                <DataTrigger Binding="{Binding EnumVal}" Value="MyEnumVal1">
                    <Setter TargetName="MyFrame" Property="Source" Value="Pages/testpage.xaml"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding EnumVal}" Value="MyEnumVal2">
                    <Setter TargetName="MyFrame" Property="Source" Value="Pages/testpage2.xaml"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding EnumVal}" Value="MyEnumVal3">
                    <Setter TargetName="MyFrame" Property="Source" Value="Pages/testpage3.xaml"/>
                    <Setter TargetName="MyFrame" Property="Background" Value="Green"/>
                </DataTrigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
    </Control.Template>
</Control>

其中 Button_Click 指的是:

private void Button_Click(object sender, RoutedEventArgs e)
{
    var vm = this.DataContext as ViewModel;

    switch (vm.EnumVal)
    {
        case MyEnum.MyEnumVal1: vm.EnumVal = MyEnum.MyEnumVal2; break;
        case MyEnum.MyEnumVal2: vm.EnumVal = MyEnum.MyEnumVal3; break;
        case MyEnum.MyEnumVal3: vm.EnumVal = MyEnum.MyEnumVal1; break;
    }
}

除此之外,testpage.xaml 包含以下行:

<Button Content="NEXTPAGE" Command="GoToPage" CommandParameter="Pages/testpage2.xaml"/>

其余页面只有一个 TextBlock 指示它是哪个页面。

如果我一遍又一遍地单击“ChangeViewModel”按钮,框架将按预期循环浏览页面(并在第 3 页上将其背景更改为绿色)。单击 testpage.xaml 中调用 GoToPage 的按钮后,Frame 将切换到 testpage2。之后,对 ChangeViewModel 的后续点击永远不会改变显示哪个页面,但仍然使 Frame 的背景在每 3 次点击时变为绿色(当 testpage3 应该显示时)。

4

1 回答 1

0

我为这个问题找到的解决方法是在自定义控件中定义的 GoToPage 处理程序中使用 Frame.Navigate(),而不是直接设置源属性。似乎是从 C# 代码设置 Source 属性是阻止触发器更改它的原因(我猜是不同的优先级)。Navigate() 没有这个问题,它实际上是我首先尝试的并且很可能是理想的实现。它以前不起作用,因为所有这些实现都在控制库中,而 Navigate() 正在执行程序集中搜索我的页面。作为其他尝试做类似事情的人的旁注,请确保在传递给 Frame.Navigate() 的任何 URI 中包含程序集引用,否则默认情况下它将在执行程序集中搜索(不一定是定义程序集,与 XAML 中指定的 URI 一样)。我的 GoToPage 处理程序现在看起来像这样:

void GoToPage_Executed(object sender, ExecutedRoutedEventArgs e)
{
    if (e.Parameter is Uri) Navigate(e.Parameter as Uri);
    else if (e.Parameter is string) Navigate(new Uri(e.Parameter as string, UriKind.RelativeOrAbsolute));
}
于 2013-04-04T20:06:02.860 回答