将逻辑树外部的DependencyObject绑定到逻辑树中的元素属性

时间:2013-09-06 03:02:07

标签: wpf data-binding combobox elementname logical-tree

被修改

问题摘要:

我有一个ObservableCollection DependencyObject的自定义控件。由于DependencyObject不是控件的子项,因此它们不在逻辑树中。但是,我需要它们使用XAML绑定到逻辑树中的元素属性。 (我不能使用代码隐藏。)我尝试使用 Source = {x:Reference blah} ,但由于周期性依赖性限制,我无法使用它。

有谁知道我如何将DependencyObject添加到逻辑树?或者有没有人有任何其他想法如何解决这个问题?

详细说明:

我正在开发自定义ComboBox。我希望我的一个ComboBox es根据同一窗口中其他ComboBox es中选择的值过滤可见项目。

实施例

一个ComboBox显示存储在我的数据库中的产品列表,另一个显示产品类型。我希望第二个ComboBox在选择项目时过滤第一个ComboBox的可见项目,我希望第一个ComboBox过滤可见项目并设置第二个项目的值。

由于我设置了“ProductTypes”表的方式,“typeName”字段不是唯一的,所以如果我希望我的dataTable.DefaultView.ToTable(unique: true, column: "typeName").DefaultView只显示产品类型的唯一名称,那么我必须使用ComboBox

代码:

自定义ObservableCollectionFilterBindingComboBox个对象,这些对象绑定到其他FilterBinding es的选定值。这是public class FilterBinding : DependencyObject { public object Value { get { return GetValue(ValueProperty); } set { SetValue(ValueProperty, value); } } public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(object), typeof(FilterBinding), new FrameworkPropertyMetadata(null, ValueChanged)); public static void ValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { FilterBinding binding = d as FilterBinding; binding.isActive = e.NewValue.IsNotNullString(); binding.parent.FilterItems(); } public bool IsActive { get { return isActive; } } bool isActive = false; public string Path { get; set; } public IonDataComboBox Parent { get; set; } } 类:

ComboBox

