WPF window 未更新 XAML 中的绑定

WPF window not updating with binding in XAML

有人可以解释一下这是怎么回事吗?我是 WPF 的新手,正在通过绑定将我的 Forms 项目迁移到 WPF。我正在使用 AvalonDock 但我没有直接绑定到任何 AvalonDock 控件。这里有几个摘录。为了简洁起见,我删除了很多内容,但如果您需要查看其他内容,请告诉我。

编辑: 这两个 StackPanel 只是测试...试图解决这些问题。
EDIT2:我最终尝试做 MVVM;我只是需要更好地处理绑定,所以我知道如何构建它。
EDIT3:参见 post 的底部。

问:第一个StackPanel根本不更新,更不用说改了再更新。我试过在 StackPanelGridTextBlock 中设置 DataContext。我做错了什么?

问:当父网格在代码后面绑定时,第二个工作正常,但前提是绑定在您看到它的地方,而不是在 MainWindow_Loaded() 方法中。这里有什么不同?

我在这里阅读了几个教程以及许多类似的问题,但没有任何内容可以帮助我理解这里的区别以及我所缺少的内容。

<Window x:Class="TestUIWPF.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:ad="http://schemas.xceed.com/wpf/xaml/avalondock"
        Title="MainWindow" Height="768" Width="1024"
        Loaded="MainWindow_Loaded"
        xmlns:vm="clr-namespace:TestUIWPF.ViewModel"
        >
<!-- lots excluded for brevity. there are no Window.Resources -->
<ad:LayoutAnchorable Title="Test" >
    <Grid x:Name="gridTest">
        <StackPanel Orientation="Vertical">
            <StackPanel Orientation="Horizontal">
                <StackPanel.DataContext>
                    <vm:EntityViewModel />
                </StackPanel.DataContext>
                <TextBlock Text="Label" />
                <TextBlock DataContext="{Binding ActiveEntity}" Text="{Binding Path=Label}" />
            </StackPanel>

            <StackPanel Orientation="Horizontal" >
                <TextBlock Text="Label Again" />
                <TextBlock Text="{Binding Path=Label}" />
            </StackPanel>
        </StackPanel>
    </Grid>
</ad:LayoutAnchorable>
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    this.DataContext = this;
    SelectedEntityViewModel = new ViewModel.EntityViewModel();
    ImportEntityXML_Click(null, null); //skips clicking the menus
}
private void ImportEntityXML_Click(object sender, RoutedEventArgs e)
{
    //omitted OpenFileDialog and XmlReader stuff
    xmlreader = new XmlReader(dlg.FileName);
    Entities.Add(xmlreader.ReadEntityFromXML());
    SimulatedEntitySelection(Entities.ElementAt(0)); //haven't built any of the UI stuff for this yet
}

private void SimulatedEntitySelection(Entity ent)
{
    SelectedEntityViewModel.ActiveEntity = ent;
    gridTest.DataContext = SelectedEntityViewModel.ActiveEntity;
}

private void button_Click(object sender, RoutedEventArgs e) 
{
    SelectedEntityViewModel.ActiveEntity.Label = "test";
}

EntityEntityViewModel 实现了 INotifyPropertyChanged,它与第二个 StackPanel 一起工作得很好。调用 button_Click() 的按钮仅用于测试绑定。 EntityViewModel 几乎只是通过 ActiveEntity 属性包装 Entity 并帮助读取 Entity.

中的集合集合

编辑3:

我也尝试了一些资源。以下是我如何处理 ObjectDataProvider:

<Window.Resources>
    <ObjectDataProvider x:Key="testVM" ObjectType="{x:Type vm:EntityViewModel}" />
    <vm:EntityViewModel x:Key="SelEntVM" />
</Window.Resources>
<!-- .... -->
<StackPanel.DataContext>
    <Binding Source="{StaticResource testVM}" />
</StackPanel.DataContext>
<TextBlock Text="Label" />
<TextBlock Text="{Binding Path=ActiveEntity.Label}" />

确实有效。您可能正在更新错误的视图模型。

DataContext 中定义视图模型后,您必须以这种方式访问​​它:

private void button_Click(object sender, RoutedEventArgs e) 
{
    var myModel = (ViewModel.EntityViewModel)(yourStackPanelName.DataContext);
    myModel.ActiveEntity.Label = "test";
}

您的第一个堆栈面板不工作,因为数据上下文是继承的。因此,一旦您将 Grid 的 DataContext 更改为 ActiveEntity 对象,第一个数据上下文中文本块上的绑定会将 TextBlock 的数据上下文设置为当前数据上下文中的 ActiveEntity,这已经是 ActiveEntity(因此 ActiveEntity.ActiveEntity),而不是尝试绑定到标签 属性 上。例如。 ActiveEntity.ActiveEntity.Label

点击之前,您将 window 的 DataContext 设置为 "this",我假设这不是 ViewModel,它是隐藏的代码?

如果您使用的是 MVVM,

你应该有这样的东西

private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    SelectedEntityViewModel = new ViewModel.EntityViewModel();
    this.DataContext = SelectedEntityViewModel;
    ImportEntityXML_Click(null, null); //skips clicking the menus
}

或提供所有必要数据的其他一些 ViewModel。

你通常会有 MainWindowViewMainWindowViewModel,至少这是约定,通常你在构造函数中设置 window 的数据上下文一次(你可以做到在 Loaded 处理程序中),在大多数情况下,您不需要手动更改后台代码中任何框架元素的 DataContext。

编辑: 示例代码:

MainWindow.xaml

<Window x:Class="SO27760357.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="300" Width="300">
    <Grid>
        <StackPanel Orientation="Vertical">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="Label" />
                <TextBlock Text="{Binding ActiveEntity.Label}"/>
            </StackPanel>

            <StackPanel Orientation="Horizontal">
                <TextBlock Text="Label Again" />
                <TextBlock Text="{Binding ActiveEntity.Label}" />
            </StackPanel>
        </StackPanel>
    </Grid>
</Window>

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = new MainWindowViewModel();
    }
}

MainWindowViewModel.cs(为简单起见省略了 INotifyPropertChanged)

public class MainWindowViewModel
{
    public EntityViewModel ActiveEntity { get; set; }
}

EntityViewModel.cs(为简单起见省略了 INotifyPropertChanged)

public class EntityViewModel
{
    public string Label { get; set; }
}

如您所见,我将 Window 的 DataContext 设置为 MainViewModel,因此 DataContext(所有绑定的根)是 MainViewModel,每个 TextBlock 需要首先访问 ActiveEntity 属性 首先,在它到达 Label proeprty 之前。

另一个选项是,如果您给我们的主堆栈面板内的所有内容都将绑定到 ActiveEntity,您可以更改该 StackPanel 的 DataContext,将其绑定到 ActiveEntity,因此所有它的子数据上下文也将是该对象。

    <StackPanel Orientation="Vertical"
                **DataContext="{Binding ActiveEntity}"**>
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Label" />
            <TextBlock **Text="{Binding Label}"**/>
        </StackPanel>

        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Label Again" />
            <TextBlock **Text="{Binding Label}"** />
        </StackPanel>
    </StackPanel>

编辑 2 - 建议

您应尽可能避免按名称引用对象,并尽可能减少代码背后的逻辑(如果有的话)。对于大多数简单的屏幕,除了 DataContext 的初始绑定之外,不需要在代码中包含任何内容(如果您没有创建 + 设置 windows DataContext 的 window 服务)