WPF自定义控件依赖项属性中未知对象的双向绑定问题 [英] Two-Way Binding Issue of Unknown Object in WPF Custom Control Dependency Property

查看:123
本文介绍了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 发送过滤的集合,此属性绑定为CustomControlListBoxItemsSource以选择项目.我们可以从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

这篇关于WPF自定义控件依赖项属性中未知对象的双向绑定问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