使用动态项绑定到Listbox的DataTemplate中的控件DependencyProperty不起作用

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用动态项绑定到Listbox的DataTemplate中的控件DependencyProperty不起作用相关的知识,希望对你有一定的参考价值。

好吧,因为我花了一天时间在stackoverflow中阅读文章而没有成功,我必须寻求帮助。完整示例如下。

简短版本:我有一个DoubleTextBox,它提供了DependencyProperty“Number”。当我直接使用DoubleTextBox时,绑定和验证工作正常。但是,当我在Listbox的DataTemplate中使用它时,ObservableCollection中对象的绑定确实失败。 ValidatesOnDataErrors也不起作用。

非常非常感谢你!

XAML:

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">

  <Window.DataContext>
    <local:ViewModel x:Name="ViewModel"/>
  </Window.DataContext>

  <Grid>
    <Grid.ColumnDefinitions>
      <ColumnDefinition/>
      <ColumnDefinition Width="5"/>
      <ColumnDefinition/>
    </Grid.ColumnDefinitions>

    <Grid Grid.Column="0">
      <StackPanel Margin="10">
        <StackPanel Orientation="Horizontal">
          <TextBlock Text="Time (s):" Width="60"/>
          <local:DoubleTextBox Width="60" 
                               Number="{Binding Time, UpdateSourceTrigger=PropertyChanged, 
            ValidatesOnDataErrors=True}" Decimals="2"/>
        </StackPanel>
      </StackPanel>
    </Grid>

    <Grid Grid.Column="1" Background="SteelBlue"/>

    <Grid Grid.Column="2">

      <Grid.Resources>
        <DataTemplate x:Key="MethodProgramViewTemplate">
          <Grid KeyboardNavigation.TabNavigation="Local">
            <Grid.ColumnDefinitions>
              <ColumnDefinition Width="60"/>
              <ColumnDefinition Width="60"/>
            </Grid.ColumnDefinitions>
            <TextBlock Grid.Column="0" Text="Flow"/>
            <local:DoubleTextBox Grid.Column="1" TabIndex="0" Number="{Binding Flow, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" Decimals="2" />
          </Grid>
        </DataTemplate>

      </Grid.Resources>

      <ListBox x:Name="PumpProgramListBox"
                 KeyboardNavigation.TabNavigation="Local"
                 ItemsSource="{Binding Path=MethodProgramView}" 
                 ItemTemplate="{StaticResource MethodProgramViewTemplate}">
        <ListBox.ItemContainerStyle>
          <Style TargetType="{x:Type ListBoxItem}">
            <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
            <Setter Property="VerticalContentAlignment" Value="Stretch"/>
          </Style>
        </ListBox.ItemContainerStyle>
      </ListBox>

    </Grid>

  </Grid>
</Window>

ViewModel:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows.Data;

namespace WpfApp1
{
  public class ViewModel : INotifyPropertyChanged, IDataErrorInfo
  {
    private double time = 0.0;
    public double Time
    {
      get => this.time;
      set
      {
        if (this.time != value)
        {
          System.Diagnostics.Debug.WriteLine(String.Format("ViewModel Time - from {0} to {1}", this.time, value));
          this.time = value;
          RaisePropertyChanged(nameof(Time));
        }
      }
    }

    public ObservableCollection<Method> methodProgram { get; private set; } = new ObservableCollection<Method>();

    private CollectionViewSource methodProgramView;
    public ICollectionView MethodProgramView
    {
      get
      {
        if (methodProgramView == null)
        {
          methodProgramView = new CollectionViewSource();
          methodProgramView.Source = methodProgram;
        }
        return methodProgramView.View;
      }
    }

    public ViewModel()
    {
      this.Time = 1.5;
      for (int i = 1; i <= 3; i++)
      {
        this.methodProgram.Add(new Method() { Flow = i });
      }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    internal void RaisePropertyChanged(string propName) =>
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));

    public string Error => "";

    public string this[string columnName]
    {
      get
      {
        string result = null;
        switch (columnName)
        {
          case nameof(Time):
            result = this.Time < 0 ? "Time must be positive" : null;
            break;

          default:
            break;
        }

        return result;
      }
    }
  }
}

“方法”对象:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;

namespace WpfApp1
{
  public class Method : INotifyPropertyChanged, IDataErrorInfo
  {
    private double flow = 0.0;
    public double Flow
    {
      get => this.flow;
      set
      {
        if (this.flow != value)
        {
          System.Diagnostics.Debug.WriteLine(String.Format("Method Flow - from {0} to {1}", this.flow, value));
          this.flow = value;
          RaisePropertyChanged(nameof(Flow));
        }
      }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    internal void RaisePropertyChanged(string propName) =>
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));