以下是我的自定义RadComboBox的代码。它实际上继承自Telerik的ComboBox,但它的行为与普通的public class IonDataComboBox : RadComboBox, IPopulatable { public object BindingValue { get { return GetValue(BindingValueProperty); } set { SetValue(BindingValueProperty, value); } } public static readonly DependencyProperty BindingValueProperty = DependencyProperty.Register("BindingValue", typeof(object), typeof(IonDataComboBox), new FrameworkPropertyMetadata(null)); public object SelectedValueBinding { get { return GetValue(SelectedValueBindingProperty); } set { SetValue(SelectedValueBindingProperty, value); } } public static readonly DependencyProperty SelectedValueBindingProperty = DependencyProperty.Register("SelectedValueBinding", typeof(object), typeof(IonDataComboBox), new FrameworkPropertyMetadata( null, SelectedValueBindingChanged)); public static void SelectedValueBindingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { (d as IonDataComboBox).SetSelectedValueFromBinding(); } public List<DbPortal.DelegatedQuery> Queries { get { return queries; } } protected List<DbPortal.DelegatedQuery> queries = new List<DbPortal.DelegatedQuery>(); public string PopulateCommand { get; set; } public ObservableCollection<FilterBinding> FilterBindings { get; set; } List<int> bindingsFilteredIndices; Collection<int> textFilteredIndices = new Collection<int>(); DataTable dataTable; public IonDataComboBox() : base() { QueryParameters = new List<DbParameter>(); FilterBindings = new ObservableCollection<FilterBinding>(); } public void Populate() { //archaic if (PopulateCommand.IsNotNullString()) { queries.Add(PopulateQueryCompleted); if (QueryParameters.Count > 0) new DbPortal().ExecuteReader(this, queries.Count - 1, PopulateCommand); } } void PopulateQueryCompleted(object result, int queryID) { dataTable = result as DataTable; DataView dataView; if (SelectedValuePath.IsNotNullString()) dataView = dataTable.DefaultView; else dataView = dataTable.DefaultView.ToTable(true, DisplayMemberPath).DefaultView; dataView.Sort = DisplayMemberPath + " asc"; ItemsSource = dataView; FilterItems(); } void SetSelectedValueFromBinding() { if (SelectedValueBinding.IsNullString()) return; string path = SelectedValuePath.IsNotNullString() ? SelectedValuePath : DisplayMemberPath; foreach (DataRowView item in ItemsSource) { if (item[path].Equals(SelectedValueBinding)) { SelectedItem = item; break; } } } List<int> FindIndicesOfItems(DataRow[] filteredItems) { List<int> indices = new List<int>(); DataView filteredItemsView; if (SelectedValuePath.IsNotNullString()) filteredItemsView = filteredItems.CopyToDataTable().DefaultView; else filteredItemsView = filteredItems.CopyToDataTable().DefaultView.ToTable(true, DisplayMemberPath).DefaultView; filteredItemsView.Sort = DisplayMemberPath + " asc"; int i = 0; foreach (DataRowView item in filteredItemsView) { while (i < Items.Count) { if (item[DisplayMemberPath].Equals((Items[i] as DataRowView)[DisplayMemberPath])) { indices.Add(i++); break; } else i++; } } return indices; } public void FilterItems() { if (ItemsSource.IsNull()) return; DataRow[] filteredItems = dataTable.Select(); foreach (FilterBinding binding in FilterBindings) { if (binding.IsActive) filteredItems = filteredItems.Where(r => r[binding.Path].Equals(binding.Value)).ToArray(); } if (filteredItems.Length > 0) { bindingsFilteredIndices = FindIndicesOfItems(filteredItems); UpdateItemsVisibility(false, null); if (bindingsFilteredIndices.Count == 1) { SelectedIndex = bindingsFilteredIndices[0]; if (SelectedItem is DataRowView) BindingValue = (SelectedItem as DataRowView)[SelectedValuePath.IsNotNullString() ? SelectedValuePath : DisplayMemberPath]; else BindingValue = SelectedItem; } } } protected override void UpdateItemsVisibility(bool showAll, Collection<int> matchIndexes) { if (matchIndexes.IsNotNull()) textFilteredIndices = matchIndexes; for (int i = 0; i < Items.Count; i++) { FrameworkElement element = ItemContainerGenerator.ContainerFromItem(Items[i]) as FrameworkElement; if (element.IsNotNull()) { bool isMatch = textFilteredIndices.Count > 0 ? textFilteredIndices.Contains(i) : true && bindingsFilteredIndices.Contains(i) && Items[i] is DataRowView ? (Items[i] as DataRowView)[DisplayMemberPath].IsNotNullString() : Items[i].IsNotNullString(); var visibility = showAll || isMatch ? Visibility.Visible : Visibility.Collapsed; element.Visibility = visibility; } } } protected override void OnInitialized(EventArgs e) { base.OnInitialized(e); DefaultStyleKey = typeof(IonDataComboBox); foreach (FilterBinding binding in FilterBindings) binding.Parent = this; Populate(); } protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue) { base.OnItemsSourceChanged(oldValue, newValue); if (!IsDropDownOpen) { IsDropDownOpen = true; IsDropDownOpen = false; } } protected override void OnSelectionChanged(SelectionChangedEventArgs e) { base.OnSelectionChanged(e); if (IsFilteringItems || !IsDropDownOpen) return; if (e.AddedItems[0] is DataRowView) BindingValue = (e.AddedItems[0] as DataRowView)[SelectedValuePath.IsNotNullString() ? SelectedValuePath : DisplayMemberPath]; else BindingValue = e.AddedItems[0]; } } 非常相似。

<Window x:Class="FluorideDrive.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:iwcd="clr-namespace:IonDrive.Windows.Controls.Data;assembly=IonDrive"
        x:Name="window" Width="300" Height="400">

    <StackPanel>
        <iwcd:IonDataComboBox x:Name="combo"
                              DisplayMemberPath="CompanyName"
                              PopulateCommand="SELECT * FROM Company"
                              SelectedValuePath="Tid"
                              SelectedValueBinding="{Binding Tid}"
                              IsEditable="True"
                              IsFilteringEnabled="True">
            <iwcd:IonDataComboBox.FilterBindings>
                <iwcd:FilterBinding Path="City" Value="{Binding BindingValue, Source={x:Reference combo1}}"/>
            </iwcd:IonDataComboBox.FilterBindings>
        </iwcd:IonDataComboBox>

        <iwcd:IonDataComboBox x:Name="combo1"
                              DisplayMemberPath="City"
                              PopulateCommand="SELECT * FROM Company"
                              SelectedValueBinding="{Binding City}"
                              IsEditable="True"
                              IsFilteringEnabled="True">
            <iwcd:IonDataComboBox.FilterBindings>
                <iwcd:FilterBinding Path="Tid" Value="{Binding BindingValue, Source={x:Reference combo}}"/>
            </iwcd:IonDataComboBox.FilterBindings>
        </iwcd:IonDataComboBox>
    </StackPanel>
</Window>

这是XAML:

DataTable

但是,它不绑定FilterBindings,因为ElementName仅适用于逻辑树中的元素。

我不使用MVVM。相反,我通过SQL获得ItemsSource。最终我将使用EntityFramework,但它不会改变将DataView分配给从LINQ派生的DataView的事实。我需要使用DisplayMemberPath的原因是因为有时ComboBox会引用具有非唯一条目的列,这些条目需要在{{1}}中显示为唯一。

1 个答案:

答案 0 :(得分:0)

如果您在视图模型或代码中进行过滤,当然可以更轻松地实现所需的功能吗?只需将选择更改的处理程序附加到ComboBox es并根据选择更新每个其他ItemsSource es的ComboBox属性。

当我做这种事情时,我的每个集合控件都有两个集合属性:

public ObservableCollection<SomeType> Items
{
    get { return items; }
    set
    {
        if (items != value) 
        {
            items= value; 
            NotifyPropertyChanged("Items");
            FilterItems();
        }
    }
}

public ObservableCollection<SomeType> FilteredItems
{
    get { return filteredItems ?? (filteredItems = Items); }
    private set { filteredItems = value; NotifyPropertyChanged("FilteredItems"); }
}

private void FilterItems()
{
    filteredItems = new ObservableCollection<SomeType>();
    if (filterText == string.Empty) filteredItems.AddRange(Items);
    else filteredItems.Add(AudioTracks.Where(m => CheckFields(m)));
    NotifyPropertyChanged("FilteredItems");
}

private bool CheckFields(SomeType item)
{
    return your.BoolCondition.Here;
}

public string FilterText
{
    get { return filterText; }
    set
    {
        if (filterText != value)
        {
            filterText = value;
            NotifyPropertyChanged("FilterText");
            FilterItems();
        }
    }
}

在此示例中,我有一个FilterText属性可触发对集合的过滤,但在您的示例中,您将从FilterItems处理程序中调用此SelectionChanged方法。在我的UI中,我绑定到FilteredItems属性,而不是Items属性...这样,我总是将所有可能的值存储在Items中,并且集合控件只显示过滤后的值。

  

请注意,我已经从我的某个项目中修改了此代码,其中我替换了自定义集合类型,允许我一次为其添加多个项目 ObservableCollection<T> 哪个没有