根据视图模型中的更改更新依赖项属性

时间:2012-09-27 15:47:52

标签: wpf mvvm viewmodel dependency-properties

我在WPF中遇到了一些数据绑定问题。这是一个场景:我制作了一个模拟拨号盘的用户控件(即12个按钮的数组,其中数字从'0'到'9'加上'#'和'Clear'键)。控件存在于类库中,并且是按照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的viewmodel得到更新)当我按下新键时,我已经用调试器检查过了。

这是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中放置了一个断点,我可以确认每次按拨号盘中的键都会执行该方法。

3 个答案:

答案 0 :(得分: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}"/>

如何从View和DialPad访问DialPadViewModel。

编辑:

现在尝试在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的值。

答案 1 :(得分:1)

DependencyProperties旨在指向其他属性以获取其值。因此,您可以将其指向DialPadViewModel.DialedNumber,也可以在使用UserControl时将其指向其他字符串(绑定或硬编码值,如“551”),但您不能同时执行这两项操作。

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

根据代码的外观和想要做的事情,有几种方法可以解决。

首先,您可以坚持要求使用您的控件的人也使用您的ViewModel,并且不要使DialedNumber成为公共依赖项属性。

因此,不允许创建属性为SomeOtherDialedNumber且绑定

的自定义类
<DialPad DialedNumber="{Binding SomeOtherDialedNumber}">

他们被迫在他们想要使用DialPad控件的任何时候在他们的代码中使用DialPadViewModel。对于这个工作,你将需要删除this.DataContext = new DialPadViewModel在你的代码隐藏的用户控件,因为用户会提供DialPadViewModel到你的用户控件,你可以使用隐式的DataTemplate告诉WPF来始终使用DialPadViewModel UserControl绘制DialPad

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

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

您需要在DialPadViewModel.DialedNumber依赖项属性发生更改时更新DialedNumber(您可能需要使用DependencyPropertyDescriptor.AddValueChanged进行属性更改通知),并且还需要编写要更新的内容随时DialedNumber更改DialPadViewModel.DialedNumber依赖项属性的来源。

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

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

答案 2 :(得分:0)

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

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

您的view-model属性可能如下所示:

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

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