MVVM UoW Binding and OnModelChanged

MVVM UoW Binding and OnModelChanged

Old forum URL: forums.lhotka.net/forums/t/10508.aspx


Russ posted on Friday, July 08, 2011

I have been using the MVVM videos to create an application in Silverlight 4 using CSLA 4.1, MVVM and bxf. This application has a very common search page that has 3 combo boxes, a search button and a search results list.

 

I have created a Search (Editable Root) object with one property for each of the 3 combo box selected values and one property for the search results (Read Only) list. I have created a SearchUoW object with 4 properties, one for each combo box list and one for the Search (Editable Root) object. I have created my ViewModel<SearchUoW> and use BeginRefresh(“GetSearchUoW”) to load the properties when the user control is created. I have also implemented a collection of ViewModels for the search result line items since each will have an Edit hyperlink.

 

I have created a search button on the view and a triggeraction to call a search method on the VM. This method calls a method on the Search object (property of SearchUoW) telling it to load the search results with the filter criteria specified in the other search properties.

 

The problem is the OnModelChanged event doesn’t fire when the SearchResults property of the Search property changes. This means the UI is never notified and never updates.

 

Is there a way to do this or have I structured my solution incorrectly?

 

Thanks

Russ.

RockfordLhotka replied on Saturday, July 09, 2011

I usually use a separate viewmodel to manage each object.

The ViewModel<T> type is a DependencyObject, and the Model property is a dependency property, so it is bindable. In other words, you can have a "root" viewmodel to hold the UOW, and then bind "child" viewmodel resources to each object retrieved by the UOW by binding the Model property to the root viewmodel.

This way your UI can bind to the viewmodel that manages a specific object - and if that object changes, then those UI elements will automatically rebind.

Russ replied on Sunday, July 10, 2011

Thanks for the reply Rocky. I’m obviously still missing something because I’m still having difficulty getting the UI to display the search results since I added the UOW.

 

I have now created 3 viewmodels:

1)      SearchPageViewModel : ViewModel<Library.SearchUOW>

2)      SearchPageSearchViewModel : DependencyObject

3)      SearchItemViewModel : DependencyObject

 

SearchPageViewModel

This is the UOW VM that has a property for each of the 3 combobox item sources and a forth property containing the editable root object. The constructor calls the model get factory method.

 

public class SearchPageViewModel : BaseViewModel<Library.SearchUOW>

{

  public SearchPageViewModel()

  {

    Shell.Instance.ShowStatus(new Status { Text = "Loading...", IsBusy = true });

    BeginRefresh("GetSearchUOW");

  }

 

// buttons

  public void Search()

  {

    //Shell.Instance.ShowStatus(new Status { Text = "Loading...", IsBusy = true });

    Model.Search.GetSearchResults();

  }

}

SearchPageSearchViewModel

This is the VM I created as a result of your last reply. I based it on DependencyObject and the Model is of type Library.Search. It contains the observable collection for SearchItemViewModels. This code worked before I started using the UOW and was basing this class on ViewModel<Library.Search> but now that the class is based on DependencyObject the OnModelChanged event is not found.

 

public class SearchPageSearchViewModel : DependencyObject

{

  public static readonly DependencyProperty ModelProperty

    = DependencyProperty.Register("Model", typeof(Library.Search), typeof(SearchPageSearchViewModel), null);

  public Library.Search Model

  {

    get { return (Library.Search)GetValue(ModelProperty); }

    set { SetValue(ModelProperty, value); }

  }

 

  public ObservableCollection<SearchItemViewModel> SearchList

  {

    get

    {

      var result = new ObservableCollection<SearchItemViewModel>();

      if (Model != null)

        foreach (var item in Model.LineItems)

          result.Add(new SearchItemViewModel { Model = item });

      return result;

    }

  }

 

  protected override void OnModelChanged(Library.Search oldValue, Library.Search newValue)

  {

    base.OnModelChanged(oldValue, newValue);    //ERROR HERE

    if (oldValue != null)

      oldValue.LineItems.CollectionChanged -= new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Model_CollectionChanged);

    if (newValue != null)

