双向绑定直接到DataContext

时间:2016-03-17 12:31:53

标签: wpf binding

我正在使用一个自定义控件,它在ItemsControl中显示了一个ComboBox列表。 ItemsControl绑定到int列表,因此每个ComboBox的DataContext只是一个int。这与SelectedIndex绑定,项目列表来自其他地方。 ItemsControl定义为

SELECT order,
       MAX( CASE WHEN attribute LIKE 'CL_ACC%G_ADSL_DETAILS1COS_BLEACHING_FLAG'
                 THEN value END ) AS tech,
       MAX( CASE WHEN attribute LIKE 'CL_ACC%G_ADSL_DETAILS1G_6LAYER_COS_CIPR_AF1'
                 THEN value END ) AS bleach,
       MAX( CASE WHEN attribute LIKE 'CL_ACC%G_ADSL_DETAILS1G_6LAYER_COS_CIPR_AF2'
                 THEN value END ) AS af1,
       MAX( CASE WHEN attribute LIKE 'CL_ACC%G_ADSL_DETAILS1G_6LAYER_COS_CIPR_AF3'
                 THEN value END ) AS af2,
       MAX( CASE WHEN attribute LIKE 'CL_ACC%G_ADSL_DETAILS1G_6LAYER_COS_CIPR_AF4'
                 THEN value END ) AS af3,
       MAX( CASE WHEN attribute LIKE 'CL_ACC%G_ADSL_DETAILS1G_6LAYER_COS_CIPR_EF'
                 THEN value END ) AS af4,
       MAX( CASE WHEN attribute LIKE 'CL_ACC%G_ADSL_DETAILS_ACCESS_TECHNOLOGY'
                 THEN TO_DATE( value, 'DD/MM/YYYY' ) END ) AS ef
FROM   attributes
WHERE  order IN ( '802605-S844' /*, ...*/ )
GROUP BY order;

这最初看起来很好,但我发现当你点击ComboBox并更改选择时,更改不会传播到基础List。

我确实很难让这个绑定工作,因为没有Path,并且发现了here.然而,想到第一个看起来就是这个奇怪的绑定直接上下文,我修改了它而是绑定到IntContainer列表,是一个只包含单个int属性的类。这很好用,但很麻烦。

尽管没有错误,即使完全跟踪绑定,我确实看到了诊断输出的差异。它的大部分都很笨拙,但是在使用直线int更改值时我看到了

<ItemsControl x:Name="itemsCtl" ItemsSource="{Binding SelectedSourceIndices}"
              Grid.Row="1">
    <ItemsControl.Resources>
        <util:BindingProxy x:Key="parent" Data="{Binding}" />
    </ItemsControl.Resources>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <ComboBox ItemsSource="{Binding Source={StaticResource parent},Path=Data.SourceFieldNames}"
                      SelectedIndex="{Binding Path=DataContext, 
                            RelativeSource={RelativeSource Self},
                            UpdateSourceTrigger=PropertyChanged,
                            Mode=TwoWay,
                            diag:PresentationTraceSources.TraceLevel=High}"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

并在使用IntContainer时更改值我看到

System.Windows.Data Warning: 90 : BindingExpression (hash=20081636): Update - got raw value '3'
System.Windows.Data Warning: 93 : BindingExpression (hash=20081636): Update - implicit converter produced '3'
System.Windows.Data Warning: 94 : BindingExpression (hash=20081636): Update - using final value '3'
System.Windows.Data Warning: 102 : BindingExpression (hash=20081636): SetValue at level 0 to ComboBox (hash=64451636) using DependencyProperty(DataContext): '3'
System.Windows.Data Warning: 101 : BindingExpression (hash=20081636): GetValue at level 0 from ComboBox (hash=64451636) using DependencyProperty(DataContext): '3'
System.Windows.Data Warning: 80 : BindingExpression (hash=20081636): TransferValue - got raw value '3'
System.Windows.Data Warning: 84 : BindingExpression (hash=20081636): TransferValue - implicit converter produced '3'
System.Windows.Data Warning: 89 : BindingExpression (hash=20081636): TransferValue - using final value '3'

