简单数据绑定无法正常工作

3

我是WPF的新手,正在尝试制作一个简单的应用程序,一个秒表。如果不进行数据绑定,它可以正常工作。以下是我的XAML代码:

<Window x:Class="StopWatch.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:s="clr-namespace:StopWatch"
    Title="MainWindow" Height="318" Width="233">
<Window.Resources>
    <s:StopWatchViewModel x:Key="swViewModel" x:Name="swViewModel"></s:StopWatchViewModel>
</Window.Resources>
<Grid DataContext="{StaticResource swViewModel}"> 
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="auto"/>
        <RowDefinition Height="128*" />
    </Grid.RowDefinitions>
    <TextBlock Grid.Row="1" Height="49" HorizontalAlignment="Left" Margin="42,50,0,0" Name="txtTime" Text="{Binding Path=Message}" VerticalAlignment="Top" Width="147" FontSize="20" TextAlignment="Center" />
    <Button Content="Start" Grid.Row="2" Height="23" HorizontalAlignment="Left" Margin="12,15,0,0" Name="startBtn" VerticalAlignment="Top" Width="58" Click="startBtn_Click" />
    <Button Content="Stop" Grid.Row="2" Height="23" HorizontalAlignment="Left" Margin="76,15,0,0" Name="stopBtn" VerticalAlignment="Top" Width="58" Click="stopBtn_Click" />
    <Button Content="Reset" Grid.Row="2" Height="23" HorizontalAlignment="Left" Margin="140,15,0,0" Name="resetBtn" VerticalAlignment="Top" Width="59"/>
</Grid>

这里是MainWindow中的代码

public partial class MainWindow : Window
{
    private StopWatchViewModel stopwatch;

    public MainWindow()
    {
        InitializeComponent();
        stopwatch = new StopWatchViewModel();
    }

    private void startBtn_Click(object sender, RoutedEventArgs e)
    {
        stopwatch.Start();
    }

    private void stopBtn_Click(object sender, RoutedEventArgs e)
    {
        stopwatch.Stop();
    }
}

以下是StopWatchViewModel.cs中的代码:

class StopWatchViewModel : INotifyPropertyChanged
{
    private DispatcherTimer timer;
    private Stopwatch stopwatch;
    private string message;

    public event PropertyChangedEventHandler PropertyChanged;

    public string Message
    {
        get
        {
            return message;
        }
        set
        {
            if (message != value)
            {
                message = value;
                OnPropertyChanged("Message");
            }
        }
    }

    public StopWatchViewModel()
    {
        timer = new DispatcherTimer();
        stopwatch = new Stopwatch();
        timer.Tick += new EventHandler(timer_Tick);
        timer.Start();
        stopwatch.Reset();
    }

    public void Start()
    {
        stopwatch.Start();
    }

    public void Stop()
    {
        stopwatch.Stop();
    }

    private void timer_Tick(object sender, EventArgs e)
    {
        Message = stopwatch.Elapsed.ToString(); // Doesn't work.
        // Message = "hello"; does not work too!
    }

    private void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

}

我不知道我哪里出了错。

编辑: 我已经让它工作了。下面是可以供任何人参考的工作代码。

XAML,将原始代码更改为以下内容

<Window x:Class="StopWatch.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:StopWatch"
Title="MainWindow" Height="318" Width="233">
<Grid> // partial code

在代码后面,基于Erno的建议更改构造函数。

public MainWindow()
    {
        InitializeComponent();
        viewModel = new StopWatchViewModel();
        this.DataContext = viewModel;
    }

谢谢大家!

3个回答

4
你的问题在于你没有任何机制让WPF知道你的属性已经更新。基本上,你有两个选择:
  1. 将Message变成一个依赖属性。
  2. 实现INotifyPropertyChanged,这样当消息被更新时,它会让GUI知道。