      newValue.LineItems.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Model_CollectionChanged);

    OnPropertyChanged("SearchList");

  }

 

  private void Model_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)

  {

    OnPropertyChanged("SearchList");

  }

}

SearchItemViewModel

This class is unchanged since the introduction of the UOW.

 

public class SearchItemViewModel : DependencyObject

{

  public static readonly DependencyProperty ModelProperty =DependencyProperty.Register("Model", typeof(Library.SearchLineItem), typeof(SearchItemViewModel), null);

  public Library.SearchLineItem Model

  {

    get { return (Library.SearchLineItem)GetValue(ModelProperty); }

    set { SetValue(ModelProperty, value); }

  }

 

  public void Edit()

  {

    MessageBox.Show("Edit pressed on SearchLineItem "+ Model.ContractPK.ToString());

  }

}

 

My SearchPage view has the following CVSs:

 

<UserControl.Resources>

  <CollectionViewSource x:Key="searchPageViewModelViewSource" d:DesignSource="{d:DesignInstance my:SearchPageViewModel, CreateList=True}" />

  <CollectionViewSource x:Key="searchPageSearchViewModelViewSource" Source="{Binding Path=Search, Source={StaticResource searchPageViewModelViewSource}}" />

  <CollectionViewSource x:Key="searchPageSearchViewModelSearchListViewSource" Source="{Binding Path=SearchList, Source={StaticResource searchPageSearchViewModelViewSource}}" />

</UserControl.Resources>

 

I have a feeling that I’m really close but I just can’t seem to get it to work!

 

I have bound the combo boxes and they work. I have bound textboxes to the Search properties for the search criteria and they are correctly being set. I have bound a textbox to Search.LineItems.Count and I have implemented a search button that calls the search method on the SearchPageViewModel. When the button is pressed the count correctly changes from zero to the search results count.

 

The major remaining issue is binding the datagrid to the search results so they will display.

 

The minor remaining issue is figuring out how to have the GetSearchResults method on the Search object be able to tell the VM when it has completed so I can clear the “Loading...” status message.

Thanks for the help

Russ.

RockfordLhotka replied on Thursday, August 04, 2011

Typically with a UOW scenario the Model property of the root viewmodel changes exactly one time - when the UOW comes back from the server.

When that happens, in your root viewmodel's OnRefreshed overload you will probably set the properties to make the various objects in the UOW available through the child viewmodels.

Your SearchItemViewModel manages an editable root object though right? So it should subclass ViewModel<T> too - thus allowing your Save and Cancel buttons to bind to the Save and Cancel methods of that particular viewmodel.

The SearchPageSearchViewModel is managing the read-only list right? So it should have a method (verb) that is triggered when the selection is changed by the user in the UI. So the datagrid's SelectedItemChanged (or similar) event should be bound to that method - and that method will create a new SearchItemViewModel.

The SearchPageSearchViewModel is the viewmodel that should have a property to expose the SearchItemViewModel object.

Russ replied on Friday, August 05, 2011

Hi Rocky,

I have modeled my objects as follows:

SearchUOW

-          List Property1 – bound to combobox1

-          List Property2 – bound to combobox2

-          List Property3 – bound to combobox3

-          Search Editable Root Property

o   Property1 – bound to selected value of combobox1

o   Property2 – bound to selected value of combobox2

o   Property3 – bound to selected value of combobox3

o   Property4 – bound to selected value of datapicker

o   ListItems Property (collection of search results)

o   GetSearchResults() method

 

SearchUOW

public class SearchUOW : ReadOnlyBase<SearchUOW>

Which contains a Search property and a number of collection properties used to populate the combo boxes.

public static readonly PropertyInfo<Search> SearchProperty = RegisterProperty<Search>(c => c.Search);

public Search Search

{

  get { return GetProperty(SearchProperty); }

  private set { LoadProperty(SearchProperty, value); }

}

 

public static readonly PropertyInfo<TypeList> TypeListProperty = RegisterProperty<TypeList>(c => c. TypeList);

public TypeList TypeList

{

  get { return GetProperty(TypeListProperty); }

  private set { LoadProperty(TypeListProperty, value); }

}

 

