WPF 数据绑定在初始化后不起作用。没有绑定错误

Posted

技术标签:

【中文标题】WPF 数据绑定在初始化后不起作用。没有绑定错误【英文标题】:WPF Data bindings not working after intiailize. No Binding Errors 【发布时间】:2021-08-27 11:19:10 【问题描述】:

我的ViewViewModel 之间的数据绑定存在问题。绑定仅在初始化时运行,然后它们不会更新。代码按预期运行,每个输出窗口都没有绑定错误,但 UI 上的绑定不会更新。

程序设置:

WPF C# 棱镜 V8.1.97 MVVM .NET Framework 4.7.2

我尝试过的事情:

XAML 设置为使用INotifyPropertyChanged 直接绑定到属性 XAML 设置为查找类型为 UserControlRelativeSource RelayCommand 更新 UI,无论有无 Invoke 到主线程。 BackgroundWorker 更新 UI,无论有没有 Invoke 到主线程。 DelegateCommand 更新 UI,无论有没有 Invoke 到主线程。 i.Interaction.EventTriggersClick 在主线程上调用 UI 更新。

所有这些都将运行代码,但不会更新 UI。我已经为BackgroundWorkerDelegate voidDispatcher.BeginInvoke 留下了一些我在程序中尝试过的代码。我写了几个程序,从来没有遇到过这个问题。我创建程序时是否设置错误?

更新:Visual Studio 2019 似乎存在问题。这可能仅在我的版本上,因为我编写的其他程序不再工作。这通常可以按预期运行。

UserControl 我正在尝试进行简单的绑定。我在底部创建了一个TextboxMode=TwoWay,看看TextBlock 是否会更新。

<UserControl x:Class="MRC.Module.ModStatus.Views.ModStatusManagerView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:MRC.Module.ModStatus.Views"
             
             xmlns:prism="http://prismlibrary.com/"  
             
             xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
             xmlns:cconv="clr-namespace:MRC.Framework.Core.Data.Converters;assembly=MRC.Framework.Core"
             
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800" >
    <UserControl.Resources>
        <cconv:RemoveSpacesConverter x:Key="IntToStringConvert"/>
    </UserControl.Resources>
    <Grid>
        <Grid.Background>
            <SolidColorBrush Color="#222222"/>
        </Grid.Background>
        
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition Width="3*"/>
            </Grid.ColumnDefinitions>
         
            <Border BorderBrush="DodgerBlue" BorderThickness="1">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition/>
                        <RowDefinition/>
                        <RowDefinition/>
                    </Grid.RowDefinitions>

                    <Grid Grid.Row="0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
                        <Border BorderThickness="1" BorderBrush="DodgerBlue" VerticalAlignment="Stretch" Height="Binding ActualHeight, RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type Grid">
                            <StackPanel VerticalAlignment="Stretch">
                                <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
                                    <Button Height="20" Margin="0,6" Content="Binding StartStop" Width="100"
                                        >
                                        <i:Interaction.Triggers>
                                            <i:EventTrigger EventName="Click" >
                                                <i:InvokeCommandAction Command="Binding ProgressCommand" />
                                            </i:EventTrigger>
                                        </i:Interaction.Triggers>
                                    </Button>
                                  
                                </StackPanel>
                                <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
                                    <TextBlock  Margin="5,0"  HorizontalAlignment="Center" Text="Binding RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type UserControl, Path=DataContext.Health, UpdateSourceTrigger=PropertyChanged, Converter=StaticResource IntToStringConvert" FontSize="16" Foreground="White"/>
                                    <TextBlock  Margin="5,0"  Text="Binding TestingText" Foreground="White" FontSize="16" AutomationProperties.Name="TestText"/>
                                </StackPanel>
                                <!--
                                Value="Binding RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type UserControl, Path=DataContext.Health, UpdateSourceTrigger=PropertyChanged"
                                -->
                                <ProgressBar Maximum="100" Minimum="0"
                                      VerticalAlignment="Bottom" Height="25" Margin="5" />

                                <TextBox Text="Binding RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type UserControl, Path=DataContext.TestingText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged" FontSize="16" Height="30" Margin="5" Width="100"/>
                            </StackPanel>
                          
                        </Border>
                    </Grid>
                   
                </Grid>
            </Border>

            <TextBlock HorizontalAlignment="Center" Grid.Column="1" VerticalAlignment="Center" Text="Mod Status" 
                       FontSize="30" Foreground="Black"/>
        </Grid>
    </Grid>
</UserControl>

代码背后

  public partial class ModStatusManagerView : UserControl
    
        public ModStatusManagerView()
        
            InitializeComponent();
            DataContext = new ModStatusManagerViewModel();
        

       
    

