0

我正在使用 MVVM 模式实现 WPF 应用程序。

该应用程序基本上是一个通信面板(comms 面板),上面放置了控制部件(例如,拨号盘、内部通信线路等)。控件小部件也已使用 MVVM 模式实现,因为这使我们能够轻松地单独测试它们。

我有这个“拨号盘”控件小部件,它在其视图模型中公开了一个 DialedNumber 公共字段:

public string DialedNumber
    {
        get { return _dialPadModel.DialedNumber; }
        set
        {
            _dialPadModel.DialedNumber = value;
            RaisePropertyChanged("DialedNumber");
        }
    }

“拨号盘”控件小部件还通过视图中的公共字段公开其视图模型:

public DialPadViewModel DialPadViewModel
    {
        get { return DataContext as DialPadViewModel; }
    }

它还通过它的视图公开它,它只是从视图模型中的公共字段写入/读取:

public string DialedNumber
    {
        get
        {
            return DialPadViewModel.DialedNumber;
        }
        set
        {
            DialPadViewModel.DialedNumber = value;
        }
    }

DialPad 放置在一个通讯面板中(也使用 MVVM 实现),它的视图模型中有一个 DialedPABXNumber 公共字段:

public string DialedPABXNumber
    {
        get { return _dialedPABXNumber; }
        set
        {
            _dialedPABXNumber = value;
            OnPropertyChanged("DialedPABXNumber");
        }
    }

现在,我希望能够将 DialPad 中的 DialedNumber 字段链接到 comms 面板中的 DialedPABXNumber 字段。但是,为了做到这一点,我正在努力想出正确的 XAML 语法。我的方法是这样的:

<PanelControls:DialPad x:Name="MyDialPad2" DialPadViewModel.DialedNumber="{Binding Path=CommsPanelViewModel.DialedPABXNumber, Mode=OneWayToSource}"/>

通过这样做,我在加载通信面板 XAML 时遇到运行时异常。进一步来说:

