简体   繁体   English

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

[英]WPF User Control - Two way binding doesn't work

I have made a UserControl with a DependencyProperty and I would like to bind 2 way. 我已经用DependencyProperty制作了一个UserControl,我想绑定2种方式。 But somehow this doesn't work. 但是,这不起作用。 The "City"-property never gets set in the AddressViewModel when the property changes. 属性更改时,永远不会在AddressViewModel中设置“城市”属性。

This is my UserControl: XAML: 这是我的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>

Code behind: 后面的代码:

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);
            }
        }
    }
}

This is where I use this control: 这是我使用此控件的地方:

<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>

And this is the ViewModel for "Address": 这是“地址”的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);
            }
        }
    }
}

When I'm debugging, I can see that this line is reached when I change the property in my UI: 调试时,可以看到在用户界面中更改属性时可以到达以下行:

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

But somehow, this never gets set: 但是以某种方式,这永远不会被设置:

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

Also, I'm sure the viewmodel is wired up correctly, because I set a breakpoint at "HouseNr" and this works correctly. 另外,我确定viewmodel正确连接了,因为我在“ HouseNr”处设置了一个断点,因此可以正常工作。

Just in case there is not enough provided, the project can be found here: https://github.com/SanderDeclerck/EasyInvoice 万一提供的内容不足,可以在以下位置找到该项目: https : //github.com/SanderDeclerck/EasyInvoice

From your code 从你的代码

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

Change this 改变这个

SetCurrentValue(SelectedCityProperty, value);

to this 对此

SetValue(SelectedCityProperty, value);

Issue is in your binding. 问题在于您的约束力。 You have set DataContext of UserControl to CityListViewModel so binding is failing since binding engine is searching for property Address.City in CityListViewModel instead of AddressViewModel . 您已将UserControl DataContext设置为CityListViewModel因此绑定失败,因为绑定引擎正在CityListViewModel中而不是AddressViewModel搜索属性Address.City

You have to explicitly resolve that binding using RelativeSource or ElementName . 您必须使用RelativeSourceElementName显式解析该绑定。

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

OR 要么

Give x:Name to UserControl say NewCustomer and bind using ElementName. x:Name给UserControl说NewCustomer并使用ElementName绑定。

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

Also you can avoid setting Mode to TwoWay since you have already specified that at time of registering of DP. 您也可以避免将Mode to TwoWay设置Mode to TwoWay因为在DP的注册时已经指定了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM