我有一个带有几个TextBox控件和ProgressBar的UserControl。 TextBox控件正确地反映了它们绑定到的代码隐藏中的属性。但是,ProgressBar不响应属性更改。
我的XAML:
<UserControl
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:Controls="clr-namespace:Cmc.Installer.Controls;assembly=Cmc.Installer.Controls" x:Class="Cmc.Installer.Modules.MobileRecruiter.MobileRecruiterModule"
mc:Ignorable="d"
d:DesignHeight="600" d:DesignWidth="800">
<Grid HorizontalAlignment="Left" Height="580" Margin="10,10,0,0" VerticalAlignment="Top" Width="780">
<Canvas>
<Label Content="Database Server" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
<TextBox Text="{Binding DatabaseServer}" Height="23" Canvas.Left="160" Canvas.Top="12" Width="160"/>
<Label Content="Database Name" HorizontalAlignment="Left" VerticalAlignment="Top" Canvas.Left="10" Canvas.Top="38"/>
<TextBox Text="{Binding DatabaseName}" Height="23" Canvas.Left="160" Canvas.Top="40" Width="160"/>
<Label Content="Database Username" HorizontalAlignment="Left" VerticalAlignment="Top" Canvas.Left="10" Canvas.Top="66"/>
<TextBox Text="{Binding DatabaseUsername}" Height="23" Canvas.Left="160" Canvas.Top="68" Width="160"/>
<Label Content="Database Password" HorizontalAlignment="Left" VerticalAlignment="Top" Canvas.Left="10" Canvas.Top="94"/>
<Controls:BindablePasswordBox Password="{Binding DatabasePassword}" Height="23" Canvas.Left="160" Canvas.Top="96" Width="160"/>
<ProgressBar Name="ProgressBar" Value="{Binding Progress}" Minimum="0" Maximum="100" Canvas.Left="10" Canvas.Top="164" Width="760" Height="24" />
</Canvas>
</Grid>
</UserControl>
它的代码隐藏(非常简略):
public partial class MobileRecruiterModule : UserControl, INotifyPropertyChanged
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private int _progress;
public MobileRecruiterModule()
{
InitializeComponent();
DataContext = this;
}
public string DatabaseServer { get; set; }
public string DatabaseName { get; set; }
public string DatabaseUsername { get; set; }
public string DatabasePassword { get; set; }
public int Progress
{
get { return _progress; }
set
{
if (value == _progress) return;
_progress = value;
OnPropertyChanged("Progress");
Logger.Trace("Progress.set() = " + _progress);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
// This is called by an external class
public void OnProgressChanged(object sender, ProgressChangedEventArgs args)
{
Progress = args.ProgressPercentage;
}
}
我知道Progress
的价值正在发生变化,因为我在NLog日志中看到了它:
2014-04-17 16:22:54.4068|TRACE|Cmc.Installer.Modules.MobileRecruiter.MobileRecruiterModule|Progress.set() = 28
我不明白为什么ProgressBar在记录调用之前在setter中触发OnPropertyChanged
时不会更新。
答案 0 :(得分:1)
我以MVVM模式复制了你的应用程序的缩小版本,并祝你好运。我使用此代码复制您的用户控件...
<UserControl x:Class="ProgressBarBinding.Login"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid HorizontalAlignment="Left" Height="580" Margin="10,10,0,0" VerticalAlignment="Top" Width="780">
<Canvas>
<Label Content="Database Server" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
<TextBox Text="{Binding DatabaseServer}" Height="23" Canvas.Left="160" Canvas.Top="12" Width="160"/>
<Label Content="Database Name" HorizontalAlignment="Left" VerticalAlignment="Top" Canvas.Left="10" Canvas.Top="38"/>
<TextBox Text="{Binding DatabaseName}" Height="23" Canvas.Left="160" Canvas.Top="40" Width="160"/>
<Label Content="Database Username" HorizontalAlignment="Left" VerticalAlignment="Top" Canvas.Left="10" Canvas.Top="66"/>
<TextBox Text="{Binding DatabaseUsername}" Height="23" Canvas.Left="160" Canvas.Top="68" Width="160"/>
<Label Content="Database Password" HorizontalAlignment="Left" VerticalAlignment="Top" Canvas.Left="10" Canvas.Top="94"/>
<ProgressBar Name="ProgressBar" Value="{Binding Progress}" Minimum="0" Maximum="100" Canvas.Left="10" Canvas.Top="164" Width="760" Height="24" />
</Canvas>
</Grid>
</UserControl>
唯一缺少的是您的专有密码控制,这不会影响解决方案。
我将此控件编码为MainWindow.xaml文件,因此......
<Window x:Class="ProgressBarBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ProgressBarBinding"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<vm:ViewModel x:Key="ViewModel"/>
</Window.Resources>
<Grid DataContext="{StaticResource ViewModel}">
<vm:Login/>
</Grid>
</Window>
请注意,窗口资源定义包括对视图模型实例的引用。大多数人使用依赖注入设置MVVM,但这种方法适用于快速试用和 Indicative Code
。视图模型被设置为网格的数据上下文。 您的控件从网格继承数据上下文。这是xaml代码的结尾。除了调用InitializeComponent(以及创建VM实例的那些)之外,MainWindow.xaml.cs文件中没有代码隐藏。
ViewModel类看起来像这样......
public class ViewModel : INotifyPropertyChanged
{
private readonly SynchronizationContext _synchronizationContext = SynchronizationContext.Current;
public ViewModel()
{
DatabaseServer = "AnyServer";
DatabaseName = "Any name";
Model m = new Model();
Task.Run(() => m.DoWork(this));
}
public string DatabaseServer { get; set; }
public string DatabaseName { get; set; }
public string DatabaseUsername { get; set; }
public string DatabasePassword { get; set; }
private int _progress;
public int Progress
{
get { return _progress; }
set
{
if (value == _progress) return;
_progress = value;
OnPropertyChanged("Progress");
Console.WriteLine(@"Progress.set() = " + _progress);
}
}
// This is called by an external class
public void OnProgressChanged(object sender, ProgressChangedEventArgs args)
{
_synchronizationContext.Send(delegate { Progress = args.ProgressPercentage; }, null);
}
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string name)
{
var handler = Interlocked.CompareExchange(ref PropertyChanged, null, null);
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
}
除了UI元素没有依赖关系外,视图模型中的大多数代码看起来都像你的。一切都是通过绑定完成的。我在回调中使用了SynchronizationContext,尽管在您的应用程序中可能没有必要。
VM的构造函数在TPL线程上启动模型。模型看起来像这样......
public class Model
{
public void DoWork(ViewModel vm)
{
int progressPercentage = 0;
for (int i = 0; i < 100000; i++)
{
vm.OnProgressChanged(this, new ProgressChangedEventArgs(progressPercentage, null));
if (i%1000 == 0)
{
++progressPercentage;
}
}
}
}
因此,将所有内容放在一起,模型在其自己的线程中运行,并且UI正在其自己的线程上进行更新。整个过程按预期工作。
ProgressBar将增加到100,并且在模型正在运行时UI将保持响应。这个答案并没有解释为什么你的原始代码不起作用,但我怀疑它与UI线程被淘汰有关。您可以通过完整的日志历史记录来证明这一点,但UI上没有任何变化。总的来说,这个答案正朝着其他人在评论中提出的方式发展:即MVVM绑定方法有很多可以提供。
答案 1 :(得分:0)
由于您在UserControl中,您需要明确地为其命名并在绑定时使用 ElementName 标记,如下所示:
<UserControl
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:Controls="clr-namespace:Cmc.Installer.Controls;assembly=Cmc.Installer.Controls" x:Class="Cmc.Installer.Modules.MobileRecruiter.MobileRecruiterModule"
mc:Ignorable="d"
d:DesignHeight="600" d:DesignWidth="800" x:Name="MyControl">
<Grid HorizontalAlignment="Left" Height="580" Margin="10,10,0,0" VerticalAlignment="Top" Width="780">
<Canvas>
<ProgressBar Name="ProgressBar" Value="{Binding Progress, ElementName=MyControl}" Minimum="0" Maximum="100" Canvas.Left="10" Canvas.Top="164" Width="760" Height="24" />
</Canvas>
</Grid>
</UserControl>
答案 2 :(得分:-1)
如果您拥有Binding
,为什么需要Actual control
。由于您未在MVVM
中执行此操作,只需立即致电ProgressBar
。
ProgressBar.Dispatcher.Invoke(() => ProgressBar.Value = Progress = args.ProgressPercentage);
很抱歉,如果您的Binding
课程中可以访问所有属性/控件,我就无法看到View
的好处。
如果您实施了MVVM
,那么绑定会更有用,更强大。