【发布时间】: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 属性分配了视图模型对象的实例。