我在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中放置了一个断点,我可以确认每次按拨号盘中的键都会执行该方法。
答案 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");
}
}
如果我误解了你的问题,请告诉我。