WPF XAML绑定不更新

9

我有一个WPF项目,在其中有4个元素。我希望通过来改变每个元素的

目前我的XAML代码如下:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <TextBlock x:Name="First" Text="{Binding FirstString}" Grid.Row="0"/>
    <TextBlock x:Name="Second" Text="{Binding SecondString}" Grid.Row="1"/>
    <TextBlock x:Name="Third" Text="{Binding ThirdString}" Grid.Row="2"/>
    <TextBlock x:Name="Fourth" Text="{Binding FourthString}" Grid.Row="3"/>
</Grid>

我的代码中有如下内容:

public partial class MainWindow : Window
{
    public string FirstString { get; set; }
    public string SecondString { get; set; }
    public string ThirdString { get; set; }
    public string FourthString { get; set; }

    public MainWindow()
    {
        InitializeComponent();

        FirstString = "First";
        SecondString = "Second";
        ThirdString= "Third";
        FourthString= "Fourth";
    }
}

但是这个“Binding”根本不起作用。我做错了什么吗?
编辑: 在遵循Chris Mantle在评论中的建议查看调试输出后(我必须启用绑定的警告),我得到以下错误:
System.Windows.Data信息:10:无法使用绑定检索值,也没有有效的回退值存在;改为使用默认值。BindingExpression:Path = FirstString; DataItem = null; 目标元素为'TextBlock'(Name ='First'); 目标属性为'Text'(类型为'String')

1
输出窗口里有什么吗? - Chris Mantle
什么也没有。我找不到原因。 - oimitro
3
请在“工具”菜单下的“选项”中打开“调试”选项卡,找到“输出窗口”和“WPF跟踪设置”,将“数据绑定”设置为“详细”或“All”,然后查看输出窗口中是否有更多详细信息。 - Chris Mantle
窗口的DataContext是谁?应该是this。 - JSJ
5个回答

22

有一些不正确的地方。 Binding 标记将查看控件中的 DataContext 属性中的对象。除非另有规定,否则此属性从声明父级继承 DataContext。开箱即用,对于 Window 控件,这是 null

解决此问题有两个选项。您可以在代码后台或 XAML 中显式设置 DataContext

// In XAML
<Window DataContext={Binding RelativeSource={RelativeSource Self}}>

or

// In the code-behind
DataContext = this;

另一个问题是绑定应用于初始化过程中。最初,您的属性为空。在 InitializeComponent 阶段之后,控件将“绑定”到(为空的)属性。当您在之后设置属性时,控件无法知道它已更改。有两种机制可以允许此操作。在控件级别上,您可以将这些属性设置为DependencyProperty 或实现 INotifyPropertyChanged 接口并引发更改。如果您想采用 INPC 路线,您可以按照以下方式实现您的属性和窗口:

public partial class MainWindow : INotifyPropertyChanged
{
    private string firstString;
    private string secondString;
    private string thirdString;
    private string fourthString;

    public string FirstString
    {
        get { return firstString; }
        set
        {
            firstString = value;
            RaisePropertyChanged("FirstString");
        }
    }

    public string SecondString
    {
        get { return secondString; }
        set
        {
            secondString = value;
            RaisePropertyChanged("SecondString");
        }
    }

    public string ThirdString
    {
        get { return thirdString; }
        set
        {
            thirdString = value;
            RaisePropertyChanged("ThirdString");
        }
    }

    public string FourthString
    {
        get { return fourthString; }
        set
        {
            fourthString = value;
            RaisePropertyChanged("FourthString");
        }
    }

    public MainWindow()
    {
        DataContext = this;
        InitializeComponent();

        FirstString = "First";
        SecondString = "Second";
        ThirdString = "Third";
        FourthString = "Fourth";
    }

    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    private void RaisePropertyChanged(string propertyName)
    {
        var handlers = PropertyChanged;

        handlers(this, new PropertyChangedEventArgs(propertyName));
    }
}

不需要这样做,因为代码在XAML后面,当上下文改变时它会自动通知。 - A.T.
4
@Arun,我不理解你的逻辑。如果属性既不是实现了INotifyPropertyChanged接口的属性(INPC),也不是依赖属性(Dependency Properties),那么对该属性的更改将不会反映在UI上。在这种简单情况下,设置属性后设置 DataContext = this可以正常工作,因为DataContext是一个依赖属性。 但是,任何代码中的进一步更改都不会反映出来。 - Simon Belanger
@oimitro 这只是一个非常牵强的例子,可以工作。但要注意,它远非最佳实践。通常您会有另一个对象(我们称之为视图模型),该对象实现接口,并将该对象分配给您的视图(在此情况下为窗口)的 DataContext。如果您想在 WPF 上下文中阅读有关 MVVM 模式的更多信息,可以查看 此 CodeProject 条目 - Simon Belanger