视图模型

 public class ModStatusManagerViewModel : ViewModelBase
    

        #region Variables
        private readonly BackgroundWorker worker = new BackgroundWorker();

        private delegate void UpdateUIDelgate(string health, string Status);
        #endregion

        #region Commands
         public ICommand ProgressCommand  get; private set; 
        private void Testing(string health, string Status)
        
            try
            
                
            
            catch(Exception ex)
            
                System.Windows.MessageBox.Show(ex.Message);
            

            
        

        private bool CanProgressExecute()
        
            return true;
        
        private void Progress()
        
            try
            


                System.Windows.Application.Current.Dispatcher.Invoke(() => 
                

                    Health = 0;
                    StartStop = "Stop";
                    TestingText = "Initialized";

                    for (int i = 0; i < 99; i++)
                    
                        Health = i;
                        System.Windows.Application.Current.MainWindow.UpdateLayout();
                        System.Threading.Thread.Sleep(100);
                    

                    TestingText = "Completed";
                    System.Windows.MessageBox.Show("Complete");

                , System.Windows.Threading.DispatcherPriority.Render);

                /*if (!System.Windows.Application.Current.Dispatcher.CheckAccess())
                
                    System.Windows.Application.Current.Dispatcher.BeginInvoke(
                       new UpdateUIDelgate(Testing), "Stop", "Initialized");
                    return;
                */


               // System.Windows.Application.Current.MainWindow.Dispatcher.BeginInvoke(
                   // new UpdateUIDelgate(Testing), "Stop", "Initialized");
                
            
            catch(Exception ex)
            
                System.Windows.MessageBox.Show(ex.Message);
                   
        


        public ICommand ProgressOffCommand  get; 
        private void ProgressOff()
        
            System.Windows.Application.Current.Dispatcher.Invoke(() =>
            
                StartStop = "Start";
            );
        
        #endregion

        #region Constructor
        public ModStatusManagerViewModel()
        
            this.ProgressCommand = new RelayCommand(Progress);
            //this.ProgressOffCommand = new RelayCommand(ProgressOff);
        

        #endregion

        #region Properties
        public bool IsEnabled
        
            get  return _isEnabled; 
            set  SetProperty(ref _isEnabled, value); 
        
        private bool _isEnabled = true;
        /// <summary>
        /// Gets or Sets the Max Health
        /// </summary>
        public string StartStop
        
            get  return _startStop; 
            set  SetProperty(ref _startStop, value); 
        
        private string _startStop = "Start";



        /// <summary>
        /// Gets or Sets the Max Health
        /// </summary>
        public bool IsOn
        
            get  return _isOn; 
            set  SetProperty(ref _isOn, value); 
        
        private bool _isOn = false;

        /// <summary>
        /// Gets or Sets the Max Health
        /// </summary>
        public double MaxHealth
        
            get  return _maxHealth; 
            set  SetProperty(ref _maxHealth, value); 
        
        private double _maxHealth = 100;

        /// <summary>
        /// Gets or Sets the Min Health
        /// </summary>
        public double MinHealth
        
            get  return _minHealth; 
            set  SetProperty(ref _minHealth, value); 
        
        private double _minHealth = 0;

        /// <summary>
        /// Gets or Sets the Min Health
        /// </summary>
        public double Health
        
            get  return _Health; 
            set  SetProperty(ref _Health, value); 
        
        private double _Health = 0;

        public string TestingText
        
            get  return _testingText; 
            set  SetProperty(ref _testingText, value); 
        
        private string _testingText = "Waiting";
        #endregion

        #region Events

        #endregion

        #region Methods
        private void worker_DoWork(object sender, DoWorkEventArgs e)
        
            while (true)
            
                while (IsOn)
                
                    Random rnd = new Random();

                    Health = rnd.Next(0, 100);

                    System.Threading.Thread.Sleep(150);
                
                System.Threading.Thread.Sleep(150);
            

        

        private void worker_RunWorkerCompleted(object sender,
                                                   RunWorkerCompletedEventArgs e)
        
            //update ui once worker complete his work
        
        #endregion
    

如果有人想查看INotifyPropertyChanged的实现

 public abstract class ViewModelBase : ModelBase
    
        public ViewModelBase()
        

        
        ~ViewModelBase()
        

        

        public bool HasAnyErrors  get; set; 
    

这是ViewModelBase 实现的ModelBase

 public abstract class ModelBase : BindableBase, INotifyPropertyChanged
    
        protected ModelBase()
        

        
        ~ModelBase()  

        public bool HasIssues  get; set; 

        public event PropertyChangedEventHandler PropertyChanged;

        public static event PropertyChangedEventHandler StaticPropertyChanged = delegate  ;

        public void OnPropertyChanged([CallerMemberName] string propertyName = null)
        
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        

        public static void OnStaticPropertyChanged(string propertyName)
        
            StaticPropertyChanged?.Invoke(
                typeof(ViewModelBase),
                new PropertyChangedEventArgs(propertyName));
        
    

