2

我在 WPF 中的数据绑定方面遇到了一些问题。下面是场景:我制作了一个模拟拨号盘的用户控件(即,一个由 12 个按钮组成的数组,数字从“0”到“9”加上“#”和“清除”键)。该控件位于类库中,它是按照 MVVM 模式实现的,主要是因为我需要类库中的组件易于进行单元测试。

控件的视图模型非常简单,每次用户按下拨号盘按键时,它基本上都会更新一个公共的“DialedNumber”字符串(内部连接到模型)。绑定工作正常,通过使用调试器,我可以确认视图模型中的“DialedNumber”变量正在更新,因为我按下拨号盘中的按钮。

这个 DialPad 控件由一个单独的 XAML 文件 (Panel.xaml) 使用,该文件列出了属于我的自定义类库的几个控件。

现在,我想在 Panel 文件中添加一个 TextBlock,以显示 DialPad 中保存的“DialedNumber”字符串。这是 Panel.xaml 中的代码片段:

<PanelControls:DialPad x:Name="MyDialPad" DialedNumber="55325"/>
<TextBlock Text="{Binding ElementName=MyDialPad, Path=DialedNumber}" />

我得到的结果是文本块在开始时显示正确的数字(即“55325”),但是当我按下拨号盘键时它的内容没有得到更新(即使拨号盘的视图模型在我按下时得到更新)新键,正如我用调试器检查过的那样)。

下面是 DialPad 视图的代码:

public partial class DialPad : UserControl
{
    public DialPad()
    {
        InitializeComponent();
        DataContext = new DialPadViewModel();
    }

    public void DialedNumberChanged(object sender, EventArgs e)
    {
        return;
    }

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

    public string DialedNumber
    {
        get
        {
            var dialPadViewModel = Resources["DialPadVM"] as DialPadViewModel;
            return (dialPadViewModel != null) ? dialPadViewModel.DialedNumber : "";
        }
        set
        {
            var dialPadViewModel = Resources["DialPadVM"] as DialPadViewModel;
            if (dialPadViewModel != null)
            {
                dialPadViewModel.DialedNumber = value;
            }
        }
    }
}

这是 DialPad 视图模型:

public class DialPadViewModel : ObservableObject
{
    public DialPadViewModel()
    {
        _dialPadModel = new DialPadModel();
    }

    #region Fields

    private readonly DialPadModel _dialPadModel;
    private ICommand _dialPadKeyPressed;

    #endregion

    #region Public Properties/Command

    public DialPadModel DialPadModel
    {
        get { return _dialPadModel; }
    }

    public ICommand DialPadKeyPressedCommand
    {
        get
        {
            if (_dialPadKeyPressed == null)
            {
                _dialPadKeyPressed = new RelayCommand(DialPadKeyPressedCmd);
            }
            return _dialPadKeyPressed;
        }
    }

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

    #endregion

    #region Private Helpers

    private void DialPadKeyPressedCmd(object parameter)
    {
        string keyPressedString = parameter.ToString();

        if (keyPressedString.Length > 0)
        {
            if (char.IsDigit(keyPressedString[0]))
            {
                DialedNumber += keyPressedString[0].ToString(CultureInfo.InvariantCulture);
            }
            else if (keyPressedString == "C" || keyPressedString == "Clr" || keyPressedString == "Clear")
            {
                DialedNumber = "";
            }
        }
    }

    #endregion
}

让我重申一下我的问题:Panel.xaml 中的文本块在开始时显示正确的数字 (55325),但当我按下 DialPadButtons 时它的值永远不会更新。我在 DialPadKeyPressedCmd 中放置了一个断点,我可以确认每次按下拨号盘中的键时都会执行该方法。

4

3 回答 3

1

如果将 DialPad 放在 View 中,则可以在 ViewViewModel 中创建 DialPadViewModel-Property (public+global):

public DialPadViewModel DialPadViewModel = new DialPadViewModel();