public static readonly PropertyInfo<ClientCodeList> ClientCodeListProperty = RegisterProperty<ClientCodeList>(c => c.ClientCodeList);

public ClientCodeList ClientCodeList

{

  get { return GetProperty(ClientCodeListProperty); }

  private set { LoadProperty(ClientCodeListProperty, value); }

}

 

public static readonly PropertyInfo<AccountManagers> AccountManagerListProperty = RegisterProperty<AccountManagers>(c => c.AccountManagerList);

public AccountManagers AccountManagerList

{

  get { return GetProperty(AccountManagerListProperty); }

  private set { LoadProperty(AccountManagerListProperty, value); }

}

Etc.

Search

public class Search : BusinessBase<Search>

 

This Editable Root object has a property for each combo box selected item and one for the search results (ListItems).

 

public static readonly PropertyInfo<int?> AccountManagerFKProperty = RegisterProperty<int?>(c => c.AccountManagerFK);

public int? AccountManagerFK

{

  get { return GetProperty(AccountManagerFKProperty); }

  set { SetProperty(AccountManagerFKProperty, value); }

}

 

public static readonly PropertyInfo<string> TypeProperty = RegisterProperty<string>(c => c. Type);

public string Type

{

  get { return GetProperty(TypeProperty); }

  set { SetProperty(TypeProperty, value); }

}

 

public static readonly PropertyInfo<String> ClientCodeProperty = RegisterProperty<String>(c => c.ClientCode);

public String ClientCode

{

  get { return GetProperty(ClientCodeProperty); }

  set { SetProperty(ClientCodeProperty, value); }

}

 

public static readonly PropertyInfo<DateTime?> DateProperty = RegisterProperty<DateTime?>(c => c.Date);

public DateTime? Date

{

  get { return GetProperty(DateProperty); }

  set { SetProperty(DateProperty, value); }

}

 

public static readonly PropertyInfo<SearchLineItems> LineItemsProperty = RegisterProperty<SearchLineItems>(c => c.LineItems);

public SearchLineItems LineItems

{

  get { return GetProperty(LineItemsProperty); }

  private set { SetProperty(LineItemsProperty, value); }

}

 

I also added an asynchronous method to get the search results based on the values in the other properties.

 

public void GetSearchResults(EventHandler<DataPortalResult<Search>> callback)

{

  // load the lineitems property for the current criteria

  SearchLineItems.GetSearchLineItems(AccountManagerFK, Type, ClientCode, Date, (o, e) =>

  {

    if (e.Error == null && e.Object != null)

    {

      // process result

      LineItems = (SearchLineItems)e.Object;

    }

    callback(null, null);

  });

}

 

SearchLineItems

public class SearchLineItems : ReadOnlyListBase<SearchLineItems, SearchLineItem>

SearchLineItem

public class SearchLineItem : ReadOnlyBase<SearchLineItem>

 

I figured the LineItems and LineItem classes should be ReadOnlyList and ReadOnlyChild respectively because they display a subset of the actual record and the VM for the search results line item would have a method to handle navigating to a page to edit the selected line item.

 

 

There are a few things I do not understand in your last reply:

When that happens, in your root viewmodel's OnRefreshed overload you will probably set the properties to make the various objects in the UOW available through the child viewmodels.

Should I be using the OnRefreshed overload? I simply let the SearchUOW fetch load the properties and the binding does the rest. Is this incorrect?

 

private void DataPortal_Fetch()

{

  Search = Search.NewSearch();

  TypeList = TypeList.GetTypes();

  ClientCodeList = ClientCodeList.GetClientCodes();

  AccountManagerList = AccountManagers.GetAccountManagers();

}

 

The SearchPageSearchViewModel is managing the read-only list right? So it should have a method (verb) that is triggered when the selection is changed by the user in the UI. So the datagrid's SelectedItemChanged (or similar) event should be bound to that method - and that method will create a new SearchItemViewModel.

My page allows the user enter search criteria into 3 combo boxes and one date picker. They can then press the Search button to display the search results. Finally, they can click on a hyperlink on the search results line item to navigate to a new page to edit the selected record.

 

The SearchPageSearchViewModel is managing the Search EditableRoot object.

 

