已编辑
问题总结:
我有一个自定义控件,它有一个ObservableCollection
of DependencyObject
s。因为DependencyObject
s 不是控件的子控件,所以它们不在逻辑树中。但是,我需要它们使用 XAML 绑定到逻辑树中元素的属性。(我不能使用代码隐藏。)我尝试使用Source={x:Reference blah},但由于周期性依赖限制,我无法使用它。
有谁知道我如何将DependencyObject
s 添加到逻辑树?或者有没有人有任何其他想法如何解决这个问题?
细节:
我正在开发一个自定义ComboBox
. 我希望我的一个es根据同一窗口上ComboBox
其他es中选择的值过滤可见的项目。ComboBox
例子:
一个ComboBox
显示存储在我的数据库中的产品列表,另一个显示产品类型。我希望第二个ComboBox
在选择项目时过滤第一个的可见项目,我希望第一个ComboBox
过滤可见项目并设置第二个的值。
由于我设置“ProductTypes”表的方式,“typeName”字段不是唯一的,所以如果我希望我ComboBox
只显示产品类型的唯一名称,那么我必须使用dataTable.DefaultView.ToTable(unique: true, column: "typeName").DefaultView
.
代码:
customComboBox
有一个ObservableCollection
ofFilterBinding
对象,它绑定到其他ComboBox
es 的选定值。这是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
.