【问题标题】:TextBlock Text property not updating on updating its source Binding property?TextBlock Text 属性在更新其源 Binding 属性时未更新?
【发布时间】:2016-09-27 04:37:57
【问题描述】:

我在 XAML 中有以下代码 sn-p。

<Grid>

    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="01*"/>
        <ColumnDefinition Width="03*"/>
        <ColumnDefinition Width="01*"/>
    </Grid.ColumnDefinitions>

    <Button Name="btnPrevious" Grid.Column="0" Content="Previous" Click="btnPrevious_Click"/>
    <TextBlock Name="txtBlockName" Grid.Column="1" Text="{Binding SelectedName, UpdateSourceTrigger=PropertyChanged}" FontSize="30" VerticalAlignment="Center" HorizontalAlignment="Center"/>
    <Button Name="btnNext" Grid.Column="2" Content="Next"  Click="btnNext_Click"/>

</Grid>

这会生成如下输出,如图 1 所示。

这背后的代码如下所示。

public partial class MainWindow : Window, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public List<string> namesList = new List<string>();

    public string SelectedName
    {
        get
        {
            return namesList[1];
        }
        set
        {
            if (value != namesList[1])
            {
                namesList[1] = value;
                NotifyPropertyChanged("SelectedName");
            }
        }
    }

    public MainWindow()
    {
        InitializeComponent();

        namesList.Add("ABC");
        namesList.Add("DEF");
        namesList.Add("GHI");


        this.DataContext = this;
    }

    private void btnPrevious_Click(object sender, RoutedEventArgs e)
    {
        string str = namesList[0];
        namesList[0] = namesList[1];
        namesList[1] = str;

        this.IsEnabled = false;
    }

    private void btnNext_Click(object sender, RoutedEventArgs e)
    {
        string str = namesList[2];
        namesList[2] = namesList[1];
        namesList[1] = str;

        this.IsEnabled = false;
    }



    protected void NotifyPropertyChanged(String propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

}

TextBlockText 属性绑定到 SelectedName 属性。并且不会在更新 SelectedName 属性时进行更新。尽管我的类实现了INotifyPropertyChanged 接口并定义了它的NotifyPropertyChanged 行为,但它仍然不起作用。

在调试时,我设置了一些断点来观察SelectedName 属性值,我观察到它正在根据要求更新,但TextBlockText 属性没有更新。如图2所示。

我在网上看到了很多问题和解决方案,但没有一个能解决我的问题。任何帮助将不胜感激。

