使用WPF和MVVM模式将值从子窗口传递到父窗口

时间:2011-09-21 22:33:34

标签: c# wpf xaml mvvm parent-child

我有父窗口,其中包含名为“SchoolName”的textBox,以及一个名为“Lookup school Name”的按钮。

该按钮打开一个包含学校名称列表的子窗口。现在,当用户从子窗口中选择学校名称,并单击“使用所选学校”按钮。我需要在父视图的文本框中填充选定的学校。

注意:我已采用Sam和其他人的建议来使这段代码有效。我更新了我的代码,以便其他人可以简单地使用它。

SelectSchoolView.xaml(父窗口)

<Window x:Class="MyProject.UI.SelectSchoolView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Parent" Height="202" Width="547">
<Grid>
    <TextBox Height="23" Width="192" 
     Name="txtSchoolNames"  
     Text="{Binding Path=SchoolNames, UpdateSourceTrigger=PropertyChanged, 
     Mode=TwoWay}" 
     />

    <Label Content="School Codes" Height="28" HorizontalAlignment="Left" 
     Margin="30,38,0,0" Name="label1" VerticalAlignment="Top" />
    <Button Content="Lookup School Code" Height="30" HorizontalAlignment="Left" 
     Margin="321,36,0,0" Name="button1" VerticalAlignment="Top" Width="163" 
     Command="{Binding Path=DisplayLookupDialogCommand}"/>
</Grid>
</Window>

SchoolNameLookup.xaml(查找学校名称的子窗口)

<Window x:Class="MyProject.UI.SchoolNameLookup"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:toolkit="http://schemas.microsoft.com/wpf/2008/toolkit"
    Title="SchoolCodeLookup" Height="335" Width="426">

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="226*" />
        <RowDefinition Height="70*" />
    </Grid.RowDefinitions>

    <toolkit:DataGrid Grid.Row="0" Grid.Column="1"   x:Name="dgSchoolList" 
                          ItemsSource="{Binding Path=SchoolList}" 
                          SelectedItem="{Binding Path=SelectedSchoolItem, Mode=TwoWay}" 
                          Width="294"
                          AutoGenerateColumns="False"
                          CanUserAddRows="False" 
                          CanUserDeleteRows="False"
                          CanUserResizeRows="False" 
                          CanUserSortColumns="True" 
                          SelectionMode="Single">

        <Button Grid.Row="1" Grid.Column="1" Content="Use Selected School Name" 
         Height="23" Name="btnSelect" Width="131" Command="{Binding 
         Path=UseSelectedSchoolNameCommand}"  />
 </Grid>
</Window>

SchoolNameLookupViewModel

   private string _schoolNames;
   public string SchoolNames
   {
        get { return _schoolNames; }
        set
        {
            _schoolNames= value;
            OnPropertyChanged(SchoolNames);
        }
   }

   private ICommand _useSelectedSchoolNameCommand;
   public ICommand UseSelectedSchoolNameCommand{
   get
    {
    if (_useSelectedSchoolNameCommand== null)
        _useSelectedSchoolNameCommand= new RelayCommand(a => 
            DoUseSelectedSchollNameItem(), p => true);
            return _useSelectedSchoolNameCommand;
      }
    set
       {
         _useSelectedSchoolNameCommand= value;
       }

    }

    private void DoUseSelectedSchoolNameItem() {
        StringBuilder sfiString = new StringBuilder();
        ObservableCollection<SchoolModel> oCol = 
                new ObservableCollection<SchoolModel>();
        foreach (SchoolModel itm in SchollNameList)
        {
            if (itm.isSelected) {
                sfiString.Append(itm.SchoolName + "; ");
                _schoolNames = sfiString.ToString();
            }
        }
                OnPropertyChanged(SchoolNames);
    }

    private ICommand _displayLookupDialogCommand;
    public ICommand DisplayLookupDialogCommand
    {
        get
        {
            if (_displayLookupDialogCommand== null)
                _displayLookupDialogCommand= new
                    RelayCommand(a => DoDisplayLookupDialog(), p => true);
            return _displayLookupDialogCommand;
        }
        set
        {
            _displayLookupDialogCommand= value;
        }
    }

    private void DoDisplayLookupDialog()
    {
        SchoolNameLookup snl = new SchoolNameLookup();
        snl.DataContext = this; //==> This what I was missing. Now my code works as I was expecting
        snl.Show();
    }

2 个答案:

答案 0 :(得分:1)

我的解决方案是将两个窗口绑定到同一个ViewModel,然后定义一个属性来保存代码的结果值,让我们称之为CurrentSchoolCodes,将标签绑定到此属性。确保CurrentSchoolCodes引发INotifyPropertyChanged事件。  然后在DoUseSelectedSchoolNameItem中设置CurrentSchoolCodes的值。

对于模型中的属性,我建议您根据需要加载它们(Lazy Load patttern)。我这个方法你的属性的get访问器检查相关字段是否仍然为空,加载并赋值给它。 代码就像这段代码:

private ObservableCollection<SchoolModel> _schoolList;
public ObservableCollection<SchoolModel> SchoolList{
    get {
        if ( _schoolList == null )
            _schoolList = LoadSchoolList();
        return _schoolList;
    }
}

这样,第一次绑定到此SchoolList属性的WPF控件尝试获取此属性的值时,将加载并缓存该值,然后返回。

注意:我必须说这种属性应该谨慎使用,因为加载数据可能是一个耗时的过程。最好在后台线程中加载数据以保持UI响应。

答案 1 :(得分:1)

Sam在这里提出的解决方案是正确的。 你没有得到的是你应该只有一个viewmodel实例,你的主页和子页应该引用同一个。 你的viewmodel应该实例化一次:也许你需要一个定位器并在那里得到实例...这样做你的ctor中的代码会触发一次,看看mvvmLight工具包,我认为它对你的使用很有帮助,你可以摆脱那些实现ICommand的类...... 您可以在此处找到使用该模式的一个很好的示例: http://blogs.msdn.com/b/kylemc/archive/2011/04/29/mvvm-pattern-for-ria-services.aspx 基本上会发生什么:

你有一个定位器

public class ViewModelLocator 
{
    private readonly ServiceProviderBase _sp;

    public ViewModelLocator()
    {
        _sp = ServiceProviderBase.Instance;

        // 1 VM for all places that use it. Just an option
        Book = new BookViewModel(_sp.PageConductor, _sp.BookDataService); 
    }

    public BookViewModel Book { get; set; }
    //get { return new BookViewModel(_sp.PageConductor, _sp.BookDataService); }

    // 1 new instance per View 
    public CheckoutViewModel Checkout
    {
        get { return new CheckoutViewModel(_sp.PageConductor, _sp.BookDataService); }
    }
}

Locator是一个StaticResource,在App.xaml

<Application.Resources>
        <ResourceDictionary>
        <app:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />
        </ResourceDictionary>
    </Application.Resources>

在您的视图中,您可以通过定位器引用视图模型:

   DataContext="{Binding Book, Source={StaticResource Locator}}"

这里Book是BookViewModel的一个实例,你可以在Locator类中看到它

BookViewModel有一个SelectedBook:

 private Book _selectedBook;
        public Book SelectedBook
        {
            get { return _selectedBook; }
            set
            {
                _selectedBook = value;
                RaisePropertyChanged("SelectedBook");
            }
        }

并且您的子窗口应该与MainView具有相同的DataContext并且工作方式如下:

<Grid Name="grid1" DataContext="{Binding SelectedBook}">