public class SearchPageViewModel : BaseViewModel<Library.SearchUOW>

{

  public SearchPageViewModel()

  {

    Shell.Instance.ShowStatus(new Status { Text = "Loading...", IsBusy = true });

    BeginRefresh("GetSearchUOW");

  }

 

  public static readonly DependencyProperty SearchPageSearchViewModelProperty =

    DependencyProperty.Register("SearchPageSearchViewModel", typeof(SearchPageSearchViewModel), typeof(SearchPageViewModel), null);

  public SearchPageSearchViewModel SearchPageSearch

  {

    get

    {

      var result = new SearchPageSearchViewModel();

      if (Model != null)

        result = new SearchPageSearchViewModel { Model = Model.Search };

      return result;

    }

  }

 

  protected override void OnModelChanged(Library.SearchUOW oldValue, Library.SearchUOW newValue)

  {

    base.OnModelChanged(oldValue, newValue);

    if (oldValue != null)

      oldValue.Search.PropertyChanged -= new System.ComponentModel.PropertyChangedEventHandler(Search_PropertyChanged);

    if (newValue != null)

      newValue.Search.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(Search_PropertyChanged);

    OnPropertyChanged("SearchPageSearch");

  }

 

  void Search_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)

  {

    OnPropertyChanged("SearchPageSearch");

  }

 

  // buttons

  public void GetSearchResults()

  {

    Shell.Instance.ShowStatus(new Status { Text = "Searching...", IsBusy = true });

    if (Model != null)

      Model.Search.GetSearchResults((o, e) =>

        {

          Shell.Instance.ShowStatus(new Status());

        });

  }

}

 

Is this an appropriate use of the business objects?

 

Thanks

Russ.

simon replied on Sunday, August 07, 2011

View:

 

 <ad:DocumentContent.Resources >

        <CollectionViewSource x:Key="MaterialsUowViewModelSource" />

 

    </ad:DocumentContent.Resources>

    <Grid Name="LayoutRoot" DataContext="{Binding Path=MaterialsViewModel, Source={StaticResource MaterialsUowViewModelSource}}">

 

        <Grid>

            <Grid.RowDefinitions>

                <RowDefinition Height="Auto"/>

                <RowDefinition/>

                <RowDefinition Height="Auto"/>

            </Grid.RowDefinitions>

            <ToolBar Height="28" VerticalAlignment="Top" >

                <Button Name="AddButton" Content="新建"  />

                <csla:TriggerAction Height="0" Name="AddItemTrigger" Width="0" TargetControl="{Binding ElementName=AddButton}" MethodName="AddNew" />

 

                <Button Content="保存" Name="SaveButton" IsEnabled="{Binding Path=CanSave}" />

                <csla:TriggerAction Height="20" Name="SaveTrigger" Width="20" TargetControl="{Binding ElementName=SaveButton}" MethodName="Save" />

 

                <Button Content="删除" Name="RemoveButton"/>

                <csla:TriggerAction Height="0" Name="RemoveTrigger" Width="0" MethodName="Remove" TargetControl="{Binding ElementName=RemoveButton}" MethodParameter="{Binding Path=Model.CurrentItem}" />

 

                <Button Content="刷新" Name="RefreshButton"/>

                <csla:TriggerAction Height="0" Name="RemoveTrigger" Width="0" MethodName="Remove" TargetControl="{Binding ElementName=RemoveButton}" MethodParameter="{Binding Path=Model}" />

 

            </ToolBar>

            <TabControl Grid.Row="1"  Name="tabControl1"  >

                <TabItem Header="概览" Name="tabItem1">

                    <local:MaterialListView DataContext="{Binding Path=Model}"/>

                </TabItem>

                <TabItem Header="基本" DataContext="{Binding Path=Model.CurrentItem}" >

                    <Grid  >

                        <Grid.RowDefinitions>

                            <RowDefinition Height="Auto" />

                            <RowDefinition Height="Auto" />

                            <RowDefinition Height="Auto" />

                            <RowDefinition Height="215*" />

                        </Grid.RowDefinitions>

                        <Grid.ColumnDefinitions>

                            <ColumnDefinition Width="70" />

                            <ColumnDefinition Width="Auto" />

                            <ColumnDefinition Width="Auto" />

                            <ColumnDefinition Width="165" />

                            <ColumnDefinition Width="36" />

                            <ColumnDefinition Width="23" />

                        </Grid.ColumnDefinitions>

                        <Label Content="ID:"  HorizontalAlignment="Left" Name="label1" VerticalAlignment="Top" Margin="0" />

                        <TextBox Grid.Row="0"  Grid.Column="1" Margin="3" Height="23" Width="120"  Text="{Binding Path=Id,Mode=OneWay }" IsReadOnly="True" />

                        <Label Content="物料编号:" HorizontalAlignment="Left" Name="label2" VerticalAlignment="Top" Margin="0" Grid.Row="1"/>

                        <TextBox Grid.Row="1"  Grid.Column="1" Margin="3" Height="23" Text="{Binding Path=No,UpdateSourceTrigger=PropertyChanged}" inf:MaskBehavior.Mask="{Binding Path=MaskList[MaterialNo], Source={StaticResource MaterialsUowViewModelSource}}" />

                        <Label Content="物料名称:" HorizontalAlignment="Left" Margin="0" Name="label3" VerticalAlignment="Top" Grid.Row="2" />

                        <TextBox Grid.Row="2" Grid.Column="1" Margin="3,3,-160,3" Height="23" Text="{Binding Path=Name,UpdateSourceTrigger=PropertyChanged}" Grid.ColumnSpan="2" Width="313" />

 

                        <csla:PropertyStatus Grid.Column="2" Grid.Row="0" Margin="5" Name="propertyStatus1" Width="20" Height="20" Property="{Binding Path=Id}" />

                        <csla:PropertyStatus Grid.Column="2" Grid.Row="1" Margin="5" Name="propertyStatus2" Width="20" Height="20" Property="{Binding Path=No}" />

                        <csla:PropertyStatus Grid.Column="4" Grid.Row="2" Margin="5" Name="propertyStatus3" Width="20" Height="20" Property="{Binding Path=Name}" />

 

                    </Grid>

                </TabItem>

            </TabControl>

        </Grid>

    </Grid>

 