【问题讨论】:

    标签: c# wpf xaml binding properties


    【解决方案1】:

    到目前为止给出的两个答案中,唯一一个甚至接近我认为合理的替代方案的是this answer 中提出的第二个选项(即在 之后“如果你想“更新”SelectedName你可以”)。

    也就是说,在我看来,您最好更改您的数据结构,以便更接近地模拟用户界面中实际发生的情况。如果您花时间去做,您的代码会更容易编写和理解,并且会更简单。

    在这个特定的示例中,这意味着,由于(出现)您希望能够在三个不同的文本值之间导航,您应该使用该属性来简单地呈现当前文本值,并使用索引变量来保持跟踪选择了哪个值。例如:

    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
    
        // Don't make fields public. If you do want to expose the list
        // use a public read-only property. And even there, think carefully
        // about whether you want callers to be able to modify the list; you
        // can return a `ReadOnlyCollection<string>` that wraps your list if
        // you only want callers to be able to examine the contents, rather than
        // modify it.
        //
        // Also, make any field that you never change "readonly".
        private readonly List<string> namesList = new List<string>();
    
        // Here's the index that keeps track of which string is selected
        private int _index;
    
        public string SelectedName
        {
            get { return namesList[_index]; }
        }
    
        public MainWindow()
        {
            InitializeComponent();
    
            namesList.Add("ABC");
            namesList.Add("DEF");
            namesList.Add("GHI");
    
            this.DataContext = this;
        }
    
        private void btnPrevious_Click(object sender, RoutedEventArgs e)
        {
            if (_index > 0)
            {
                _index--;
                NotifyPropertyChanged(nameof(SelectedName));
            }
    
            // I don't know what you expect this to do. You are setting the window's
            // "IsEnabled" property to false, which doesn't seem useful. More likely,
            // you intend to set the "Previous" button's enabled state, but even there
            // you really only want to set it to false if the _index value is 0. If the
            // _index value had been 2 and was just set to 1, you still want the
            // "Previous" button enabled. This would actually be an excellent place for
            // you to learn how to implement an ICommand, to have its CanExecute() method
            // return a value based on the _index value (where the "Previous" ICommand
            // object would return true unless _index is 0, and the "Next" ICommand
            // object would return true unless _index is namesList.Count - 1). Then you
            // can bind the button's "Command" property to the appropriate ICommand
            // object for the button and WPF will automatically deal with enabling
            // or disabling the button according to the command's state
            this.IsEnabled = false;
        }
    
        private void btnNext_Click(object sender, RoutedEventArgs e)
        {
            if (_index < namesList.Count - 1)
            {
                _index++;
                NotifyPropertyChanged(nameof(SelectedName));
            }
    
            // See comment above.
            this.IsEnabled = false;
        }
    
        protected void NotifyPropertyChanged(String propertyName)
        {
            PropertyChanged?.DynamicInvoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    

    请注意,对于其他两个答案中提出的任何更改,您仍然会遇到与每个文本值之间的导航相关的错误。最初单击按钮可能会起作用,但之后您的数据结构将变得不连贯,您将无法获得您想要的结果。

    此答案中的示例解决了所有这些问题,以及您最初的担忧。

    【讨论】:

    • 我对我的代码和 Dan 的代码感到困惑?请向我澄清它们之间的区别。谢谢
    • @Peter,100% 与你同在,我也不知道“IsEnabled = false”是什么意思,得出了相同的结论,但我不想在指出更改通知遗漏的代码...我认为这是问题的意图
    • @Waqas:您的原始代码与 Dan 帖子中的答案之间的显着区别在于,当 SelectedName 属性的值发生变化时,Dan 会引发 PropertyChanged 事件,而您的代码不会。您的两个代码示例只有在第一次单击按钮时才能正常工作。我的示例与您的代码有相同的区别,因为它引发了PropertyChanged 事件,另外的区别在于它将在随后的按钮点击中起作用,而不仅仅是在第一次按钮点击时起作用。
    • @PeterDuniho,我也尝试过 Dan 的第二个选项,它与我的代码相同。它还只更新属性,而不是 UI。
    • @PeterDuniho,我已经用你的一些代码尝试了我的代码,它可以正常工作。并更新属性和 UI。
    【解决方案2】:

    从未调用过SelectedName Setter 属性。因此,您可以使用如下所示的 BindingExpression.UpdateSource() 方法更新 TextBlock.Text,

     private void btnPrevious_Click(object sender, RoutedEventArgs e)
        {
            string str = namesList[0];
            namesList[0] = namesList[1];
            namesList[1] = str;
            BindingExpression be = txtBlockName.GetBindingExpression(TextBlock.TextProperty);
            be.UpdateSource();
    
            this.IsEnabled = false;
        }
    

    【讨论】:

      【解决方案3】:

      您永远不会设置“SelectedName”,您可以将其设为只读。

      永远不会引发PropertyChanged。

      如果你想让绑定注意到它,你应该这样做

      NotifyPropertyChanged("SelectedName");
      

      在您更改了 _namesLIst 的索引 #1 后,

      public partial class MainWindow : Window , INotifyPropertyChanged
      {
          public event PropertyChangedEventHandler PropertyChanged;
      
          private readonly List<string> _namesList = new List<string>();
      
          public string SelectedName
          {
              get { return _namesList[1]; }            
          }
      
          public MainWindow()
          {
              InitializeComponent();
      
              _namesList.Add("ABC");
              _namesList.Add("DEF");
              _namesList.Add("GHI");
      
              DataContext = this;
          }
      
          private void btnPrevious_Click(object sender, RoutedEventArgs e)
          {
              var str = _namesList[0];
              _namesList[0] = _namesList[1];
              _namesList[1] = str;
              NotifyPropertyChanged(nameof(SelectedName));
              IsEnabled = false;
          }
      
          private void btnNext_Click(object sender, RoutedEventArgs e)
          {
              var str = _namesList[2];
              _namesList[2] = _namesList[1];
              _namesList[1] = str;
              NotifyPropertyChanged(nameof(SelectedName));
      
              IsEnabled = false;
          }
      
          private void NotifyPropertyChanged(string propertyName)
          {
              PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
          }
      
      }
      

      如果你想“更新”SelectedName,你可以

          public string SelectedName
          {
              get { return _namesList[1]; }
              set
              {
                  if (value == _namesList[1]) return;
                  _namesList[1] = value;
                  NotifyPropertyChanged("SelectedName");
              }
          }
      
          private void btnPrevious_Click(object sender, RoutedEventArgs e)
          {
              var str = _namesList[0];
              _namesList[0] = _namesList[1];
              SelectedName = str;            
              IsEnabled = false;
          }
      
          private void btnNext_Click(object sender, RoutedEventArgs e)
          {
              var str = _namesList[2];
              _namesList[2] = _namesList[1];
              SelectedName = str;            
              IsEnabled = false;
          }
      

      【讨论】:

      • 当我控制更多具有更多不同文本属性的文本块与它们绑定时该怎么办。您的代码总是在单击 Next 或 Previous 时更新属性“SelectedName”。
      • 代码不会更新 SelectedName,它会通知它已更改,并且绑定到它的任何内容都将更新为当前值
      • 现在你的代码和我的代码有什么区别?
      • 唯一的区别是你没有更新“SelectedName”,也没有通知它的变化......就是第一个版本通知,第二个版本在设置属性时间接通知,这就是我以为你在问,为什么 UI 上的值没有更新? .. 答案是 ... 因为 UI 不知道。
      • 我也试过你的第二个选项,它和我的代码一样。它还只更新属性,而不是 UI。
      猜你喜欢
      • 2018-08-20
      • 1970-01-01
      • 2012-10-26
      • 1970-01-01
      • 2012-02-04
      • 2017-02-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多