这里是RelayCommand

 public class RelayCommand : ICommand
    

        #region Private Members
        private Action _action;
        private Action<object> _actionOb;

        Action<object> _execteMethod;
        Func<object, bool> _canexecuteMethod;
        #endregion

        #region Public Events
        /// <summary>
        /// Basic Command
        /// </summary>
        public event EventHandler CanExecuteChanged = (sender, e) =>  ;
        #endregion

        #region Constructors
        /// <summary>
        /// Default Constructor
        /// </summary>
        /// <param name="action"></param>
        public RelayCommand(Action action)
        
            _action = action;
        

        /*public RelayCommand(System.Collections.ObjectModel.ObservableCollection<Frames.Model.Category> category, Action<object> action)
        
            _actionOb = action;
        */


        /// <summary>
        /// Default Constructor that passes an object
        /// </summary>
        /// <param name="execteMethod"></param>
        /// <param name="canexecuteMethod"></param>
        public RelayCommand(Action<object> execteMethod, Func<object, bool> canexecuteMethod)
        
            _execteMethod = execteMethod;
            _canexecuteMethod = canexecuteMethod;
        

        /// <summary>
        /// Default Constructor that determines if an action can execute before executing
        /// </summary>
        /// <param name="action"></param>
        /// <param name="CanExecute"></param>
        public RelayCommand(Action action, bool CanExecute)
        
            if (CanExecute)
                return;
            _action = action;
        

        public RelayCommand(Action<object> action, bool CanExecuteOb)
        
            _actionOb = action;
        
        #endregion

        #region Command Methods
        /// <summary>
        /// Returns True if bool Parameter is not busy
        /// </summary>
        /// <param name="parameter"></param>
        /// <returns></returns>
        public bool CanExecute(object parameter)
        
            if (parameter != null)
            
                try
                
                    if ((bool)parameter)
                        return false;
                    return true;
                
                catch
                
                    return true;
                
            
            else
            
                return true;
            


        

        public bool CanExecuteOb(object parameter)
        
            return true;
        

        /// <summary>
        /// Executes an Action that is not busy
        /// </summary>
        /// <param name="parameter"></param>
        public void Execute(object parameter)
        
            Task.Run(async () =>
            
                _action();
            );
        
        /// <summary>
        /// Executes an Action that is not busy with a <href="Param">
        /// </summary>
        /// <param name="param"></param>
        public void ExecuteOb(object param)
        
            Task.Run(async () =>
            
                _actionOb(param);
            );
        
        #endregion

    

【问题讨论】:

请注意Binding RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type UserControl, Path=DataContext.TestingText 等同于Binding TestingText。但是,我们通常错误的是,UserControl 将自己的 DataContext 设置为应用程序不知道的私有视图模型对象。 "很多不同的人" - 仍然是完全错误的。这么多人可能是我们每天看到提问者犯这个错误的问题的原因。当 UserControl 有一个您不知道的私有视图模型时,您希望应用程序的其余部分如何与它进行交互? 你真的在视图模型中有System.Windows.Application.Current.MainWindow.UpdateLayout()吗?好吧,这太可怕了。视图模型必须不了解特定视图元素,即使没有 MainWindow。 在您的 ModStatusManagerViewModel 实现中,您可以使用 SetProperty 方法更改属性。但是 ViewModelBase 和 ModelBase 的实现没有这个方法。如果它是在 BindableBase 中实现的,那么它将无法从那里引发 ModelBase.PropertyChanged 事件。 OnPropertyChanged 方法不会在您的代码中的任何位置调用。您显示的实现与您的代码不匹配,或者您的实现不正确。 A UserControl 要么公开可绑定属性(就像框架中的任何其他控件一样),而且根本不了解视图模型。或者,它会在其 XAML 中针对其 DataContext 中对象的属性进行绑定。 DataContext 的值将从控件的父元素继承,例如使用 DataTemplate 的 ContentControl,该 DataTemplate 包含控件的声明,并且其 Content 属性分配了视图模型对象的实例。 【参考方案1】:

更新:SetProperty 的功能似乎已停止工作。

我更改了我的属性

 public string TestingText
        
            get  return _testingText; 
            set  SetProperty(ref _testingText, value);
        
        private string _testingText = "Testing";

 public string TestingText
        
            get  return _testingText; 
            set
            
                if (_testingText == value)
                    return;
                _testingText = value;
                OnPropertyChanged("TestingText");
            
        
        private string _testingText = "Testing";

现在一切正常。

【讨论】:

SetProperty 正在按预期工作,但它会引发 BindableBase.PropertyChanged,您会用 ModelBase.PropertyChanged 覆盖它...Visual Studio 甚至会就此发出警告。 直到上周,这才成为一个问题。这是第一次,即使我目前的设置已经停止工作,这就是我看到很多人在这个平台上展示如何创建这个基础的方式。

以上是关于WPF 数据绑定在初始化后不起作用。没有绑定错误的主要内容,如果未能解决你的问题,请参考以下文章

WPF - 嵌套列表框绑定不起作用

WPF双向绑定不起作用

为啥 WPF 绑定在绑定到元素本身的标签时不起作用

C# WPF - Listview 绑定不起作用

DataGrid 数据绑定/更新中的 WPF 组合框不起作用

按钮上的绑定命令不起作用wpf mvvm