ViewModel:

 

namespace MES.Presentation.WPF.Client.MM.Views.MaterialViews

{

    [Export(typeof(MaterialCompositeViewModel))]

    [PartCreationPolicy(CreationPolicy.NonShared)]

    public class MaterialCompositeViewModel:ViewModel<MaterialsUow>

    {

        #region Property

 

        public static readonly DependencyProperty MaterailsViewModelProperty = DependencyProperty.Register("MaterailsViewModel", typeof(MaterialsViewModel), typeof(MaterialsUow), null);

        public MaterialsViewModel MaterialsViewModel

        {

            get { return (MaterialsViewModel)GetValue(MaterailsViewModelProperty); }

            set { SetValue(MaterailsViewModelProperty, value);}

 

        }

 

        public Dictionary<string,string> MaskList

        {

            get;set;

        }

 

 

        #endregion

 

        #region Private members

 

        private readonly IRegionManager regionManager;

        private readonly IEventAggregator eventAggregator;

 

 

 

        #endregion

 

        #region Commandes

        public ICommand NavItemDoubleClickCommand { get; private set; }

        #endregion

 

        #region Constructor

        [ImportingConstructor]

        public MaterialCompositeViewModel(IRegionManager regionManager, IEventAggregator eventAggregator)

        {

 

            this.InitializeCommands();

 

            this.regionManager = regionManager;

            this.eventAggregator = eventAggregator;

 

            InitData();

 

        }

 

        #endregion

 

        #region InitializeCommands Method

 

        /// <summary>

        /// Initiliazes the commands

        /// </summary>

        public void InitializeCommands()

        {

            //this.LoginCommand = new DelegateCommand(this.OnLogin,() => !string.IsNullOrEmpty(this.UserName) && !string.IsNullOrEmpty(this.Password));

            //this.LoginCommand = new DelegateCommand(this.OnLogin);

 

            //this.LogoutCommand = new DelegateCommand(OnLogout);

        }

 

