1

已编辑

问题总结:

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

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

细节:

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

例子:

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

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

代码:

customComboBox有一个ObservableCollectionofFilterBinding对象,它绑定到其他ComboBoxes 的选定值。这是FilterBinding课程:

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。它实际上继承自 Telerik 的RadComboBox,但它的行为非常像普通的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];
    }
}

这是 XAML:

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

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

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

4

1 回答 1

0

如果您在视图模型或后面的代码中进行过滤,您所需的功能肯定会更容易实现吗?只需将选择更改的处理程序附加到您的ComboBoxes 并根据选择更新ItemsSource每个其他ComboBoxes 的属性。

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

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> 其中添加多个项目,而没有

于 2013-09-06T09:47:47.693 回答