【问题标题】:Binded Propertys' Setter called before View finished Loading在视图完成加载之前调用绑定属性的设置器
【发布时间】:2012-06-09 23:29:50
【问题描述】:

我目前正在 WPF(Surface 2.0)中进行开发,并在我的应用程序的大部分部分使用 MVVM 模式。不幸的是,我目前面临一个相当复杂的问题,希望你们能帮助我:

我有一个 View 和一个 ViewModel 属于它。 View 包含到 ViewModel 中属性的双向绑定:

<pb:PivotBar ItemsSource="{Binding PivotBarEntries}" 
SelectedItemIndex="{Binding SelectedPivotItemIndex, Mode=TwoWay}" />

(...)

<local:SomeOtherView />

第一次加载视图时,会调用 SelectedPivotItemIndex 的设置器。这很好,只是在加载视图的其余部分之前调用了 setter。由于 setter 将消息(通过 MVVMLight 的 Messenger)发送到稍后在视图中创建的其他视图模型,这是一个问题 - 这些消息永远不会到达它们的目的地,因为到目前为止还没有为它们注册接收者。

public int SelectedPivotItemIndex
{
    get
    {
        return this.selectedPivotItemIndex;
    }
    set
    {
        if (value != this.selectedPivotItemIndex)
        {
            this.selectedPivotItemIndex = value;
            this.ReportPropertyChanged("SelectedPivotItemIndex");

            (...)

            ChangeSomeOtherViewModelProperty msg = new ChangeSomeOtherViewModelProperty { Property = newValueCalculatedBefore };
            Messenger.Default.Send<ChangeSomeOtherViewModelProperty>(msg);
        }
    }
}

我现在能想到的唯一解决方案是在 ViewModel 中创建一个 LoadedEventHandler 并调用 SelectedPivotItemIndex 设置器再次。不过我不太喜欢这样:

  • setter 再次运行(这会创建一个相当大的集合并传递给消息)。不知道它是否真的会影响性能,但似乎仍然没有必要。
  • 其次,这对我来说似乎有点骇人听闻且容易出错,因为每个属性都必须在加载的事件中手动初始化。

这个问题有没有比手动调用setter更好的解决方案?

【问题讨论】:

  • 首先,视图中 SelectedPivotItemIndex 的更改是否反映在 ViewModel 中,即双向绑定是否正常工作?能否向我们展示一个用于在其他类中注册消息的代码示例?
  • 您好,感谢您的回复。是的,代码正在运行 - 调用了 setter(通过断点检查)。此外,消息注册非常简单:在构造函数中:Messenger.Default.Register&lt; MessageName&gt;(this, (msg) =&gt; ReceivedMessage(msg));。稍后在某处:private void ReceivedMessage(MessageName msg) { ... }。消息接收也正常 - 再次,当我在加载的事件中发送消息时它工作正常!
  • 您是否尝试过使用 DispatcherHelper 类来确保它不需要调用。查看here,并尝试 Messenger.Default.Register>( this, (action) => DispatcherHelper.CheckBeginInvokeOnUI( () => this.Item = action.NewValue ) );
  • 使用 viewmodel 第一种方法,你的问题就消失了 :)
  • 不幸的是,DispatcherHelper 似乎不起作用 - 因为注册代码是在消息发送代码之后执行的,所以这无济于事。我认为发送方需要做一些事情。至于视图模型第一种方法:你有这方面的教程吗?我不太确定如何实施。谢谢。

标签: c# wpf mvvm properties initialization


【解决方案1】:

我首先没有关于 viewmodel 的教程,但我确信那里有很多示例。 viewmodel first 仅此而已,然后您首先拥有 viewmodel 实例,然后让 wpf 创建视图(通过数据模板)。

假设您的主视图应该显示带有您的 PivotBarEntries 的视图。所以你现在要做的是在你的 mainviewmodel 中创建一个 pivotbarviewmodel(DI、MEF、new() 等等)。您的 mainviewmodel 将 pivotvw 公开为一个属性并将其绑定到您的 mainview 中的 ContentPresenter.Content 。至少您必须为您的 pivotvw DataType 创建一个 DataTemplate。

<DataTemplate DataType="{x:Type local:PivotViewModel>
 <view:MyPivotView/>
</DataTemplate>

首先是关于视图模型,您不再依赖视图上的加载事件,因为您的 vm 是首先创建的。

当然,对于您的具体问题,您只需确保应该创建所有听信使的组件(VM)

你的xml

<ContentPresenter Content="{Binding MyPivotDataVM}" />
<ContentPresenter Content="{Binding MySomeOtherStuffVM}" />

而不是先查看

<pb:PivotBar ItemsSource="{Binding PivotBarEntries}" 
SelectedItemIndex="{Binding SelectedPivotItemIndex, Mode=TwoWay}" />

(...)

<local:SomeOtherView />

编辑:非常简单的视图模型示例。 ps:我使用 DI 和 MEF 来创建我的对象路径。

app.xaml