        #endregion

 

 

        private void InitData()

        {

            BeginRefresh("GetMaterials","MaterialCompositeView");

        }

 

 

        protected override void OnPropertyChanged(string propertyName)

        {

            base.OnPropertyChanged(propertyName);

            if (propertyName == "IsBusy")

               eventAggregator.GetEvent<BusyIndicatorEvent>().Publish(new BusyIndicatorEvent(IsBusy, "正在加载数据..."));

 

 

        }

 

        protected override void OnModelChanged(MaterialsUow oldValue, MaterialsUow newValue)

        {

             base.OnModelChanged(oldValue, newValue);

             MaterialsViewModel = new MaterialsViewModel(newValue.Materials);

 

             OnPropertyChanged("MaterialsViewModel");

 

             if (MaskList == null)

             {

                 var list = new Dictionary<string, string>();

                 foreach (var item in newValue.MaskList)

                     list.Add(item.Key, item.Value);

                 this.MaskList = list;

                 OnPropertyChanged("MaskList");

             }

 

        }

 

        private void Model_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)

        {

            OnPropertyChanged("MaterialsViewModel");

        }

 

        protected override void OnError(System.Exception error)

        {

            throw (error);

 

        }

    }

}

 

child ViewModel:

 

 public class MaterialsViewModel:ViewModel<Materials>

    {

        public MaterialsViewModel(Materials materials)

        {

            Model = materials;

         }

    }

 

 

Model:

 [Serializable]

    public class MaterialsUow:ReadOnlyBase <MaterialsUow>

    {

        public static readonly PropertyInfo<Materials > MaterialsProperty = RegisterProperty<Materials >(c => c.Materials);

        public Materials  Materials

        {

            get { return GetProperty(MaterialsProperty); }

            private set { LoadProperty(MaterialsProperty, value); }

        }

 

        public MaskList MaskList { get; private set; }

 

        #region Factory Methods

 

        public static void GetMaterials(string viewName, EventHandler<DataPortalResult<MaterialsUow>> callback)

        {

            DataPortal.BeginFetch<MaterialsUow>(viewName,callback);

        }

 

        #endregion

        #region Data Access

        private void DataPortal_Fetch(string viewName)

        {

            Materials = Materials.GetMaterials();

            MaskList = MaskList.GetList(viewName);

 

        }

      #endregion

    }

 

These code work well. but there a problem that is the NVL can't directly used in xaml as below:

Binding Path=MaskList.Key("ViewName")

so i changed to use dictionary<string,string>

Russ replied on Monday, July 11, 2011

I have it working now!

I have learned the following lessons:

1)      Only the root ViewModel is derived from ViewModel<T>. All other child ViewModels are derived from DependecyObject.

2)      All business object properties that have associated VMs have either a DependencyProperty or an ObservableCollection<T> property.

3)      Extreme care must be taken to ensure all XAML bindings are made to the child VM properties and NOT to the model properties.

 

[Rocky, please let me know if any of these three statements are incorrect.]

 

Thanks again for the help Rocky.

Russ.

stefan replied on Tuesday, July 12, 2011

Hi Russ,

could you please be more specific, and tell us what were the missing pieces concerning your previous post?

Thanks.

Russ replied on Tuesday, July 12, 2011

Here’s how the working code ended up.

SearchPageViewModel

 

public class SearchPageViewModel : BaseViewModel<Library.SearchUOW>

{

  public SearchPageViewModel()

  {

    Shell.Instance.ShowStatus(new Status { Text = "Loading...", IsBusy = true });

    BeginRefresh("GetSearchUOW");

  }

 

  public static readonly DependencyProperty SearchPageSearchViewModelProperty =

    DependencyProperty.Register("SearchPageSearchViewModel", typeof(SearchPageSearchViewModel), typeof(SearchPageViewModel), null);

  public SearchPageSearchViewModel SearchPageSearch

  {

    get

    {

      var result = new SearchPageSearchViewModel();

      if (Model != null)

        result = new SearchPageSearchViewModel { Model = Model.Search };

      return result;

    }

  }

 

