2014-02-20 46 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。它在內部設置值。 –