Two-Way Binding Issue of Unknown Object in WPF Custom Control Dependency Property

后端 未结 1 1262
暗喜
暗喜 2021-01-07 02:00

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 c

相关标签:
1条回答
  • 2021-01-07 02:05

    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);
            });
          }
        }
    
      }
    

    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:

    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);
          }
    
        }
    
      }
    

    Note

    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

    0 讨论(0)
提交回复
热议问题