Привязка объекта DependencyObject вне логического дерева к свойству элемента в логическом дереве

отредактированный

Краткое описание проблемы:

У меня есть пользовательский элемент управления, который имеет ObservableCollection из DependencyObjects. Поскольку DependencyObjectОни не являются потомками элемента управления, они не находятся в Логическом Дереве. Однако мне нужно, чтобы они связывались со свойствами элементов в логическом дереве с помощью XAML. (Я не могу использовать code-behind.) Я попытался использовать Source = {x: Reference blah}, но я не могу использовать его из-за ограничений циклической зависимости.

Кто-нибудь знает, как я могу добавить DependencyObjectс логическим деревом? Или у кого-нибудь есть другие идеи, как обойти эту проблему?

Подробности:

Я разрабатываю кастом ComboBox, Я хочу один из моих ComboBoxДля фильтрации видимых элементов по значениям, выбранным в других ComboBoxВ том же окне.

Пример:

Один ComboBox отображает список продуктов, хранящихся в моей базе данных, а другой отображает типы продуктов. Я хочу второй ComboBox фильтровать видимые элементы первого, когда элемент выбран, и я хочу первый ComboBox отфильтровать видимые элементы и установить значение второго.

Из-за того, как у меня настроена таблица "ProductTypes", поле "typeName" не является уникальным, поэтому, если я хочу ComboBox чтобы показывать только уникальные названия типов продуктов, тогда я должен использовать dataTable.DefaultView.ToTable(unique: true, column: "typeName").DefaultView,

Код:

Обычай ComboBox имеет ObservableCollection из FilterBinding объекты, которые привязываются к выбранным значениям других 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, Это на самом деле наследует от Telerik's 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,

1 ответ

Наверняка требуемая функциональность станет намного проще, если вы будете выполнять фильтрацию в модели представления или в коде? Просто прикрепите выбранные обработчики к вашему ComboBoxи обновите ItemsSource свойство каждого другого 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 обработчики вместо. В моем интерфейсе я привязываю к FilteredItems собственность, а не Items свойство... таким образом, у меня всегда есть все возможные значения, хранящиеся в Items и элементы управления коллекцией показывают только отфильтрованные значения.

Обратите внимание, что я адаптировал этот код из одного из моих проектов, где я заменил пользовательский тип коллекции, который позволяет мне добавлять несколько элементов в него одновременно ObservableCollection<T> что нет.

Другие вопросы по тегам