<Application x:Class="WpfViewModelFirst.App"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:WpfViewModelFirst="clr-namespace:WpfViewModelFirst">
<!--StartUp Uri is removed-->
<Application.Resources>
    <!--comment these datatemplates and see what happens-->
    <DataTemplate DataType="{x:Type WpfViewModelFirst:PivotViewModel}">
        <WpfViewModelFirst:PivotView/>
    </DataTemplate>
    <DataTemplate DataType="{x:Type WpfViewModelFirst:OtherViewModel}">
        <WpfViewModelFirst:OtherView/>
    </DataTemplate>
    <DataTemplate DataType="{x:Type WpfViewModelFirst:OtherChildViewModel}">
        <WpfViewModelFirst:OtherChildView/>
    </DataTemplate>
</Application.Resources>
</Application>

app.xaml.cs

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        //to be fair, sometimes i create the ApplicationRoot(JUST MainWindow with view first, and just the rest with viewmodel first.)
        var mainvm = new MainViewModel();
        var mainview = new MainWindow {DataContext = mainvm};
        this.MainWindow = mainview;
        this.MainWindow.Show();
    }
}

mainview.xaml

<Window x:Class="WpfViewModelFirst.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="auto" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>        
    <TextBlock Text="{Binding MyProp}" Grid.ColumnSpan="2" Grid.Row="0"/>        
    <ContentPresenter Content="{Binding MyPivot}" Grid.Row="1" Grid.Column="0" />
    <ContentPresenter Content="{Binding MyOther}" Grid.Row="1" Grid.Column="1" />        
</Grid>
</Window>

mainviewmodel.cs

public class MainViewModel
{
    public string MyProp { get; set; }
    public PivotViewModel MyPivot { get; set; }
    public OtherViewModel MyOther { get; set; }

    public MainViewModel()
    {
        this.MyProp = "Main VM";
        this.MyPivot = new PivotViewModel();
        this.MyOther = new OtherViewModel();
    }
}

透视视图模型

public class PivotViewModel
{
    public string MyProp { get; set; }
    public ObservableCollection<string> MyList { get; set; }

    public PivotViewModel()//Dependency here with constructor injection
    {
        this.MyProp = "Test";
        this.MyList = new ObservableCollection<string>(){"Test1", "Test2"};
    }
}

其他视图模型

public class OtherViewModel
{
    public string MyProp { get; set; }
    public OtherChildViewModel MyChild { get; set; }

    public OtherViewModel()
    {
        this.MyProp = "Other Viewmodel here";
        this.MyChild = new OtherChildViewModel();
    }
}

其他子视图模型

public class OtherChildViewModel
{
    public string MyProp { get; set; }

    public OtherChildViewModel()//Dependency here with constructor injection
    {
        this.MyProp = "Other Child Viewmodel";
    }
}

透视图

<UserControl x:Class="WpfViewModelFirst.PivotView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="auto" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <TextBlock Text="{Binding MyProp}" Grid.Row="0"/>
    <ListBox ItemsSource="{Binding MyList}" Grid.Row="1"/>
</Grid>
</UserControl>

其他视图

<UserControl x:Class="WpfViewModelFirst.OtherView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition  />
        <RowDefinition  />
    </Grid.RowDefinitions>
    <TextBlock Text="{Binding MyProp}" Grid.Row="0" />
    <ContentPresenter Content="{Binding MyChild}" Grid.Row="1"/>
</Grid>
</UserControl>

其他子视图

<UserControl x:Class="WpfViewModelFirst.OtherChildView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
    <TextBlock Text="{Binding MyProp}" />
</Grid>
</UserControl>

【讨论】:

  • 感谢您的好评。一个后续问题:这不会违反 MVVM 吗?在这种情况下,mainviewmodel 创建pivotbarviewmodel,然后它又必须创建pivotbarview - 但在MVVM 中,viewmodel 不应该知道它的视图,afaik。我在这里监督什么吗?
  • 视图模型对视图一无所知,它也不在乎。 pivotviewmodel(pvm) 没有创建视图。 mainviewmodel 对 wpf 简单地说,使用我在 contentpresenter 中制作的绑定显示我的 pvm。现在wpf问“如何渲染你的viewmodel?然后你说:)看看资源我定义了一个数据模板如何渲染我的pvm。wpf说好的它完成了。我在contentpresenter中显示你的pivotview,并将datacontext绑定到pvm。你的bindings 现在可以工作了。我希望你看到 viewmodel 根本没有对 view 的引用。任何问题让我知道。我总是在我的项目中使用 vmfirst。
  • 您能否在设置透视视图和 SomeOtherView 的数据上下文的位置发布代码。然后我可以更新我的示例
  • 我的方法是在 XAML 文件中创建视图模型,所以基本上在每个视图 XAML 中我都会这样做(“ViewBase”是我的视图的基类):&lt;local:ViewBase.DataContext&gt; &lt;local:MyViewModel x:Name="Model" /&gt; &lt;/local:ViewBase.DataContext&gt;
  • 首先是经典视图。我将添加一个非常简单的视图模型第一个示例。顺便说一句,如果你先做 viewmmodel,那就别想你的意见了 :)
猜你喜欢
  • 1970-01-01
  • 2011-12-05
  • 1970-01-01
  • 1970-01-01
  • 2012-12-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多