WPF 数据绑定在初始化后不起作用。没有绑定错误
Posted
技术标签:
【中文标题】WPF 数据绑定在初始化后不起作用。没有绑定错误【英文标题】:WPF Data bindings not working after intiailize. No Binding Errors 【发布时间】:2021-08-27 11:19:10 【问题描述】:我的View
和ViewModel
之间的数据绑定存在问题。绑定仅在初始化时运行,然后它们不会更新。代码按预期运行,每个输出窗口都没有绑定错误,但 UI 上的绑定不会更新。
程序设置:
WPF C# 棱镜 V8.1.97 MVVM .NET Framework 4.7.2我尝试过的事情:
XAML 设置为使用INotifyPropertyChanged
直接绑定到属性
XAML 设置为查找类型为 UserControl
的 RelativeSource
RelayCommand
更新 UI,无论有无 Invoke
到主线程。
BackgroundWorker
更新 UI,无论有没有 Invoke
到主线程。
DelegateCommand
更新 UI,无论有没有 Invoke
到主线程。
i.Interaction.EventTriggers
和 Click
在主线程上调用 UI 更新。
所有这些都将运行代码,但不会更新 UI。我已经为BackgroundWorker
、Delegate void
和Dispatcher.BeginInvoke
留下了一些我在程序中尝试过的代码。我写了几个程序,从来没有遇到过这个问题。我创建程序时是否设置错误?
更新:Visual Studio 2019 似乎存在问题。这可能仅在我的版本上,因为我编写的其他程序不再工作。这通常可以按预期运行。
UserControl 我正在尝试进行简单的绑定。我在底部创建了一个Textbox
和Mode=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 数据绑定在初始化后不起作用。没有绑定错误的主要内容,如果未能解决你的问题,请参考以下文章