  protected override void OnModelChanged(Library.SearchUOW oldValue, Library.SearchUOW newValue)

  {

    base.OnModelChanged(oldValue, newValue);

    if (oldValue != null)

      oldValue.Search.PropertyChanged -= new System.ComponentModel.PropertyChangedEventHandler(Search_PropertyChanged);

    if (newValue != null)

      newValue.Search.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(Search_PropertyChanged);

    OnPropertyChanged("SearchPageSearch");

  }

 

  void Search_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)

  {

    OnPropertyChanged("SearchPageSearch");

  }

 

  // buttons

  public void GetSearchResults()

  {

    //Shell.Instance.ShowStatus(new Status { Text = "Searching...", IsBusy = true });

    if (Model != null)

      Model.Search.GetSearchResults();

  }

}

SearchPageSearchViewModel

 

public class SearchPageSearchViewModel : DependencyObject

{

  public static readonly DependencyProperty ModelProperty

    = DependencyProperty.Register("Model", typeof(Library.Search), typeof(SearchPageSearchViewModel), null);

  public Library.Search Model

  {

    get { return (Library.Search)GetValue(ModelProperty); }

    set { SetValue(ModelProperty, value); }

  }

 

  public ObservableCollection<SearchItemViewModel> SearchList

  {

    get

    {

      var result = new ObservableCollection<SearchItemViewModel>();

      if (Model != null)

        foreach (var item in Model.LineItems)

          result.Add(new SearchItemViewModel { Model = item });

      return result;

    }

  }

}

SearchItemViewModel

 

public class SearchItemViewModel : DependencyObject

{

  public static readonly DependencyProperty ModelProperty =DependencyProperty.Register("Model", typeof(Library.SearchLineItem), typeof(SearchItemViewModel), null);

  public Library.SearchLineItem Model

  {

    get { return (Library.SearchLineItem)GetValue(ModelProperty); }

    set { SetValue(ModelProperty, value); }

  }

 

  public void Edit()

  {

    MessageBox.Show("Edit pressed on SearchLineItem "+ Model.ContractPK.ToString());

  }

}

SearchPage.xaml

 

<UserControl.Resources>

  <CollectionViewSource x:Key="searchPageViewModelViewSource" d:DesignSource="{d:DesignInstance my:SearchPageViewModel, CreateList=True}" />

  <CollectionViewSource x:Key="searchPageSearchViewModelSearchListViewSource" Source="{Binding Path=SearchPageSearch.SearchList, Source={StaticResource searchPageViewModelViewSource}}" />

</UserControl.Resources>

Search : BusinessBase<Search>

public void GetSearchResults()

{

  // load the lineitems property for the current criteria

  SearchLineItems.GetSearchLineItems(ManagerID, Type, ClientCode, Date, (o, e) =>

  {

    if (e.Error == null && e.Object != null)

    {

      // process result

      LineItems = (SearchLineItems)e.Object;

    }

  });

}

 

The only remaining issue is the GetSearchResults() method. I need to pass it a callback handler so I can properly update the status message when it has completed.

simon replied on Thursday, August 04, 2011

Hi Russ,

Have you implemented the save method?  

Russ replied on Thursday, August 04, 2011

Hi Simon,

This is a search page.  There is no save method.

Russ replied on Thursday, August 11, 2011

Russ

I have learned the following lessons:

1) Only the root ViewModel is derived from ViewModel<T>. All other child ViewModels are derived from DependecyObject.

2) All business object properties that have associated VMs have either a DependencyProperty or an ObservableCollection<T> property.

3) Extreme care must be taken to ensure all XAML bindings are made to the child VM properties and NOT to the model properties.

 

I have worked with this some more and have read as many posts as I could find and have now realized that Item #1 in my previous post was wrong.  Child ViewModels can be devired from DependencyObject or ViewModel<T> depending on the functionality you require.  I am basing them on DependencyObject when the underlying Model is ReadOnly and I simply need a VM to handle a click event. I am basing them on ViewModel<T> when the underlying Model is Editable and I required the properties that the ViewModel<T> provides.

I hope this helps anyone who was as confounded as I have been for the last month.

Russ

 

Copyright (c) Marimer LLC