在View完成加载之前调用Binded Properties的Setter [英] Binded Propertys' Setter called before View finished Loading

查看:57
本文介绍了在View完成加载之前调用Binded Properties的Setter的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm currently developing in WPF (Surface 2.0) and using the MVVM pattern for most parts of my application. I am, unfortunately, currently facing a rather complicated issue I hope you guys can assist me on:

我有一个View和一个ViewModel属于它.视图包含与ViewModel中的属性的双向绑定:

I have a View and a ViewModel that belongs to it. The View contains a two-way binding to a property in the ViewModel:

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

(...)

<local:SomeOtherView />

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

While the View is first loaded, the setter of SelectedPivotItemIndex is called. This is fine, except that the setter is called before the rest of the view loaded. Since the setter sends messages (via MVVMLight's Messenger) to other viewmodels that are created later in the view, this is a problem - those messages never reach their destination since no receiver is registered for them so far.

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 .不过,我真的不是很喜欢:

The only solution I can think of right now, would be to create a LoadedEventHandler in the ViewModel and call the SelectedPivotItemIndex setter again. I don't really like that, though:

  • 设置器再次运行一次(这将创建一个很大的集合,该集合将传递给消息).不知道这是否真的会影响性能,但似乎仍然没有必要.
  • 第二,对我来说,这似乎有点骇人听闻和容易出错,因为每个属性都必须在已加载的事件中手动初始化.

有没有比手动调用设置器更好的解决方案?

Is there any solution to this problem better than just manually calling the setter?

推荐答案

我首先没有关于viewmodel的教程,但是我敢肯定那里有很多例子.首先没有viewmodel,然后首先有了viewmodel实例,然后让wpf(通过datatemplate)创建视图.

i dont have a tutorial for viewmodel first, but i'm sure the are a lot examples out there. viewmodel first is nothing more then you have the viewmodel instance first and then let wpf create the view(via datatemplate).

让我们说您的主视图应显示带有PivotBarEntries的视图.因此,您现在要做的是在主视图模型中创建一个数据透视图模型(DI,MEF,new()等等).您的mainview模型将ivotvw作为属性公开,并将其绑定到mainview中的ContentPresenter.Content.至少您必须为ivotvw DataType创建一个DataTemplate.

let say your mainview should show a view with your PivotBarEntries. so what you do now is to create a pivotbarviewmodel in your mainviewmodel (DI, MEF, new() what ever). your mainviewmodel expose the pivotvw as a property and bind it to a ContentPresenter.Content in your mainview. at least you have to create a DataTemplate for your pivotvw DataType.

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

那是关于viewmodel的第一件事,因为您的vm是首先创建的,所以您不再依赖于view上的加载事件.

thats about viewmodel first, you do not rely on load events on view anymore, because your vm is created first.

当然,对于您的特定问题,您只需确保应创建所有用于监听Messenger的组件(VM)

of course for your specific problem you just have to be sure that all your components(VM's) which listen to your messenger should be created

您的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结合使用来创建对象路径.

very simple example for viewmodel first. ps: i use DI with MEF to create my object path.

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

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

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

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

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

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

OtherChildViewmodel

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

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

PivotView

PivotView

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

OtherView

OtherView

<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

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>

这篇关于在View完成加载之前调用Binded Properties的Setter的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