将逻辑树外部的依赖对象绑定到逻辑树中的元素属性



已编辑

问题摘要:

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

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

详:

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

例:

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

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

法典:

自定义ComboBox具有FilterBinding对象的ObservableCollection,这些对象绑定到其他ComboBox的选定值。 这是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的代码。 它实际上继承了泰莱里克的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>

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

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

如果您在视图模型或代码隐藏中进行过滤,您所需的功能肯定会更容易实现吗?只需将选择更改的处理程序附加到您的ComboBox es,并更新依赖于选择的其他ComboBox es 的 ItemsSource 属性。

当我执行此类操作时,我的每个集合控件都有两个集合属性:

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 属性,用于触发集合的筛选,但在您的示例中,您将改为从SelectionChanged处理程序调用此FilterItems方法。在我的 UI 中,我绑定到 FilteredItems 属性,而不是 Items 属性...这样,我始终将所有可能的值存储在Items中,并且集合控件仅显示筛选的值。

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

相关内容

  • 没有找到相关文章

最新更新