无法设置未知成员“{http://schemas.microsoft.com/winfx/2006/xaml/presentation}DialPadModel.DialedNumber”。

如何在 XAML 中指定我想访问 DialPadViewModel.DialedNumber?

编辑:添加有关组件如何组合在一起的背景信息。应用程序的主窗口有两个子窗口:左侧的控制面板和适当的通讯面板,它是动态加载的。

<Window x:Class="Comms.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Views="clr-namespace:Comms.View"
    xmlns:ViewModel="clr-namespace:Comms.ViewModel"
    Title="Comms" Height="350" Width="525" Closing="WindowClosing">
<Window.Resources>
    <DataTemplate x:Name="CommsControlPanelViewModel" DataType="{x:Type ViewModel:CommsControlPanelViewModel}">
        <Views:CommsControlPanelView x:Name="CommsControlPanelView"/>
    </DataTemplate>
    <DataTemplate x:Name="CommsPanelViewModel" DataType="{x:Type ViewModel:CommsPanelViewModel}">
        <Views:CommsPanelView x:Name="CommsPanelView"/>
    </DataTemplate>
</Window.Resources>

<StackPanel x:Name="Layout" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Orientation="Horizontal">
    <ContentControl Content="{Binding CommsControlPanelView}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
    <ContentControl Content="{Binding CommsPanelView}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</StackPanel>

通讯面板是动态加载的。这是面板的 XAML 文件:

<Border x:Name="CommsPanelBorder"
    Style="{DynamicResource BorderTemplate}"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:PanelControls="clr-namespace:CommsPanelControlsLib.View;assembly=CommsPanelControlsLib"
    VerticalAlignment="Top">
<StackPanel>
    <!-- PABX Dial Pad -->
    <StackPanel Orientation="Horizontal">
    <PanelControls:DialPad x:Name="MyDialPad2" DialPadViewModel.DialedNumber="{Binding Path=CommsPanelViewModel.DialedPABXNumber, Mode=OneWayToSource}"/>
    </StackPanel>
</StackPanel>

4

3 回答 3

1

我对您要执行的操作感到有些困惑,但是听起来您正在尝试将一个对象的属性绑定到另一个对象中的属性

首先,有两种类型的属性:常规属性和依赖属性。如果要设置具有绑定的属性,则它必须是 a DependencyPropertyDialPadViewModel.DialedNumber如果该值将由您拥有的绑定提供,则必须是依赖属性。

其次,您的实际绑定正在尝试引用CommsPanelViewModel.DialedPABXNumber. 这实际上将绑定到MyDialPad2.DataContext.CommsPanelViewModel.DialedPABXNumber,如果我正确理解您的代码,我认为它不是有效属性。

我认为您要做的是找到CommsPanelViewModelDataContext forCommsPanelView并绑定到它的DialedPABXNumber属性。

为此,您首先需要更改绑定的源以CommsPanelView通过RelativeSourceElementName绑定找到您,并且您需要设置Pathto DataContext.DialedPABXNumber,因为DataContextforCommsPanelView是 a CommsPanelViewModel,并且该视图模型具有属性DialedPABXNumber

<PanelControls:DialPad x:Name="MyDialPad2" DialPadViewModel.DialedNumber="{Binding ElementName=CommsPanelView, Path=DataContext.DialedPABXNumber, Mode=OneWayToSource}"/>

CommsPanelView也就是说,由于涉及的模板,我不确定您是否能够使用当前的 XAML 布局找到具有绑定的绑定。试一试,但如果不是这种情况,您仍然可以使用一些选项。

您可以尝试将所需的属性存储在未使用的依赖属性中,例如Tag属性,然后您可以使用RelativeSourceorElementName属性找到 ContentControl 并绑定到其Tag属性

<ContentControl Content="{Binding CommsControlPanelView}" Tag="{Binding CommsPanelView.DialedPABXNumber}" />

您还可以使用某种消息系统,例如 MVVM LightMessenger或 PrismEventAggregator来传递值。(我在博客上的一篇文章中对这些进行了简要总结)

或者您可以链接 ViewModel 层中的两个属性,因此CommsControlPanelViewModel包含要存储的属性,CommsViewModel.DialedPABXNumber并且该属性会在需要时从 ViewModel 层本身更新。

于 2012-10-18T14:24:02.807 回答
0

要绑定到属性,此属性必须是 DependencyProperty。您可以在您的视图上创建一个虚拟属性,并通过 PropertyChangedCallback 操作您的 ViewModel:

public static DependencyProperty TestProperty = DependencyProperty.Register("Test", typeof(string), typeof(View), new PropertyMetadata("default", new PropertyChangedCallback(TestPropertyChanged)));
public string Test
{
    get { return GetValue(TestProperty).ToString(); }
    set { SetValue(TestProperty, value); }
}
public static void TestPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
    ViewModel vm= ((View)obj).DataContext;
    if (vm.Test != e.NewValue.ToString())
    {
        vm.Test = e.NewValue.ToString();
    }
} 

现在您可以在视图中绑定到此属性。

但我认为这违反了 MVVM 模式,不是吗?

于 2012-10-18T11:37:31.520 回答
0

在与 Blachshma 长时间交谈后(感谢 Blachshma!),我修改了 DialPad 代码,去掉了它的模型并将视图模型中的代码移动到其后面的代码中。

我在面板的 XAML 文件中使用的绑定如下:

<PanelControls:DialPad x:Name="MyDialPad2" DialedNumber="{Binding Path=CommsPanelViewModel.DialedPABXNumber, Mode=OneWayToSource, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}"/>

我可以确认单击拨号盘中的按钮有效地触发了 CommsPanelViewModel 中的 DialedPABXNumber 字段的更改,这正是我想要的。但是,为了做到这一点,我对 DialPad 的代码进行了一些重大修改,现在我无法对其执行单元测试。我会将问题标记为已回答,然后我会打开一个新问题来处理这个新问题。

谢谢各位的意见!

于 2012-10-18T14:42:27.777 回答