所以它看起来像我设置绑定的方式,当使用直接int时,最初得到正确的值,然后将更改写回ComboBox本身,而不是写入支持数组。这很奇怪,当然也没有意义。

有没有人知道如何更改绑定以便更新List中的值?

1 个答案:

答案 0 :(得分:1)

SelectedIndex绑定到对象而不是对象的属性。当它改变时,新值是与旧对象完全不同的对象 - 您不能将对象更改为另一个对象(变量/属性是,对象否)。这个新对象不在你的数组中。

您需要将int列表转换为具有int的对象列表。您已经在使用BindingProxy,因此您可以将其转换为这些列表并更新SelectedIndex绑定。

如果你真的想保留你的整体列表,你需要使用别的东西作为代理。在下面的代码中,我使用转换器将int列表转换为新类'IntProxy'的列表。

以下是我为您的问题所做的整个测试案例:

XAML:

<Window x:Class="WpfApplication32.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication32"
        xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="525">
    <StackPanel>
        <StackPanel.Resources>
            <local:IntProxyConverter x:Key="IntProxyConverter" />
        </StackPanel.Resources>
        <StackPanel.DataContext>
            <local:VM />
        </StackPanel.DataContext>
        <ItemsControl x:Name="itemsCtl" ItemsSource="{Binding SelectedSourceIndices, Converter={StaticResource IntProxyConverter}}">
            <ItemsControl.Resources>
                <local:BindingProxy x:Key="parent" Data="{Binding}" />
            </ItemsControl.Resources>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <ComboBox ItemsSource="{Binding Source={StaticResource parent},Path=Data.SourceFieldNames}"
                            SelectedIndex="{Binding Path=Value,
                                UpdateSourceTrigger=PropertyChanged,
                                Mode=TwoWay,
                                diag:PresentationTraceSources.TraceLevel=High}" />
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
        <TextBlock Text="{Binding AsString}" />
    </StackPanel>
</Window>

CS:

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Data;

namespace WpfApplication32
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }

    public class VM : INotifyPropertyChanged
    {
        public ObservableCollection<int> SelectedSourceIndices { get; set; } = new ObservableCollection<int>(new int[] { 5, 3, 6, 1, 0, 2, 4 });
        public string[] SourceFieldNames { get; set; } = new string[] { "S0", "S1", "S2", "S3", "S4", "S5", "S6", "S7" };


        // The rest of this class is just to visualize the above properties in realtime
        public event PropertyChangedEventHandler PropertyChanged;
        public string AsString
        {
            get {
                StringBuilder sb = new StringBuilder();
                foreach (var s in SelectedSourceIndices)
                    sb.AppendFormat("int = {0}", s).AppendLine();
                foreach (var s in SourceFieldNames)
                    sb.AppendFormat("name = {0}", s).AppendLine();
                return sb.ToString();
            }
        }

        public VM()
        {
            SelectedSourceIndices.CollectionChanged += SelectedSourceIndices_CollectionChanged;
        }

        private void SelectedSourceIndices_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs("AsString"));
        }
    }

    public class IntProxyConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var src = value as Collection<int>;
            var col = new ObservableCollection<IntProxy>();
            if (src != null)
            {
                for (int i = 0; i < src.Count(); i++)
                {
                    col.Add(new IntProxy() { Index = i, Source = src });
                }
            }
            return col;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

    public class IntProxy : INotifyPropertyChanged
    {
        public int Value { get { return Source.ElementAt(Index); } set { if (Source[Index] != value) { Source[Index] = value; OPC("Value"); } } }
        public int Index { get; set; } // This shouldn't be changing
        public Collection<int> Source { get; set; } // This shouldn't be changing either

        public event PropertyChangedEventHandler PropertyChanged;
        private void OPC(string name)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(name));
        }
    }

    public class BindingProxy : Freezable
    {
        #region Overrides of Freezable

        protected override Freezable CreateInstanceCore()
        {
            return new BindingProxy();
        }

        #endregion

        public object Data
        {
            get { return (object)GetValue(DataProperty); }
            set { SetValue(DataProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Data.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DataProperty =
            DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
    }
}