4
除非另有指定,否则绑定路径相对于元素的DataContext。在你的情况下,我怀疑你根本没有指定DataContext...

由于属性是在MainWindow类中声明的,最简单的修复方法是添加:

DataContext = this;

在构造函数的末尾。

我使用了 DataContext = this; 但仍然没有任何作用。 - oimitro
将InitializeComponent();移动到构造函数的底部,然后你至少应该看到一些东西。尽管如此,Nicolas是正确的。 - Golvellius
将您的写作 DataContext = this; 移动到底部。 - A.T.

3

由于您没有通知 FirstString, SecondString, ThirdStringFourthString 已更改,因此这些更改将不会反映在UI中。您可以实现 INotifyPropertyChanged 或处理 DependencyProperty

同时设置您的 Datacontext

在我看来,DependencyProperty 更适合此用途。以下是一个示例:

public partial class MainWindow : Window
{
    #region Public
    public string FirstString
    {
        get { return (string)GetValue(FirstStringProperty); }
        set { SetValue(FirstStringProperty, value); }
    }
    public string SecondString
    {
        get { return (string)GetValue(SecondStringProperty); }
        set { SetValue(SecondStringProperty, value); }
    }
    public string ThirdString
    {
        get { return (string)GetValue(ThirdStringProperty); }
        set { SetValue(ThirdStringProperty, value); }
    }
    public string FourthString
    {
        get { return (string)GetValue(FourthStringProperty); }
        set { SetValue(FourthStringProperty, value); }
    }

    #region Dependency Properties
    public static readonly DependencyProperty FirstStringProperty = DependencyProperty.Register("FirstString", typeof(string), typeof(MainWindow), new PropertyMetadata("default value"));
    public static readonly DependencyProperty SecondStringProperty = DependencyProperty.Register("SecondString", typeof(string), typeof(MainWindow), new PropertyMetadata("default value"));
    public static readonly DependencyProperty ThirdStringProperty = DependencyProperty.Register("ThirdString", typeof(string), typeof(MainWindow), new PropertyMetadata("default value"));        
    public static readonly DependencyProperty FourthStringProperty = DependencyProperty.Register("FourthString", typeof(string), typeof(MainWindow), new PropertyMetadata("default value"));
    #endregion
    #endregion

    public MainWindow()
    {
        InitializeComponent();    

        FirstString = "First";
        SecondString = "Second";
        ThirdString= "Third";
        FourthString= "Fourth";

        this.DataContext = this;
    }
}

你的构造函数示例中,ThirdString= "Third 处缺少第二个引号。 - Mafii

3
我建议你创建另一个名为 MainWindowViewModel 的类。
public class MainWindowViewModel
{
    public string FirstString { get; set; }
    public string SecondString { get; set; }
    public string ThirdString { get; set; }
    public string FourthString { get; set; }

    public MainWindowViewModel()
    {    
        FirstString = "First";
        SecondString = "Second";
        ThirdString= "Third";
        FourthString= "Fourth";
    }
}

在调用窗口类的show方法之前,先设置MainWindow对象的DataContext。
MainWindow wnd = new MainWindow();
wnd.DataContext = new MainWindowViewModel();
wnd.Show();

您可以通过从App.xaml中删除StartupUri="MainWindow.xaml",并通过在App.xaml.cs中重写OnStartup来手动创建和显示主窗口来完成此最后一步。


@Sinon Belanger,MainWindowViewModel 类上没有 InitializeComponent() 方法。 - Zenchovey
哦,抱歉。我没意识到它是一个视图模型。我会删除我的评论。 - Simon Belanger

3

要使其工作,应该是这样的:

<Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="*"/>
                <RowDefinition Height="*"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <TextBlock x:Name="First" Text="{Binding FirstString}" Grid.Row="0"/>
            <TextBlock x:Name="Second" Text="{Binding SecondString}" Grid.Row="1"/>
            <TextBlock x:Name="Third" Text="{Binding ThirdString}" Grid.Row="2"/>
            <TextBlock x:Name="Fourth" Text="{Binding FourthString}" Grid.Row="3"/>
        </Grid>

而C#代码将如下所示:

public string FirstString { get; set; }
public string SecondString { get; set; }
public string ThirdString { get; set; }
public string FourthString { get; set; }

public MainWindow()
{
    InitializeComponent();    

    FirstString = "First";
    SecondString = "Second";
    ThirdString = "Third";
    FourthString= "Fourth";
    this.DataContext = this;  //here you set the context to current instance of window

}

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