在View完成加载之前调用Binded Properties'Setter

时间:2012-06-05 22:30:54

标签: c# wpf mvvm properties initialization

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

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

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

(...)

<local:SomeOtherView />

首次加载View时,会调用SelectedPivotItemIndex的setter。这很好,除了在加载视图的其余部分之前调用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再次运行(这会创建一个传递给消息的相当大的集合)。不知道它是否真的会影响性能,但似乎仍然没必要。
  • 其次,它似乎有点hackish和容易出错,因为每个属性都必须在加载的事件中手动初始化。

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

1 个答案:

答案 0 :(得分:2)

我首先没有viewmodel的教程,但我确信那里有很多例子。首先是viewmodel,然后先让viewmodel实例,然后让wpf创建视图(通过datatemplate)。

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

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

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

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

你的xaml

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

而不是先查看

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

(...)

<local:SomeOtherView />

编辑:首先是一个非常简单的viewmodel示例。 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();
    }
}

PivotViewmodel

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"};
    }
}

OtherViewmodel

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

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

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>

OtherChildView

<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>