WPF自定义控件依赖项属性中未知对象的双向绑定问题
我有一个自定义控件-为自动完成文本框实现. 我从以下问题中得到了所有想法
I'm having a Custom Control - Implemented for AutoComplete TextBox. I got all idea's from the following question Create a Custom Control with the combination of multiple controls in WPF C#. In that Custom Control, they suggest the following code for adding item, its perfectly working and the Two-Way Binding too.
(this.ItemsSource as IList<string>).Add(this._textBox.Text);
但是,我将以下代码更改为未知对象",因此将IList<string>
更改为IList<object>
But, I changed the following code to Unknown Object, so I changed IList<string>
to IList<object>
(this.ItemsSource as IList<object>).Add(item);
XAML:
<local:BTextBox
ItemsSource="{Binding Collection}"
ProviderCommand="{Binding AutoBTextCommand}"
AutoItemsSource="{Binding SuggCollection}" />
但是它不会更新ViewModel属性 Collection
.我也在xaml中尝试了以下更改
But it's not updating the ViewModel Property Collection
. I too tried the following changes in the xaml
ItemsSource="{Binding Collection, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
我不知道我在哪里犯了错误.
I don't know where I did the mistake.
功能:CustomControl
中的TextBox
接受用户的输入,并触发 ProviderCommand
,该命令根据以下内容过滤远程数据用户输入并通过 AutoItemsSource
发送过滤的集合,此属性绑定为CustomControl
内ListBox
的ItemsSource
以选择项目.我们可以从ListBox
项目中选择项目,通过单击项目,它会触发CustomControl
类中的命令 AddCommand
,它将所选项目添加到ItemSource
属性中CustomControl
的.我在此属性 ItemsSource
中遇到双向绑定问题.仅从此属性中,我们可以将选定"项作为集合.
Functionality: The TextBox
inside the CustomControl
takes the input from the User and it triggers the ProviderCommand
, that Command filters the Remote data based on the User Input and sends the Filtered Collection via AutoItemsSource
, this Property is binded as a ItemsSource
of the ListBox
inside that CustomControl
to select the Item. We can select the Item from the ListBox
Item, by clicking the Item, it triggers the Command AddCommand
which is in the CustomControl
Class, it add the selected item in ItemSource
Property of the CustomControl
. I'm having the Two-Way Binding issue in this Property ItemsSource
. From this property only we may get the Selected item as a Collection.
这是我的完整源代码
自定义控件C#代码:
public class BTextBox : ItemsControl
{
static BTextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(BTextBox), new FrameworkPropertyMetadata(typeof(BTextBox)));
}
#region Private Members
private TextBox _textBox;
private ItemsControl _itemsView;
#endregion
#region Dependency Property Private Members
public static readonly DependencyProperty ProviderCommandProperty = DependencyProperty.Register("ProviderCommand", typeof(ICommand), typeof(BTextBox), new PropertyMetadata(null));
public static readonly DependencyProperty AutoItemsSourceProperty = DependencyProperty.Register("AutoItemsSource", typeof(IEnumerable<dynamic>), typeof(BTextBox), new PropertyMetadata(null, OnItemsSourceChanged));
#endregion
#region Dependency Property Public members
public IEnumerable<dynamic> AutoItemsSource
{
get { return (IEnumerable<dynamic>)GetValue(AutoItemsSourceProperty); }
set { SetValue(AutoItemsSourceProperty, value); }
}
#endregion
#region Listener Methods
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var tb = d as BTextBox;
if ((e.NewValue != null) && ((tb.ItemsSource as IList<object>) != null))
{
(tb.AutoItemsSource as IList<object>).Add(e.NewValue);
}
}
#endregion
#region Override Methods
public override void OnApplyTemplate()
{
this._textBox = this.GetTemplateChild("PART_TextBox") as TextBox;
this._itemsView = this.GetTemplateChild("PART_ListBox") as ItemsControl;
this._textBox.TextChanged += (sender, args) =>
{
if (this.ProviderCommand != null)
{
this.ProviderCommand.Execute(this._textBox.Text);
}
};
base.OnApplyTemplate();
}
#endregion
#region Command
public ICommand ProviderCommand
{
get { return (ICommand)GetValue(ProviderCommandProperty); }
set { SetValue(ProviderCommandProperty, value); }
}
public ICommand AddCommand
{
get
{
return new DelegatingCommand((obj) =>
{
(this.ItemsSource as IList<object>).Add(obj);
});
}
}
#endregion
}
Generic.xaml 代码为
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SampleControl">
<Style TargetType="{x:Type local:BTextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:BTextBox}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox x:Name="PART_TextBox" Grid.Row="0" Width="*" VerticalAlignment="Center" />
<ListBox ItemsSource="{TemplateBinding AutoItemsSource}" Grid.Row="1" x:Name="PART_ListBox_Sugg" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Value.IsChecked}" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:BTextBox}}, Path=AddCommand}" CommandParameter="{Binding }" Foreground="#404040">
<CheckBox.Content>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding }" Visibility="Visible" TextWrapping="Wrap" MaxWidth="270"/>
</StackPanel>
</CheckBox.Content>
</CheckBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
MainWindow.xaml 代码为
<Window x:Class="SampleControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SampleControl"
Title="MainWindow" Height="400" Width="525">
<Grid>
<local:BTextBox
ItemsSource="{Binding Collection}"
ProviderCommand="{Binding AutoBTextCommand}"
AutoItemsSource="{Binding SuggCollection}" />
</Grid>
</Window>
MainWindow.xaml的 C#代码后面的代码
The MainWindow.xaml's Code Behind C# Code
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new StringModel();
}
}
我有两个ViewModels
I'm having TWO ViewModels
ViewModel#1 StringModel
ViewModel #1 StringModel
class StringModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<string> _collection = new ObservableCollection<string>();
private ObservableCollection<string> _suggCollection = new ObservableCollection<string>();
private ObservableCollection<string> _primaryCollection = new ObservableCollection<string>();
public ObservableCollection<string> Collection
{
get { return _collection; }
set
{
_collection = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Collection"));
}
}
public ObservableCollection<string> SuggCollection
{
get { return _suggCollection; }
set
{
_suggCollection = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("SuggCollection"));
}
}
public StringModel()
{
_primaryCollection = new ObservableCollection<string> {
"John", "Jack", "James", "Emma", "Peter"
};
}
public ICommand AutoBTextCommand
{
get
{
return new DelegatingCommand((obj) =>
{
Search(obj as string);
});
}
}
private void Search(string str)
{
SuggCollection = new ObservableCollection<string>(_primaryCollection.Where(m => m.ToLowerInvariant().Contains(str.ToLowerInvariant())).Select(m => m));
}
}
ViewModel#2 IntModel
ViewModel #2 IntModel
class IntModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<int> _collection = new ObservableCollection<int>();
private ObservableCollection<int> _suggCollection = new ObservableCollection<int>();
private ObservableCollection<int> _primaryCollection = new ObservableCollection<int>();
public ObservableCollection<int> Collection
{
get { return _collection; }
set
{
_collection = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Collection"));
}
}
public ObservableCollection<int> SuggCollection
{
get { return _suggCollection; }
set
{
_suggCollection = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("SuggCollection"));
}
}
public IntModel()
{
_primaryCollection = new ObservableCollection<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 16, 17, 18, 19, 20 };
}
public ICommand AutoBTextCommand
{
get
{
return new DelegatingCommand((obj) =>
{
Search(obj as string);
});
}
}
private void Search(string str)
{
int item = 0;
int.TryParse(str, out item);
SuggCollection = new ObservableCollection<int>(_primaryCollection.Where(m => m == item).Select(m => m));
}
}
首先,这篇文章在CodeReview中会更好.
First of all, this post would have fitted better in CodeReview.
第二,我可以想象,你确实想做什么. 为简化起见,我建议您不要针对您的情况使用通用集合.
Second, i can imagine, what you did want to do. To shorten things, i recommend you to not use generic collections in your case.
我对控件做了一些修改:
I've modified the Control a bit:
public class BTextBox : ItemsControl {
static BTextBox() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(BTextBox), new FrameworkPropertyMetadata(typeof(BTextBox)));
}
private TextBox _textBox;
private ItemsControl _itemsView;
public static readonly DependencyProperty ProviderCommandProperty = DependencyProperty.Register("ProviderCommand", typeof(ICommand), typeof(BTextBox), new PropertyMetadata(null));
public static readonly DependencyProperty AutoItemsSourceProperty = DependencyProperty.Register("AutoItemsSource", typeof(IEnumerable), typeof(BTextBox), new PropertyMetadata(null, OnItemsSourceChanged));
public IEnumerable AutoItemsSource {
get {
return (IEnumerable)GetValue(AutoItemsSourceProperty);
}
set {
SetValue(AutoItemsSourceProperty, value);
}
}
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var tb = d as BTextBox;
if ((e.NewValue != null) && ((tb.ItemsSource as IList) != null)) {
foreach (var item in e.NewValue as IEnumerable) {
(tb.AutoItemsSource as IList).Add(item);
}
}
}
public override void OnApplyTemplate() {
this._textBox = this.GetTemplateChild("PART_TextBox") as TextBox;
this._itemsView = this.GetTemplateChild("PART_ListBox_Sugg") as ItemsControl;
this._itemsView.ItemsSource = this.AutoItemsSource;
this._textBox.TextChanged += (sender, args) => {
this.ProviderCommand?.Execute(this._textBox.Text);
};
base.OnApplyTemplate();
}
public ICommand ProviderCommand {
get {
return (ICommand) this.GetValue(ProviderCommandProperty);
}
set {
this.SetValue(ProviderCommandProperty, value);
}
}
public ICommand AddCommand {
get {
return new RelayCommand(obj => {
(this.ItemsSource as IList)?.Add(obj);
});
}
}
}
然后,我修复了您的XAML,以使它甚至可以编译和运行:
Then i've fixed your XAML to get thing to even compile and run:
<Style TargetType="{x:Type local:BTextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:BTextBox}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox x:Name="PART_TextBox" Grid.Row="0" VerticalAlignment="Center" />
<ListBox ItemsSource="{TemplateBinding AutoItemsSource}" Grid.Row="1" x:Name="PART_ListBox_Sugg" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:BTextBox}}, Path=AddCommand}" CommandParameter="{Binding}" Foreground="#404040">
<CheckBox.Content>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding }" Visibility="Visible" TextWrapping="Wrap" MaxWidth="270"/>
</StackPanel>
</CheckBox.Content>
</CheckBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
最后一个很有价值的评论:
At last a valuable remark:
永远不要在您的ItemsSources上使用二传手.如果覆盖它们,绑定将中断.如下所示,请使用.Clear()
和.Add()
:
Never ever allow setters on your ItemsSources. If you override them, the binding will break. Use .Clear()
and .Add()
instead as you see below:
public class StringModel : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
private readonly ObservableCollection<string> _collection = new ObservableCollection<string>();
private readonly ObservableCollection<string> _suggCollection = new ObservableCollection<string>();
private readonly ObservableCollection<string> _primaryCollection = new ObservableCollection<string>();
public ObservableCollection<string> Collection => this._collection;
public ObservableCollection<string> SuggCollection => this._suggCollection;
public StringModel() {
this._primaryCollection.Add("John");
this._primaryCollection.Add("Jack");
this._primaryCollection.Add("James");
this._primaryCollection.Add("Emma");
this._primaryCollection.Add("Peter");
}
public ICommand AutoBTextCommand {
get {
return new RelayCommand(obj => {
this.Search(obj as string);
});
}
}
private void Search(string str) {
this.SuggCollection.Clear();
foreach (var result in this._primaryCollection.Where(m => m.ToLowerInvariant().Contains(str.ToLowerInvariant())).Select(m => m)) {
this.SuggCollection.Add(result);
}
}
}
注意
因为我没有您的DelegateCommand
实现,所以我改用了RelayCommand
.您可以在遇到任何问题时进行更改.我认为它是一样的东西,但名称不同.
您也可以考虑从一开始就显示您的建议.这样可能会提供更好的用户体验,但这仅是我的意见
Sice i didnt have your DelegateCommand
-implementation, i've used my RelayCommand
instead. You can change it withour any issues. I think its the same thing but a different name for it.
You also might consider to display your suggestions right from the start. This might provide a better user-expierience, but thats just my opinion