被修改
我有一个ObservableCollection
DependencyObject
的自定义控件。由于DependencyObject
不是控件的子项,因此它们不在逻辑树中。但是,我需要它们使用XAML绑定到逻辑树中的元素属性。 (我不能使用代码隐藏。)我尝试使用 Source = {x:Reference blah} ,但由于周期性依赖性限制,我无法使用它。
有谁知道我如何将DependencyObject
添加到逻辑树?或者有没有人有任何其他想法如何解决这个问题?
我正在开发自定义ComboBox
。我希望我的一个ComboBox
es根据同一窗口中其他ComboBox
es中选择的值过滤可见项目。
一个ComboBox
显示存储在我的数据库中的产品列表,另一个显示产品类型。我希望第二个ComboBox
在选择项目时过滤第一个ComboBox
的可见项目,我希望第一个ComboBox
过滤可见项目并设置第二个项目的值。
由于我设置了“ProductTypes”表的方式,“typeName”字段不是唯一的,所以如果我希望我的dataTable.DefaultView.ToTable(unique: true, column: "typeName").DefaultView
只显示产品类型的唯一名称,那么我必须使用ComboBox
。
自定义ObservableCollection
有FilterBinding
个ComboBox
个对象,这些对象绑定到其他FilterBinding
es的选定值。这是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
的代码。它实际上继承自Telerik的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];
}
}
非常相似。
<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>
这是XAML:
DataTable
但是,它不绑定FilterBindings,因为ElementName仅适用于逻辑树中的元素。
我不使用MVVM。相反,我通过SQL获得ItemsSource
。最终我将使用EntityFramework,但它不会改变将DataView
分配给从LINQ派生的DataView
的事实。我需要使用DisplayMemberPath
的原因是因为有时ComboBox
会引用具有非唯一条目的列,这些条目需要在{{1}}中显示为唯一。
答案 0 :(得分:0)
如果您在视图模型或代码中进行过滤,当然可以更轻松地实现所需的功能吗?只需将选择更改的处理程序附加到ComboBox
es并根据选择更新每个其他ItemsSource
es的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
方法。在我的UI中,我绑定到FilteredItems
属性,而不是Items
属性...这样,我总是将所有可能的值存储在Items
中,并且集合控件只显示过滤后的值。
请注意,我已经从我的某个项目中修改了此代码,其中我替换了自定义集合类型,允许我一次为其添加多个项目
ObservableCollection<T>
哪个没有。