Привязка объекта DependencyObject вне логического дерева к свойству элемента в логическом дереве
отредактированный
Краткое описание проблемы:
У меня есть пользовательский элемент управления, который имеет ObservableCollection
из DependencyObject
s. Поскольку 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>
что нет.