    public string Error => "";

    public string this[string columnName]
    {
      get
      {
        string result = null;
        switch (columnName)
        {
          case nameof(Flow):
            result = this.Flow < 0 ? "Flow must be positive" : null;
            break;

          default:
            break;
        }

        return result;
      }
    }
  }
}

DoubleTextBox:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;

namespace WpfApp1
{
  public class DoubleTextBox : TextBox
  {
    #region Properties
    public double Number
    {
      get { return (double)this.GetValue(NumberProperty); }
      set { this.SetValue(NumberProperty, value); }
    }

    public static readonly DependencyProperty NumberProperty = DependencyProperty.Register(
        nameof(Number), typeof(double), typeof(DoubleTextBox),
        new FrameworkPropertyMetadata(3.3d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
        new PropertyChangedCallback(NumberProperty_PropertyChanged))
    );

    public int Decimals
    {
      get { return (int)this.GetValue(DecimalsProperty); }
      set { this.SetValue(DecimalsProperty, value); }
    }

    public static readonly DependencyProperty DecimalsProperty = DependencyProperty.Register(
        nameof(Decimals), typeof(int), typeof(DoubleTextBox),
        new FrameworkPropertyMetadata((int)2, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
        new PropertyChangedCallback(DecimalsProperty_PropertyChanged))
    );
    #endregion Properties

    #region Constructor
    public DoubleTextBox()
    {
      this.TextChanged += DoubleTextBox_TextChanged;
      this.LostFocus += DoubleTextBox_LostFocus;
      this.PreviewTextInput += DoubleTextBox_PreviewTextInput;
      this.RefreshText();
    }
    #endregion Constructor

    #region Methods
    private static void NumberProperty_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      System.Diagnostics.Debug.WriteLine(String.Format("NumberProperty_PropertyChanged - from {0} to {1}", e.OldValue, e.NewValue));
      (d as DoubleTextBox).RefreshText();
    }

    private static void DecimalsProperty_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      (d as DoubleTextBox).RefreshText();
    }

    private void RefreshText()
    {
      System.Diagnostics.Debug.WriteLine(String.Format("DoubleTextBox - DataContext: {0}", this.DataContext));
      Text = Number.ToString("N" + this.Decimals.ToString());
    }

    private void DoubleTextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
    {
      e.Handled = !IsTextAllowed(e.Text);
    }

    private static bool IsTextAllowed(string text)
    {
      string CultureName = System.Threading.Thread.CurrentThread.CurrentCulture.Name;
      System.Globalization.CultureInfo ci = new System.Globalization.CultureInfo(CultureName);
      char DecimalSeparator = ci.NumberFormat.NumberDecimalSeparator[0];
      Regex regex = new Regex(String.Format("([-+0-9]+[{0}][0-9]+)|([-+0-9{0}])", DecimalSeparator));  //regex that matches only numbers
      bool match = regex.IsMatch(text);

      return regex.IsMatch(text);
    }

    void DoubleTextBox_LostFocus(object sender, System.Windows.RoutedEventArgs e)
    {
      RefreshText();
    }

    void DoubleTextBox_TextChanged(object sender, TextChangedEventArgs e)
    {
      double number;
      if (string.IsNullOrEmpty(Text))
      {
        Number = 0;
      }
      else if (double.TryParse(Text, out number))
      {
        Number = Double.Parse(number.ToString("N" + this.Decimals.ToString()));
      }
      else
      {
        BindingExpression bex = GetBindingExpression(NumberProperty);
        if (bex != null)
        {
          ValidationError validationError = new ValidationError(new ExceptionValidationRule(), bex);
          validationError.ErrorContent = "Number is not valid";
          Validation.MarkInvalid(bex, validationError);
        }
      }
    }
    #endregion Methods
  }
}
答案

必须删除DoubleTextBox的构造函数中的RefreshText()调用。仍然不知道为什么,但它的工作原理。

感谢@ user1481065和这个问题:WPF Binding to DependencyProperty of UserControl in DataTemplate is not working

以上是关于使用动态项绑定到Listbox的DataTemplate中的控件DependencyProperty不起作用的主要内容,如果未能解决你的问题,请参考以下文章

如何将ListBox项绑定到用户控件上?

wpf中如何绑定到listbox中的选定项

使用 SelectionMode = Multiple 对 ListBox 进行数据绑定

ListBox TwoWay 绑定到 SelectedItem

wpf画面ListBox绑定的数据发生变化时 画面闪烁

DataBind listBox 选定项到文本框