2014-02-20 78 views
1

我用一个DependencyProperty做了一个UserControl,我想绑定2种方式。但不知何故,这是行不通的。当属性更改时,“城市”属性从不会在AddressViewModel中设置。WPF用户控件 - 双向绑定不起作用

这是我的用户: XAML:

<UserControl x:Class="EasyInvoice.UI.CityPicker" 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      mc:Ignorable="d" 
      d:DesignHeight="30" d:DesignWidth="300" 
      DataContext="{Binding CityList, Source={StaticResource Locator}}"> 
    <Grid> 
     <Grid.ColumnDefinitions> 
      <ColumnDefinition Width="60"/> 
      <ColumnDefinition Width="*"/> 
     </Grid.ColumnDefinitions> 
     <Grid.RowDefinitions> 
      <RowDefinition Height="Auto"/> 
      <RowDefinition Height="Auto"/> 
     </Grid.RowDefinitions> 
     <TextBox IsReadOnly="True" Margin="3" Text="{Binding SelectedCity.PostalCode, Mode=OneWay}" Background="#EEE" VerticalContentAlignment="Center" HorizontalContentAlignment="Right"/> 
     <ComboBox Margin="3" Grid.Column="1" ItemsSource="{Binding Path=Cities}" DisplayMemberPath="CityName" SelectedItem="{Binding SelectedCity}"/> 
    </Grid> 
</UserControl> 

后面的代码:

using EasyInvoice.UI.ViewModel; 
using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 
using System.Windows; 
using System.Windows.Controls; 
using System.Windows.Data; 
using System.Windows.Documents; 
using System.Windows.Input; 
using System.Windows.Media; 
using System.Windows.Media.Imaging; 
using System.Windows.Navigation; 
using System.Windows.Shapes; 

namespace EasyInvoice.UI 
{ 
    /// <summary> 
    /// Interaction logic for CityPicker.xaml 
    /// </summary> 
    public partial class CityPicker : UserControl 
    { 
     public CityPicker() 
     { 
      InitializeComponent(); 

      ((CityListViewModel)this.DataContext).PropertyChanged += CityPicker_PropertyChanged; 
     } 

     private void CityPicker_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) 
     { 
      if (e.PropertyName == "SelectedCity") 
       SetCurrentValue(SelectedCityProperty, ((CityListViewModel)this.DataContext).SelectedCity); 
     } 

     public static readonly DependencyProperty SelectedCityProperty = 
      DependencyProperty.Register("SelectedCity", typeof(CityViewModel), typeof(CityPicker), 
      new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)); 

     public CityViewModel SelectedCity 
     { 
      get 
      { 
       return (CityViewModel)GetValue(SelectedCityProperty); 
      } 
      set 
      { 
       SetCurrentValue(SelectedCityProperty, value); 
      } 
     } 
    } 
} 

这是我用这个控制:

<UserControl x:Class="EasyInvoice.UI.NewCustomerView" 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      mc:Ignorable="d" 
      Height="135" Width="450" 
      xmlns:xctk="clr-namespace:Xceed.Wpf.Toolkit;assembly=Xceed.Wpf.Toolkit" 
      xmlns:einvoice="clr-namespace:EasyInvoice.UI"> 
    <UserControl.Resources> 
     <Style TargetType="xctk:WatermarkTextBox"> 
      <Setter Property="Margin" Value="3"/> 
     </Style> 
    </UserControl.Resources> 
    <Grid> 
     <Grid.RowDefinitions> 
      <RowDefinition Height="Auto"/> 
      <RowDefinition Height="5"/> 
      <RowDefinition Height="Auto"/> 
      <RowDefinition Height="Auto"/> 
      <RowDefinition Height="10"/> 
      <RowDefinition Height="Auto"/> 
     </Grid.RowDefinitions> 
     <Grid.ColumnDefinitions> 
      <ColumnDefinition Width="*"/> 
      <ColumnDefinition Width="*"/> 
      <ColumnDefinition Width="*"/> 
      <ColumnDefinition Width="*"/> 
     </Grid.ColumnDefinitions> 
     <xctk:WatermarkTextBox Grid.ColumnSpan="2" Watermark="Voornaam" Text="{Binding FirstName}"/> 
     <xctk:WatermarkTextBox Grid.ColumnSpan="2" Watermark="Famillienaam" Grid.Column="2" Text="{Binding LastName}"/> 


     <xctk:WatermarkTextBox Grid.ColumnSpan="2" Watermark="Straat" Grid.Row="2" Text="{Binding Address.Street}"/> 
     <xctk:WatermarkTextBox Grid.ColumnSpan="1" Watermark="Huisnummer" Grid.Column="2" Grid.Row="2" Text="{Binding Address.HouseNr}"/> 
     <xctk:WatermarkTextBox Grid.ColumnSpan="1" Watermark="Busnummer" Grid.Column="3" Grid.Row="2" Text="{Binding Address.BusNr}"/> 


     <einvoice:CityPicker Grid.Row="3" Grid.ColumnSpan="4" SelectedCity="{Binding Address.City, Mode=TwoWay}"/> 

     <Button Grid.Row="5" Content="Opslaan en sluiten" Grid.ColumnSpan="4" Style="{StaticResource PopupButton}"/> 
    </Grid> 