为了确保你拥有所有使INotifyPropertyChanged工作的部分,请检查是否完成以下步骤:
  1. 定义PropertyChanged事件。
  2. 创建一个私有的NotifyPropertyChanged方法来引发事件。该方法应该带有一个字符串参数(属性名称),并像这样引发事件:PropertyChanged(this, new PropertyChangedEventArgs(<nameofproperty>)。之所以要创建这个方法,是为了将空值检查和调用细节放在一个地方。
  3. 在属性设置器中,在值更改后使用正确的名称(区分大小写)调用NotifyPropertyChanged方法。

我已经编辑了我的代码。它仍然无法工作。你能再次检查一下吗? - Shulhi Sapli
如果还是不行,我认为我需要看到你重新实现INotifyPropertyChanged的更新代码,因为那个实现肯定有错误。请将该代码作为编辑添加到您上面的帖子中,我们会解决它 :) - Øyvind Bråthen
还有一件事。您是否尝试在tick方法内设置断点,以查看程序是否实际调用Tick并修改了消息?(应该会调用,但如果该语句从未被调用,那可能就可以解释您的问题) - Øyvind Bråthen
我已经让它工作了,但它只会在每个时刻更新一次。它停留在00:00:00。好吧,至少有所改进。 - Shulhi Sapli
你尝试设置DispatcherTimer的时间间隔了吗?虽然我不太熟悉它,但是因为你没有给出任何时间间隔,所以也许它只运行一次(将时间设置为00:00:00),然后不再运行。尝试将时间间隔设置为new TimeSpan(0,0,1)。然后它应该至少每秒更新一次。对于秒表来说可能更新频率不够快,但至少你会知道它是否奏效。 - Øyvind Bråthen
请看Emo在这里的答案。问题是(在您上面的代码重构之后),开始和停止按钮与您在代码中创建的ViewModel实例相关联,但TextBlock绑定到您从XAML创建的实例,这就是它始终显示00:00:00的原因,因为您没有启动它的方式。 - Øyvind Bråthen

3

只需像这样替换您的 Message 属性,它就会正常工作:

 public string Message
 {
     get { return (string)GetValue(MessageProperty); }
     set { SetValue(MessageProperty, value); }
 }

 public static readonly DependencyProperty MessageProperty =
     DependencyProperty.Register("Message", typeof(string), 
     typeof(MainWindow), new UIPropertyMetadata(String.Empty));

编辑:在发布我的解决方案后,我注意到你将所有的代码都改成了ViewModel的解决方案......如果愿意,请忽略或返回到你的第一组代码。

在你的新代码中,你创建了两个ViewModel实例,一个在代码中,一个在资源中。这不好,因为你正在操作代码中的一个,并绑定到资源(xaml)中的另一个。

编辑:将你的构造函数更改为以下内容:

public MainWindow()
{
    InitializeComponent();
    stopwatch = new StopWatchViewModel();
    this.DataContext = stopwatch;
}

这是全部内容。


谢谢您的建议。我也会尝试这样做。我已经成功绑定了,但现在它只更新一次属性。 - Shulhi Sapli
这正是问题所在:您的按钮启动/停止私有秒表,但绑定却链接到资源-视图模型。 您应该移除资源,而是提供一个公共属性“ViewModel”,以公开您的视图模型,当然您还需要绑定到此属性。您可以给窗口添加一个属性x:Name="wnd",然后将Grid的DataContext绑定更改为类似以下内容: <Grid DataContext="{Binding ViewModel, ElementName=wnd}"> - Simon D.
是的,那就是问题所在。Emo说得好。 - Øyvind Bråthen
我认为我理解了问题。但我不太确定如何实现你所说的。目前,我已经在XAML中删除了ViewModel的实例,但我不知道如何公开ViewModel。我将ViewModel字段设置为public,但即使它能工作,也不会是一个好的设计。 - Shulhi Sapli
只需创建 ViewModel 的一个实例(就像您所做的那样),并将其分配给窗口的 DataContext。不需要属性。如果您无法弄清楚,请告诉我。 - Emond
抱歉,我仍然无法弄清楚。我的做法是,在XAML中删除资源,在代码后台中,我有wnd.DataContext = this;但现在它不显示任何内容。在此之前,至少我有00:00:00。如果可能的话,你能展示一下代码吗?非常感谢。 - Shulhi Sapli

1

如果您没有正确实现INotifyPropertyChanged,则不会注意到属性的更改。因此,您应该首先这样做。也许您之前做的时候错过了什么。


嗨,我已经实现了INotifyPropertyChanged。我也编辑了我的帖子。 - Shulhi Sapli

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接