已编辑
问题摘要:
我有一个自定义控件,其ObservableCollection
为 DependencyObject
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>
而没有。