</UserControl> 

这是视图模型为“地址”:

using EasyInvoice.Model; 
using GalaSoft.MvvmLight; 
using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 

namespace EasyInvoice.UI.ViewModel 
{ 
    public class AddressViewModel : ViewModelBase 
    { 
     private string _street; 
     private string _houseNr; 
     private string _busNr; 
     private CityViewModel _city; 

     public AddressViewModel(Address address) 
     { 
      LoadAddress(address); 
     } 

     public AddressViewModel() : this(new Address()) { } 

     private Address Address { get; set; } 

     public string Street 
     { 
      get 
      { 
       return _street; 
      } 
      set 
      { 
       if (_street == value) 
        return; 
       _street = value; 
       RaisePropertyChanged("Street"); 
      } 
     } 

     public string HouseNr 
     { 
      get 
      { 
       return _houseNr; 
      } 
      set 
      { 
       if (_houseNr == value) 
        return; 
       _houseNr = value; 
       RaisePropertyChanged("HouseNr"); 
      } 
     } 

     public string BusNr 
     { 
      get 
      { 
       return _busNr; 
      } 
      set 
      { 
       if (_busNr == value) 
        return; 
       _busNr = value; 
       RaisePropertyChanged("BusNr"); 
      } 
     } 

     public string BusNrParen 
     { 
      get 
      { 
       return string.Concat("(", BusNr, ")"); 
      } 
     } 

     public bool HasBusNr 
     { 
      get 
      { 
       return !string.IsNullOrWhiteSpace(_busNr); 
      } 
     } 

     public CityViewModel City 
     { 
      get 
      { 
       return _city; 
      } 
      set 
      { 
       if (_city == value) 
        return; 
       _city = value; 
       RaisePropertyChanged("City"); 
      } 
     } 

     public void LoadAddress(Address address) 
     { 
      this.Address = address; 

      if(address == null) 
      { 
       _street = ""; 
       _houseNr = ""; 
       _busNr = ""; 
       _city = new CityViewModel(null); 
      } 
      else 
      { 
       _street = address.StreetName; 
       _houseNr = address.HouseNr; 
       _busNr = address.BusNr; 
       _city = new CityViewModel(address.City); 
      } 
     } 
    } 
} 

当我调试,我可以看到,当我改变我的UI特性达到该行:

SetCurrentValue(SelectedCityProperty, ((CityListViewModel)this.DataContext).SelectedCity); 

但不知何故,这永远不会被设置:

 public CityViewModel City 
     { 
      get 
      { 
       return _city; 
      } 
      set 
      { 
       if (_city == value) 
        return; 
       _city = value; 
       RaisePropertyChanged("City"); 
      } 
     } 

另外,我确定viewmodel连接正确,因为我在“HouseNr”设置了一个断点,这个工作正常。

万一没有足够的规定,项目可以在这里找到:https://github.com/SanderDeclerck/EasyInvoice

+0

你的代码不能编译。你的城市选择器控件没有实现'INotifyPropertyChange'它怎么能够订阅这个事件? –

+0

在这里,它编译,我的CityPicker也不使用INotifyPropertyChanged接口,这只是在我的viewmodels(在这种情况下CityListViewModel和AddressViewModel) –

回答

2

问题在于你的约束。您已将DataContextUserControl设置为CityListViewModel,因此绑定失败,因为绑定引擎正在CityListViewModel中搜索属性Address.City而不是AddressViewModel

您必须使用RelativeSourceElementName明确解析该绑定。

SelectedCity="{Binding DataContext.Address.City,RelativeSource={RelativeSource 
       Mode=FindAncestor, AncestorType=UserControl}, Mode=TwoWay}" 

OR

x:Name到用户控件说NewCustomer和使用的ElementName绑定。

SelectedCity="{Binding DataContext.Address.City, ElementName=NewCustomer}" 

您也可以避免设置Mode to TwoWay因为你已经指定,在DP的注册时间。

+1

为我修好了,非常感谢! –

+0

不客气Sander .. !! –

3

从您的代码

public CityViewModel SelectedCity 
    { 
     get 
     { 
      return (CityViewModel)GetValue(SelectedCityProperty); 
     } 
     set 
     { 
      SetCurrentValue(SelectedCityProperty, value); 
     } 
    } 

更改此

SetCurrentValue(SelectedCityProperty, value); 

这个

+0

我试图改变这一点,但不幸的是,它不能解决这个问题。 。 –

+0

@Ill - 'SetCurrentValue'和'SetValue'是一样的。只是第一个保留触发器和绑定,而后来没有。所以,SetValue将工作SetCurrentValue将始终工作。 –

+0

当从XAML更改值时,setter从不会被调用DP。它在内部设置值。 –