【问题标题】:WPF User Control - Two way binding doesn't workWPF 用户控件 - 两种方式绑定不起作用
【发布时间】:2014-02-20 21:43:17
【问题描述】:

我制作了一个带有 DependencyProperty 的 UserControl,我想绑定 2 种方式。但不知何故,这不起作用。当属性更改时,“City”-属性永远不会在 AddressViewModel 中设置。

这是我的用户控件: 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");
            }
        }

另外,我确定视图模型连接正确,因为我在“HouseNr”处设置了一个断点,并且它可以正常工作。

以防万一提供的不够,项目可以在这里找到:https://github.com/SanderDeclerck/EasyInvoice

【问题讨论】:

  • 您的代码无法编译。您的城市选择器控件没有实现 INotifyPropertyChange 它是如何订阅事件的?
  • 这里,它编译,我的 CityPicker 也没有使用 INotifyPropertyChanged 接口,它只在我的视图模型上(在这种情况下是 CityListViewModel 和 AddressViewModel)

标签: c# wpf xaml mvvm binding


【解决方案1】:

来自您的代码

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

改变这个

SetCurrentValue(SelectedCityProperty, value);

到这里

SetValue(SelectedCityProperty, value);

【讨论】:

  • @Ill - SetCurrentValueSetValue 相同。只有第一个保留触发器和绑定,而后一个则不保留。因此,SetValue 将在哪里起作用 SetCurrentValue 将始终起作用。
  • 当值从 XAML 更改时,从不为 DP 调用 setter。它在内部设置值。
【解决方案2】:

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

您必须使用 RelativeSourceElementName 显式解析该绑定。

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-08-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-13
    • 2017-04-06
    相关资源
    最近更新 更多