现在将 View 的 DataContext-Binding 设置为 ViewViewModel 并将 DialPads DataContext 也绑定到它,例如

<local:DialPad DataContext="{Binding}"/>

现在您可以绑定到 DialPadViewModel 中的属性:

<TextBox Text="{Binding Path=DialPadViewModel.DialedNumber}"/>

这就是你如何从你的视图和你的拨号盘访问你的拨号盘视图模型。

编辑:

现在尝试更改 DialPad.xaml.cs 中的 DialedNumber 属性,如下所示:

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

编辑2:我发现了问题:

在您的 DialPad.xaml 中,您的所有命令都从资源绑定到 DialPadViewModel,而 TextBloc 绑定到 DialPads DataContext,这是 DialPadViewModel 的另一个实例。

因此,每次您点击 DialPad-Button 时,您都会从资源的 DPVM 实例中更改 DialedNumber 的值,而不是从 DataContext 的 DPVM 实例中更改 DialedNumber。

于 2012-09-27T16:11:29.100 回答
1

DependencyProperties 旨在指向其他一些属性以获取它们的值。因此,您可以将其指向您的DialPadViewModel.DialedNumber,也可以在使用 UserControl 时将其指向其他字符串(绑定或硬编码值,如“551”),但您不能两者都做。

在您的情况下,当有人绑定到DialedNumber依赖项属性时,他们正在用新值替换当前值(绑定到DialPadViewModel.DialedNumber)。

根据您的代码的外观和您想要做什么,有几种方法可以解决它。

首先,您可以坚持让想要使用您的控件的人也使用您的 ViewModel,并且不要创建DialedNumber公共依赖属性。

因此,而不是被允许创建具有属性SomeOtherDialedNumber和绑定的自定义类

<DialPad DialedNumber="{Binding SomeOtherDialedNumber}">

他们被迫DialPadViewModel在他们想要使用 DialPad 控件的任何时候在他们的代码中使用。为此,您需要删除this.DataContext = new DialPadViewModelUserControl 代码隐藏中的DialPadViewModelDialPadViewModelDialPad

<DataTemplate DataType="{x:Type DialPadViewModel}">
    <local:DialPad />
</DataTemplate>

我能想到的另一种选择是将您的 DependencyProperty 与您的 ViewModel 属性与一些 PropertyChange 通知同步。

您需要DialPadViewModel.DialedNumberDialedNumber依赖属性更改时进行更新(您可能需要使用DependencyPropertyDescriptor.AddValueChanged进行属性更改通知),并且您还必须编写一些内容来在更改时更新DialedNumber依赖属性的源DialPadViewModel.DialedNumber

就个人而言,如果我的 UserControl 有一个 ViewModel,那么我使用第一个选项。如果不是,我完全摆脱 ViewModel 并在代码隐藏中为我的 UserControl 构建逻辑,而不使用 ViewModel。

这样做的原因是 WPF 使用两个层:UI 层和数据层。这DataContext是数据层,ViewModel 通常是数据层的一部分。通过在 UserControl 的构造函数中显式设置数据层 ( DataContext),您将数据层与 UI 层结合起来,这违背了使用 MVVM 的最大原因之一:关注点分离。UserControl 应该只是一个漂亮的外壳,您应该能够将它放置在您想要的任何数据层之上。

于 2012-09-27T16:21:08.827 回答
0

听起来您可以在视图中添加一个 TextBox 并将其 Text 属性绑定到您的视图模型的 DialedNumber 属性。

 <TextBox Text="{Binding Path=DialedNumber}"></TextBox>

您的视图模型属性可能如下所示:

    private string _dialedNumber;
    [DefaultValue("551")]
    public string DialedNumber
    {
        get { return _dialedNumber; }
        set
        {
            if (value == _dialedNumber)
                return;
            _dialedNumber= value;
            _yourModel.DialedNumber= _dialedNumber;
            this.RaisePropertyChanged("DialedNumber");
        }
    }

如果我误解了你的问题,请告诉我。

于 2012-09-27T16:05:12.143 回答