WPF用户控制 - 双向绑定不起作用

时间:2014-02-20 21:43:17

标签: c# wpf xaml mvvm binding

我已经使用DependencyProperty创建了一个UserControl,我想以2方式绑定。但不知何故,这不起作用。当属性发生变化时,“City”属性永远不会在AddressViewModel中设置。

这是我的UserControl: 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>

这是“地址”的ViewModel:

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

2 个答案:

答案 0 :(得分:3)

从您的代码中

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

更改此

SetCurrentValue(SelectedCityProperty, value);

到这个

SetValue(SelectedCityProperty, value);

答案 1 :(得分:2)

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

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

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

OR

x:Name提供给UserControl说NewCustomer并使用ElementName进行绑定。

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

此外,您可以避免设置Mode to TwoWay,因为您在注册DP时已经指定了此项。